/* sp-elementor-fix.css — reconciling Elementor's wrappers with sh-theme.css.
 *
 * ---------------------------------------------------------------------------
 * THIS FILE IS DELIBERATELY ALMOST EMPTY.
 *
 * The first version carried three rules that an audit showed were all no-ops:
 *   - `padding:0; margin:0` on .elementor-widget-container — Elementor sets
 *     neither by default, and once a user does, Elementor's per-post CSS
 *     outspecifies this selector and wins anyway.
 *   - `width:100%` on .sh-hero — it is a <section>, already width:auto, i.e.
 *     already the full width of its containing block. It does NOT escape the
 *     parent's max-width, which was the stated intent.
 *   - `min-height:120px` in the editor — .sh-hero's own padding already totals
 *     120px, so it could never apply.
 *
 * A fix that isn't doing anything is worse than no fix: it looks like the
 * problem is handled. Rules go in here once they've been shown to be needed
 * against a real install, not before.
 * ---------------------------------------------------------------------------
 *
 * THE ACTUAL FULL-BLEED REQUIREMENT — configuration, not CSS.
 *
 * The source sections were direct children of .sh-page and spanned the viewport.
 * In Elementor they sit inside a container with a default max-width (~1140px)
 * and horizontal padding, and .sh-container adds a second 1200px/1.5rem gutter
 * inside that. Two nested gutters, and a hero panel that stops short of the
 * viewport edge.
 *
 * Fix it in the editor, once, on the container holding StronkPRESS widgets:
 *     Layout → Content Width → Full Width
 *     Layout → Padding → 0
 * Then save it as a container preset so page generation can reuse it rather
 * than re-setting it per page.
 *
 * If that turns out not to be workable, the CSS escape hatch is below. It is
 * opt-in via a class rather than automatic, because 100vw includes the
 * scrollbar and will introduce horizontal scroll on any ancestor that isn't
 * clipping. Add `spr-fullbleed` in Advanced → CSS Classes to use it.
 *
 * NOTE: the attribute selector matches every sh_* widget, not just the hero —
 * sh-areas-strip, rt-redcta and sh-intro are all full-bleed colour bands too.
 * Selectors target .elementor-widget-sh_*, NOT
 * .elementor-widget-container. Elementor's Optimized Markup experiment (3.25+,
 * default-on for new installs) removes that inner div entirely.
 */

[class*="elementor-widget-sh_"].spr-fullbleed {
	margin-left: calc( 50% - 50vw );
	margin-right: calc( 50% - 50vw );
}

/* Pair with clipping on an ancestor, or the scrollbar width becomes a
   horizontal scrollbar. clip rather than hidden so sticky/fixed children in the
   header still work. */
.spr-fullbleed-host {
	overflow-x: clip;
}


/* ===========================================================================
   THE STICKY HEADER — sticky was applied to an element that cannot travel.
   ---------------------------------------------------------------------------
   sh-theme.css:2446 carries the ported rule:

       .sh-header { position: sticky; top: 0; z-index: 1000; }

   It is correct, unnested, and never overridden. It also never did anything,
   and the site looked merely un-sticky rather than broken, which is why it
   survived the port unnoticed.

   ⚠ A STICKY ELEMENT IS CONFINED TO ITS PARENT'S CONTENT BOX. Measured on a
   real install 2026-08-25, the chain under the rendered header template is:

       body
         └ div.elementor                       [data-elementor-id="2343"]
             └ div.e-con-full.e-flex.e-con.e-parent
                 └ div.elementor-widget-sh_site_header
                     └ div.elementor-widget-container
                         └ header.sh-header    ← the sticky rule is here

   Every one of those wrappers is exactly header-height, because the header
   template contains the header and nothing else. So .sh-header has zero
   distance to travel inside its parent, and a sticky element with no travel
   renders identically to a static one. `body` is the first ancestor that spans
   the document.

   ⚠ OVERFLOW WAS RULED OUT, NOT ASSUMED. Every ancestor computes `visible` on
   overflow, overflow-x and overflow-y — checked in the browser, because a
   scroll-container ancestor disables sticky outright and would have been a
   different fix entirely. .spr-fullbleed-host above uses `clip` rather than
   `hidden` precisely so it stays out of this, and it does.

   ⚠ THE FIRST FIX WAS WRONG, 2026-08-25, and the way it was wrong is the
   reason this one is shaped as it is. It made the outermost wrapper sticky:

       body > .elementor:has( .sh-header ) { position: sticky; top: 0; }

   That pins the header, and it also pins THE USP ROTATOR STRIP, because the
   widget renders both and they share every wrapper:

       div.elementor-widget-container
         ├ div.sh-usp-rotator-bar     ← must scroll away
         └ header.sh-header           ← must stick

   The strip is a 40px band of rotating strapline text. It is meant to leave.
   Sticking the common ancestor cannot express "one of these, not the other",
   so no amount of adjusting that rule was going to work.

   ⚠ THE REAL PROBLEM IS THAT ELEMENTOR ADDED FOUR BOXES THE SOURCE DID NOT
   HAVE. In the original both nodes are children of <body>, so `.sh-header`'s
   containing block is the document and it travels the whole page while its
   sibling strip does not. Every rule in sh-theme.css was written against that
   arrangement.

   So rather than move the stickiness somewhere it does not belong, remove the
   boxes. `display: contents` drops an element's own box while leaving its
   children in place, so the strip and the header become children of <body>
   again and the ported rule means what it says. No magic numbers, nothing to
   re-derive if the strip's height changes, and one less rule that has to agree
   with another rule in another file.

   ⚠ WHAT THIS COSTS: any padding, background or flex setting on the header
   template's own Elementor container stops applying, because that container no
   longer generates a box. For THIS template that is intended — it holds one
   widget and is configured Full Width / padding 0 (see the full-bleed note
   above). If a future header template needs container-level layout, this is
   the rule that will have quietly eaten it.

   ⚠ THE CHAIN IS DESCRIBED, NOT ENUMERATED — 2026-08-25, second correction.

   The first version listed Elementor's wrapper classes by name and anchored on
   `body > .elementor`. That broke the moment anything else appeared in the
   chain: the editor's own .spr-edit-part wrapper (see SPR_Placement) sits
   between <body> and .elementor, so `body > .elementor` stopped matching and
   the whole rule went inert IN THE EDITOR ONLY. The page was fine; the preview
   was not; and the two disagreeing is the failure that matters most here.

   ⚠ AND display:contents ON THAT WRAPPER DID NOT FIX IT. It removes the
   element's BOX, not its place in the document tree, and the child combinator
   is defined on the document tree. `body > .elementor` still does not match.
   Box tree and selector-matching tree are different things.

   So state the actual requirement instead: EVERY ELEMENT BETWEEN <body> AND
   .sh-header MUST BE BOXLESS. `:has()` can say exactly that. An element in the
   chain is one that contains .sh-header; .sh-header itself does not contain
   .sh-header, so the chain stops there on its own, and the rotator strip —
   which contains no .sh-header either — keeps its box and keeps scrolling.

   This also drops the dependency on .elementor-widget-container, which
   Elementor's Optimized Markup experiment removes outright, and on .e-con,
   which is a name from the container rewrite and will change again.

   ⚠ STILL ANCHORED AT `body >`, so the chain must reach a top-level element.
   A .sh-header nested somewhere inside page content is left alone.

   :has() is already load-bearing here — the USP alternation below depends on
   it — so this adds no new support requirement.
   =========================================================================== */

body > *:has( .sh-header ),
body > *:has( .sh-header ) *:has( .sh-header ) {
	display: contents;
}


/* ---------------------------------------------------------------------------
   ...AND THEN IT STICKS UNDERNEATH THE WORDPRESS ADMIN BAR.
   ---------------------------------------------------------------------------
   #wpadminbar is `position: fixed; top: 0; z-index: 99999`, so `top: 0` on the
   header means "under the toolbar", not "at the top of the screen". Only
   logged-in users ever see this — visitors have no admin bar and no problem —
   which is exactly why it would otherwise stay broken: the only people who look
   at the site every day are the only people who see it.

   ⚠ THE OFFSET IS READ FROM CORE, NOT TYPED IN. WordPress declares

       html { --wp-admin--admin-bar--height: 32px; }
       @media (max-width: 782px) { html { --wp-admin--admin-bar--height: 46px } }

   in wp-includes/css/admin-bar.css, which is only enqueued when the bar is
   actually showing. Using the variable means the 32/46 switch is core's to make
   and there is no breakpoint of ours to keep in sync with theirs. The 32px
   fallback covers WordPress older than the variable.

   ⚠ BELOW 600px THE BAR IS `position: absolute` AND SCROLLS AWAY — core's own
   "Smartphone" block. The variable still reports 46px there, because it
   describes the bar's height and not whether it is pinned. Offsetting by it
   would leave a 46px gap above the header for the whole page. So this one
   breakpoint IS ours, and it is copied from core's, not chosen.

   ⚠ GATED ON body.admin-bar, which core adds in get_body_class() when
   is_admin_bar_showing(). Confirmed present in the live DOM alongside
   `logged-in` before this was written.

   NOTE ON WHERE THIS LIVES: it is not an Elementor problem, so this file is
   really "reconciling the ported theme with its environment" now. It sits here
   rather than in sh-theme.css because sh-theme.css is a verbatim port and
   should keep saying what the source said.
   --------------------------------------------------------------------------- */

body.admin-bar .sh-header {
	top: var( --wp-admin--admin-bar--height, 32px );
}

@media screen and ( max-width: 600px ) {
	body.admin-bar .sh-header {
		top: 0;
	}
}


/* ---------------------------------------------------------------------------
   ...AND ANYTHING SCROLLED TO NOW LANDS UNDER THE STICKY HEADER.
   ---------------------------------------------------------------------------
   A consequence of the header sticking for the first time, not a pre-existing
   bug: fragment links and scrollIntoView() put their target at the top of the
   viewport, which is now behind the header bar. It applies to the USP nav
   pills too — sh-behaviors.js:269 uses scrollIntoView({behavior:'smooth'}),
   and scroll-padding governs that as well as ordinary anchor navigation.

   ⚠ --sh-anchor-offset IS MEASURED, NOT DECLARED. sh-behaviors.js writes it
   from .sh-header-inner's rendered height on load and on resize. It is not a
   token and must never become one: the height depends on a local custom
   property scoped inside .sh-header, a ≤1024px override, and a per-client
   :root value the plugin does not own. The end of sh-behaviors.js explains
   the whole reasoning.

   ⚠ html:root, NOT html — SPECIFICITY, NOT STYLE. Core already sets
   `html { scroll-padding-top: var(--wp-admin--admin-bar--height) }` in
   admin-bar.css. That is the same selector as ours, so which one wins would
   come down to stylesheet order, and the order between core's admin-bar CSS
   and a plugin stylesheet is not something to depend on. :root is a
   pseudo-class, so html:root is (0,1,1) against core's (0,0,1) and wins
   regardless of order.

   ⚠ BOTH var()s FALL BACK TO 0px, deliberately. Before the JS runs, with JS
   off, or for a logged-out visitor (core only enqueues admin-bar.css when the
   bar is showing), this computes to 0 and behaves exactly as it did before —
   never worse than the state it replaces.

   The ≤600px branch drops the admin-bar term for the same reason as the rule
   above it: core unpins #wpadminbar there and it scrolls away.
   --------------------------------------------------------------------------- */

html:root {
	scroll-padding-top: calc( var( --sh-anchor-offset, 0px ) + var( --wp-admin--admin-bar--height, 0px ) );
}

@media screen and ( max-width: 600px ) {
	html:root {
		scroll-padding-top: var( --sh-anchor-offset, 0px );
	}
}


/* ===========================================================================
   USP SECTION ALTERNATION
   ---------------------------------------------------------------------------
   The source alternates by DOM position among ALL children of .sh-page:

       .sh-usp-section:nth-child(even) { background: var(--sh-surface-warm) !important; }
       .sh-usp-section:nth-child(even) .sh-usp-inner     { direction: rtl; }
       .sh-usp-section:nth-child(even) .sh-usp-inner > * { direction: ltr; }
       @media (max-width: 1024px) { …(even) .sh-usp-inner { direction: ltr; } }

   That cannot be written against the section itself here: it is the only child
   of .elementor-widget-container, so it is forever child 1. It is written
   against the WIDGET one level up, which is a sibling of every other band.

   ⚠ THIS RULE LOST ITS :has() ON 2026-09-06, and the reason is worth keeping.
   Until then the importer emitted one top-level container per widget, so the
   element being COUNTED (the container) was not the element being IDENTIFIED
   (the widget inside it), and :has() was the bridge between them. The page
   shape merged to one container holding every band, so the widgets are the
   siblings now and the counted element carries .elementor-widget-sh_usp_section
   itself. One selector doing one thing.

   That also means this rule is a live check on the document shape: go back to a
   container per widget and every band is an only child, so nothing alternates.

   ⚠ SO THE COUNTING IS THE SOURCE'S, NOT "EVERY SECOND USP". Position among
   all sibling bands is the same thing as position among all children of
   .sh-page, quirk included: put a feature banner between two USPs and the
   pattern shifts from there on. prompt.txt documented that as the behaviour,
   so reproducing it is the point rather than a limitation.

   ⚠ IT REPLACED A PER-SECTION SWITCH. Until 2026-08-17 the widget carried an
   `alternate` control that the generator had to set, which meant every page
   came out all-white whenever the model left it alone — which was always,
   because every worked example showed it empty. Nothing has to set anything
   now, and every page already built alternates without being touched.

   ⚠ THE SELECTOR STILL NAMES NEITHER THE CONTAINER CLASS NOR A DIRECT CHILD
   BETWEEN THE CONTAINER AND THE WIDGET, and both of those were wrong in the
   first cut. The merge of 2026-09-06 did not make them safe — it moved the
   descendant step, it did not remove it:

     - Elementor has called this element .e-container and .e-con in different
       versions, so naming one pins the rule to a version.
     - A BOXED container inserts .e-con-inner between itself and its widgets,
       so `> .elementor-widget-…` silently matches nothing there. Our containers
       are full-width and have no inner, but the rule must not depend on a
       setting somebody can change in the editor in one click.

   What is stable is that the top-level container is a child of .elementor and
   carries .elementor-element, and that a DESCENDANT step from there reaches the
   widget whether or not an inner wrapper is in the way. nth-child then counts
   the widget among its own siblings — whichever element turns out to be its
   parent.

   ⚠ THE EDITOR PUTS A WRAPPER BETWEEN .elementor AND THE CONTAINERS, and that
   is why the alternation was dead there while working on the page.

   Measured 2026-08-25 in the editor preview, page document:

       DIV.page-content
         └ div.elementor                       [data-elementor-type="wp-page"]
             ├ div.elementor-section-wrap.ui-sortable   ← the containers are HERE
             └ div.elementor-add-section.elementor-visible-desktop

   So `.elementor > .elementor-element` matched NOTHING in the editor. The
   reported numbers: 0 elements matched, every .sh-usp-section computed
   background rgb(255,255,255), every .sh-usp-inner direction ltr. The front end
   has no section-wrap, which is why the same selector works there.

   :is( .elementor, .elementor-section-wrap ) accepts either shape. The
   front-end branch is unchanged, so this cannot regress the page.

   ⚠ TWO EARLIER THEORIES ABOUT THIS WERE WRONG, recorded so they are not tried
   again. First: that editor chrome shifted nth-child parity. It does not —
   .elementor-add-section is a sibling of the section-wrap, not of the
   containers, so it never enters the count. Second: that the fix was
   `of .elementor-element`. That was a no-op for this bug.

   `of .elementor-element` is KEPT, and it is load-bearing NOW in a way it was
   not before. Two claims on it:

     - The editor wrapper is jQuery-sortable (`ui-sortable`) and inserts
       placeholder siblings mid-drag; counting only real elements is right.
     - Since 2026-09-06 the widgets are counted among the CONTAINER'S children,
       and Elementor's own editor chrome lives there too. Counting everything
       would let a chrome node shift the parity of every band below it.

   Requires `nth-child(… of …)`. It no longer requires :has() — see the note
   above — so the support floor went DOWN, not up.
   =========================================================================== */

:is( .elementor, .elementor-section-wrap ) > .elementor-element .elementor-widget-sh_usp_section:nth-child( even of .elementor-element ) .sh-usp-section {
	background: var(--sh-surface-warm) !important;
}

:is( .elementor, .elementor-section-wrap ) > .elementor-element .elementor-widget-sh_usp_section:nth-child( even of .elementor-element ) .sh-usp-inner {
	direction: rtl;
}

:is( .elementor, .elementor-section-wrap ) > .elementor-element .elementor-widget-sh_usp_section:nth-child( even of .elementor-element ) .sh-usp-inner > * {
	direction: ltr;
}

/* Matches the source's own breakpoint — below this the grid stacks and a
   flipped column order would put the media above the text. */
@media ( max-width: 1024px ) {
	:is( .elementor, .elementor-section-wrap ) > .elementor-element .elementor-widget-sh_usp_section:nth-child( even of .elementor-element ) .sh-usp-inner {
		direction: ltr;
	}
}


/* ===========================================================================
   ELEMENTOR'S IMAGE RESET vs sh-theme.css
   ---------------------------------------------------------------------------
   Elementor's frontend stylesheet carries a global image reset, roughly:

       .elementor img { height: auto; max-width: 100%; border-radius: 0;
                        box-shadow: none; border: none; }

   That selector is (0,1,1). Every image rule in sh-theme.css is ALSO (0,1,1)
   — `.sh-intro-image img`, `.sh-usp-single-img img`, `.sh-mosaic-item img`,
   `.sh-ba-slider img`. A specificity tie is broken by source order, and
   Elementor's sheet loads after ours, so Elementor wins every one of them.

   Two symptoms seen on the first real page, both from this single cause:
     - SH Intro's photo lost its border-radius and box-shadow (reset to 0/none).
     - SH USP Section's photo stopped filling its 4/3 frame, because
       `height: 100%` became `height: auto` — so the gradient card behind it
       showed as a thick blue box instead of a thin border.

   FIXED BY SPECIFICITY, NOT !important. Prefixing `.sh-page` takes these to
   (0,2,1), which beats Elementor's reset regardless of load order. !important
   would win too, but it would also outrank sh-theme.css's own responsive
   overrides — several of which target these same elements at narrow widths.
   Raising specificity keeps the theme's cascade intact and only displaces
   Elementor.

   These are verbatim restatements of sh-theme.css. When that file is
   regenerated from the theme, re-check these against it — a divergence here is
   silent and will look like a rendering bug in the widget.
   =========================================================================== */

/* B2 Intro — sh-theme.css:488 */
.sh-page .sh-intro-image img {
	width: 100%;
	max-width: 400px;
	border-radius: var(--sh-radius-lg);
	box-shadow: var(--sh-shadow-lg);
}

/* C4 single image — sh-theme.css:947. height:100% is what makes the photo
   cover the card instead of floating inside it. */
.sh-page .sh-usp-single-img img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	border-radius: var(--sh-radius-lg);
}

/* C5 mosaic — sh-theme.css:1024 */
.sh-page .sh-mosaic-item img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var(--sh-transition);
	display: block;
}

/* C3 before/after — sh-theme.css:845 and :860. Both images are absolutely
   positioned and must fill the slider; height:auto collapses the comparison. */
.sh-page .sh-ba-slider img {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* ⚠ RIGHT-PINNED, MATCHING sh-theme.css. The overlay is anchored right since
   2026-09-10; an editor copy still pinning the image left would slide the after
   frame as the handle moved, so the canvas and the front end would disagree
   about a drag. */
.sh-page .sh-ba-after-wrap img {
	position: absolute;
	top: 0;
	left: auto;
	right: 0;
	height: 100%;
	max-width: none;
	object-fit: cover;
}

/* C2 map — sh-theme.css:812. Elementor does not reset iframes today, but this
   is the same class of dependency and costs nothing to pin. */
.sh-page .sh-usp-map-wrap iframe {
	width: 100%;
	height: 100%;
	border: 0;
	display: block;
}


/* D7 trust item links — sh-theme.css:1797.
   Same class of defect as the image reset above, different element. Elementor
   ships `.elementor a { text-decoration: none; box-shadow: none; }` at (0,1,1),
   tying with the theme's rule and winning on load order. D7's Text field goes
   through inline_kses(), which permits <a>, so an editor's inline link would
   lose its underline and become colour-only — a WCAG 1.4.1 failure. No source
   instance has a link in a trust item, so a straight port never trips this;
   it is pinned because the next hand-written one would. */
.sh-page .sh-trust-item a {
	color: var(--sh-accent-bright) !important;
	font-weight: 600;
	text-decoration: underline;
}


/* D1 right-column image — sh-theme.css:1898 */
.sh-page .sh-why-choose-image img {
	width: 100%;
	max-width: 420px;
	border-radius: var(--sh-radius-lg);
	box-shadow: var(--sh-shadow-lg);
}

/* D4 answer links — sh-theme.css:1582 */
.sh-page .sh-faq-answer-content a {
	color: var(--sh-accent-bright) !important;
	font-weight: 600;
	text-decoration: underline;
}

/* Z1 overlay image — sh-theme.css:1155 */
.sh-page .sh-mosaic-lightbox img {
	max-width: 90vw;
	max-height: 85vh;
	object-fit: contain;
	border-radius: var(--sh-radius-lg);
	box-shadow: 0 20px 60px rgba(0,0,0,0.5);
	cursor: default;
}

/* ---------------------------------------------------------------------------
   THE SAME RESET vs THE rt-* HOME SECTIONS — 2026-08-13

   The nine home widgets went in without these restatements, so every one of
   them hit the identical cascade problem the block above exists to solve.
   Reported symptoms, all one cause:

     service tiles ragged        .rt-svc__ph img  height:100% → auto, so the
                                 photo no longer fills its 16/9 frame and each
                                 tile grew to its own photo's aspect ratio
     post card image blank       .rt-card__ph img, same, inside a 4/3 frame
     about + mailer logos wrong  height:64px / 40px → auto
     warranty badge squished     see the note on .rt-gp__badge below

   ⚠ .rt-gp__badge IS NOT A TIE. The class sits on the <img> itself, so the
   selector is (0,1,0) against Elementor's (0,1,1) — it loses on specificity,
   not on source order, and would lose even if our sheet loaded last. It is
   the one rule here that was never going to work without this file.

   Verbatim restatements of sh-theme.css, line numbers cited. A divergence
   here is silent and reads as a widget bug.
   --------------------------------------------------------------------------- */

/* Badge Strip — sh-theme.css:4214 */
.sh-page .rt-badge img {
	height: 64px;
	width: 64px;
	object-fit: contain;
	display: block;
	margin: 0 auto;
}

/* About Panel logo — sh-theme.css:4245 */
.sh-page .rt-about__logo img {
	display: block;
	width: 100%;
	max-width: 280px;
	height: 64px;
	object-fit: contain;
	margin: 0 auto;
}

/* About Panel badges — sh-theme.css:4248 */
.sh-page .rt-about__badges img {
	height: 64px;
	width: 64px;
	object-fit: contain;
}

/* Project Band + Post Cards share .rt-card — sh-theme.css:4310 */
.sh-page .rt-card__ph img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Door Hanger mailer logo — sh-theme.css:4379 */
.sh-page .rt-mailer__logo img {
	height: 40px;
	width: 100%;
	object-fit: contain;
	display: block;
}

/* Warranty Panel badge — sh-theme.css:4396. Specificity, not order — see above. */
.sh-page .rt-gp__badge {
	width: 180px;
	max-width: 60%;
	margin: 0 auto;
	display: block;
}

/* Service Tiles — sh-theme.css:4414 */
.sh-page .rt-svc__ph img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Areas Map embed — sh-theme.css:4466 */
.sh-page .rt-areas__map iframe {
	display: block;
	width: 100%;
	height: 300px;
	border: 0;
}

/* The theme's own responsive steps, restated at the same lifted specificity.
   Without these the base rules above would win at every width — raising
   specificity displaces Elementor but also outranks sh-theme.css's media
   queries, which is the trap the !important note at the top of this section
   warns about. */

@media (min-width: 700px) {
	/* sh-theme.css:4222 */
	.sh-page .rt-badge img {
		height: 88px;
		width: 88px;
	}
}

@media (min-width: 960px) {
	/* sh-theme.css:4406 */
	.sh-page .rt-gp__badge {
		width: 240px;
		max-width: 100%;
	}

	/* sh-theme.css:4469 */
	.sh-page .rt-areas__map iframe {
		height: 380px;
	}
}

/* ===========================================================================
   MISSING CONTENT KEEPS ITS SPACE. DELIBERATE ABSENCE COLLAPSES.
   ---------------------------------------------------------------------------
   ⚠ THIS BLOCK WAS REWRITTEN 2026-08-15 BECAUSE IT SAID SOMETHING BROADER THAN
   THE DECISION IT RECORDED, AND THE OVERSTATEMENT WENT ON TO MISLEAD.

   It used to read "REMOVED — SINGLE-COLUMN COLLAPSE, per Robbie", and argue
   from `:only-child, :has(), :empty → 0 occurrences in theme/02-CSS.html` that
   the port must never collapse a two-column grid at all. Robbie, on being shown
   it: "That isn't my note. I was trying to fix widgets not leaving space when
   its content is missing like with the warranty panel, but if you're selecting
   nothing, then that content is not missing because it's not meant to be
   included."

   THE RULE IS ABOUT INTENT, AND ONLY THE WIDGET KNOWS IT:

     content MISSING      a badge nobody set, an image left empty — something
                          was meant to be there. LEAVE THE SPACE. The original
                          does this: P-service-area renders a feature banner
                          with no icon boxes and simply leaves the right half
                          empty at desktop.

     absence CHOSEN       "Nothing — full-width copy" on SH Intro, "Full-width
                          media" on SH USP Section. Nothing is missing.
                          COLLAPSE, and say so with a modifier class.

   WHAT WAS ACTUALLY REMOVED, AND STAYS REMOVED: `.spr-single-col`, which
   collapsed a grid whenever a widget found its second column EMPTY — on
   .sh-why-choose-inner, .sh-feature-banner-inner and .rt-hero__grid. That is
   the first case above, and collapsing was the wrong answer to it. The three
   widget conditionals went with it. Do not reintroduce THAT.

   The selector census still stands and is still the reason not to reach for
   `:only-child` or `:has()`: CSS can see a missing child, and cannot see why it
   is missing. A modifier class carries the intent that the selector cannot.
   Both consumers do it that way — .sh-usp-section--fullwidth (sh-theme.css:1173,
   an archetype modifier the original defines) and .sh-intro--fullwidth (a port
   addition, below).

   ⚠ ONE KNOWN INCONSISTENCY, LEFT ALONE. SH USP Section also adds
   --fullwidth when its media branch renders NOTHING (class-spr-usp-section.php,
   `$full || '' === $media_html`), on the grounds that half-width text beside a
   blank column appears nowhere in the 28 source instances. By the rule above
   that is the MISSING case and should keep its space, as SH Warranty Panel now
   does. Flagged rather than changed: it is a live behaviour on gallery pages and
   the call is Robbie's, not this file's.
   =========================================================================== */

/* ===========================================================================
   Z1 ON THE EDITOR CANVAS
   ---------------------------------------------------------------------------
   .sh-mosaic-lightbox is display:none until .sh-active, so in the editor the
   widget is a zero-height element: nothing to see, nothing to click, and no way
   to answer the question an editor most needs answered — "is there already one
   on this page?" (the ids are page-unique, so a second one is a bug).

   Editor only. The front end must keep it hidden.
   =========================================================================== */

.elementor-editor-active .sh-page .sh-mosaic-lightbox {
	display: block;
	position: static;
	inset: auto;
	min-height: 44px;
	background: var(--sh-surface-warm);
	opacity: 1;
	visibility: visible;
	pointer-events: none;
}


/* Hero variants — same Elementor image reset as everything above. */
/* .rt-hero__bg — sh-theme.css:4139 */
.sh-page .rt-hero__bg {
	position:absolute;
	inset:0;
	width:100%;
	height:100%;
	object-fit:cover;
}


/* ===========================================================================
   FORM STATUS LINE — SUCCESS ONLY

   .spr-form-status is NEW markup: the original redirected on success and so
   never needed an inline "Thanks", but this form can stay on the page. It
   collapses to zero height when empty, so it adds no gap to a card that has
   never been submitted.

   ⚠ IT NO LONGER CARRIES ERRORS — 2026-08-14. It briefly did, and a rejected
   submit then read as a status update: same size, same weight, sitting below
   the button in a polite live region. Errors have their own node now, styled
   as the original styled them. See .scrm-form-error below.
   =========================================================================== */

.sh-page .spr-form-status {
	margin: 0.75rem 0 0;
	font-size: 0.9375rem;
	line-height: 1.5;
	color: var(--sh-primary);
}

.sh-page .spr-form-status:empty {
	margin: 0;
}


/* ===========================================================================
   FORM-LEVEL ERROR — stronkcrm-forms.js:459-462 VERBATIM

   The red box sp-form.js inserts immediately above the submit button, for both
   a failed client check and a server rejection. Same values as the original:
   the same three reds, the 7px radius, the 0.82rem/1.4 system stack.

   It reads as a PROBLEM rather than an update, which the status line above
   deliberately does not — that distinction is the whole reason there are two
   of them.
   =========================================================================== */

.sh-page .scrm-form-error {
	margin: 0 0 0.6rem;
	padding: 0.6rem 0.75rem;
	border-radius: 7px;
	background: #fdecea;
	border: 1px solid #f5c2bd;
	color: #8a1c10;
	font: 600 0.82rem/1.4 -apple-system, "Segoe UI", Roboto, system-ui, sans-serif;
}


/* ===========================================================================
   FIELD-LEVEL VALIDATION MESSAGES

   The phone and email `pattern` attributes have been on the inputs since the
   form was built, but a failing pattern only produced the browser's native
   bubble: untranslatable, unstyleable, gone on the next click, and on a phone
   frequently rendered nowhere near the field. sp-form.js now suppresses that
   bubble and inserts its own <p class="scrm-field-error"> after the field,
   marking the field itself with .scrm-bad. These are the rules for that.

   VALUES ARE stronkcrm-forms.js:459-486 VERBATIM — the same reds, radii, font
   stack and the caret. Only the delivery changed.

   ⚠ A STYLESHEET, NOT injectErrorStyles(). The original is a drop-in script
   with nowhere to put CSS, so it appends a <style> at runtime and carries a
   documented latching bug it had to fix — a single failed append, before
   <head> existed, permanently disabled styling for the page, and since the
   bubble is already suppressed by then the visitor got no feedback at all.
   This plugin has a stylesheet and already styles .spr-form-status here, so
   that whole failure mode is designed out rather than guarded against. It also
   means no inline <style> for a strict CSP to reject.

   Class names stay in the scrm- namespace because the markup they attach to
   is the original's, right down to the .scrm-hp honeypot wrapper the renderer
   still emits.
   =========================================================================== */

/* Sits directly under its own field with a caret pointing up at it, so there
   is never a question which field is being talked about. */
.sh-page .scrm-field-error {
	position: relative;
	margin: 0.4rem 0 0;
	padding: 0.45rem 0.6rem;
	border-radius: 6px;
	background: #fdecea;
	border: 1px solid #f5c2bd;
	color: #8a1c10;
	font: 600 0.78rem/1.35 -apple-system, "Segoe UI", Roboto, system-ui, sans-serif;
}

.sh-page .scrm-field-error::before {
	content: "";
	position: absolute;
	top: -5px;
	left: 14px;
	width: 8px;
	height: 8px;
	background: #fdecea;
	border-left: 1px solid #f5c2bd;
	border-top: 1px solid #f5c2bd;
	transform: rotate( 45deg );
}

/* The field itself is marked too, so a long form reads at a glance. !important
   because sh-theme.css sets the input border at (0,1,1) and this is (0,2,0) —
   a win on specificity, but the theme also restates the border inside :focus
   and :hover, which a bare declaration here would lose to while the visitor is
   still in the field they need to fix. */
.sh-page .scrm-bad {
	border-color: #d9534f !important;
	box-shadow: 0 0 0 2px rgba( 217, 83, 79, 0.14 ) !important;
}

/* ============================================================
   HEADER LOGO — 2026-08-11
   The same Elementor image reset that this file already restates
   ten rules for, in the one place those restatements could not
   reach: they are all prefixed .sh-page, and the header renders
   OUTSIDE .sh-page by design.

     sh-theme.css:2362  .sh-header-logo img { height: … }   (0,1,1)
     Elementor          .elementor img { height: auto }     (0,1,1)

   A tie, broken by source order, and Elementor's frontend CSS
   wins — so the logo rendered at its natural size.

   .sh-header in front lifts this to (0,2,1) and settles it
   without depending on load order. It reuses the same custom
   property, so the mobile override at sh-theme.css:2513 (which
   only redefines the variable, not the rule) still applies.

   max-width:none is deliberate: Elementor sets max-width:100%,
   which fights width:auto and squashes a wide logo in a narrow
   flex track. The theme sets no max-width at all.
   ============================================================ */
.sh-header .sh-header-logo img {
	height: var(--sh-header-logo-height-local, 110px);
	width: auto;
	max-width: none;
	display: block;
}

/* ============================================================
   HEADER FONT — 2026-08-11
   The header subsystem is fixed to Inter, deliberately: it does
   NOT follow the brand heading token. sh-theme.css now reads
   var(--sh-header-font, 'Inter') in all five places it used to
   hardcode 'Source Sans 3', so adding that token later needs no
   code change.

   This block exists only as a guard. Older token files carry

     .sh-header, ... { font-family: 'Montserrat' !important; }

   which was there to re-point the header at the brand heading
   font. Token CSS prints at wp_head priority 20 — AFTER this
   stylesheet — so on equal specificity it would still win.
   `html` in front lifts these to (0,1,1) and settles it without
   depending on load order.

   These use the same var, so they do not fight the future token
   either — they only outrank a stale literal.
   ============================================================ */
html .sh-usp-rotator-item,
html .sh-header,
html .sh-header-cta,
html .sh-topbar-tag,
html .sh-topbar-phone,
html .sh-header-nav-link,
html .sh-header-dropdown-link {
	font-family: var(--sh-header-font, 'Inter'), system-ui, -apple-system, sans-serif !important;
}

/* ============================================================
   NON-LINKING MENU ITEMS — 2026-08-11
   Not an Elementor fix; it belongs with the SPR PATCH in
   sh-behaviors.js that made `placeholder` menu items drop their
   href instead of pointing at "#".

   Removing the href stops the navigation, but the hover states
   still said "clickable" — the footer underlines and shifts to
   the accent colour, the header lifts its background. On an item
   that does nothing that is the same lie in a quieter voice.

   a:not([href]) targets exactly the patched items and nothing
   else, so no extra class or markup change is needed. Mirrors
   what sh-theme.css:4618-4621 already does for headings.
   ============================================================ */
/* cursor:text, not cursor:default. The arrow reads as "inert chrome"; the
   I-beam reads as "this is a piece of text", which is exactly what an unlinked
   area name is. It also matches the surrounding body copy, so moving the
   pointer across a mixed column of linked and unlinked items changes the
   cursor in a way that tells the truth about each one. */
.sh-page .sh-site-footer-list a:not([href]),
.sh-header-nav-link:not([href]),
.sh-header-dropdown-link:not([href]) {
	cursor: text;
}

.sh-page .sh-site-footer-list a:not([href]):hover {
	color: var(--sh-white) !important;
	text-decoration: none !important;
}

.sh-header-nav-link:not([href]):hover,
.sh-header-dropdown-link:not([href]):hover {
	background: transparent !important;
	color: var(--sh-header-text-local);
}

/* ===========================================================================
   .rt-btn — ICON SPACING THAT WORKS ON EITHER SIDE

   sh-theme.css:4180 spaces the icon inside an rt-btn with a margin:

       .rt-btn .lucide { … margin-right:6px; display:inline-block }

   A margin on one side only spaces a LEADING icon. Put the icon after the
   label and the 6px lands on its outer edge, against the button's own padding,
   and the gap between label and icon is whatever the single space character in
   SPR_Section_Widget::anchor() renders as. That is the reported symptom: the
   icon looks glued to the text in SH Form Hero and correct in SH Hero.

   IT IS THE ONLY BUTTON IN THE SYSTEM THAT DOES THIS. All eleven other classes
   handed to button_out() — sh-btn, sh-fp-readmore, sh-header-cta,
   sh-header-mobile-cta, sh-topbar-phone, sh-service-card-link,
   sh-thanks-while-you-wait-link and the sh-btn modifiers — are inline-flex with
   a gap, which is side-agnostic and already correct. Checked, 2026-08-14.

   THE ORIGINAL NEVER HIT IT: 62 rt-btn anchors across theme/, 59 with a leading
   icon and 0 with a trailing one. The port exposes it because
   add_icon_position() offers "Right of the label" on every button, rt-btn
   included. Undefined territory in the source rather than a contradiction of it
   — same shape as .sh-post-hero collapsing with no image.

   WHY A FLEX GAP RATHER THAN A SMARTER MARGIN. CSS cannot tell the two cases
   apart: in "Label <svg>" the svg is the only ELEMENT child, so it matches
   :first-child exactly as it does in "<svg> Label" — the thing that differs is
   the position of a text node, which no selector can see. The alternative was a
   port-only class on the anchor, which is markup, and markup is what the render
   diff compares. A gap needs no discriminator and no markup change, so every
   fixture is untouched.

   This is also just what the rest of the theme does. .sh-btn is inline-flex,
   centred, gap 0.5rem; this makes .rt-btn agree with it while keeping its own
   6px and its own block/inline-block switch at 600px.
   =========================================================================== */

.sh-page .rt-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
}

/* Otherwise a leading icon gets the gap AND the margin — 12px, not 6px. */
.sh-page .rt-btn .lucide {
	margin-right: 0;
}

/* Mirrors the source's own breakpoint (sh-theme.css:4181-4183), where .rt-btn
   stops being full-width and sits inline. width:auto still comes from that
   rule; this only restores the inline half of inline-block. */
@media ( min-width: 600px ) {
	.sh-page .rt-btn {
		display: inline-flex;
	}
}

/* ===========================================================================
   AN EYEBROW IS A LABEL ABOVE SOMETHING, NOT BESIDE IT

   THE SYMPTOM: on SH About Panel, a call card holding only an eyebrow and its
   button rendered the eyebrow to the LEFT of the button instead of above it.

   THE CAUSE: both eyebrow classes are `display:inline-block`, and nothing in
   the card stacks its children — .rt-topo__inner is a plain block. The source's
   one instance carries eyebrow + <h3> + <p> + button, and those two BLOCK
   elements do all the line-breaking. Strip it to eyebrow + button and an
   inline-block span is followed by an inline-flex .rt-btn, so the two share a
   line. Same family as the button-baseline fix above: inline-level boxes
   behaving like inline-level boxes, in a shape the source never produces.

   WHY THIS IS SAFE GLOBALLY, counted rather than assumed. Across theme/P-*.html
   an eyebrow is ALWAYS followed by a heading:

       .rt-eyebrow    h2 x30, h1 x2, h3 x1        33 of 33
       .sh-eyebrow    h2 x94, h1 x29             123 of 123

   Never anything inline, never beside anything. A block heading already breaks
   the line, so `display:block` renders all 156 existing instances identically.
   Neither rule carries a margin that behaves differently between inline-block
   and block, so spacing does not move either.

   THE THEME ALREADY PATCHES THIS FOUR TIMES — .sh-eyebrow.sh-text-center,
   .sh-thanks-hero, .sh-faq-image-side and .sh-callback-intro each force
   display:block in their own context. This is that fix, applied once, where the
   default is. Those four are LEFT IN PLACE: they also set margin-bottom, so
   removing them is separate cleanup and not free.

   It closes the same latent fault everywhere else a button can follow an
   eyebrow with no heading between — SH Final CTA with an empty heading, for
   one. A fix scoped to .rt-about__call would have left those.
   =========================================================================== */

.sh-page .rt-eyebrow,
.sh-page .sh-eyebrow {
	display: block;
}

/* ===========================================================================
   SH INTRO — "NOTHING" REALLY IS FULL WIDTH

   media_type offers Image, Google Business Profile card, and "Nothing —
   full-width copy". .sh-intro-inner is `1fr 1fr`, so with no media the copy was
   the only child, landed in column one, and rendered at HALF width beside an
   empty half. The control promised something the grid had never been asked for:
   none of the 24 .sh-intro sections in theme/ uses "Nothing".

   ⚠ INTENT, NOT ABSENCE. A badge nobody set on SH Warranty Panel is content
   MISSING and keeps its space; choosing "Nothing" here is content deliberately
   absent and collapses. CSS cannot tell those apart — it sees one missing child
   either way — so the widget decides and says so with a class, which is the
   same idiom .sh-usp-section--fullwidth already uses (sh-theme.css:1173).
   =========================================================================== */

.sh-page .sh-intro.sh-intro--fullwidth .sh-intro-inner {
	grid-template-columns: 1fr;
}

/* ===========================================================================
   SH WARRANTY PANEL — THE COPY STAYS IN COLUMN TWO WITH NO BADGE

   .rt-gp__inner is `grid-template-columns:240px 1fr` at >=960px, and its two
   children are the badge <img> then .rt-gp__copy. The img is omitted when no
   badge is set, which leaves the copy as the ONLY child — so it is auto-placed
   into column ONE and the text renders squeezed into the 240px badge column.

   ⚠ THIS IS NOT THE SINGLE-COLUMN-COLLAPSE QUESTION, and it does not reopen it.
   The block below records that decision: the original has no content-driven
   collapse, and where it renders a feature banner with no icon boxes it simply
   LEAVES THE RIGHT HALF EMPTY. That is still the behaviour, and it is what
   Robbie asked for here — "leaving a space for the image".

   The difference is which column goes empty. .sh-feature-banner-inner is
   `1fr 1fr`, so its one remaining child lands in a half-width column and looks
   correct by accident. .rt-gp__inner puts the NARROW column first, so the same
   auto-placement puts body copy in a 240px gutter. Naming the column is the
   whole fix; nothing collapses and nothing is conditional on content.

   ⚠ SCOPED TO THE TWO-COLUMN BREAKPOINT. Below 960px the grid is a single
   `1fr`, and `grid-column:2` there would create an implicit SECOND column and
   push the copy off the side. The media query mirrors sh-theme.css's own.
   =========================================================================== */

@media ( min-width: 960px ) {
	.sh-page .rt-gp__copy {
		grid-column: 2;
	}
}

/* ===========================================================================
   TWO BUTTONS SIDE BY SIDE, ALIGNED WHATEVER THEIR ICONS DO

   THE SYMPTOM: on a generated page, two buttons in one row sat at different
   heights. Matching icons looked fine; icon-left beside icon-right, or beside
   a button with no icon at all, did not.

   THE CAUSE IS THE INLINE-FLEX BASELINE RULE. The block above makes .rt-btn
   inline-flex, and .sh-btn already was. THE BASELINE OF AN INLINE-FLEX BOX IS
   THE BASELINE OF ITS FIRST FLEX ITEM. So a button whose first child is the
   icon takes its baseline from the ICON, and one whose first child is the label
   takes it from the TEXT. Put those two in a row that is not itself a flex
   container — .rt-hero__actions is nothing but margin-top:4px — and they line
   up on two different baselines.

   THE ORIGINAL NEVER HIT IT, for the same reason it never hit the icon-spacing
   bug fixed above: 59 of 62 rt-btn anchors in theme/ lead with their icon, none
   trails, and every .rt-redcta pair across all 28 templates is
   ['before','before']. Mixed positions are territory the source never enters.
   The port opens it because add_icon_position() offers "Right of the label" on
   every button — and it is reachable BY HAND in the editor, not only by the
   generator, which is why this is fixed here rather than constrained upstream.

   WHY vertical-align RATHER THAN MAKING THE ROWS FLEX. Making each row a flex
   container is more correct in principle and is a change per row — .rt-hero__actions,
   the CTA rows, the about panel — each with its own wrapping behaviour to
   re-check at every breakpoint. vertical-align takes the buttons out of
   baseline alignment entirely, which is the actual fault, in one declaration on
   the thing that is misaligned. No markup changes, so every fixture and the
   render diff are untouched.
   =========================================================================== */

.sh-page .rt-btn,
.sh-page .sh-btn {
	vertical-align: middle;
}

/* .rt-mailer__cta overrides display back to block at ALL widths on purpose
   (sh-theme.css:4386-4387 — "stays block at all widths"). Keep it full-width
   and stacked, but as a flex container so its icon is spaced the same way. */
.sh-page .rt-btn.rt-mailer__cta,
.sh-page .rt-mailer__cta {
	display: flex;
	width: 100%;
}

/* ===========================================================================
   THE OPENING-HOURS TABLE IS NOT A DATA TABLE

   THE SYMPTOM: on a real install the hours in SH Contact Split render with
   cell borders and a background, as a spreadsheet rather than as the two
   quiet columns the source has.

   THE CAUSE IS AN OMISSION ON OUR SIDE, NOT AN OVERRIDE ON THEIRS. sh-theme.css
   styles .sh-contact-info-hours (width, border-collapse, font-size) and its
   td (padding, colour, vertical-align) and sets NEITHER border NOR background
   on either. In the flat file that was correct and complete: a standalone page
   with no stylesheet but ours, where the UA default for a td is no border and
   no background. Nothing had to be reset because nothing was setting it.

   In WordPress there is a theme underneath us, and generic `table`/`td` styling
   is close to universal in themes. Those declarations are not losing to ours —
   there is nothing of ours to lose to. We never declare border or background
   here, so whatever the theme says is what applies. Confirmed by exclusion: no
   stylesheet in this plugin can reach this element. Every table rule we ship is
   class-scoped to .sh-decide-table or .sh-cmp-table in sh-blog-post.css.

   WHY IT IS A <table> AT ALL, since that is the obvious next question: the two
   columns are what keep the times aligned under ragged day labels, and it is
   what the source uses. Changing the markup to a definition list would move the
   render diff off the source for cosmetic reasons. See the note at the top of
   class-spr-contact-split.php.

   NOT !important, PER THE RULE AT LINE 122 OF THIS FILE. Specificity is enough
   and is the honest fix. Adding .sh-contact-info to the chain takes these to
   (0,3,0) and (0,3,1), which beats both a bare `td` and the usual themed form
   of it — `.entry-content table td`, (0,2,1) — regardless of load order. The
   tie our own (0,2,1) rule in sh-theme.css would have had is the thing being
   broken here.

   ⚠ IF BORDERS SURVIVE THIS, the theme is using !important or a deeper chain,
   and the fix needs the theme's name to be written correctly. Do not escalate
   to !important blind — find the rule first.
   =========================================================================== */

.sh-page .sh-contact-info .sh-contact-info-hours,
.sh-page .sh-contact-info .sh-contact-info-hours tr,
.sh-page .sh-contact-info .sh-contact-info-hours td,
.sh-page .sh-contact-info .sh-contact-info-hours th {
	border: 0;
	background: none;
	box-shadow: none;
}

/* ===========================================================================
   A GAP UNDER THE LAST ROW OF THE BLOG TABLES

   THE SYMPTOM: on a real install, the comparison and decision tables end with
   a band of empty white between the last row and the wrapper's bottom border.
   The flat-file page does not do this — confirmed against the original, so
   unlike the FAQ spacing this one IS a WordPress regression.

   NOTHING HAS DRIFTED, which is why this is here and not in sh-blog-post.css.
   That file is byte-identical to theme-reference/P-blog-post.css, and
   cmp_html()/decide_html() emit the same structure as P-blog-post.html. The
   fault is entirely in what WordPress puts underneath us.

   THE CAUSE IS A MARGIN WITH NOWHERE TO GO. Neither .sh-cmp-table nor
   .sh-decide-table declares a margin, in 02-CSS's blog sheet or here, so both
   take whatever the theme says — and `table { margin: 0 0 1.5em }` is stock in
   any Underscores-derived theme and common everywhere else. Standing alone in
   a browser the UA default for a table is no margin at all, so the flat file
   never had one to deal with.

   ⚠ AND THE WRAPPER IS WHY IT SHOWS AS A GAP RATHER THAN SPACE AFTER THE
   TABLE. .sh-cmp-table-wrap and .sh-decide-wrap are `overflow-x: auto` for
   horizontal scrolling on narrow screens, which makes each one a block
   formatting context. A BFC does not let its children's margins collapse
   through its edges, so the table's bottom margin is trapped INSIDE the
   wrapper — below the last row, above the wrapper's own 1px border. Remove
   the overflow and the same margin would simply push the next paragraph down
   and nobody would notice.

   Declaring the value is the fix: the tables are spaced by their wrappers'
   own `margin: 1.5rem 0 2rem`, which is what the source intended.
   =========================================================================== */

.sh-page .sh-cmp-table-wrap .sh-cmp-table,
.sh-page .sh-decide-wrap .sh-decide-table {
	margin: 0;
}

/* ===========================================================================
   AND THE SAME TABLES DRAW A FULL GRID WHERE THE SOURCE DRAWS RULES

   THIS IS THE HOURS TABLE AGAIN, one layer up. There the omission was total —
   no border, no background, anywhere. Here the sheet declares SOME sides and
   leaves the rest, which is harder to spot and produces a subtler wrong thing:
   not an obviously bordered table, a table with too many lines in it.

   WHAT sh-blog-post.css ACTUALLY SAYS, for both tables:

       thead th    nothing at all
       tbody th    border-top only
       tbody td    border-top and border-left only

   So the design is horizontal rules between rows, plus ONE vertical line
   separating the two comparison columns, and nothing around the header. Every
   other edge — every border-right, every border-bottom, the whole header box —
   was left at the UA default of no border, which standing alone in a browser
   is exactly right and under a theme is whatever `th, td { border: 1px solid }`
   says. That declaration is stock in Underscores-derived themes, and it is
   also what wp-block-library asserts for tables.

   ⚠ ZERO THE MISSING SIDES, DO NOT RESTATE THE DECLARED ONES. border-top and
   the td's border-left stay owned by sh-blog-post.css, so if the source's
   design changes there is still one place that decides it. This file only
   says "and nothing anywhere else", which is the part the source said by
   saying nothing.

   Scoped through the wrapper for (0,3,1), which clears a themed
   `.entry-content table th` at (0,2,1). The mobile card rules in
   sh-blog-post.css already set `border: none` on these cells, so they agree
   with this and nothing is fighting at any width.
   =========================================================================== */

.sh-page .sh-cmp-table-wrap .sh-cmp-table thead th,
.sh-page .sh-decide-wrap .sh-decide-table thead th {
	border: 0;
}

.sh-page .sh-cmp-table-wrap .sh-cmp-table tbody th,
.sh-page .sh-decide-wrap .sh-decide-table tbody th {
	border-right: 0;
	border-bottom: 0;
	border-left: 0;
}

.sh-page .sh-cmp-table-wrap .sh-cmp-table tbody td,
.sh-page .sh-decide-wrap .sh-decide-table tbody td {
	border-right: 0;
	border-bottom: 0;
}

/* ===========================================================================
   THE FIGURE AND THE PULL QUOTE LOSE THEIR MARGINS

   THE SYMPTOM: a blog figure sits flush against the paragraph beneath it, with
   none of the 2.5rem the source leaves under it.

   ⚠ THIS IS NOT THE OMISSION BUG ABOVE, AND THE FIX IS THE OTHER SHAPE. The
   hours table, the table borders and the table margins were all cases where we
   DECLARED NOTHING and the theme filled the vacuum; the fix was to declare.
   Here sh-blog-post.css declares `margin: 2rem 0 2.5rem` and DevTools shows it
   struck through with a computed 0 — the value is being beaten, not missed.
   Confirmed on the install, not inferred; and it is only worth writing down
   because the two look identical from the page and take opposite fixes.

   WHY IT TAKES !important, IN A FILE WHOSE RULE IS NOT TO USE IT. Our selector
   is `.sh-page .sh-figure`, (0,2,0). A theme's `figure { margin: 0 }` at
   (0,1,1) loses, and so does theme.json's `:root :where(figure)`. Something is
   winning anyway, which leaves either a three-class selector or !important —
   and raising ours to (0,3,0) only covers the first of those. !important
   covers both.

   THE DOCTRINE AT THE TOP OF THIS FILE PERMITS THIS, and its reasoning is what
   makes it safe rather than an exception being waved through: !important is
   refused there because it "would also outrank sh-theme.css's own responsive
   overrides — several of which target these same elements at narrow widths".
   Neither of these two margins has a responsive override, at any width, in any
   sheet we ship — checked, not assumed. There is nothing of ours left to
   outrank.

   ⚠ THE FIGURE ONLY. .sh-pullquote is a <blockquote> and was fixed here too on
   the reasoning that `figure, blockquote { margin: 0 }` is one line in every
   reset — Robbie checked, and the pull quote keeps its margins. So whatever
   wins is aimed at FIGURE specifically, not a generic element reset. Worth
   knowing if this ever needs chasing to its source: look for figure/image
   handling, not for a normalize block.

   Not applied to .sh-tldr or .sh-callout either: both are <div>, and nothing
   resets a div.

   ⚠ A VERBATIM RESTATEMENT OF sh-blog-post.css, like the image block above.
   When that file is regenerated from theme/P-blog-post.css, re-check this
   value against it — a divergence here is silent.
   =========================================================================== */

.sh-page .sh-figure {
	margin: 2rem 0 2.5rem !important;
}

/* ===========================================================================
   HEADER CHROME — INTERACTIVE STATES PINNED AGAINST THE THEME'S RESET
   ---------------------------------------------------------------------------
   ⚠ 2026-08-24. Reported by Robbie and traced by him in devtools to the
   `a:hover` / `a:active` colour rules in the ACTIVE THEME'S reset.css. This is
   not a port error — sh-theme.css matches 02-CSS.html exactly, and the flat
   original had no WordPress theme underneath it to compete with.

   WHY IT ONLY SHOWS ON HOVER, which is the whole diagnosis:

       .sh-topbar-phone       { color: #fff; }   (0,1,0)   loses to a:hover
       a:hover                { color: …;    }   (0,1,1)   WINS
       a                      { color: …;    }   (0,0,1)   loses

   The resting state is correct because a bare `a` is weaker than the class;
   the hover state is not, because `a:hover` is stronger. So the button looked
   right until you pointed at it.

   ⚠ THE FIRST CUT OF THIS BLOCK WAS TOO NARROW, TWICE. It looked only at
   `color`, and only at anchors, so it fixed .sh-topbar-phone and missed the
   hamburger — reported separately a few minutes later, same cause, different
   property and a different tag. The rule the scan should have applied, and
   now does: ANY appearance property (color, background, border-color) set on
   an <a> or <button> in the chrome at (0,1,0) or weaker, with no state rule
   of its own, loses to the theme's `tag:state` reset.

   Walking every header/topbar rule in sh-theme.css under that rule gives four
   declarations across three elements:

       .sh-topbar-phone        <a>       color        (topbar call button)
       .sh-header-mobile-cta   <a>       color        (CTA in the mobile drawer)
       .sh-header-mobile-cta   <a>       background
       .sh-header-hamburger    <button>  background    background: none

   The hamburger is the one that reads as a bug rather than a nit: its own
   rule says `background: none`, so a `button:hover` background from the reset
   has nothing to beat and the whole button fills with the theme's accent.

   ⚠ NONE OF THESE HAS AN INTERACTIVE STATE IN THE SOURCE. Checked against
   02-CSS.html: .sh-header-hamburger and .sh-header-mobile-cta have base rules
   and the `.is-open` transforms, and no :hover, :focus or :active at all. So
   pinning the base values IS the port — this restores "nothing happens", it
   does not invent a hover style.

   Not exposed, and deliberately left alone: .sh-header-cta and
   .sh-header-nav-link carry explicit hover colours at (0,2,0); .sh-header,
   .sh-header-dropdown and .sh-header-mobile-menu match the specificity shape
   but are <div>/<nav>; .sh-topbar-tag is a <span>. Nothing resets those tags.

   ⚠ NOT PINNED, AND NOT TRACED: .sh-header-hamburger also declares
   `border: none` at (0,1,0). A reset that sets a border on button:hover would
   win the same way, but no such rule has been OBSERVED — only the background
   was reported and confirmed. Left out rather than guessed at; if a border
   ever appears on hover, this is the line to add.

   ⚠ NO !important, AND NO `html` PREFIX. `a.sh-topbar-phone:hover` is (0,2,1),
   which already outranks a:hover on its own. The `html` idiom further up this
   file exists for rules that had to beat an equally specific competitor; using
   it here would overstate what the problem was.

   ⚠ #fff IS THE SOURCE'S OWN LITERAL, copied from .sh-topbar-phone's base rule
   in 02-CSS.html:4468, not a token. The buttons are accent-filled in every
   brand, so the text is white in every brand; tokenising it would invent a
   variable the original never had.

   :focus is included alongside :hover and :active. The reset does not name it,
   but a keyboard user reaching this button gets the same state a mouse user
   does, and leaving it out would make the two disagree.
   =========================================================================== */

a.sh-topbar-phone:hover,
a.sh-topbar-phone:focus,
a.sh-topbar-phone:active,
a.sh-header-mobile-cta:hover,
a.sh-header-mobile-cta:focus,
a.sh-header-mobile-cta:active {
	color: #fff;
}

/* The same var the base rule uses, so this changes the SPECIFICITY and not
   the value — the drawer CTA stays whatever the brand's header accent is. */
a.sh-header-mobile-cta:hover,
a.sh-header-mobile-cta:focus,
a.sh-header-mobile-cta:active {
	background: var(--sh-header-accent-local);
}

/* `none`, restated, because that is what the source says. */
button.sh-header-hamburger:hover,
button.sh-header-hamburger:focus,
button.sh-header-hamburger:active {
	background: none;
}
