/*
Theme Name: SINO FRESH
Author: Shandong SINO FRESH Pet Food Co., Ltd.
Text Domain: sinofresh
Version: 2.10.90
Requires at least: 6.0
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Description: Block theme (FSE) for SINO FRESH pet supplements. Visual system v2: single typeface (Inter 400/600/700), 6-colour palette — Ink #1C2B24 / Forest #1B4D3E / Mist #F3F6F4 / Paper #FFFFFF / Line #DCE2DF / Kiln #B54E0F (CTA only). Flat surfaces, hairlines instead of shadows, unified 6px radius.
*/

/* Fonts load from theme.json fontFace (assets/fonts/plex-{400,600,700}.woff2). */

/* === 0. Horizontal overflow guard ----------------------------------------
   A single element wider than the viewport means horizontal scrolling on
   every real phone. Clip at the root so nothing can push the page sideways;
   `clip` (not `hidden`) never creates a scroll container, so position:sticky
   keeps working. Pre-2022 browsers fall back to hidden via the first line. */
html,
body {
	overflow-x: hidden;
	overflow-x: clip;
}

/* === 1. Header navigation submenu (dropdown) ===
   WP's Navigation block has no submenu colour attributes (submenus inherit the
   nav text colour = card-white on a white popup -> invisible). */
.wp-block-navigation .wp-block-navigation__submenu-container {
	background-color: var(--wp--preset--color--card-white);
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	box-shadow: none;
	min-width: 180px;
}
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content a,
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
	color: var(--wp--preset--color--text-primary);
	font-weight: 400;
}
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item {
	border-radius: 6px;
	transition: background-color 0.15s ease;
}
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item:hover {
	background-color: var(--wp--preset--color--bg-light);
}

/* Keep header on one line: let the nav shrink instead of pushing the CTA out */
.wp-block-navigation {
	flex-shrink: 1;
	min-width: 0;
}

/* === 2. Tables === */
.wp-block-table {
	overflow-x: auto;
}
.wp-block-table table {
	border-collapse: collapse;
	width: 100%;
}
.wp-block-table th,
.wp-block-table td {
	border: 1px solid var(--wp--preset--color--border-light);
	padding: 10px 14px;
	text-align: left;
	vertical-align: top;
	font-size: 14px;
	line-height: 1.5;
}
.wp-block-table thead th {
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
	font-family: var(--wp--preset--font-family--heading);
	font-size: 13px;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	white-space: nowrap;
}
.wp-block-table tbody td:first-child {
	font-weight: 600;
	color: var(--wp--preset--color--text-primary);
}
.wp-block-table.is-style-stripes tbody tr:nth-child(odd) {
	background-color: var(--wp--preset--color--bg-light);
}
.wp-block-table.is-style-stripes {
	border-bottom: none;
}
/* Core's .is-style-stripes sets border-color:transparent on cells at (0,2,1);
   re-assert the token colour at higher specificity so the grid stays visible. */
.wp-block-table.is-style-stripes table th,
.wp-block-table.is-style-stripes table td {
	border-color: var(--wp--preset--color--border-light);
}

/* === 3. Fluent Forms ===
   (was Gravity Forms; appearance replicated for the GF→FF template switch.
   Baseline measured on the GF render at 2.10.80: button bg --cta, radius 6px,
   padding 14px/24px, full card width; text/email/tel inputs 48px tall,
   radius 6px, border --border-medium; selects self-sized; focus border --cta;
   16px row gap; 6px label margin. GF's phone-country-selector rules are gone
   with GF — FF phone fields are plain tel inputs.) */
.fluentform .ff-btn-submit,
.fluentform input[type="submit"],
.fluentform button[type="submit"] {
	background-color: var(--wp--preset--color--cta) !important;
	background-image: none !important;
	color: #FFFFFF !important;
	border: none !important;
	border-radius: 6px !important;
	padding: 14px 24px !important;
	width: 100% !important;
	font-family: var(--wp--preset--font-family--heading) !important;
	font-size: 15px !important;
	font-weight: 600 !important;
	cursor: pointer;
	transition: background-color 0.15s ease;
}
.fluentform .ff-btn-submit:hover,
.fluentform input[type="submit"]:hover,
.fluentform button[type="submit"]:hover {
	background-color: var(--wp--preset--color--cta-hover) !important;
	background-image: none !important;
}
.fluentform .ff-el-form-control {
	border: 1px solid var(--wp--preset--color--border-medium) !important;
	border-radius: 6px !important;
	padding: 12px 14px !important;
	font-size: 16px !important;
}
/* Selects: let the control size itself so the option text is never clipped
   (same fix the GF render needed — a fixed height starves the content box). */
select.ff-el-form-control {
	height: auto !important;
	line-height: normal !important;
	padding-top: 12px !important;
	padding-bottom: 12px !important;
}
/* Single-line inputs: 48px keeps the 44px touch-target minimum; height wins
   over padding so the browser centres the text vertically. */
input.ff-el-form-control[type="text"],
input.ff-el-form-control[type="email"],
input.ff-el-form-control[type="tel"] {
	height: 48px !important;
	padding-top: 0 !important;
	padding-bottom: 0 !important;
}
.fluentform input:focus,
.fluentform select:focus,
.fluentform textarea:focus {
	border-color: var(--wp--preset--color--cta) !important;
	outline: none;
}
/* Compact row rhythm: FF's default group margin and label spacing are airier
   than the GF render — 16px / 6px keeps the lead form on roughly one screen. */
.fluentform .ff-el-group {
	margin-bottom: 16px !important;
}
.fluentform .ff-el-input--label {
	margin-bottom: 6px !important;
}
/* WhatsApp fallback hint (Form 2; reimplemented on the Fluent Forms submit
   hook — see functions.php). Standalone class so it works inside FF's button
   wrapper, which is a flex row like GF's footer was. */
.fluentform .sf-gf-wa-hint {
	flex: 0 0 100%;
	width: 100%;
	margin: 0 !important;
	padding: 0 !important;
	font-family: var(--wp--preset--font-family--body) !important;
	font-size: 13px !important;
	font-weight: 400 !important;
	line-height: 1.5 !important;
	color: var(--wp--preset--color--text-secondary) !important;
	text-align: center;
}
/* Default success (confirmation) messages: FF renders them inside the form
   wrapper, which sits on the white .sf-form-card — pin the dark body color so
   the message never lands light-on-white. (The COA certificate result keeps
   its own .sf-cert-result styles, reimplemented with the FF hook.) */
.fluentform .ff-message-success {
	color: var(--wp--preset--color--text-primary);
}
/* Field labels: forms live inside white cards that sit on deep-green sections
   carrying textColor:card-white — the card sets its own background but never
   resets text colour, so FF labels inherit white-on-white. Pin the dark body
   colour at the wrapper level (same root cause / fix as .ff-message-success
   above). Labels keep FF's own weight/size; only the colour source changes. */
.fluentform {
	color: var(--wp--preset--color--text-primary);
}

/* === 4. Core search block (templates/search.html) === */
.wp-block-search__input {
	border: 1px solid var(--wp--preset--color--border-medium) !important;
	border-radius: 6px !important;
	padding: 10px 14px !important;
	font-size: 15px !important;
}
.wp-block-search__input:focus {
	border-color: var(--wp--preset--color--cta) !important;
	outline: none;
}

/* === 5. Dynamic post blocks: inner anchors follow the block's own colour ===
   theme.json paints every link Forest. Inside the Forest post header that makes
   the category tag and the author name invisible, so let these two inherit. */
.wp-block-post-terms a,
.wp-block-post-author-name a {
	color: inherit;
}

/* === 6. Focus visibility (WCAG 2.2 — never remove the focus ring) === */
a:focus-visible,
button:focus-visible,
.wp-element-button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
	outline: 2px solid var(--wp--preset--color--cta);
	outline-offset: 2px;
}

/* === 7. Content width ---------------------------------------------------
   WP does not emit the `is-layout-constrained` class for every top-level
   section of this theme's front page, so the content cap that theme.json
   declares (contentSize: 1200px) is re-asserted here at ZERO specificity:
   wherever WP does apply its own layout or alignment rules they still win,
   and inside this theme the 1364px full-width fallback that text was
   inheriting becomes a readable 1200px measure. */
:where(.wp-site-blocks > .wp-block-group > *) {
	max-width: var(--wp--style--global--content-size, 1200px);
	margin-inline: auto;
}
/* the FAQ list hangs off the same left edge as the section heading and
   subtitle: it adopts the very same content measure and centring box the
   headings use, so both edges line up to the pixel. The hairline rules and
   the +/− markers then land on the heading's right edge too. */
.sf-faq {
	/* FAQ lists read best at a narrow centred measure. The group's own WP
	   layout caps its children, but this rule caps the GROUP itself — without
	   it the group element stays at the 1200px global measure and left-aligned
	   headings above it stop lining up with the accordion's left edge. */
	max-width: 800px;
	margin-inline: auto;
}
/* centred-heading variant (quality page: the h2 lives inside the group and
   is centre-aligned) — keep that design's narrower centred measure */
.sf-faq:has(> .has-text-align-center) {
	max-width: 800px;
}

/* === 8. Card system v2 -------------------------------------------------
   Three treatments replace the "white rounded box" reflex that made every
   section look alike. A card is defined by its hairline, not by a fill or a
   drop shadow; the band behind it supplies the tone. Radius is unified at
   6px. The inline `border-radius:8px` and per-card padding were removed from
   the markup, so these rules are the single source of truth. */
.sf-card {
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	transition: border-color 0.2s ease, transform 0.2s ease;
}
/* Hover = a 2px lift + a darker hairline. No shadow (flat surfaces rule) and
   no colour fill, so a row of five cards stays calm when the pointer sweeps
   across it. The lift is declared on the card, never on its children.

   Edge-jitter guard: a card that lifts on hover oscillates when the pointer
   sits exactly on its edge (card moves up -> pointer "leaves" -> card drops
   -> pointer "enters" -> repeat). The transparent ::after extends the hover
   hit area 4px past the border, so the pointer stays inside the card's hit
   region after the 2px lift. z-index:-1 paints it behind the card's content,
   so links inside stay clickable; it is invisible by design. */
.sf-card,
.sf-tile {
	position: relative;
}
.sf-card::after,
.sf-tile::after {
	content: '';
	position: absolute;
	top: -4px;
	left: -4px;
	right: -4px;
	bottom: -4px;
	z-index: -1;
}
.sf-card:hover,
.sf-tile:hover {
	transform: translateY(-2px);
	border-color: var(--wp--preset--color--accent);
}
.sf-card--roomy { padding: 24px; }
.sf-card--quote { padding: 24px; }
.sf-card--flush { padding: 0; overflow: hidden; }

/* Stretched link: the title anchor covers the whole card via an inset
   pseudo-element, so image, title, and padding all activate the same link
   with a single tab stop. The card is position:relative (declared above);
   the -4px hover hit-area ::after sits at z-index:-1 behind the content, so
   this z-index:1 overlay never competes with it. The Read More affordance is
   a non-interactive <span> (its own styling kept below). */
.sf-card__title-link {
	color: inherit;
	text-decoration: none;
}
/* WP core sets :where(.wp-block-group.wp-block-group-is-layout-constrained)
   {position:relative}, which makes the card's inner content group the
   containing block for the title-link ::after — so inset:0 would cover only
   the text block and never the image slot above it. Reset that group to
   static here so the ::after resolves to .sf-card (position:relative above)
   and covers the whole card, image included. */
.sf-card--flush .wp-block-group-is-layout-constrained {
	position: static;
}
.sf-card__title-link::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
}
.sf-card__more {
	font-weight: inherit;
	color: var(--wp--preset--color--primary);
}
.sf-card__title-link:focus-visible {
	outline: none;
}
.sf-card__title-link:focus-visible::after {
	outline: 2px solid var(--wp--preset--color--brand-green);
	outline-offset: 2px;
}

/* Equal-height tour cards: the two Tour Options white cards sit in flex
   columns that stretch to equal height, but the cards themselves only grow
   to their own text. Pull them to 100% so the bottoms align. border-box is
   required — .wp-block-column is content-box and height:100% would otherwise
   overflow past the column by the card's padding. */
.sf-tour-card {
	height: 100%;
	box-sizing: border-box;
}

/* Cards stretch to their column so all card bottoms in a row align (the
   columns container is a flex row, so cards of unequal content height
   otherwise end at different baselines). Applies to both plain and
   stretch-aligned columns: Cooperation Models + OEM/ODM (--roomy) and
   Latest Articles (--flush). Plain .sf-card (Certifications) is excluded —
   its cards already align with each other. On mobile the columns stack and
   the column height becomes indefinite, so height:100% resolves to auto
   and stacking is unchanged. */
.wp-block-column > .sf-card--roomy,
.wp-block-column > .sf-card--flush {
	box-sizing: border-box;
	height: 100%;
}

/* Product tiles: full-bleed image (flush to the card border on top and
   both sides, no plinth letterboxing), copy block keeps its own padding.
   object-fit:cover lets the square render crop instead of showing gaps. */
.sf-tile {
	display: flex;
	flex-direction: column;
	height: 100%;
	padding: 0 0 18px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	transition: border-color 0.2s ease, transform 0.2s ease;
}
.sf-tile__media {
	margin: 0 0 14px !important;
	/* the media box is a fixed-size frame: flex must never stretch or shrink
	   it, otherwise engines that resolve aspect-ratio boxes differently
	   (WebKit/Safari) reflow the whole tile when the image loads */
	flex: 0 0 auto;
	/* top corners follow the card radius; the card clips overflow anyway */
	border-radius: 6px 6px 0 0;
	/* the plinth clips the 1.03 zoom so the render never spills past the tile */
	overflow: hidden;
}
/* Linked media (wp:image linkDestination:"custom") emits <figure><a><img></a></figure>.
   Core block-library sets `.wp-block-image > a { display: inline-block }`; an
   inline-block whose only child is block-level (our `.sf-tile__media img` is
   display:block) takes its baseline from its bottom margin edge, so the figure's
   line box keeps the strut descender below the render — measured 7.59px at 16px/1.6,
   a light band between the image and the card copy. Block-level the anchor and the
   media box is image-height again, so a linked tile is geometrically identical to
   the unlinked one it replaced. */
.sf-tile__media > a {
	display: block;
}
.sf-tile__media img {
	display: block;
	width: 100%;
	height: auto;
	/* The square frame lives on the IMG, not on the figure. WebKit mis-computes
	   an aspect-ratio box that is also a flex item with a percentage-height
	   img inside (figure grew to ~2x the column width while the img painted
	   at half of it -> a large blank band above/below the render). With the
	   ratio on the replaced element itself and no percentage heights anywhere,
	   Chrome, Safari and Firefox derive identical geometry. Safari < 15.4
	   (no aspect-ratio) falls back to the images' natural 1:1 ratio. */
	aspect-ratio: 1 / 1;
	object-fit: cover;
	transition: transform 0.3s ease;
}
.sf-tile:hover .sf-tile__media img {
	transform: scale(1.03);
}
/* the image bleeds, so the copy block carries the horizontal padding */
.sf-tile > h3,
.sf-tile > p {
	padding-left: 18px;
	padding-right: 18px;
}
.sf-tile h3 {
	margin: 0 0 6px;
	font-size: 16px;
	line-height: 1.3;
}
.sf-tile p {
	margin: 0;
}
/* the Learn More link sinks to the card foot so eight cards bottom-align */
.sf-tile p:last-child {
	margin-top: auto !important;
	padding-top: 12px;
}

/* === 9. Dosage grid: an even 4 x 2 catalogue ---------------------------
   The "lead tile spans two columns" experiment is gone: with an even count it
   only produced a ragged third row and ~200px of extra height. */
.wp-block-columns.sf-dosage-grid {
	display: grid !important;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 24px;
	align-items: stretch;
}
.wp-block-columns.sf-dosage-grid > .wp-block-column {
	margin: 0;
}

/* === 10. Statement strip: five claims on one ruled line -----------------
   "Formulated Clean" is a compliance statement, not a product grid, so it
   reads as a single ruled row instead of five centred cards. */
.wp-block-columns.sf-strip {
	display: grid !important;
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: 0;
}
.sf-strip > .wp-block-column {
	margin: 0;
	padding: 0 18px;
	border-left: 1px solid var(--wp--preset--color--border-light);
}
.sf-strip > .wp-block-column:first-child {
	padding-left: 0;
	border-left: none;
}
.sf-strip p {
	margin: 0;
}
/* each claim leads with a grass-green check mark (brand-green) in front of
   deep forest text: flex keeps the check on the same line as the text.
   The section headline is centred, so each claim centres inside its cell. */
.sf-strip .sf-claim {
	display: flex;
	align-items: flex-start;
	justify-content: center;
	gap: 8px;
	text-align: left;
}
.sf-claim__check {
	flex: 0 0 auto;
	color: var(--wp--preset--color--brand-green);
	font-size: 20px;
	font-weight: 700;
	line-height: 1;
	/* optically centre the glyph against the 16px first text line: a bare
	   line-height:1 span rides high next to a 1.6 line box */
	margin-top: 3px;
}

/* one-line commercial terms under the stat panel */
.sf-params {
	margin-top: 24px !important;
	letter-spacing: 0.01em;
}

/* === 11. Ruled panel: gapless cells for process and data ----------------
   Used by Quality Control (3 x 2), the enquiry process (4 + 3) and the export
   figures (1 x 4). Cells are separated by a hairline and nothing else, so the
   group reads as one table rather than as N cards. */
.wp-block-columns.sf-panel {
	display: grid !important;
	gap: 0;
	margin-bottom: 0;
}
.wp-block-columns.sf-panel--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.wp-block-columns.sf-panel--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* === 11a. Bare panel: process steps (Inquiry → After-Sales) --------------
   Each step's sf-cell carries an inline 2px accent border-top AND the panel
   base rule gives the column a 1px border-light border-top: two stacked
   lines. Bare means unframed, so the grey column border goes away and the
   accent line moves up onto the column, bridging the 30px column gaps so
   the 7 step markers read as one continuous process line. Row-start and
   row-end columns stay inside the container; below 781px the panel stacks
   to one column and the bridging collapses. */
.wp-block-columns.sf-panel--bare > .wp-block-column { border-top: 0; position: relative; }
.sf-panel--bare > .wp-block-column:has(> .sf-cell)::before {
	content: "";
	position: absolute;
	top: 0;
	left: -16px;
	right: -16px;
	border-top: 2px solid var(--wp--preset--color--accent);
}
.sf-panel--bare > .wp-block-column:nth-child(4n + 1):has(> .sf-cell)::before { left: 0; }
.sf-panel--bare > .wp-block-column:nth-child(4n):has(> .sf-cell)::before,
.sf-panel--bare > .wp-block-column:last-child:has(> .sf-cell)::before { right: 0; }
.sf-panel--bare .sf-cell { border-top-width: 0 !important; }
/* 11a-b. Mobile: the step number needs air below the bridged accent line.
   Plain-column pages (drops/liquids/fish-oil/dental-chews) get it from the
   group's inline spacing-20 padding-top (16px); the sf-panel--bare variant
   relied on column padding, which the R5 equal-height rule zeroes — so give
   the group its own 16px and both variants read identically. */
@media (max-width: 768px) {
	.sf-panel--bare .sf-cell { padding-top: 16px !important; }
}
.sf-panel > .wp-block-column {
	margin: 0;
	padding: 18px 0 20px;
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-panel--3 > .wp-block-column:not(:nth-child(3n + 1)) { padding-left: 20px; }
.sf-panel--3 > .wp-block-column:not(:nth-child(3n)) { padding-right: 20px; }
.sf-panel--4 > .wp-block-column:not(:nth-child(4n + 1)) { padding-left: 20px; }
.sf-panel--4 > .wp-block-column:not(:nth-child(4n)) { padding-right: 20px; }
.sf-panel--3:not(.sf-panel--bare) > .wp-block-column:not(:nth-child(3n)) {
	border-right: 1px solid var(--wp--preset--color--border-light);
}
.sf-panel--4:not(.sf-panel--bare) > .wp-block-column:not(:nth-child(4n)) {
	border-right: 1px solid var(--wp--preset--color--border-light);
}
/* bare variant: horizontal rules only plus a gutter. Used where the rows are
   not the same length (7 steps across 4 columns) — a vertical rule there would
   stop mid-table. */
.wp-block-columns.sf-panel--bare { column-gap: 30px; }
.sf-panel--bare > .wp-block-column { padding-right: 0; }
.sf-panel h3 {
	margin: 0 0 6px;
	font-size: 17px;
}
.sf-panel p {
	margin: 0;
}
.sf-panel > .wp-block-column > p:first-child {
	font-variant-numeric: tabular-nums;
}

/* === 12. Asset slots: honest, compact frames for assets not yet shot ----
   The template still holds real placeholders (the "待上传…" labels). They keep
   their place in the DOM but no longer reserve 400px of empty height; each
   variant is sized for the asset it will receive. */
.sf-slot {
	display: grid !important;
	place-items: center;
	border-radius: 4px;
	font-size: 12px;
	letter-spacing: 0.03em;
	text-align: center;
	color: var(--wp--preset--color--text-secondary);
}
.sf-slot p { margin: 0; }
.sf-slot--document,
.sf-slot--map {
	background-color: var(--wp--preset--color--bg-light);
}
.sf-slot--photo {
	background-color: var(--wp--preset--color--card-white);
}
.sf-slot--document { min-height: 88px; margin-bottom: 14px; }
.sf-slot--document img { width: 100%; height: auto; display: block; border-radius: 4px; }
.sf-slot--photo { min-height: 92px; margin-top: 14px; }
/* photo slots carry a real image now: bleed it edge-to-edge (no white frame
   left over from the old placeholder plinth) */
.sf-slot--photo img { border-radius: 0; }
.sf-slot--map { min-height: 260px; }
.sf-slot--video { min-height: 320px; }
/* Photo slots wrap the image in a 12px paragraph; the parser closes that <p>
   before the <figure>, so the empty <p> becomes the container's first child
   and the constrained-layout gap rule (:root :where(.is-layout-constrained) > *)
   charges the figure a 24px margin-top — a white strip above every photo.
   Slots are single-image frames; the photo must sit flush at the top. */
.sf-slot .wp-block-image { margin-top: 0; }
/* Panel columns carry unequal gutter padding (20/40/20), so width-driven
   image heights drift a few px across a row. Source photos are exactly 4:3,
   so pinning the ratio evens the row without any visible crop. */
.sf-panel .sf-slot--photo img { aspect-ratio: 4 / 3; object-fit: cover; }

/* avatar placeholders share the slot treatment but stay circular; the flex
   container collapses their paragraph to zero width, so the content is laid
   out with grid instead */
.sf-avatar {
	display: grid !important;
	place-items: center;
	overflow: hidden;
	padding: 0;
}
.sf-avatar p {
	width: 100%;
	font-size: 10px;
	line-height: 1.25;
}

/* === 13. FAQ accordion (core/details, pure CSS, no JS) ------------------
   One <details> per Q&A; the first ships with `open`. The summary row is
   the whole click target (min-height 44px for touch). The + / − marker is
   drawn with two pseudo-elements: the vertical bar rotates away when the
   item opens. Content stays in the DOM (SEO/AI) — details only hides it. */
.sf-faq summary {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	/* border-box keeps the declared min-height as the real hit area (44px
	   touch target) instead of 44 + padding */
	box-sizing: border-box;
	min-height: 44px;
	padding: 12px 0;
	cursor: pointer;
	list-style: none;
}
.sf-faq summary::-webkit-details-marker { display: none; }
.sf-faq summary h3 {
	margin: 0;
	font-size: 17px;
	font-weight: 600;
	color: var(--wp--preset--color--text-primary);
}
.sf-faq summary:hover h3 {
	color: var(--wp--preset--color--primary);
}
.sf-faq__icon {
	position: relative;
	flex: 0 0 auto;
	width: 18px;
	height: 18px;
}
.sf-faq__icon::before,
.sf-faq__icon::after {
	content: "";
	position: absolute;
	inset: 0;
	margin: auto;
	border-radius: 1px;
	background: var(--wp--preset--color--primary);
	transition: transform 220ms ease, opacity 220ms ease;
}
.sf-faq__icon::before { width: 18px; height: 2px; }
.sf-faq__icon::after  { width: 2px; height: 18px; }
.sf-faq details[open] .sf-faq__icon::after {
	opacity: 0;
	transform: rotate(90deg);
}
/* The constrained layout injects a 24px block-gap between sibling details,
   which read as a floating white band above every question. Zero it out and
   keep a tight 10px gutter; the hairline then hugs the question row and the
   whole block loses ~220px of height across eight items. */
/* The constrained layout caps every child of the FAQ group at the block's own
   800px contentSize and re-centres it (margin auto !important), which is what
   pushed the rows 200px right of the heading. Release both so the rows span
   exactly the same content box as the heading and subtitle above them. */
.sf-faq details {
	max-width: none !important;
	margin-inline: 0 !important;
	margin-block: 0;
	padding: 0;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sf-faq details + details {
	margin-block-start: 10px;
}
.sf-faq details:first-of-type {
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-faq details p {
	margin: 8px 0 14px;
	/* the list now runs the full content width; answers keep a readable
	   measure instead of stretching to 1200px */
	max-width: 820px;
	font-size: 14px;
	line-height: 1.6;
	color: var(--wp--preset--color--text-secondary);
}
@keyframes sf-faq-open {
	from { opacity: 0; transform: translateY(-4px); }
	to   { opacity: 1; transform: none; }
}
.sf-faq details[open] > p {
	animation: sf-faq-open 220ms ease;
}
@media (prefers-reduced-motion: reduce) {
	.sf-faq details[open] > p { animation: none; }
	.sf-faq__icon::before,
	.sf-faq__icon::after { transition: none; }
}

/* Flex rows whose layout class WP does not emit on this template: the CTA
   check rows (marker + text) and the article cover slots. */
.sf-row {
	display: flex;
	align-items: flex-start;
	gap: 12px;
}
.sf-row p { margin: 0; }
.sf-slot--cover {
	aspect-ratio: 4 / 3;
	width: 100%;
}

/* === 14. Section headings: eyebrow label replaces the decorative rule ---
   H2s used to carry a grass-green underline. The anchor is now an uppercase
   eyebrow label; where a section has one the underline is suppressed, and the
   sections that keep the rule get the restrained Forest version. */
.sf-eyebrow {
	/* vertical margins only. The horizontal ones belong to the layout layer
	   (constrained layout / the zero-specificity content-width shim); setting
	   them here clobbered that `auto` and hung the label at the band padding
	   edge, 82px left of the heading it introduces. */
	margin-block: 0 10px;
	font-size: 12px;
	font-weight: 600;
	line-height: 1.4;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--primary);
}
.sf-eyebrow.has-text-align-center {
	margin-right: auto;
	margin-left: auto;
}
h2.wp-block-heading::after {
	content: "";
	display: block;
	width: 28px;
	height: 2px;
	margin-top: 14px;
	background-color: var(--wp--preset--color--primary);
}
h2.wp-block-heading.has-text-align-center::after {
	margin-right: auto;
	margin-left: auto;
}
.sf-eyebrow + h2.wp-block-heading::after {
	content: none;
}
/* on a Forest band the eyebrow and the rule both have to go light */
.has-primary-background-color .sf-eyebrow {
	color: var(--wp--preset--color--card-white);
	opacity: 0.72;
}
.has-primary-background-color h2.wp-block-heading::after {
	background-color: rgba(255, 255, 255, 0.4);
}

/* === 14b. Header top bar ---------------------------------------------------
   Utility strip above the nav: hours + badges + social (7 icons) left, email
   right. 12px type, quiet colour; on phones only the email survives. */
.sf-topbar {
	row-gap: 6px;
}
.sf-topbar p {
	margin: 0;
}
.sf-topbar a {
	color: inherit;
	text-decoration: none;
}
.sf-topbar a:hover {
	text-decoration: underline;
}
/* inline-flex kills the old baseline hack: the 14px SVG no longer pokes
   above the 12px em box, icon and text share one optical centre */
.sf-topbar__email,
.sf-topbar__meta {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	line-height: 1;
}
.sf-topbar__email svg,
.sf-topbar__meta svg {
	width: 14px;
	height: 14px;
	flex-shrink: 0;
	display: block;
}
.sf-topbar__email a {
	font-size: 13px;
}
.sf-topbar-badges .sf-cert-badge {
	/* badges come from Site Settings; a row with a URL renders as an <a>,
	   without one as a <span> — both get the same pill */
	display: inline-block;
	margin-right: 6px;
	padding: 1px 8px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	letter-spacing: 0.05em;
}
.sf-topbar-badges a.sf-cert-badge:hover {
	color: var(--wp--preset--color--primary);
	border-color: var(--wp--preset--color--primary);
	text-decoration: none;
}
@media (max-width: 1023px) {
	/* hours / badges wrap onto a second line on narrow desktops;
	   two classes: must out-rank WP's `body .is-layout-flex { display:flex }`
	   (0,1,1) — the single-class selectors used to silently lose */
	.sf-topbar .sf-topbar-left {
		flex-wrap: wrap;
		row-gap: 4px;
	}
}

/* === 14c. Footer ----------------------------------------------------------
   Brand column + three nav columns; the nav columns are core/details so the
   same markup is an always-open heading on desktop and a tap accordion on
   phones. */
.sf-footer-grid {
	align-items: stretch;
}
.sf-footer .wp-block-column > p,
.sf-footer .wp-block-column > div {
	margin-top: 0;
}
.sf-footer-brand {
	letter-spacing: 0.06em;
}
/* social icon row: flex with a real gap — the old per-anchor margin-right
   (14px) stacked on top of the gap and double-spaced the row */
.sf-social {
	display: flex;
	align-items: center;
	gap: 12px;
}
.sf-social a {
	color: var(--wp--preset--color--card-white);
}
.sf-social svg {
	/* solid Simple-Icons silhouettes read visually heavier than the old
	   stroke set, so the row runs one step smaller (20 -> 18) */
	width: 18px;
	height: 18px;
	display: block;
}
@media (max-width: 768px) {
	.sf-social {
		gap: 10px;
	}
	.sf-social svg {
		width: 16px;
		height: 16px;
	}
}
/* Breathing room between the company blurb and the social icon row
   (specificity must beat the column-child margin reset above). */
.sf-footer .wp-block-column > p.sf-social {
	margin-top: 20px;
}
.sf-social a:hover {
	/* accent (#1B4D3E) equals the footer band here — dark on dark, invisible.
	   Brand green keeps the hover legible on the forest background. */
	color: var(--wp--preset--color--brand-green);
}
.sf-footer .sf-footcol {
	margin: 0 0 4px;
}
.sf-footcol summary {
	list-style: none;
	cursor: default;
	font-weight: 600;
	font-size: 13px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	padding: 6px 0;
	margin-bottom: 12px;
}
/* Keep each column heading visually grouped with its links: the WP default
   paragraph margin opens a 24px hole under the summary — close it. */
.sf-footcol .sf-footlinks {
	margin-top: 0;
}
.sf-footcol summary::-webkit-details-marker {
	display: none;
}
.sf-footlinks {
	display: flex;
	flex-direction: column;
	gap: 7px;
}
.sf-footlinks a {
	color: rgba(255, 255, 255, 0.78);
	text-decoration: none;
}
.sf-footlinks a:hover {
	color: var(--wp--preset--color--card-white);
	text-decoration: underline;
}
.sf-footcontact span {
	color: rgba(255, 255, 255, 0.6);
}
.sf-footer-legal p {
	margin: 0;
	color: rgba(255, 255, 255, 0.75);
}
.sf-legal-links span {
	margin: 0 10px;
	opacity: 0.4;
}
.sf-legal-links a {
	color: rgba(255, 255, 255, 0.75);
	text-decoration: none;
}
.sf-legal-links a:hover {
	color: var(--wp--preset--color--card-white);
}
@media (min-width: 782px) {
	/* desktop: the accordion stays open and the toggle is inert */
	.sf-footcol summary {
		pointer-events: none;
	}
}
@media (max-width: 781px) {
	.sf-footcol summary {
		cursor: pointer;
	}
	.sf-footcol summary::after {
		content: "+";
		float: right;
		font-size: 16px;
		font-weight: 700;
		line-height: 1;
	}
	.sf-footcol[open] summary::after {
		content: "\2212";
	}
}

/* === 15. Dark-band guard (transitional) ---------------------------------
   `accent` is Forest now, so an accent-coloured label on a Forest band would
   be invisible (site title in the header, hero labels on the 8 dosage pages).
   Flip those to white. A later markup pass replaces the classes outright and
   this rule can be dropped. */
.has-primary-background-color .has-accent-color,
.has-primary-background-color.has-accent-color {
	color: var(--wp--preset--color--card-white) !important;
}
/* exception: the nav-bar site title keeps its grass-green brand colour */
.has-primary-background-color .wp-block-site-title a {
	color: var(--wp--preset--color--brand-green) !important;
}

/* === 16. Hero: full-viewport 4-slide carousel ----------------------------
   The section is the stage; .sf-hero-track is the sliding strip (WP flex
   layout, gap 0, children stretch) holding four core/cover slides. JS only
   drives translateX; everything else is CSS. A left gradient scrim keeps
   white copy readable on any frame. Height = first screen minus header. */
.sf-hero-slider {
	position: relative;
	height: calc(100vh - 80px);
	min-height: 600px;
	max-height: 960px;
	overflow: hidden;
	background: var(--wp--preset--color--primary);
}
.sf-hero-track {
	/* the section's constrained layout caps children at content-size (1200px)
	   with forced auto margins — lift it so slides span the full stage */
	width: 100% !important;
	max-width: none !important;
	margin-inline: 0 !important;
	height: 100%;
	transition: transform 0.6s ease;
	will-change: transform;
}
.sf-slide {
	position: relative;
	flex: 0 0 100%;
	height: 100%;
	overflow: hidden;
}
.sf-slide img.wp-block-cover__image-background {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
/* left scrim: strongest where the copy sits, fading out to the right */
.sf-slide::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(90deg, rgba(18, 28, 23, 0.66) 0%, rgba(18, 28, 23, 0.38) 46%, rgba(18, 28, 23, 0.1) 100%);
	pointer-events: none;
}
.sf-slide .wp-block-cover__inner-container {
	position: relative;
	z-index: 1;
	width: 100%;
	height: 100%;
}
/* copy block: left-aligned, about half the stage wide */
.sf-slide__copy {
	width: min(620px, 52%);
	margin-left: clamp(24px, 5vw, 72px);
	height: 100%;
	justify-content: center;
}
.sf-slide__eyebrow {
	color: var(--wp--preset--color--brand-green);
}
.sf-slide__title {
	margin: 0 0 14px;
	font-size: clamp(30px, 4vw, 52px);
	font-weight: 700;
	line-height: 1.12;
	letter-spacing: -0.01em;
	color: #fff;
	text-shadow: 0 1px 24px rgba(0, 0, 0, 0.35);
}
h1.sf-slide__title {
	font-size: clamp(32px, 4.2vw, 56px);
}
.sf-slide__subtitle {
	margin: 0;
	max-width: 560px;
	font-size: 17px;
	line-height: 1.6;
	color: rgba(255, 255, 255, 0.92);
	text-shadow: 0 1px 12px rgba(0, 0, 0, 0.4);
}
/* CTA pair: one editable instance, pinned bottom-left like the copy */
.sf-hero__cta {
	position: absolute;
	left: clamp(24px, 5vw, 72px);
	bottom: clamp(28px, 5vh, 56px);
	z-index: 2;
	margin: 0;
}
.sf-hero__cta .wp-block-buttons {
	gap: 12px;
}
.sf-hero__cta .is-style-outline .wp-block-button__link {
	background: transparent !important;
	color: #fff !important;
	border: 1px solid rgba(255, 255, 255, 0.85);
}
.sf-hero__cta .is-style-outline .wp-block-button__link:hover {
	background: rgba(255, 255, 255, 0.14) !important;
}
/* arrows: white glyph on a translucent ink circle */
.sf-hero-btn {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	z-index: 2;
	display: grid;
	place-items: center;
	width: 46px;
	height: 46px;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: rgba(18, 28, 23, 0.5);
	color: #fff;
	cursor: pointer;
	transition: background 0.2s ease;
}
.sf-hero-btn:hover {
	background: rgba(18, 28, 23, 0.8);
}
.sf-hero-prev { left: 20px; }
.sf-hero-next { right: 20px; }
/* dots: bottom-centre, solid for the current slide */
.sf-hero-dots {
	position: absolute;
	bottom: 22px;
	left: 0;
	right: 0;
	z-index: 2;
	display: flex;
	justify-content: center;
	gap: 10px;
}
.sf-hero-dots button {
	position: relative;
	width: 10px;
	height: 10px;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.45);
	cursor: pointer;
	transition: background 0.2s ease, transform 0.2s ease;
}
/* invisible hit-area extension: visual dot stays 10px, touch target ≥32px */
.sf-hero-dots button::after {
	content: "";
	position: absolute;
	inset: -11px;
	border-radius: 50%;
}
.sf-hero-dots button:hover {
	transform: scale(1.2);
}
.sf-hero-dots button.is-active {
	background: #fff;
}

/* === 16b. Slider autoplay progress (home hero) =============================
   3px track hugging the bottom edge of .sf-hero-slider, below
   the dots (22px/12px) so the two never collide. The green bar is driven by
   transform:scaleX from hero-slider.js rAF loop — same
   visual language as the article reading bar (.sf-progress, section 53) but
   absolutely positioned inside the stage. Hidden entirely when the visitor
   prefers reduced motion (JS skips driving it too). */
.sf-slider-progress {
	position: absolute;
	bottom: 0;
	left: 0;
	right: 0;
	height: 3px;
	background: rgba(255, 255, 255, 0.15);
	z-index: 10;
	pointer-events: none;
}
.sf-slider-progress__bar {
	height: 100%;
	background: #5ab735;
	transform-origin: left center;
	transform: scaleX(0);
	will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
	.sf-slider-progress { display: none; }
}

/* === 17. (retired) Stat panel -> section 28 ------------------------------
   The six-cell spec grid was folded into the front-page stat bar; cell
   rules, column counts and the 3% hover now live in section 28. */
/* === 18. Flat surfaces: no shadows anywhere ---------------------------- */
.wp-block-group,
.wp-block-column,
.wp-block-columns,
.wp-block-cover,
.wp-block-image img {
	box-shadow: none;
}
.wp-block-button__link,
.wp-element-button {
	box-shadow: none;
	text-decoration: none;
}

/* === 19. Tabular figures for specification data ------------------------ */
.wp-block-table table,
.sf-num {
	font-variant-numeric: tabular-nums;
}

/* === 20. Responsive ----------------------------------------------------
   Panels keep their desktop column count down to 781px — at that width a
   3- or 4-column panel is still legible, and not reflowing avoids re-drawing
   the row-aware rules. Below 781px everything collapses to one column and the
   column-aware rules are switched off, which is why the two `!important`
   declarations below are intentional. */
@media (max-width: 1023px) {
	.sf-eyebrow {
		letter-spacing: 0.06em;
	}
	.wp-block-columns.sf-dosage-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}
@media (max-width: 781px) {
	.wp-block-columns.sf-dosage-grid {
		grid-template-columns: minmax(0, 1fr);
		gap: 16px;
	}
	/* below 781px the strip reflows to a wrapping grid (2-up on a phone) */
	.wp-block-columns.sf-strip {
		grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
		row-gap: 12px;
	}
	.sf-strip > .wp-block-column {
		padding: 0;
		border-left: none;
		border-top: none;
	}
	.wp-block-columns.sf-panel--3,
	.wp-block-columns.sf-panel--4 {
		grid-template-columns: minmax(0, 1fr);
	}
	.sf-panel > .wp-block-column {
		padding: 16px 0 !important;
		border-right: none !important;
	}
	.wp-block-columns.sf-panel--bare {
		column-gap: 0;
	}
	/* single column: no gaps to bridge, keep the accent line inside */
	.sf-panel--bare > .wp-block-column:has(> .sf-cell)::before {
		left: 0;
		right: 0;
	}
	/* hero keeps the interactive slider on phones (2026-09: retired the
	   static-first-slide degradation) — dots + touch swipe replace the
	   arrows, which stay hidden on touch */
	.sf-hero-slider {
		height: 74vh;
		min-height: 460px;
		touch-action: pan-y;
	}
	.sf-hero-btn {
		display: none;
	}
	.sf-slide__copy {
		width: auto;
		margin-inline: 20px;
		padding-bottom: 96px;
	}
	.sf-hero__cta {
		left: 20px;
		right: 20px;
		bottom: 20px;
	}
	/* mobile: centre the hero button pair (home + dosage heroes); the copy
	   above stays left-aligned and desktop keeps the bottom-left pin */
	.sf-hero__cta .wp-block-buttons,
	.sf-hero-inner .wp-block-buttons {
		justify-content: center;
	}
	/* centred buttons fill the width where the dots used to live — drop the
	   dots to their own strip below the pair (still centred, follows them) */
	.sf-hero-dots {
		bottom: 6px;
	}
	.sf-slide__subtitle {
		font-size: 15px;
	}
	.sf-slot--map { min-height: 200px; }
	.sf-slot--video { min-height: 220px; }
	.sf-card--roomy,
	.sf-card--quote {
		padding: 18px;
	}
}
@media (max-width: 768px) {
	.wp-block-table thead th {
		white-space: normal;
	}
	.wp-block-table th,
	.wp-block-table td {
		padding: 8px 10px;
		font-size: 13px;
	}
	h2.wp-block-heading::after {
		margin-top: 12px;
	}
}

/* === 21. Motion: hover feedback and scroll reveal ----------------------
   Two mechanisms, both cheap: `:hover` + `transition` in CSS for pointers,
   and one IntersectionObserver in assets/js/interactions.js for the way in.
   Nothing here animates layout — only `transform` and `opacity`, both
   composited. No library, no scroll listener, no `will-change`.

   21a. Card media zoom. The zoom lives on the image, the clip on its figure,
   so the card's own box (and any focus ring inside it) is never cut off. */
.sf-card img {
	transition: transform 0.3s ease;
}
.sf-card .wp-block-image,
.sf-tile .wp-block-image {
	overflow: hidden;
}
.sf-card:hover img {
	transform: scale(1.03);
}

/* 21b. Buttons: 150ms, colour + a 2% swell. Filled buttons darken to Kiln
   hover (declared in theme.json); the outlined secondary buttons have an
   inline `background-color:transparent`, which only `!important` can beat —
   they take the Forest tint instead of the dark fill. */
.wp-block-button__link {
	transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.wp-block-button__link:hover,
.wp-block-button__link:focus-visible {
	transform: scale(1.02);
}
.wp-block-button__link.has-border-color:hover,
.wp-block-button__link.has-border-color:focus-visible {
	background-color: var(--wp--preset--color--cta-selected-bg) !important;
}

/* 21b-2. Outlined secondary buttons: the "View All …" buttons used to carry
   their whole look as inline styles, so each copy drifted on its own. One
   class now holds the declarations the button blocks printed inline. The
   hover tint still comes from the has-border-color rule above, and the
   .wp-block-button prefix outspecifies the theme.json element button, whose
   CTA-orange fill would otherwise win the tie on source order. */
.wp-block-button .sf-btn-outline {
	border: 2px solid var(--wp--preset--color--primary);
	border-radius: 6px;
	background-color: transparent;
	color: var(--wp--preset--color--primary);
	font-weight: 600;
}

/* 21c. Header navigation. The nav sits on the Forest band, so WP's global
   link hover (dark ink + underline) would paint the item invisible. White
   text, and the underline is drawn with a border instead so it cannot shift
   the baseline of a 15px item. */
.wp-block-navigation .wp-block-navigation-item__content {
	transition: color 0.15s ease, border-color 0.15s ease;
	border-bottom: 1px solid transparent;
	padding-bottom: 2px;
}
.wp-block-navigation .wp-block-navigation-item__content:hover,
.wp-block-navigation .wp-block-navigation-item__content:focus-visible {
	color: var(--wp--preset--color--card-white);
	border-bottom-color: currentColor;
	text-decoration: none;
}
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content:hover {
	border-bottom-color: transparent;
}
/* Dropdown items live on the light popup: the global white-text hover above
   paints them invisible (white on near-white). Higher specificity keeps the
   dark ink on hover. Contrast: #1B4D3E on #F3F6F4 = 9.6:1 (WCAG AAA). */
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content:hover,
.wp-block-navigation .wp-block-navigation__submenu-container .wp-block-navigation-item__content:focus-visible {
	color: var(--wp--preset--color--primary);
	text-decoration: none;
}
/* Mobile overlay menu: WP paints the modal white when the nav block itself
   has no background class, while the nav's card-white text bleeds in — white
   on white, unreadable. The doubled .is-menu-open outranks WP's default. */
@media (max-width: 781px) {
	.wp-block-navigation:not(.has-background) .wp-block-navigation__responsive-container.is-menu-open.is-menu-open {
		background-color: var(--wp--preset--color--primary);
	}
	.wp-block-navigation .wp-block-navigation__responsive-container.has-modal-open .wp-block-navigation-item__content,
	.wp-block-navigation .wp-block-navigation__responsive-container.has-modal-open .wp-block-navigation-item__content a {
		color: var(--wp--preset--color--card-white);
	}
	.wp-block-navigation__responsive-container.has-modal-open .wp-block-navigation__responsive-container-close {
		color: var(--wp--preset--color--card-white);
	}
	.wp-block-navigation .wp-block-navigation__responsive-container.has-modal-open .wp-block-navigation__submenu-container {
		background-color: rgba(255, 255, 255, 0.08);
		border: none;
	}
	.wp-block-navigation .wp-block-navigation__responsive-container.has-modal-open .wp-block-navigation__submenu-container .wp-block-navigation-item__content a,
	.wp-block-navigation .wp-block-navigation__responsive-container.has-modal-open .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
		color: var(--wp--preset--color--card-white);
	}
}

/* 21d. Scroll reveal. `.sf-motion` and `.sf-pending` are both added by the
   script, and only to sections that start below the fold — so the hero, the
   LCP element, is never hidden, and a browser without JS (or with reduced
   motion) simply renders the page as before. Desktop only, by the script. */
.sf-motion .wp-site-blocks > .wp-block-group {
	transition: opacity 0.4s ease, transform 0.4s ease;
}
.sf-motion .wp-site-blocks > .wp-block-group.sf-pending {
	opacity: 0;
	transform: translateY(20px);
	transition: opacity 0.4s ease, transform 0.4s ease;
}

/* 21e. Reduced motion: honour the OS setting instead of animating fast. */
@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;
	}
	.sf-card:hover,
	.sf-tile:hover,
	.wp-block-button__link:hover,
	.wp-block-button__link:focus-visible {
		transform: none;
	}
	/* later same-specificity hover rules would win on source order —
	   assert with !important instead */
	.sf-statbar__cell:hover .sf-statbar__num {
		transform: none !important;
	}
	/* the stagger classes are JS-only, but never leave a cell hidden */
	.sf-statbar.sf-stagger .sf-statbar__cell {
		opacity: 1;
		transform: none;
	}
	.sf-motion .wp-site-blocks > .wp-block-group.sf-pending {
		opacity: 1;
		transform: none;
	}
}

/* === 22. Client-stories marquee: one row, drifting right-to-left --------
   The track holds TWO copies of the six-card group; animating it by
   exactly -50% of its own width lands the second copy where the first
   started, so the loop is seamless with zero JS. Each group carries its
   own trailing gap (padding-right) so -50% stays exact. */
.sf-marquee {
	overflow: hidden;
	/* bleed to the section edges so cards glide out of view, not into padding */
	margin-top: var(--wp--preset--spacing--60, 40px);
}
/* full-bleed escape hatch: WP's constrained layout forces `margin:auto
   !important` on section children, so the bleed needs same-weight
   declarations at higher specificity (and a lifted max-width, otherwise
   the negative margins have nothing to bleed into). */
.wp-site-blocks .sf-marquee {
	margin-inline: calc(50% - 50vw) !important;
	max-width: none !important;
}
.sf-marquee__track {
	display: flex;
	width: max-content;
	animation: sf-marquee-left 90s linear infinite;
	will-change: transform;
}
.sf-stories__group {
	display: flex;
	align-items: stretch;
	gap: 24px;
	padding-right: 24px;
}
@keyframes sf-marquee-left {
	from { transform: translateX(0); }
	to { transform: translateX(-50%); }
}
/* Pause while the pointer is over the strip (covers the cards too). */
.sf-marquee:hover .sf-marquee__track,
.sf-marquee:focus-within .sf-marquee__track {
	animation-play-state: paused;
}

/* Cards: fixed width keeps rhythm; flex column + stretch equalises height
   so short and long quotes share one top and bottom edge. */
.sf-story {
	display: flex;
	flex-direction: column;
	flex: 0 0 auto;
	width: 340px;
	margin: 0;
	text-decoration: none;
	color: inherit;
}
.sf-story h3 {
	margin: 0 0 2px;
	font-size: 16px;
}
.sf-story__role {
	margin: 0 0 12px;
	font-size: 14px;
	color: var(--wp--preset--color--text-secondary);
}
.sf-story__quote {
	margin: 0 0 14px;
	font-size: 16px;
	font-style: italic;
	font-weight: 400;
	color: var(--wp--preset--color--text-primary);
}
.sf-story__type {
	margin: auto 0 0;
	font-size: 12px;
	color: var(--wp--preset--color--text-secondary);
}
/* Initials avatar: a quiet plinth until the real headshots are uploaded. */
.sf-story__avatar {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 56px;
	height: 56px;
	margin-bottom: 14px;
	border-radius: 999px;
	background: var(--wp--preset--color--bg-light);
	color: var(--wp--preset--color--accent);
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 0.04em;
}
/* Keyboard users get the same affordance pointer users have. */
.sf-story:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
}
/* No lift on hover: the marquee clips at its own top edge (overflow:hidden,
   cards sit flush with it), so translateY(-2px) cuts off the top border.
   Border stays green via .sf-card:hover; a soft shadow carries the affordance. */
.sf-story:hover {
	transform: none;
	box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

@media (max-width: 1023px) {
	.sf-story { width: 300px; }
}
@media (max-width: 781px) {
	.sf-marquee { margin-top: 24px; }
	.sf-stories__group { gap: 16px; padding-right: 16px; }
	.sf-story { width: 260px; }
	.sf-marquee__track { animation-duration: 60s; }
}

/* === 23. Sticky header -----------------------------------------------------
   The nav is a template part only ~119px tall, so position: sticky has no
   room to engage (sticky is confined to its parent). Instead sticky-header.js
   toggles .is-sticky (fixed, ~60px, solid forest, hairline shadow) once the
   100px sentinel leaves the viewport, and marks the part .sf-header-stuck so
   a matching spacer keeps the page from jumping. The bar's block padding is
   inline, hence the !important overrides. */
.sf-header-sentinel {
	position: absolute;
	top: 0;
	left: 0;
	height: 100px;
	width: 1px;
	pointer-events: none;
}
.sf-header {
	transition: padding 250ms ease, box-shadow 250ms ease, background-color 250ms ease;
}
.sf-header.is-sticky {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 999;
	padding-top: 6px !important;
	padding-bottom: 6px !important;
	background-color: var(--wp--preset--color--primary) !important;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
/* reserve the bar's in-flow height while it is fixed */
.sf-header-stuck {
	padding-bottom: 80px;
}
/* keep the bar below the admin bar for logged-in views */
body.admin-bar .sf-header.is-sticky {
	top: 32px;
}
/* Anchor targets for the smart-scroll Get a Quote CTAs (quote-cta.js):
   land the form below the 61px sticky bar on desktop. On phones the
   html { scroll-padding-top: 64px } rule in section 27p already covers
   the 57px bar, so the targets zero their own margin there. */
#inquiry-form,
#quote,
#booking-form {
	scroll-margin-top: 96px;
}
@media (prefers-reduced-motion: reduce) {
	.sf-header {
		transition: none;
	}
}

/* === 23b. Brand logo (header nav + footer) --------------------------------
   Both slots render core/site-logo, so ops swap the logo in Appearance ->
   Editor (site identity) without touching templates. The seeded PNG is the
   tagline-free render of the source .ai (white wordmark + #5AB735 mark);
   height-capped so any future logo stays legible at bar scale. */
.sf-logo {
	margin: 0;
	line-height: 0;
	display: flex;
	align-items: center;
}
.sf-logo img {
	height: 34px;
	width: auto;
	max-width: 200px;
	display: block;
	object-fit: contain;
}
.sf-footer-logo {
	margin: 0 0 12px;
	line-height: 0;
	display: flex;
	align-items: center;
}
.sf-footer-logo img {
	height: 48px;
	width: auto;
	max-width: 260px;
	display: block;
	object-fit: contain;
}
@media (max-width: 768px) {
	.sf-logo img {
		/* the nav CTA is gone on phones, so the wordmark gets its room back */
		height: 34px;
		max-width: 190px;
	}
}

/* === 24. Generated asset imagery -----------------------------------------
   The former text placeholders now hold AI-generated stand-in photos; the
   slot frames stay but the image carries the layout. Adds a play-button
   overlay for video covers and a circular fit for client-story avatars. */
.sf-slot img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 6px;
}
.sf-slot--map img {
	border-radius: 8px;
}
.sf-slot--video {
	padding: 0;
}
.sf-slot--video img {
	border-radius: 4px;
}
/* Video cover: 16:9 poster + a click-to-load YouTube player.
   It used to be a 400px-tall green card with a 512x288 poster floating in the
   middle of it. Now the poster IS the card: a fixed 16:9 box, so the layout
   never moves when the image lands (no CLS) and never letterboxes (object-fit).
   Core pads every `has-background` group by 20px 38px — cleared here or the
   poster cannot reach the edges. about.js builds the iframe on click only, so
   until then the page makes zero requests to YouTube. */
.sf-video-cover {
	position: relative;
	overflow: hidden;
	aspect-ratio: 16 / 9;
	padding: 0;
}
.sf-video-cover .wp-block-image {
	position: absolute;
	inset: 0;
	display: block;
	margin: 0;
}
.sf-video-cover img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: 8px;
}

/* The whole 16:9 frame is the hit area, far beyond the 44px touch minimum;
   the visible ring is drawn by the span inside it. */
.sf-video__play {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100%;
	padding: 0;
	border: 0;
	background: transparent;
	cursor: pointer;
}
.sf-video__play-icon {
	position: relative;
	width: 76px;
	height: 76px;
	border-radius: 999px;
	background: rgba(27, 77, 62, 0.8);
	transition: background-color 0.25s ease, transform 0.25s ease;
}
.sf-video__play-icon::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-36%, -50%);
	border-left: 24px solid #fff;
	border-top: 14px solid transparent;
	border-bottom: 14px solid transparent;
}
.sf-video__play:hover .sf-video__play-icon,
.sf-video__play:focus-visible .sf-video__play-icon {
	background: rgba(27, 77, 62, 0.95);
	transform: scale(1.06);
}
.sf-video__play:focus-visible {
	outline: 3px solid #fff;
	outline-offset: -8px;
}

/* playing state: poster and button step aside for the player */
.sf-video-cover.is-playing .wp-block-image,
.sf-video-cover.is-playing .sf-video__play {
	display: none;
}
.sf-video__iframe {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
	border-radius: 8px;
}

@media (max-width: 768px) {
	.sf-video__play-icon {
		width: 64px;
		height: 64px;
	}
	.sf-video__play-icon::after {
		border-left-width: 20px;
		border-top-width: 12px;
		border-bottom-width: 12px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.sf-video__play-icon {
		transition: none;
	}
	.sf-video__play:hover .sf-video__play-icon,
	.sf-video__play:focus-visible .sf-video__play-icon {
		transform: none;
	}
}
.sf-story__avatar img {
	width: 100%;
	height: 100%;
	border-radius: 999px;
	object-fit: cover;
}

/* === 15. Breadcrumbs — embedded at the top of each inner-page hero === */
.sf-breadcrumb {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 4px 8px;
	margin: 0 0 18px;
	font-size: 13px;
	line-height: 1.4;
	color: rgba(255, 255, 255, 0.7);
}
.sf-breadcrumb__crumb {
	color: rgba(255, 255, 255, 0.7);
	text-decoration: none;
	transition: color 0.2s ease;
}
.sf-breadcrumb a.sf-breadcrumb__crumb:hover {
	color: #ffffff;
	text-decoration: underline;
}
.sf-breadcrumb__current {
	color: rgba(255, 255, 255, 0.9);
}
.sf-breadcrumb__sep {
	color: rgba(255, 255, 255, 0.45);
}
@media (prefers-reduced-motion: reduce) {
	.sf-breadcrumb__crumb { transition: none; }
}
@media (max-width: 767px) {
	.sf-breadcrumb { font-size: 12px; margin-bottom: 14px; }
	/* 3-level trails fold to ← Mid / Current */
	.sf-breadcrumb--d3 .sf-breadcrumb__crumb--home,
	.sf-breadcrumb--d3 .sf-breadcrumb__sep--home { display: none; }
	.sf-breadcrumb--d3 .sf-breadcrumb__crumb--mid::before { content: "←\00a0"; }
	/* 2-level trails keep Home as ← Home */
	.sf-breadcrumb--d2 .sf-breadcrumb__crumb--home::before { content: "←\00a0"; }
	/* 4-level trails (Home / Products / Form / Formula, i.e. single-sf_formula)
	   fold TWO levels: Home and Products are both implied by "← Form". The
	   3-level rule above cannot be reused — its prefix is a different trail —
	   so the pair of hidden crumbs is spelled out here. The visible result is
	   the same shape as the 3-level fold, one level deeper: ← Soft Chews /
	   <formula name>. */
	.sf-breadcrumb--d4 .sf-breadcrumb__crumb--home,
	.sf-breadcrumb--d4 .sf-breadcrumb__sep--home,
	.sf-breadcrumb--d4 .sf-breadcrumb__crumb--products,
	.sf-breadcrumb--d4 .sf-breadcrumb__sep--products { display: none; }
	.sf-breadcrumb--d4 .sf-breadcrumb__crumb--form::before { content: "←\00a0"; }
}

/* === 25. Header-to-hero seam ---------------------------------------------
   The WP layout engine applies --wp--style--block-gap (24px) between the
   header template part and the hero section, exposing a white body strip
   between the two dark surfaces. Collapse it, and lift the nav with a
   hairline shadow so the green-on-green boundary stays readable. */
.wp-site-blocks > header.wp-block-template-part + * {
	margin-top: 0;
}
.sf-header {
	position: relative;
	z-index: 10;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
	border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
/* === 25c. Nav-bar content edges ------------------------------------------
   The constrained sections centre a contentSize (1200px) box inside a full-
   width band padded 38px, so their text edge sits at
   max(38px, (viewport - 1200px) / 2). Reproduce exactly that formula on the
   nav bar (inline 24px padding overridden) so the logo's left edge and the
   Get a Quote button's right edge land on the content column's edges. */
.sf-header {
	padding-left: max(38px, calc((100% - var(--wp--style--global--content-size, 1200px)) / 2)) !important;
	padding-right: max(38px, calc((100% - var(--wp--style--global--content-size, 1200px)) / 2)) !important;
}
/* === 25b. Inner-page hero tone -------------------------------------------
   Same-hue step down from the nav (#1B4D3E) so the two dark surfaces read
   as tiers instead of one flat block. Inner-page templates mark their hero
   with .sf-hero-inner; the home hero keeps its photo/overlay treatment. */
.sf-hero-inner {
	background-color: #2E6B54;
}

/* === 25d. Dosage page: specification strip (hero -> Standard Formulas) ---
   Five-field quick-reference table for the form on this page, lifted from the
   page's own FAQ answer and Standard Specs copy. Deliberately carries NO
   heading: toc-nav.js numbers its entries from the H2 order (sf-sec-N), so an
   h2 here would shift every later anchor on the page. The <caption> supplies
   the accessible name instead.

   Hairline rows and no vertical rules, matching the ruled panels used across
   the site. The header row is branded green with ink text: white on
   brand-green resolves to 2.7:1, which fails AA at this size, while
   ink-on-brand-green is 5.8:1. ------------------------------------------ */
.sf-spectable {
	padding-top: 40px !important;
	padding-bottom: 0 !important;
}

/* Phone: six columns of spec copy cannot be read sideways, so each field
   stacks as a labelled row. display:block on the table parts drops the table
   semantics for AT, which is the accepted trade for the stacked pattern; the
   data-label attributes carry the field names that the hidden header row
   supplied on wider screens. --------------------------------------------- */
@media (max-width: 767px) {
	.sf-spectable {
		padding-top: 24px !important;
	}
}

/* === 26. Dosage page refinements (Soft Chews first) ---------------------
   Merged Specifications/Formula/Packaging band, compact spec list, and
   Related tiles reusing the homepage catalogue tile. */

/* Merged band: Specifications | Formula Options | Packaging Options.
   Natural heights — top-aligned, each column ends where its content ends.
   Two classes + !important on purpose: WP's global block CSS ships
   `.wp-block-columns { align-items: normal !important }` (normal = stretch
   in grid, which silently re-equalised the columns); matching the flag and
   out-ranking the specificity wins the cascade. */
.wp-block-columns.sf-triple {
	display: grid !important;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 24px;
	align-items: flex-start !important;
}
.sf-triple > .wp-block-column {
	margin: 0;
	position: relative;
	min-width: 0;
}
.sf-triple > .wp-block-column h3 {
	margin: 0 0 4px;
	font-size: 17px;
}
.sf-triple__list {
	padding-left: 18px;
}
/* Compact rhythm matched to the spec table on the left: same 10px vertical
   padding and 1.5 leading, so all three columns read at one pace. Columns
   keep their natural heights (flex-start), top-aligned. */
.sf-triple__list li {
	line-height: 1.5 !important;
	padding: 10px 0;
}
.sf-triple__list li + li {
	margin-top: 0;
}
.sf-triple__list li::marker {
	color: var(--wp--preset--color--accent);
}
@media (min-width: 1024px) {
	.sf-triple > .wp-block-column + .wp-block-column::before {
		content: "";
		position: absolute;
		top: 4px;
		bottom: 4px;
		left: -12px;
		width: 1px;
		background-color: var(--wp--preset--color--border-light);
	}
}
@media (max-width: 1023px) {
	/* same double-class specificity as the base rule above, or the base
	   (0,2,0) would out-rank these responsive overrides */
	.wp-block-columns.sf-triple {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
	.sf-triple > .wp-block-column:nth-child(3) {
		grid-column: 1 / -1;
	}
}
@media (max-width: 767px) {
	.wp-block-columns.sf-triple {
		grid-template-columns: minmax(0, 1fr);
	}
	.sf-triple > .wp-block-column:nth-child(3) {
		grid-column: auto;
	}
}

/* Compact 8-row spec list (was 16-20px vertical padding per row) */
.sf-spec-list {
	margin-top: 14px;
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-spec-row {
	display: grid;
	grid-template-columns: 38% 1fr;
	gap: 10px;
	align-items: baseline;
	padding: 10px 0;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sf-spec-term {
	font-size: 13px;
	color: var(--wp--preset--color--text-secondary);
}
.sf-spec-value {
	font-size: 14px;
	font-weight: 700;
	color: var(--wp--preset--color--primary);
	font-variant-numeric: tabular-nums;
}

/* Related Dosage Forms: homepage catalogue tiles with a page-scoped 4:3
   frame (the homepage's own tiles keep their 1:1 ratio) */
.sf-related-grid .sf-tile__media img {
	aspect-ratio: 4 / 3;
	-webkit-backface-visibility: hidden;
	backface-visibility: hidden;
}
.sf-related-grid .sf-tile {
	overflow: hidden;
	-webkit-mask-image: -webkit-radial-gradient(white, black);
}
/* iOS touch: no hover state — a short press-down acknowledgement instead */
@media (hover: none) {
	.sf-related-grid .sf-tile:active {
		transform: scale(0.98);
	}
}
@media (prefers-reduced-motion: reduce) {
	.sf-related-grid .sf-tile,
	.sf-related-grid .sf-tile__media img {
		transition: none;
	}
}

/* === 26b. Global reach map (front page) ==================================
   Inline-SVG world map: stylized continent silhouettes + pulse dots.
   Land = forest tint, dots = forest, origin (China) = brand green.
   Animation is opacity/transform only; disabled under reduced motion. */
/* .sf-slot forces display:grid (section 12); its implicit column is
   content-sized, which makes the SVG's percentage width resolve cyclically
   and fall back to the 300px replaced-element default. Give the map slot a
   real fluid column so width:100% has something definite to resolve against. */
.sf-slot--map {
	grid-template-columns: minmax(0, 1fr);
	background: #F5F7F2;
}
.sf-map {
	width: 100%;
	height: auto;
	display: block;
}
.sf-map__land {
	fill: #E5EBE3;
	stroke: #D0D7CC;
	stroke-width: .5;
}
.sf-map__dot {
	fill: #1B4D3E;
	opacity: .7;
}
.sf-map__dot--dest {
	opacity: .85;
}
.sf-map__dot--origin {
	fill: var(--wp--preset--color--brand-green);
	opacity: 1;
}
.sf-map__halo {
	fill: none;
	stroke: var(--wp--preset--color--brand-green);
	stroke-width: 2px;
	opacity: .5;
}
.sf-map__halo--far {
	stroke-width: 1.5px;
	opacity: .25;
}
.sf-map__pulse {
	animation: sfPulse 2.4s ease-in-out infinite;
	transform-box: fill-box;
	transform-origin: center;
}
@keyframes sfPulse {
	0%, 100% { opacity: 1; transform: scale(1); }
	50% { opacity: .55; transform: scale(1.25); }
}
/* 26c export arcs: static dashed route + flowing light segment (0 JS).
   Flow layer uses a short dash cycling over the path via stroke-dashoffset;
   per-path dasharray period = arc length (inlined on each path). */
.sf-map__arc {
	fill: none;
	stroke: rgba(90, 183, 53, 0.4);
	stroke-width: 1.5;
	stroke-dasharray: 4 4;
	stroke-linecap: round;
}
.sf-map__arc-flow {
	fill: none;
	stroke: rgba(90, 183, 53, 0.9);
	stroke-width: 2;
	stroke-linecap: round;
	animation: sfArcFlow 3.6s linear infinite;
}
@keyframes sfArcFlow {
	from { stroke-dashoffset: var(--arc-len, 610); }
	to { stroke-dashoffset: 0; }
}
/* WP emits .is-layout-flex rules AFTER the theme stylesheet, so the grid
   needs !important to win the cascade (same pattern as other sf- blocks). */
.wp-block-group.sf-map__regions {
	display: grid !important;
	grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
	gap: 28px !important;
	margin-top: 32px !important;
}
.sf-map__region h3 {
	font-size: 15px;
	margin: 0 0 6px;
}
.sf-map__region p {
	margin: 0;
}
@media (max-width: 781px) {
	.wp-block-group.sf-map__regions {
		grid-template-columns: 1fr !important;
		gap: 18px !important;
	}
}
@media (prefers-reduced-motion: reduce) {
	.sf-map__pulse,
	.sf-map__arc-flow {
		animation: none;
	}
}

/* === 27. Mobile optimisation (<= 768px) ==================================
   Phone-specific compression pass. Every rule lives inside a media query, so
   desktop (>= 769px) is byte-for-byte the cascade it was before. Nothing is
   deleted from the DOM: the top bar's WhatsApp/address/hours/badges, the nav
   CTA and the certificate copy all stay in the markup for crawlers — the
   phone just stops painting them. Accordions are native <details> (core
   details / core navigation), so collapsed content is still in the HTML. */

/* --- 27a. Top bar: one 34px line, email only ------------------------------ */
@media (max-width: 768px) {
	.sf-topbar {
		padding-top: 6px !important;
		padding-bottom: 6px !important;
		padding-left: 16px !important;
		padding-right: 16px !important;
		line-height: 1.45;
	}
	/* hours / badges all drop on phones — the email stays reachable and the
	   social icons live in the footer only */
	.sf-topbar .sf-topbar-left {
		display: none;
	}
	.sf-topbar .sf-topbar-contact {
		flex-wrap: nowrap;
		align-items: center;
		justify-content: center;
	}
	.sf-topbar .sf-topbar-contact p {
		white-space: nowrap;
	}
}

/* --- 27b. Nav bar: [logo] .... [hamburger], no CTA -----------------------
   The quote button wrapped onto its own 40px row on phones, doubling the bar
   height; the hero already carries the same action. The hamburger gets a real
   44px touch target instead of a 24px glyph. */
@media (max-width: 768px) {
	.sf-header {
		padding-top: 10px !important;
		padding-bottom: 10px !important;
		padding-left: 16px !important;
		padding-right: 16px !important;
		flex-wrap: nowrap;
		align-items: center;
	}
	/* Hide the Get a Quote button on phones (it wrapped onto its own 40px
	   row); the hero already carries the same action. The basket icon that
	   used to sit beside it went in batch H7f. */
	.sf-header .sf-header__cta .wp-block-button {
		display: none;
	}
	.sf-header .wp-block-navigation {
		flex: 0 0 auto;
	}
	/* Hug the right edge: under space-between the auto margin soaks up all
	   free space, so the hamburger stops floating mid-bar. (Before batch H7f
	   it hugged the basket button that sat to its right.) Child combinator is
	   required — the overlay's inner <ul> ALSO carries .wp-block-navigation,
	   and an auto margin there shoves the whole open menu column to the right
	   edge. */
	.sf-header > .wp-block-navigation {
		margin-left: auto;
	}
	.sf-header .wp-block-navigation__responsive-container-open {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		margin-right: -10px;   /* the glyph is centred; trim the dead edge */
	}
}
/* WP flips its nav to the inline list at 600px, where eight items overflow a
   600-768px viewport. Keep the overlay across the whole phone/small-tablet
   range instead (out-ranks WP's `min-width:600px` rules by class + order). */
@media (min-width: 601px) and (max-width: 768px) {
	.sf-header .wp-block-navigation__responsive-container:not(.is-menu-open) {
		display: none !important;
	}
	.sf-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex;
	}
}

/* --- 27c. Overlay menu: 44-48px rows, collapsed submenus ------------------ */
@media (max-width: 768px) {
	.wp-block-navigation__responsive-container.is-menu-open {
		padding: 20px !important;
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content {
		max-height: 80vh;
		overflow-y: auto;
	}
	/* top-level rows: 48px touch rows with a hairline between them */
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__container
		> .wp-block-navigation-item {
		min-height: 48px;
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__container
		> .wp-block-navigation-item
		> .wp-block-navigation-item__content {
		display: flex;
		align-items: center;
		width: 100%;   /* the hairline has to span the row, not the label */
		min-height: 48px;
		font-size: 16px;
		font-weight: 600;
		border-bottom: 1px solid rgba(255, 255, 255, 0.14);
	}
	/* submenu toggle: a 44px chevron pinned to the row's right edge (WP hides
	   it inside the modal, which also left the submenu permanently expanded) */
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.has-child
		> .wp-block-navigation__submenu-icon {
		display: flex !important;
		align-items: center;
		justify-content: center;
		position: absolute;
		top: 2px;
		right: -10px;
		z-index: 2;   /* the stretched row link would otherwise take the tap */
		width: 44px;
		height: 44px;
		margin: 0;
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.has-child
		> .wp-block-navigation__submenu-icon
		svg {
		width: 16px;
		height: 16px;
		transition: transform 0.18s ease;
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.has-child
		> .wp-block-navigation__submenu-icon[aria-expanded="true"]
		svg {
		transform: rotate(180deg);
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.has-child
		> .wp-block-navigation-item__content {
		padding-right: 40px;   /* keep the label clear of the chevron */
	}
	/* submenus start collapsed; the chevron opens them. WP marks the state on
	   the button's aria-expanded, so this is pure CSS off that attribute. */
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.has-child
		> .wp-block-navigation__submenu-container {
		height: 0 !important;
		opacity: 0 !important;
		overflow: hidden !important;
		visibility: hidden !important;
		width: auto !important;
		min-width: 0 !important;
		margin: 0 !important;
		padding: 0 0 0 42px !important;
		transition: opacity 0.18s ease;
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.has-child
		> .wp-block-navigation__submenu-icon[aria-expanded="true"]
		+ .wp-block-navigation__submenu-container {
		height: auto !important;
		opacity: 1 !important;
		overflow: initial !important;
		visibility: visible !important;
		padding-top: 4px !important;
		padding-bottom: 4px !important;
	}
	/* sub items: text lands 52px from the screen edge (WP's own list box sits
	   10px left of the overlay padding, so the indent is 42px there), with one
	   continuous hairline marking the level at 40px */
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.wp-block-navigation__submenu-container {
		position: relative !important;   /* WP forces static inside the modal */
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.wp-block-navigation__submenu-container::before {
		content: "";
		position: absolute;
		left: 30px;
		top: 6px;
		bottom: 6px;
		width: 2px;
		background: rgba(255, 255, 255, 0.22);
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.wp-block-navigation__submenu-container
		.wp-block-navigation-item {
		min-height: 44px;
	}
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content
		.wp-block-navigation__submenu-container
		.wp-block-navigation-item__content {
		display: flex;
		align-items: center;
		width: 100%;
		min-height: 44px;
		font-size: 14px;
		font-weight: 400;
	}
}

/* --- 27d. Section rhythm: 48 -> 32, 40 -> 28, 56 -> 36 ------------------- */
@media (max-width: 768px) {
	.wp-site-blocks .sf-section--large {
		padding-top: 32px !important;
		padding-bottom: 32px !important;
	}
	.wp-site-blocks .sf-section--medium {
		padding-top: 28px !important;
		padding-bottom: 28px !important;
	}
	.wp-site-blocks .sf-section--xl {
		padding-top: 36px !important;
		padding-bottom: 36px !important;
	}
	/* the 48px gap a section leaves before its grid shrinks with it */
	.sf-section--large > .wp-block-group,
	.sf-section--medium > .wp-block-group {
		margin-top: 24px !important;
	}
}

/* --- 27e. 8 dosage forms: two-up catalogue tiles -------------------------- */
@media (max-width: 768px) {
	.wp-block-columns.sf-dosage-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 14px;
	}
	/* the image is the tile's identity, so it keeps a square frame */
	.sf-dosage-grid .sf-tile__media img {
		aspect-ratio: 1 / 1;
	}
	.sf-dosage-grid .sf-tile {
		padding-bottom: 12px;
	}
	.sf-dosage-grid .sf-tile > h3,
	.sf-dosage-grid .sf-tile > p {
		padding-left: 12px;
		padding-right: 12px;
	}
	.sf-dosage-grid .sf-tile h3 {
		font-size: 15px;
		line-height: 1.25;
		margin-bottom: 3px;
	}
	.sf-dosage-grid .sf-tile p {
		font-size: 12px;
		line-height: 1.35;
	}
	.sf-dosage-grid .sf-tile p:last-child {
		padding-top: 8px;
	}
	/* the eyebrow ("BESTSELLER") rides tighter above the title */
	.sf-dosage-grid .sf-tile p.has-cta-color {
		font-size: 10px;
		letter-spacing: 0.06em;
		margin-bottom: 2px;
	}
	/* a 12px copy block inside a ~140px column reads as a wall; 11px keeps it
	   to a hint. Text stays in the DOM — only the size changes. */
	.sf-dosage-grid .sf-tile p.has-text-secondary-color {
		font-size: 11px;
	}
}

/* --- 27f. Formulated Clean: two claims per row, left-aligned -------------- */
@media (max-width: 768px) {
	.wp-block-columns.sf-strip {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		row-gap: 14px;
		column-gap: 16px;
	}
	.sf-strip > .wp-block-column {
		padding: 0;
	}
	.sf-strip .sf-claim {
		justify-content: flex-start;
	}
	.sf-claim {
		font-size: 14px;
	}
}

/* --- 27g. Certifications: the home band (compact) + quality detail rows ----
   Home (待办18): was a static 3x2 grid of six seal cards — an 88px seal over an
   H3 name over an issuer line, ~205px per card, ~430px for the grid, ~700px
   for the section. It now states the same six credentials as one line of six
   32px chips under a single lede, with the link to the full page at the
   bottom, which is what the band actually needs to say; the long form lives on
   /quality/ where the certificate scans are. The six names survive verbatim as
   TEXT (crawlers and screen readers read them exactly as before); it is the
   88px seals and the per-card chrome that go. The seals are decorative next to
   their own label, so the SVGs lost role="img"/aria-label and became
   aria-hidden — before this change each chip announced "FDA certification
   seal" and then "FDA Registered".

   The six cards' rules (.sf-certgrid / .sf-certcard*) were DELETED with them,
   not left behind: front-page.html was their only consumer. Nothing else in
   the theme referenced them (batch H7k checked; the three _certbar_*.js probes
   under sinofresh-theme/tools/ still name .sf-certgrid and are stale — they
   were already unshipped scratch probes).

   Quality: one row per credential with the certificate scan on the left and
   details/links on the right, separated by 1px rules. Rows without a scan yet
   (HACCP, BRC) show a neutral placeholder. Unchanged by this batch. */
.sf-certstrip__lede {
	margin: 0 0 16px;
	font-size: 17px;
	font-weight: 600;
	line-height: 1.4;
	text-align: center;
	color: var(--wp--preset--color--primary);
}
.sf-certstrip__row {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 16px;
	margin: 0;
	padding: 0;
	list-style: none;
}
/* 待办4 (H7l) — the strip becomes six cards. As a bare row the band read as a
   footnote: a 32px glyph inline with 14px text, in a grid whose three columns
   made the second row look like the continuation of the first. As cards it is
   a grid of equals and the icon has a surface to sit on.
   Mist and Line are the theme's own tokens (theme.json bg-light / border-light)
   spelled as tokens rather than hex, so a palette edit still reaches this band;
   the radius is the brief's 8px, not the visual system's 6px — deliberate, one
   step rounder so six small surfaces read as cards and not as table cells. */
.sf-certstrip__badge {
	display: flex;
	align-items: center;
	gap: 12px;
	min-width: 0;
	padding: 16px;
	background: var(--wp--preset--color--bg-light);
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 8px;
}
.sf-certstrip__icon {
	flex: 0 0 auto;
	width: 40px;
	height: 40px;
	color: var(--wp--preset--color--primary);
}
.sf-certstrip__name {
	font-size: 14px;
	font-weight: 600;
	line-height: 1.35;
	color: var(--wp--preset--color--heading, inherit);
}
.sf-certstrip__more {
	margin: 16px 0 0;
	text-align: center;
}
.sf-certstrip__more a {
	font-size: 14px;
	font-weight: 600;
	color: var(--wp--preset--color--brand-green);
	text-decoration: none;
}
.sf-certstrip__more a:hover {
	text-decoration: underline;
}
.sf-certdetail {
	margin-top: var(--wp--preset--spacing--60, 40px);
}
.sf-certrow {
	display: grid;
	grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
	gap: 32px;
	align-items: start;
	padding: 28px 0;
}
.sf-certrow + .sf-certrow {
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-certrow__media {
	margin: 0;
}
.sf-certrow__media a {
	display: block;
}
.sf-certrow__media img {
	display: block;
	width: 100%;
	height: auto;
	max-height: 320px;
	object-fit: contain;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	background: var(--wp--preset--color--card-white);
}
.sf-certrow__media--placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 4 / 3;
	max-height: 320px;
	background: var(--wp--preset--color--bg-light);
	border: 1px dashed var(--wp--preset--color--border-light);
	border-radius: 6px;
}
.sf-certrow__media--placeholder span {
	font-size: 13px;
	color: var(--wp--preset--color--text-secondary);
	letter-spacing: 0.4px;
}
.sf-certrow__name {
	margin: 0;
	font-size: 20px;
	font-weight: 600;
}
.sf-certrow__issuer {
	margin: 6px 0 0;
	font-size: 14px;
	font-weight: 600;
	color: var(--wp--preset--color--primary);
}
.sf-certrow__desc {
	margin: 12px 0 0;
	font-size: 15px;
	line-height: 1.7;
	color: var(--wp--preset--color--text-secondary);
}
.sf-certrow__links {
	display: flex;
	flex-wrap: wrap;
	gap: 20px;
	margin: 16px 0 0;
	font-size: 14px;
	font-weight: 600;
}
@media (max-width: 900px) {
	.sf-certrow {
		grid-template-columns: minmax(0, 1fr);
		gap: 18px;
		padding: 22px 0;
	}
	.sf-certrow__media img,
	.sf-certrow__media--placeholder {
		max-height: 260px;
	}
}
@media (max-width: 1024px) {
	/* 待办4 (H7l): two columns from the tablet down. The retired ≤768px step is
	   gone with the row it served — these are cards now, and the tablet boundary
	   is the one the rest of the theme already uses (120/38/20 gutter). */
	.sf-certstrip__row {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
	.sf-certstrip__lede {
		font-size: 16px;
	}
	.sf-certstrip__name {
		font-size: 13px;
	}
}
@media (max-width: 420px) {
	/* The phone keeps TWO columns — six stacked cards were exactly the height
	   problem 待办18 named when it took the retired card grid back to one. Two
	   columns at 375px leave ~96px of text width, so the icon and the padding
	   step down with them; at 40px/16px the same card gives the text 77px and
	   every credential wraps to three lines. Measured, not guessed. */
	.sf-certstrip__row {
		gap: 12px;
	}
	.sf-certstrip__badge {
		gap: 10px;
		padding: 12px;
	}
	.sf-certstrip__icon {
		width: 32px;
		height: 32px;
	}
}

/* --- 27h. Export figures: 2 x 2 ------------------------------------------ */
@media (max-width: 768px) {
	.wp-block-columns.sf-panel--4 {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 20px;
	}
	/* one horizontal rule per row, not one per cell */
	.wp-block-columns.sf-panel--4 > .wp-block-column {
		padding: 14px 0 16px;
	}
	.wp-block-columns.sf-panel--4 > .wp-block-column .sf-cell p:first-child {
		font-size: 32px;
	}
	.wp-block-columns.sf-panel--4 > .wp-block-column .sf-cell p:last-child {
		font-size: 13px;
	}
}

/* --- 27i. About: full-width CTA ------------------------------------------- */
@media (max-width: 768px) {
	.sf-about__cta {
		display: block;
		margin-top: 20px !important;
	}
	.sf-about__cta .wp-block-button {
		display: block;
		width: 100%;
	}
	.sf-about__cta .wp-block-button__link {
		display: block;
		width: 100%;
		text-align: center;
		min-height: 48px;
		padding: 14px 24px;
	}
}

/* --- 27j. Footer ----------------------------------------------------------
   The three nav columns are already core <details>; the phone only closes
   them by default (the brand column stays open — it is the footer's payload).
   Links stay in the HTML, so crawlers still see the full sitemap. */
@media (max-width: 768px) {
	.sf-footer {
		padding-top: 32px !important;
	}
	.sf-footcol .sf-footlinks {
		padding-bottom: 12px;
	}
	.sf-footcol summary {
		min-height: 44px;
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 10px 0;
		margin-bottom: 0;
		border-bottom: 1px solid rgba(255, 255, 255, 0.18);
	}
	.sf-footcol summary::after {
		float: none;
		margin-left: auto;
	}
	/* the + / - glyph already swaps via the existing 781px rule */
	.sf-footer-legal {
		flex-direction: column;
		gap: 6px;
	}
}

/* --- 27k. Marquee + misc compression -------------------------------------- */
@media (max-width: 768px) {
	.sf-story {
		width: 240px;
	}
	.sf-story__quote {
		font-size: 14px;
	}
	.sf-card--roomy,
	.sf-card--quote {
		padding: 16px;
	}
	.sf-faq details + details {
		margin-block-start: 6px;
	}
	.sf-faq summary {
		min-height: 48px;
	}
	.sf-faq summary h3 {
		font-size: 15px;
	}
	/* the quote form card no longer needs 32px of air on a phone */
	.wp-block-columns .has-card-white-background-color[style*="border-radius"] {
		padding: 20px !important;
	}
	/* honour the OS motion setting for the new transitions */
	@media (prefers-reduced-motion: reduce) {
		.wp-block-navigation__submenu-icon svg,
		.wp-block-navigation__responsive-container.is-menu-open
			.wp-block-navigation__responsive-container-content
			.has-child
			> .wp-block-navigation__submenu-container {
			transition: none;
		}
	}
}


/* the hero stage: 74vh pushes the first CTA below the fold on short phones */
@media (max-width: 768px) {
	.sf-hero-slider {
		height: 62vh;
		min-height: 430px;
	}
	.sf-slide__copy {
		padding-bottom: 120px;
	}
}

/* --- 27l. In-card vertical rhythm -----------------------------------------
   WP's constrained layout charges every child after the first a 24px
   block-gap. On a 375px screen a six-paragraph card spends 120px of that on
   air alone. Tighten it inside the content containers only — desktop keeps
   the 24px rhythm. */
@media (max-width: 768px) {
	/* :where() keeps the selector at (0,1,0) so the theme's own explicit
	   zero-margin rules (.sf-tile p, .sf-panel p) still win where they exist */
	:root :where(.sf-card.is-layout-constrained, .sf-cell.is-layout-constrained,
		.sf-tile.is-layout-constrained,
		.sf-panel > .wp-block-column.is-layout-constrained) > * + * {
		margin-block-start: 8px;
	}
	/* a heading directly followed by body copy reads better at 6px */
	:root :where(.sf-card.is-layout-constrained, .sf-cell.is-layout-constrained)
		> h3 + * {
		margin-block-start: 6px;
	}
	/* the check rows (marker + text) sit tighter still */
	.sf-row {
		gap: 8px;
	}
}

/* --- 27m. Mobile type scale + media ratios --------------------------------
   The brief explicitly prefers "调小字号 / 行高 / padding" over hiding copy.
   p keeps specificity (0,0,1) so every paragraph that carries an explicit
   size (12/13/14px inline or classed) is untouched — only the 16px body
   defaults drop a step. */
@media (max-width: 768px) {
	p {
		font-size: 15px;
		line-height: 1.55;
	}
	/* illustration crops: 4:3 is generous on a phone, 16:9 keeps the row honest */
	.sf-panel .sf-slot--photo img {
		aspect-ratio: 16 / 9;
	}
	.sf-panel .sf-cell p:last-child {
		font-size: 13px;
	}
}

/* --- 27n. Two-up panel cells + article covers -----------------------------
   Quality Control and the enquiry process are uniform card grids that stack to
   six and seven full-width rows on a phone — the same problem task 5 solves
   for the dosage catalogue, so they get the same two-up treatment. */
@media (max-width: 768px) {
	.wp-block-columns.sf-panel--3,
	.wp-block-columns.sf-panel--4.sf-panel--bare {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 16px;
	}
	.wp-block-columns.sf-panel--3 > .wp-block-column,
	.wp-block-columns.sf-panel--4.sf-panel--bare > .wp-block-column {
		padding: 12px 0 14px !important;
	}
	.wp-block-columns.sf-panel--3 > .wp-block-column:nth-child(odd),
	.wp-block-columns.sf-panel--4.sf-panel--bare > .wp-block-column:nth-child(odd) {
		padding-right: 12px !important;
	}
	.sf-panel h3 {
		font-size: 15px;
		line-height: 1.3;
	}
	.sf-panel .sf-cell p:first-child {
		font-size: 26px;
	}
	/* article cards: the 4:3 cover is inline on the slot, so !important */
	.sf-slot--cover {
		aspect-ratio: 16 / 9 !important;
	}
	.sf-card--flush h3 {
		font-size: 16px;
		line-height: 1.3;
	}
}

/* --- 27o. Final pass: rhythm to hit the -30% page-height target ----------- */
@media (max-width: 768px) {
	.sf-section--large {
		padding-top: 28px !important;
		padding-bottom: 28px !important;
	}
	.sf-section--medium {
		padding-top: 24px !important;
		padding-bottom: 24px !important;
	}
	.sf-section--xl {
		padding-top: 32px !important;
		padding-bottom: 32px !important;
	}
	.sf-hero-slider {
		height: 56vh;
		min-height: 440px;
	}
	.sf-card--roomy,
	.sf-card--quote {
		padding: 14px;
	}
	/* repeated spec lines inside a card read as a definition list, not prose */
	:root :where(.sf-card.is-layout-constrained) > p + p {
		margin-block-start: 5px;
	}
	.sf-faq summary {
		min-height: 44px;
		padding: 9px 0;
	}
	.sf-card--flush > .wp-block-group {
		padding: 12px !important;
	}
	/* lead form: keep every field, trim the air between them */
	.fluentform .ff-el-group {
		margin-bottom: 12px !important;
	}
	.fluentform .ff-el-input--label {
		margin-bottom: 4px !important;
	}
	input.ff-el-form-control[type="text"],
	input.ff-el-form-control[type="email"],
	input.ff-el-form-control[type="tel"] {
		height: 44px !important;
	}
}

/* FAQ answer air + marquee lead-in */
@media (max-width: 768px) {
	.sf-faq details p {
		margin: 6px 0 10px;
	}
	.sf-marquee {
		margin-top: 16px;
	}
	.sf-about__cta {
		margin-top: 16px !important;
	}
}

/* slot frames must not add height beyond the media they now hold */
@media (max-width: 768px) {
	.sf-slot--video,
	.sf-slot--map {
		min-height: 0;
	}
	.sf-params {
		margin-top: 16px !important;
	}
}

/* WP justifies the overlay's column at flex-start, so each row shrinks to its
   label: the hairline stopped at the text and the chevron hugged it. Stretch
   every row to the full overlay width instead. */
@media (max-width: 768px) {
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content,
	.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container,
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__submenu-container {
		align-items: stretch !important;
	}
	.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container
		> .wp-block-navigation-item,
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__submenu-container
		> .wp-block-navigation-item {
		width: 100%;
	}
}

/* --- 27p. Mobile fixes round 2: sticky bar, step cells, cover crops, footer
   accordion rows. All phone-only; desktop >= 769px untouched. ------------- */
@media (max-width: 768px) {
	/* P1a. WP hides #wpadminbar below 782px but keeps body.admin-bar on <body>,
	   so the logged-in offset rule up in section 23 (top: 32px) still matched
	   and the fixed bar hung 32px below the top with content sliding through
	   the gap. Pin it to the top like the logged-out bar. */
	body.admin-bar .sf-header.is-sticky {
		top: 0;
	}
	/* P1b. the flow spacer still reserves the old desktop bar height (80px);
	   the phone bar is 57px, the difference showed as a background strip
	   between the bar and the first content while it engaged. */
	.sf-header-stuck {
		padding-bottom: 57px;
	}
	/* anchor jumps must land below the fixed bar, not underneath it */
	html {
		scroll-padding-top: 64px;
	}
	/* scroll-padding already covers the phone bar; zero the target margin so
	   the two offsets don't stack to 160px of dead air */
	#inquiry-form,
	#quote,
	#booking-form {
		scroll-margin-top: 0;
	}

	/* P2. the step grids are already two-up (27n); compress each cell so six
	   steps plus the heading fit one phone screen. The number and description
	   paragraphs carry inline font sizes, so these need !important. */
	.wp-block-columns.sf-panel--bare > .wp-block-column {
		padding: 10px 0 12px !important;
	}
	.sf-panel--bare .sf-cell p:first-child {
		font-size: 22px !important;
	}
	.sf-panel--bare .sf-cell h3 {
		font-size: 14px;
		line-height: 1.3;
		margin-block-start: 4px;
	}
	.sf-panel--bare .sf-cell p:last-child {
		font-size: 12px !important;
		line-height: 1.45 !important;
	}

	/* P3. the cover frame was switched to 16/9 (27n) but the 4:3 photo kept
	   its own ratio and, flex-centered, spilled 19px past the frame bottom
	   onto the category row — the tag looked like it floated on the image.
	   Crop the photo to the frame instead. */
	.sf-slot--cover .wp-block-image {
		width: 100%;
	}
	.sf-slot--cover img {
		aspect-ratio: 16 / 9;
		object-fit: cover;
	}

	/* P4. the three footer accordions sat in stacked columns that kept the
	   24px flex row-gap plus a 4px details margin — 28px of dead space per
	   row. Collapse both and slim the summary rows. */
	.sf-footer-grid {
		gap: 0 !important;
	}
	.sf-footcol {
		margin: 0 !important;
	}
	.sf-footcol summary {
		box-sizing: border-box;
		padding: 8px 0;
		min-height: 40px;
		line-height: 1.3;
	}
}

/* --- 27q. QC step grid: the six quality-control steps live in two sibling
   wp:columns groups of three, so any two-up grid strands the third column of
   each group (03 and 06) on its own row. Merge both groups into one phone
   grid via display: contents — no template change, desktop untouched. ----- */
@media (max-width: 768px) {
	section:has(> .wp-block-columns.sf-panel--3) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 20px 14px;
		align-items: stretch;
	}
	/* the two column groups stop generating boxes; their columns become grid
	   items in document order: 01|02, 03|04, 05|06. !important is required:
	   the base theme pins .sf-panel to display:grid !important (section 11). */
	section:has(> .wp-block-columns.sf-panel--3) > .wp-block-columns.sf-panel--3 {
		display: contents !important;
	}
	/* headings and intro copy span the full row; the WP block-gap margins are
	   replaced by the grid row gap */
	section:has(> .wp-block-columns.sf-panel--3) > :not(.wp-block-columns) {
		grid-column: 1 / -1;
	}
	section:has(> .wp-block-columns.sf-panel--3) > * {
		margin-block: 0 !important;
	}
	/* equal cell heights per row: stretch the column, let the inner group fill
	   it, and pin the photo slot to the bottom so photos align across a row */
	.sf-panel--3 > .wp-block-column {
		display: flex;
		flex-direction: column;
		padding: 0 !important;
	}
	.sf-panel--3 .sf-cell {
		display: flex;
		flex-direction: column;
		height: 100%;
	}
	/* WP's constrained layout pins its children with margin-left/right: auto
	   !important; inside a flex column those auto margins absorb the free
	   space and shrink-to-fit single-line headings into centred boxes.
	   Match the importance to pin everything left. */
	.sf-panel--3 .sf-cell > * {
		margin-inline: 0 !important;
	}
	.sf-panel--3 .sf-cell .sf-slot--photo {
		margin-top: auto;
		min-height: 0;
	}
	/* number / title / description scale (the number and description carry
	   inline font sizes, so !important is required) */
	.sf-panel--3 .sf-cell > p:first-child {
		font-size: 26px !important;
	}
	.sf-panel--3 .sf-cell h3 {
		font-size: 14px;
		line-height: 1.3;
		min-height: calc(2 * 1.3em);
	}
	.sf-panel--3 .sf-cell > p:not(:first-child) {
		font-size: 12px !important;
		line-height: 1.4;
		min-height: calc(3 * 1.4em);
	}
	/* fixed photo crop: height wins over the 4/3 ratio rule up in section 12 */
	.sf-panel--3 .sf-slot--photo .wp-block-image {
		width: 100%;
	}
	.sf-panel--3 .sf-slot--photo img {
		height: 120px;
		aspect-ratio: auto;
		width: 100%;
		object-fit: cover;
		border-radius: 6px;
	}
	/* drop the WP parser's stray empty paragraph inside the photo slot */
	.sf-panel--3 .sf-slot--photo > p:empty {
		display: none;
	}
}

/* --- 27r. Dosage pages: merge the split step + related grids ---------------
   drops / liquids / fish-oil / dental-chews were authored without the sf-*
   vocabulary the four standard dosage pages use, so their "How We Work" (4+3)
   and "Related Dosage Forms" (4+3) blocks are bare wp:columns groups that wrap
   to a single full-width column each on a phone — 1,483px and 3,328px of
   stacked cards at 375px. display: contents drops the two group boxes and
   promotes their columns into one grid owned by the section, so the seven
   cards flow 01|02, 03|04, 05|06, 07 with the odd seventh spanning both
   tracks. No template change, no JS.

   Scope = body.page-child.parent-pageid-19, i.e. the eight pages under
   /products/. Home (page-id-11) and the Services process block carry the same
   bare markup and belong to a later batch, so they are deliberately excluded.
   As of the batch-2 follow-up the A (How We Work) selector no longer carries
   the :not([class*="sf-"]) guard: the four standard pages share the same 4+3
   split, so they now merge too and the eight pages render identically. B
   (Related) keeps the guard — the standard pages' Related grid (.sf-related-
   grid / .sf-tile) is already two-up and uses different card classes. R2's
   display: contents needs !important because the standard pages' groups carry
   .sf-panel, whose base "display: grid !important" would otherwise win.

   A = How We Work      : the section whose columns hold top-bordered step
                          groups.
   B = Related Dosage   : the section whose columns hold two sibling groups
       Forms              and card-white product cards. The two-group test
                          excludes the single-group hero and quote blocks. --- */
@media (max-width: 768px) {
	/* R1. both blocks become a two-track grid; A and B share every structural
	   rule, so they are matched through :is() */
	body.page-child.parent-pageid-19
		:is(
			section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color),
			section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 14px;
		row-gap: 18px;
		align-items: stretch;
	}

	/* R2. the two column groups stop generating boxes; their columns become grid
	   items of the section in document order: 01|02, 03|04, 05|06, 07 */
	body.page-child.parent-pageid-19
		:is(
			section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color),
			section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		)
	> .wp-block-columns {
		/* !important: the standard dosage pages' groups carry .sf-panel, whose
		   base rule "display: grid !important" would otherwise win (27q lesson) */
		display: contents !important;
	}

	/* R3. heading + intro span the row; the WP block-gap margins give way to the
	   grid row gap */
	body.page-child.parent-pageid-19
		:is(
			section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color),
			section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		)
		> :not(.wp-block-columns) {
		grid-column: 1 / -1;
	}
	body.page-child.parent-pageid-19
		:is(
			section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color),
			section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		)
		> * {
		margin-block: 0 !important;
	}

	/* R4. seven cards over two tracks leaves the seventh alone on its row; let it
	   span both so the block closes on a full-width card instead of a gap */
	body.page-child.parent-pageid-19
		:is(
			section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color),
			section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		)
		> .wp-block-columns + .wp-block-columns
		> .wp-block-column:last-child {
		grid-column: 1 / -1;
	}

	/* R5. equal cell heights: stretch the column, let the card fill it and pin
	   whatever sits last to the bottom so a row lines up */
	body.page-child.parent-pageid-19
		:is(
			section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color),
			section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		)
		> .wp-block-columns
		> .wp-block-column {
		display: flex;
		flex-direction: column;
		padding: 0 !important;
	}
	body.page-child.parent-pageid-19
		:is(
			section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color),
			section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		)
		> .wp-block-columns
		> .wp-block-column
		> .wp-block-group {
		display: flex;
		flex-direction: column;
		flex: 1 1 auto;
	}

	/* R6. How We Work cells. WP's constrained layout pins the group's children
	   with margin-left/right: auto !important; inside a flex column those auto
	   margins absorb the free space, so match the importance to pin left. The
	   number and description carry inline font sizes and need !important.
	   The group's own 16px inline padding-top is left alone — the 2px accent
	   rule above the number needs that air. */
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		> * {
		margin-inline: 0 !important;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		> p:first-child {
		font-size: 26px !important;
		line-height: 1.05;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		h3 {
		font-size: 14px;
		line-height: 1.3;
		margin-block: 4px 0;
		min-height: calc(2 * 1.3em);
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		> p:last-child {
		font-size: 12px !important;
		line-height: 1.4;
		margin-block: 6px 0;
		min-height: calc(3 * 1.4em);
	}

	/* R7. Related cards. The card carries 24px of inline padding and its image
	   frame 40px, around a 4:3 photo — fine at 299px, absurd in a 143px column.
	   Both beat author rules, so !important is required. The frame is a flex row
	   of [empty p, figure, empty p] with a 24px gap: the two parser stubs add
	   48px of pure gap, so the gap goes and the stubs are dropped (they hold no
	   text). Square-crop the photo to the frame, matching the catalogue tiles
	   the standard dosage pages use, and step the copy down.
	   Note: an earlier phone rule (section 27) pins .has-card-white-background-
	   color[style*="border-radius"] to 20px !important for the quote card; the
	   longer selector here wins on specificity. */
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color {
		padding: 10px !important;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		> * {
		margin-inline: 0 !important;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		> .wp-block-group {
		padding: 8px !important;
		margin: 0;
		gap: 0;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		> .wp-block-group
		> p:empty {
		display: none;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		figure,
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		figure
		> a {
		display: block;
		width: 100%;
		margin: 0;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		img {
		width: 100%;
		height: auto;
		aspect-ratio: 1 / 1;
		object-fit: cover;
		border-radius: 4px;
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		h3 {
		font-size: 15px !important;
		line-height: 1.25;
		margin-block: 10px 0 !important;
		min-height: calc(2 * 1.25em);
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		> p.has-text-secondary-color {
		font-size: 11px !important;
		line-height: 1.35;
		margin-block: 4px 0;
		min-height: calc(3 * 1.35em);
	}
	body.page-child.parent-pageid-19
		section:has(> .wp-block-columns:not([class*="sf-"]) + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color)
		.wp-block-column
		> .wp-block-group.has-card-white-background-color
		> p:last-child {
		font-size: 12px !important;
		margin-top: auto;
		padding-top: 8px;
	}

	/* R8. the specifications band keeps an inline 60px block padding while every
	   other band on these pages sits at 48 → 40; bring it onto the same rhythm.
	   Its signature is the wrapper group holding term/value column groups split
	   by hairlines (seven of them); the four standard dosage pages spell the
	   same information as a plain list, so this matches them zero times. */
	body.page-child.parent-pageid-19
		section:has(> .wp-block-group > .wp-block-columns + .wp-block-separator) {
		padding-top: 40px !important;
		padding-bottom: 40px !important;
	}
}

/* --- 27s. Sub-page hero rhythm: every hero band to 40px -------------------
   .sf-hero-inner carries an inline padding preset, so !important is required:
   preset 100 (64px) on the content pages, preset 80 (48px) on the dosage and
   legal pages. One value unifies the top of every sub-page; the home hero is a
   different block (.sf-hero-slider) and is untouched.

   Inline side padding: the hero marks its background via CSS (.sf-hero-inner)
   instead of a background-color attribute, so it never gets WP core's
   :where(.wp-block-group.has-background) 2.375em side padding that every
   other section leans on — below 1200px the constrained layout has no
   auto-margin gutter left and the breadcrumb/H1/subtitle sit flush at x=0.
   20px matches the home hero's mobile .sf-slide__copy margin-inline.

   The side inset is split out into its own 1024px block: the flush-edge
   failure happens at any viewport below the 1200px measure, so tablets
   (769-1024) were still hitting x=0. Only the horizontal inset is extended
   to 1024 — the 40px vertical rhythm stays a <=768 treatment so tablet hero
   height is untouched. -------------------------------------------------- */
@media (max-width: 1024px) {
	.wp-site-blocks .sf-hero-inner {
		padding-left: 20px !important;
		padding-right: 20px !important;
	}
}
@media (max-width: 768px) {
	.wp-site-blocks .sf-hero-inner {
		padding-top: 40px !important;
		padding-bottom: 40px !important;
	}
}

/* --- 27t. Batch-2 content pages: about / quality / services + GF comfort --
   The five content pages already stack to one column below 782px (WP's own
   breakpoint), so the problem is not sideways squeeze but stacked length:
   quality alone was 12,846px at 375px. Three treatments, all inside this
   media block, zero template change:

   T1  single columns groups whose columns each hold one card become 2-track
       grids — about Core Values (5 tiles), about Our Team (5), quality QC
       Laboratory (3). Odd counts close on a full-width card, like the
       dosage pages' step 07. The nth-child(3) signature keeps quality's
       2-column Certificate-of-Analysis pair (which uses the same card
       vocabulary) stacked.
   T2  split pairs (about Inside Our Factory 3+3, quality Quality Control at
       Every Step 3+3, services How We Work 4+3) merge through the section
       grid + display: contents pattern proven in 27r. Only services has an
       odd seventh card, so only services spans.
   T3  quality Certifications (four portrait 800x1100 scans) becomes a
       horizontal snap scroller — the 27g home pattern, no dots (dots are a
       template injection and this stays CSS-only).

   Every candidate selector was leak-tested against all twenty site pages
   (tools/b2_sel_test.js) and the guarded forms are matched only through
   Gravity Forms' own wrapper classes — no form markup is touched. ---------- */
@media (max-width: 768px) {
	/* T1. card grids from single groups */
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-border-light-border-color):not(:has(figure)),
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.sf-team__avatar),
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 14px;
		row-gap: 14px;
		align-items: stretch;
	}
	/* NB: the bare-wrapper page inset these grids used to carry themselves is
	   now handled once, at section level, by section 32 (.wp-site-blocks >
	   bare group). No column-level padding-inline belongs here any more --
	   it would stack on top of the section inset (38 + 16 = 54). */
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-border-light-border-color):not(:has(figure))
		> .wp-block-column:last-child,
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.sf-team__avatar)
		> .wp-block-column:last-child,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		> .wp-block-column:last-child {
		grid-column: 1 / -1;
	}
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-border-light-border-color):not(:has(figure)) > .wp-block-column,
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.sf-team__avatar)
		> .wp-block-column,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		> .wp-block-column {
		display: flex;
		flex-direction: column;
		padding: 0 !important;
		margin: 0 !important;
	}
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-border-light-border-color):not(:has(figure)) .wp-block-group,
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.sf-team__avatar)
		.wp-block-group:not(.sf-team__avatar),
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		.wp-block-group {
		display: flex;
		flex-direction: column;
		flex: 1 1 auto;
		/* the value/factory cards carry inline 40px preset paddings — absurd
		   in a 143px track, so the inline style must be beaten */
		padding: 14px !important;
	}
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-border-light-border-color):not(:has(figure)) .wp-block-group > *,
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.sf-team__avatar)
		.wp-block-group > *,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		.wp-block-group > * {
		margin-inline: 0 !important;
	}
	/* the batch-2 lab photos are bounded and cropped; team avatars keep their
	   own inline 120px circle (section 33) */
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		img {
		width: 100%;
		height: 120px;
		aspect-ratio: auto;
		object-fit: cover;
		border-radius: 6px;
	}
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		figure {
		margin: 0;
	}
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-border-light-border-color):not(:has(figure))
		h3,
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.sf-team__avatar)
		h3,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		h3 {
		font-size: 14px;
		line-height: 1.3;
	}
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-border-light-border-color):not(:has(figure))
		.wp-block-group
		> p:last-child,
	body.page-id-14
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.sf-team__avatar)
		.wp-block-group
		> p:last-child,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(3) > .wp-block-group.has-bg-light-background-color.is-content-justification-center)
		.wp-block-group
		> p:last-child {
		font-size: 12px !important;
		line-height: 1.4;
	}

	/* T2. merged pair grids (27r pattern) */
	body.page-id-15
		section:has(> .wp-block-columns + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > p.has-accent-color),
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 14px;
		row-gap: 18px;
		align-items: stretch;
	}
	body.page-id-15
		section:has(> .wp-block-columns + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > p.has-accent-color)
		> .wp-block-columns,
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		> .wp-block-columns {
		display: contents !important;
	}
	body.page-id-15
		section:has(> .wp-block-columns + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > p.has-accent-color)
		> :not(.wp-block-columns),
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		> :not(.wp-block-columns) {
		grid-column: 1 / -1;
	}
	body.page-id-15
		section:has(> .wp-block-columns + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > p.has-accent-color)
		> *,
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		> * {
		margin-block: 0 !important;
	}
	body.page-id-15
		section:has(> .wp-block-columns + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > p.has-accent-color)
		> .wp-block-columns
		> .wp-block-column,
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		> .wp-block-columns
		> .wp-block-column {
		display: flex;
		flex-direction: column;
		padding: 0 !important;
		margin: 0 !important;
	}
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		> .wp-block-columns
		> .wp-block-column
		> .wp-block-group {
		display: flex;
		flex-direction: column;
		flex: 1 1 auto;
	}
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		> * {
		margin-inline: 0 !important;
	}
	/* only services runs seven cards — its 07 closes the row full-width */
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		> .wp-block-columns
		+ .wp-block-columns
		> .wp-block-column:last-child {
		grid-column: 1 / -1;
	}
	/* services step numbers carry an inline 40px; QC's inline 24px is already
	   the right size, so only services is overridden */
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		> p:first-child {
		font-size: 24px !important;
		line-height: 1.05;
	}
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		h3,
	body.page-id-15
		section:has(> .wp-block-columns + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > p.has-accent-color)
		h3 {
		font-size: 14px;
		line-height: 1.3;
	}
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		.wp-block-column
		> .wp-block-group
		> p:last-child,
	body.page-id-15
		section:has(> .wp-block-columns + .wp-block-columns):has(> .wp-block-columns > .wp-block-column > p.has-accent-color)
		.wp-block-column
		> p:last-child {
		font-size: 12px !important;
		line-height: 1.4;
	}

	/* T2b. quality Palatability (4 bare step cards) and Traceability (4 text
	   cards): straight 2x2 grids, no spanning. Both grids sit inside the
	   page inset now carried by section 32, so neither may add a second
	   padding-inline of its own. */
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(4)):not(:has(> .wp-block-column:nth-child(5))):has(> .wp-block-column > p.has-accent-color),
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-bg-light-background-color.is-layout-constrained) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: 14px;
		row-gap: 14px;
		align-items: stretch;
	}
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(4)):not(:has(> .wp-block-column:nth-child(5))):has(> .wp-block-column > p.has-accent-color)
		> .wp-block-column,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-bg-light-background-color.is-layout-constrained)
		> .wp-block-column {
		display: flex;
		flex-direction: column;
		padding: 0 !important;
		margin: 0 !important;
	}
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-bg-light-background-color.is-layout-constrained)
		.wp-block-group {
		display: flex;
		flex-direction: column;
		flex: 1 1 auto;
		padding: 14px !important;
	}
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-bg-light-background-color.is-layout-constrained)
		.wp-block-group
		> * {
		margin-inline: 0 !important;
	}
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(4)):not(:has(> .wp-block-column:nth-child(5))):has(> .wp-block-column > p.has-accent-color)
		h3,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-bg-light-background-color.is-layout-constrained)
		h3 {
		font-size: 14px;
		line-height: 1.3;
	}
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column:nth-child(4)):not(:has(> .wp-block-column:nth-child(5))):has(> .wp-block-column > p.has-accent-color)
		> .wp-block-column
		> p:last-child,
	body.page-id-15
		.wp-block-columns:has(> .wp-block-column > .wp-block-group.has-bg-light-background-color.is-layout-constrained)
		.wp-block-group
		> p:last-child {
		font-size: 12px !important;
		line-height: 1.4;
	}

	/* T3. Certifications: horizontal snap scroller (27g pattern, no dots) */
	body.page-id-15
		section.has-bg-light-background-color:has(> .wp-block-columns > .wp-block-column:nth-child(4) > .wp-block-group.has-card-white-background-color)
		> .wp-block-columns {
		display: flex !important;
		flex-wrap: nowrap !important;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		-webkit-overflow-scrolling: touch;
		gap: 12px;
		margin-right: -16px;
		padding-right: 16px;
		scrollbar-width: none;
	}
	body.page-id-15
		section.has-bg-light-background-color:has(> .wp-block-columns > .wp-block-column:nth-child(4) > .wp-block-group.has-card-white-background-color)
		> .wp-block-columns::-webkit-scrollbar {
		display: none;
	}
	body.page-id-15
		section.has-bg-light-background-color:has(> .wp-block-columns > .wp-block-column:nth-child(4) > .wp-block-group.has-card-white-background-color)
		> .wp-block-columns
		> .wp-block-column {
		flex: 0 0 62% !important;
		max-width: 62% !important;
		scroll-snap-align: start;
		margin: 0 !important;
		display: flex;
		flex-direction: column;
	}
	body.page-id-15
		section.has-bg-light-background-color:has(> .wp-block-columns > .wp-block-column:nth-child(4) > .wp-block-group.has-card-white-background-color)
		.wp-block-group {
		flex: 1 1 auto;
		padding: 14px !important;
	}
	body.page-id-15
		section.has-bg-light-background-color:has(> .wp-block-columns > .wp-block-column:nth-child(4) > .wp-block-group.has-card-white-background-color)
		img {
		width: 100%;
		aspect-ratio: 3 / 4;
		object-fit: cover;
		object-position: top;
		border-radius: 6px;
	}

	/* T4. Gravity Forms comfort, CSS only: 16px inputs (iOS Safari zooms on
	   focus below that) at every phone width; the single-column field grid
	   only below 640px — at 768 a half/half row still reads fine and stacking
	   it there cost factory-tour +9% */
	.fluentform .ff-el-form-control {
		font-size: 16px !important;
	}
	@media (max-width: 640px) {
		.fluentform .ff-t-container {
			display: block !important;
		}
		.fluentform .ff-t-cell {
			display: block !important;
			width: 100% !important;
		}
	}
}

/* --- 27u. Batch 3: cooperation / 404 / cookie tables -------------------- */
/* Scope notes: cooperation = body.page-id-30, cookie = body.page-id-37,
   404 only exists under body.error404, so nothing here can leak onto other
   pages. The old body.blog card rules were replaced by section 36 (mobile
   blog list redesign). */
@media (max-width: 768px) {
	/* U1. 404 hero. The template inlines spacing-100 (64px) on the first
	   section; the site-wide hero rhythm since 27s is 40px. */
	body.error404 .wp-site-blocks > section:first-of-type {
		padding-top: 40px !important;
		padding-bottom: 40px !important;
	}

	/* U3. Cooperation "Who We Work With": merge the two 3-card rows into one
	   even 2-col grid (6 cards, no spanning row needed). Section-level grid +
	   display:contents on the column groups, same pattern as 27r/27t. The
	   card groups carry inline 40px padding — compressed for a 143px card. */
	body.page-id-30 section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color) {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 12px;
	}
	body.page-id-30 section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color) > h2 {
		grid-column: 1 / -1;
	}
	body.page-id-30 section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color) > .wp-block-columns {
		display: contents;
	}
	body.page-id-30 section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color) .wp-block-group.has-card-white-background-color {
		padding: 14px !important;
	}
	body.page-id-30 section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color) h3 {
		font-size: 14px;
		line-height: 1.3;
	}
	body.page-id-30 section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-card-white-background-color) p {
		font-size: 11px !important;
		line-height: 1.4;
		margin-top: 5px;
		margin-bottom: 0;
	}

	/* U4. Compare table (cooperation, 4 cols) and cookie table (3 cols): WP
	   core already scrolls the figure; give the cells real room and add
	   gentle scroll snap so swiping lands on column starts. */
	body.page-id-30 .wp-block-table table {
		min-width: 560px; /* 4 cols × ~140px instead of 57px squeezes */
	}
	body.page-id-37 .wp-block-table table {
		min-width: 420px;
	}
	body.page-id-30 .wp-block-table,
	body.page-id-37 .wp-block-table {
		scroll-snap-type: x proximity;
		-webkit-overflow-scrolling: touch;
	}
	body.page-id-30 .wp-block-table th,
	body.page-id-30 .wp-block-table td,
	body.page-id-37 .wp-block-table th,
	body.page-id-37 .wp-block-table td {
		scroll-snap-align: start;
	}
}

/* === 27. Floating UI: cookie consent + contact stack + back to top ======== */
/* Cookie banner — fixed bottom strip, brand deep-green surface. */
.sf-cookie-banner {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 9999;
	background-color: #1B4D3E;
	color: #ffffff;
	box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.2);
}

.sf-cookie-banner[hidden] {
	display: none;
}

.sf-cookie-banner__inner {
	max-width: 1200px;
	margin: 0 auto;
	padding: 16px 24px;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 24px;
}

.sf-cookie-banner__text {
	margin: 0;
	font-size: 14px;
	line-height: 1.55;
	color: #ffffff;
}

.sf-cookie-banner__text a {
	color: #ffffff;
	text-decoration: underline;
	text-underline-offset: 2px;
}

.sf-cookie-banner__actions {
	display: flex;
	align-items: center;
	gap: 12px;
	flex-shrink: 0;
}

.sf-cookie-banner__btn {
	padding: 10px 18px;
	min-height: 44px;
	box-sizing: border-box;
	border-radius: 6px;
	font-size: 14px;
	font-weight: 600;
	cursor: pointer;
	transition: filter 0.2s ease, background-color 0.2s ease;
}

.sf-cookie-banner__btn--accept {
	background-color: #E8923A;
	color: #ffffff;
	border: none;
}

.sf-cookie-banner__btn--accept:hover {
	filter: brightness(1.08);
}

.sf-cookie-banner__btn--reject {
	background-color: transparent;
	color: #ffffff;
	border: 1px solid #ffffff;
}

.sf-cookie-banner__btn--reject:hover {
	background-color: rgba(255, 255, 255, 0.12);
}

/* Floating contact stack — bottom-right column. */
.sf-float-stack {
	position: fixed;
	right: 24px;
	bottom: 24px;
	z-index: 9998;
	display: flex;
	flex-direction: column;
	gap: 12px;
	/* Every child hugs the right edge. Without it the column's width is set by
	   its widest child — still 52px until batch H4, when the 150px capsule
	   joined it — and the circles would then sit at the column's *left* edge,
	   98px away from the offset appendix A.5 #7 measured. A no-op while all
	   three children are the same width, and the reason a wider child can be
	   added later without moving them. */
	align-items: flex-end;
}

.sf-float-btn {
	width: 52px;
	height: 52px;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: none;
	cursor: pointer;
	color: #ffffff;
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
	transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.3s ease;
}

.sf-float-btn:hover {
	transform: scale(1.08);
	box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
}

.sf-float-btn--whatsapp {
	background-color: #25D366;
}

.sf-float-btn--email,
.sf-float-btn--top {
	background-color: #1B4D3E;
}

.sf-float-btn--top {
	opacity: 0;
	pointer-events: none;
}

.sf-float-btn--top.is-visible {
	opacity: 1;
	pointer-events: auto;
}

/* Cookie banner pushes the stack up so the two never overlap. */
body.has-cookie-banner .sf-float-stack {
	bottom: 100px;
}

/* TranslatePress injects its floating switcher, not us, and it anchors to the
   same bottom edge at z-index 99999 — an order of magnitude above the banner,
   which is how the banner's own right-hand side became unclickable. It gets
   the same treatment as the stack above. `bottom` is written directly rather
   than through the plugin's own --bottom variable: the declaration wins on
   specificity (0,2,1 against the plugin's 0,1,0), so no `!important` is needed
   and no variable is fought over. */
body.has-cookie-banner .trp-floating-switcher {
	bottom: 88px;
}

@media (prefers-reduced-motion: reduce) {
	.sf-cookie-banner__btn,
	.sf-float-btn {
		transition: none;
	}

	.sf-float-btn:hover {
		transform: none;
	}
}

@media (max-width: 767px) {
	.sf-cookie-banner__inner {
		flex-direction: column;
		align-items: stretch;
		gap: 12px;
		padding: 16px;
	}

	.sf-cookie-banner__actions {
		flex-direction: column;
		align-items: stretch;
		gap: 8px;
	}

	.sf-cookie-banner__btn {
		width: 100%;
		min-height: 44px;
	}

	.sf-float-stack {
		right: 16px;
		bottom: 16px;
	}

	.sf-float-btn {
		width: 44px;
		height: 44px;
	}

	body.has-cookie-banner .sf-float-stack {
		/* Calibrated against the stacked mobile banner height (~248px). */
		bottom: 268px;
	}

	/* The switcher rides the same stacked height as the stack above. */
	body.has-cookie-banner .trp-floating-switcher {
		bottom: 280px;
	}
}

/* --- 28. One merged stat bar: 10 figures under the hero ------------------
   The purchase-decision strip and the Factory figures are now a single
   ruled panel: 5 x 2 from 1200px, 4-up on a tablet, and a two-column dot
   list on a phone. Figures keep the .sf-num hook so the count-up in
   interactions.js still drives them; the cells are plain containers and
   the JS staggers them in (classes are only ever added when motion is
   allowed, so a no-JS or reduced-motion visitor sees the finished bar). */
.wp-block-columns.sf-statbar__grid {
	display: grid !important;
	grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
	gap: 0 !important;
	margin-bottom: 0 !important;
	background: #fff;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	overflow: hidden;
}
.sf-statbar__grid > .sf-statbar__cell {
	display: flex;
	flex-direction: column;
	justify-content: center;
	margin: 0 !important;
	padding: 20px;
	border-right: 1px solid var(--wp--preset--color--border-light);
}
/* at 5-up the second row starts at cell 6; the per-breakpoint rules below
   re-draw both edges as soon as the column count changes */
.sf-statbar__grid > .sf-statbar__cell:nth-child(n + 6) {
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-statbar__grid > .sf-statbar__cell:nth-child(5n) {
	border-right: none;
}
.sf-statbar__num {
	margin: 0 0 6px !important;
	font-size: 38px;
	font-weight: 700;
	line-height: 1.1;
	letter-spacing: -0.02em;
	white-space: nowrap;
	color: var(--wp--preset--color--text-primary);
}
.sf-statbar__label {
	margin: 0 !important;
	font-size: 13px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-secondary);
}
/* Certifications label links through to the quality page anchor */
.sf-certlink {
	color: var(--wp--preset--color--accent);
	text-decoration: none;
}
.sf-certlink:hover,
.sf-certlink:focus {
	text-decoration: underline;
}
/* hover: only the figure reacts — 6% larger, brand green. Transform only,
   so neither the cell nor the panel ever reflows. */
.sf-statbar__num {
	transition: transform 0.2s ease, color 0.2s ease;
}
.sf-statbar__cell:hover .sf-statbar__num {
	transform: scale(1.06);
	color: var(--wp--preset--color--brand-green);
}
/* Staggered fade-in, 60ms apart (delays are set inline by the script). */
.sf-statbar.sf-stagger .sf-statbar__cell {
	opacity: 0;
	transform: translateY(6px);
	transition: opacity 0.4s ease, transform 0.4s ease;
}
.sf-statbar.sf-stagger-in .sf-statbar__cell {
	opacity: 1;
	transform: none;
}

/* 1024-1199: still 5-up, tighter cell and a step down in figure size */
@media (max-width: 1199px) and (min-width: 1024px) {
	.sf-statbar__grid > .sf-statbar__cell {
		padding: 18px;
	}
	.sf-statbar__num {
		font-size: 32px;
	}
	.sf-statbar__label {
		font-size: 12px;
	}
}

/* 768-1023: 4-up -> rows of 4/4/2. 5n/4n are re-asserted because the desktop
   rules above key off the 5-up row boundary. */
@media (max-width: 1023px) and (min-width: 768px) {
	.wp-block-columns.sf-statbar__grid {
		grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
	}
	.sf-statbar__grid > .sf-statbar__cell {
		padding: 16px;
	}
	.sf-statbar__grid > .sf-statbar__cell:nth-child(5n) {
		border-right: 1px solid var(--wp--preset--color--border-light);
	}
	.sf-statbar__grid > .sf-statbar__cell:nth-child(4n) {
		border-right: none;
	}
	.sf-statbar__grid > .sf-statbar__cell:nth-child(n + 5) {
		border-top: 1px solid var(--wp--preset--color--border-light);
	}
	.sf-statbar__num {
		font-size: 28px;
	}
	.sf-statbar__label {
		font-size: 12px;
	}
}

/* <=767: 2 x 5 dot list. No panel, no rules - the brand-green dot separates
   the items and the figure/label stack keeps "MOQ units minimum" on one line
   inside a ~142px cell. */
@media (max-width: 767px) {
	.sf-statbar {
		padding-top: 24px !important;
		padding-bottom: 24px !important;
	}
	.wp-block-columns.sf-statbar__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
		column-gap: 14px !important;
		row-gap: 2px !important;
		background: transparent;
		border: none;
		border-radius: 0;
		overflow: visible;
	}
	.sf-statbar__grid > .sf-statbar__cell {
		display: grid !important;
		grid-template-columns: 6px minmax(0, 1fr);
		column-gap: 8px;
		padding: 2px 0 !important;
		border: none !important;
		background: transparent;
	}
	.sf-statbar__grid > .sf-statbar__cell::before {
		content: "";
		grid-column: 1;
		grid-row: 1 / span 2;
		width: 6px;
		height: 6px;
		margin-top: 6px;
		border-radius: 50%;
		background: #5AB735;
	}
	.sf-statbar__num {
		grid-column: 2;
		font-size: 16px;
		margin-bottom: 1px !important;
		letter-spacing: 0;
	}
	.sf-statbar__label {
		grid-column: 2;
		font-size: 11px;
		line-height: 1.35;
	}
}

/* --- 22b. Client stories: finger-swipe carousel (phones + reduced motion) --
   Section 22 makes the strip an auto-drifting marquee: the track holds TWO
   copies of the six-card group and animates to -50% so the loop is seamless.
   That works with a pointer, but a finger can neither pause it nor scrub it,
   so <=768px drops the animation and hands the strip over to native scrolling
   with snap points. The duplicate group exists only to make -50% land
   seamlessly, so it is hidden there — otherwise the visitor would swipe into
   six repeated cards. Every card snaps to the scrollport's left edge, and at
   85vw the next card still peeks in (~40px on a 375px phone) to advertise
   that the row moves. The group's own trailing padding-right stays as
   end-of-scroll breathing room, and the 16px group gap is untouched.

   prefers-reduced-motion gets the same unlock at EVERY width. Section 21's
   global freeze left the track parked at translateX(0), which made cards 4-6
   permanently unreachable; scrolling makes them reachable again without the
   strip ever moving on its own. Card widths are deliberately left alone there
   so the desktop rhythm stays exactly as authored. */
@media (max-width: 768px) {
	.sf-marquee.sf-stories {
		overflow-x: auto;
		overscroll-behavior-x: contain;
		scroll-snap-type: x mandatory;
		/* centered snap: (100vw - 85vw) / 2 inset lets the first and last
		   cards reach the centered resting position too */
		scroll-padding-inline: 7.5vw;
		-webkit-overflow-scrolling: touch;
		/* the strip is an affordance, not a scrollbar showcase */
		scrollbar-width: none;
	}
	.sf-marquee.sf-stories::-webkit-scrollbar {
		display: none;
	}
	.sf-marquee.sf-stories .sf-marquee__track {
		animation: none;
		will-change: auto;
	}
	.sf-marquee.sf-stories .sf-stories__group[aria-hidden="true"] {
		display: none;
	}
	/* content-side inset: scroll-padding alone can't center the FIRST card
	   (that would need a negative scrollLeft) — the strip needs real space
	   before card 1, so the resting scroll position starts centered. */
	.sf-marquee.sf-stories .sf-stories__group {
		padding-inline: 7.5vw;
	}
	.sf-story {
		/* border-box so 85vw is the full footprint, padding included */
		box-sizing: border-box;
		width: 85vw;
		scroll-snap-align: center;
	}
}

@media (prefers-reduced-motion: reduce) {
	.sf-marquee.sf-stories {
		overflow-x: auto;
		overscroll-behavior-x: contain;
		scroll-snap-type: x mandatory;
		scroll-padding-inline: 7.5vw;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
	}
	.sf-marquee.sf-stories::-webkit-scrollbar {
		display: none;
	}
	.sf-marquee.sf-stories .sf-marquee__track {
		animation: none;
		will-change: auto;
	}
	.sf-marquee.sf-stories .sf-stories__group[aria-hidden="true"] {
		display: none;
	}
	.sf-marquee.sf-stories .sf-stories__group {
		padding-inline: 7.5vw;
	}
	.sf-story {
		scroll-snap-align: center;
	}
}

/* --- 22c. Article covers: the photo has to fill its frame ------------------
   Root cause, measured on the live page: WP core pads every group that
   carries a background colour — `:where(.wp-block-group.has-background)
   { padding: 1.25em 2.375em }` — and .sf-slot sets a 12px font-size, so this
   slot was handed 15px / 28.5px of padding. Combined with a content-sized
   implicit grid track, that laid the 1200x900 cover out at 325x243.75 inside
   a 382x286.5 frame: a 28.5px light matte down each side, 21px top and
   bottom (12px / 6.75px on a phone). Slots are single-image frames, so the
   padding goes and the track is pinned to the frame — exactly what
   .sf-slot--video already does. The photo then covers the frame via
   object-fit, cropping instead of letterboxing: no crop at all in a 4:3
   desktop frame (the source is 4:3) and the intended top/bottom crop in a
   16:9 phone frame. */
.sf-slot--cover {
	padding: 0 !important;
	grid-template-columns: minmax(0, 1fr);
	grid-template-rows: minmax(0, 1fr);
	border-radius: 8px 8px 0 0 !important;
	overflow: hidden;
}
.sf-slot--cover .wp-block-image {
	width: 100%;
	height: 100%;
	margin: 0;
}
.sf-slot--cover img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* the 6px radius belongs to framed art, not to a full-bleed cover */
	border-radius: 0;
}

/* 22c-2. Rhythm: the articles band must land inside one 900px screen, so
   three desktop values come down — section padding 48/48 -> 32/32 and the
   48px gaps above the card row and the CTA -> 32px. The phone rhythm was
   tuned in 27o and is left untouched. All three are inline styles in the
   template, hence !important. */
@media (min-width: 769px) {
	.sf-section:has(.sf-slot--cover) {
		padding-top: 32px !important;
		padding-bottom: 32px !important;
	}
	.sf-section:has(.sf-slot--cover) .wp-block-columns,
	.sf-section:has(.sf-slot--cover) .wp-block-buttons {
		margin-top: 32px !important;
	}
}

/* --- 28. Latest Articles: bleed cover + one-screen rhythm ------------------
   (a) COVER NEVER RENDERED. .sf-slot is `display:grid; place-items:center`,
       WP styles figure.wp-block-image.aligncenter as `display:table`
       (shrink-to-fit), and .sf-slot img asks for width:100% — the three
       together form a circular width dependency, so the figure collapsed to
       0px. Cards 2 and 3 showed an empty grey frame; card 1 showed a
       thumbnail floating in the middle. The 768px block already patched this
       (see "P3" above); the fix now applies at every width: stretch the
       figure across its grid cell and crop the photo to the frame.
   (b) The frame carried its own letterbox (computed padding 15px 28.5px) and
       a 6px radius on the photo, so the image could never touch the card
       edge. The card's border-box is the frame — the slot must not add one.
   (c) RHYTHM. Section + cards + View All button have to fit one 1440x900
       screen, so the cover->copy gap, the copy's run of 24px WP block gaps,
       and the two 48px section margins all come down. The desktop-only copy
       padding keeps the 768px block's own 12px rule winning on phones. */

.sf-slot--cover {
	padding: 0 !important;
	overflow: hidden;
	border-radius: 8px 8px 0 0;
}
.sf-slot--cover .wp-block-image {
	display: block !important;
	width: 100% !important;
	height: 100% !important;
	justify-self: stretch;
	align-self: stretch;
	margin: 0 !important;
}
.sf-slot--cover img {
	display: block;
	width: 100% !important;
	height: 100% !important;
	object-fit: cover;
	border-radius: 0 !important;
}

/* cover sits flush under the copy: the 24px was the card's own constrained
   block-gap between its two child groups */
.sf-card--flush > .wp-block-group + .wp-block-group {
	margin-block-start: 0 !important;
}
/* copy stack: eyebrow -> h3 -> excerpt -> Read More, 24px -> 10px */
.sf-card--flush > .wp-block-group > * + * {
	margin-block-start: 10px !important;
}
@media (min-width: 769px) {
	.sf-card--flush > .wp-block-group:last-child {
		padding: 14px !important;
	}
}
/* columns + button: 48px -> 28px (inline preset values need !important) */
.sf-section--large:has(.sf-card--flush) > .wp-block-columns,
.sf-section--large:has(.sf-card--flush) > .wp-block-buttons {
	margin-top: 28px !important;
}

/* --- 22d. Phone cover keeps its own frame --------------------------------
   Line 2568 slims the flush card's two child groups on phones with
   `.sf-card--flush > .wp-block-group { padding: 12px !important }` — right
   for the copy group, wrong for the first child, which is the cover slot:
   at (0,2,0) it outranks the slot's own `padding: 0` at (0,1,0), so the
   photo was still ringed by a 12px light matte on every phone (measured
   273x143.06 inside a 297x167.06 frame). Scope the reset to the slot with a
   higher weight; the copy group keeps its 12px, and the desktop 14px rule
   above is untouched. */
.sf-card--flush > .wp-block-group.sf-slot--cover {
	padding: 0 !important;
}

/* === 30. Legacy-engine fallback (html.no-has) =============================
   Engines without :has() (iOS < 15.4 WKWebView, old Android WebView) silently
   drop every :has() rule — 161 of the 172 live inside the mobile breakpoint.
   The layout then HALF-applies: the plain nth-child grids survive while the
   :has()-based display:contents merges vanish, stranding columns in wrong
   rows (QC grid 2+1+2+1, cert rows, about/factory columns; measured +2271px
   on /about, +3570px on /quality at 440px).
   Detection is an inline <head> probe (functions.php): any engine failing
   CSS.supports('selector(:has(*))') gets html.no-has. This block then strips
   the hybrid layout back to plain single-column flow — plain but tidy.
   New engines never match html.no-has and are untouched. */
@media (max-width: 768px) {
	/* section-level merges built on section:has(...) fall back to block flow */
	html.no-has section {
		display: block !important;
	}
	/* every columns combination reverts to stacked single column */
	html.no-has .wp-block-columns {
		display: flex !important;
		flex-direction: column !important;
	}
	/* the stats grid never relied on :has() (nth-child only) — keep its 2x5.
	   Same for the dosage catalogue (self-contained repeat(2,...) at 27e)
	   and the claims strip (auto-fit grid at the 781px block). */
	html.no-has .sf-statbar__grid,
	html.no-has .sf-dosage-grid,
	html.no-has .sf-strip {
		display: grid !important;
		flex-direction: row !important;
	}
}

/* --- 31. About: Our Journey timeline (rail + nodes + hover + reveal) -------
   Core renders each milestone as a flex row with `flex-wrap: wrap` and the
   copy group at flex-basis:auto. The copy's max-content width therefore had to
   fit next to the year: the six-certification line measured ~750px and pushed
   the year onto a line of its own, while shorter copy stayed inline. One rogue
   line was the whole bug — flex:1 1 0% + min-width:0 on the copy group makes
   the row independent of copy length for good.
   The rail is one segment per milestone — from just under this node down to the
   centre of the next one, which masks the overlap. The last entry draws none,
   so the rail starts at the first node and stops at the last one without
   anybody having to know how many entries the editor left in place. Segment
   length is keyed to the same preset var the row's inline margin uses, so the
   two can never drift apart.
   Every milestone wraps node + year + copy in .sf-journey__item so the group
   can lift on hover; the node itself is a real .sf-journey__dot element,
   absolutely positioned to stay out of the flex row's flow. -------------- */
.sf-journey__item {
	position: relative;
	padding-left: 34px;
	/* core ships `.is-layout-flex { flex-wrap: wrap }` after this stylesheet */
	flex-wrap: nowrap !important;
	/* grow leftwards from the rail, so a hovered row never runs past the copy
	   column's right edge */
	transform-origin: left center;
	transition: transform 0.25s ease;
}

.sf-journey__item:hover {
	transform: scale(1.04);
}

.sf-journey__item::after {
	content: "";
	position: absolute;
	left: 8px;
	top: 22px;
	bottom: calc(-1 * var(--wp--preset--spacing--30) - 13px);
	width: 2px;
	border-radius: 1px;
	background: var(--wp--preset--color--border-medium);
}

.sf-journey__item--last::after {
	display: none;
}

/* milestone node, centred on the year's first line (20px at line-height 1.3) */
.sf-journey__dot {
	position: absolute;
	left: 0;
	top: 4px;
	width: 18px;
	height: 18px;
	box-sizing: border-box;
	border: 3px solid var(--wp--preset--color--primary);
	border-radius: 50%;
	background: var(--wp--preset--color--card-white);
	transition: transform 0.25s ease;
}

.sf-journey__item:hover .sf-journey__dot {
	transform: scale(1.5);
}

.sf-journey__year {
	flex: 0 0 auto !important;
	min-width: 76px !important;
	line-height: 1.3;
	color: var(--wp--preset--color--primary);
}

.sf-journey__body {
	flex: 1 1 0% !important;
	min-width: 0 !important;
}

/* One-shot reveal. The wrapper only gets .sf-journey--live from about.js, so
   with JS off (or IntersectionObserver missing) every milestone stays visible.
   The hidden state is scoped to :not(.is-revealed) and the landed state sets
   no transform of its own — that keeps `transform` free for :hover, which a
   "revealed { transform: none }" rule would otherwise outrank. ------------ */
.sf-journey--live .sf-journey__item {
	transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.sf-journey--live .sf-journey__item:not(.is-revealed) {
	opacity: 0;
	transform: translateY(14px);
}

.sf-journey--live .sf-journey__item:not(.is-revealed) .sf-journey__dot {
	transform: scale(0.35);
}

/* entrance over: transform belongs to :hover from here on (0.25s) */
.sf-journey--live .sf-journey__item.is-revealed {
	transition: opacity 0.55s ease, transform 0.25s ease;
}

@media (prefers-reduced-motion: reduce) {
	.sf-journey--live .sf-journey__item,
	.sf-journey--live .sf-journey__item .sf-journey__dot {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
	.sf-journey__item,
	.sf-journey__dot {
		transition: none;
	}
	.sf-journey__item:hover,
	.sf-journey__item:hover .sf-journey__dot {
		transform: none;
	}
}

@media (max-width: 768px) {
	/* narrower gutter: 375px leaves ~205px of copy measure with this split */
	.sf-journey__item {
		padding-left: 26px;
		column-gap: 14px !important;
	}
	.sf-journey__dot {
		top: 3px;
		width: 14px;
		height: 14px;
		border-width: 2px;
	}
	.sf-journey__item::after {
		left: 6px;
		top: 17px;
		bottom: calc(-1 * var(--wp--preset--spacing--30) - 10px);
	}
	.sf-journey__year {
		min-width: 56px !important;
	}
}

/* --- 32. About: Core Values — one box height for all five ----------------
   .wp-block-columns already stretches its five columns (`align-items:
   normal` resolves to stretch), but the white card inside each column is a
   block child, so it stopped at its own copy: "Quality First" and "No Brand
   Conflict" ended 22.4px above the other three. Making the column a flex
   container lets its single card stretch to the full column height, and
   flex:1 1 auto keeps that card full-width instead of collapsing to its
   max-content width. The ≤768px grid rule already relies on the same
   display:flex, so mobile behaves exactly as before. -------------------- */
.sf-core > .wp-block-column {
	display: flex;
	align-items: stretch;
}

.sf-core > .wp-block-column > .wp-block-group {
	flex: 1 1 auto;
	min-width: 0;
}

/* --- 33. About · Our Team — headshots fill the circle, tight stack --------
   The avatar was a 120px bg-light plinth whose only child, an .aligncenter
   figure, shrink-to-fits to a table box — and because the grey came from a
   backgroundColor attribute, WP core's
   :where(.wp-block-group.has-background){1.25em 2.375em} padding applied,
   leaving a 44px content box: a 44px thumbnail floated inside a 120px grey
   circle, while the plinth itself (inline width, flex-start default) sat
   50.4px left of the centred name/role/copy axis.

   Fix: the bg-light attribute is gone from the markup (the core padding
   goes with it), the figure and the image both take the full 120px box with
   the circle cut on the image itself (object-fit: cover), and
   margin-inline: auto re-centres the circle on the text axis. The WP flow
   gap of 24px between every child collapses to 16px under the circle, 8px
   name → role, 10px role → copy — same values on mobile, where the batch-2
   T1 grid (27t) keeps the 2-track layout: its selector now keys on
   .sf-team__avatar, the stable class that replaced the bg-light signature,
   and the card rule (padding: 14px / flex: 1 1 auto) explicitly excludes
   the avatar so the circle can neither inherit padding nor stretch to the
   row height (that stretch is what made Kevin's circle 140px tall). ---- */
.sf-team > .wp-block-column > .sf-team__avatar {
	margin-inline: auto;
	padding: 0;
	overflow: hidden;
}
.sf-team__avatar > figure.wp-block-image {
	display: block;
	width: 100%;
	height: 100%;
	margin: 0;
}
.sf-team__avatar > figure.wp-block-image img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	border-radius: 999px;
}
.sf-team > .wp-block-column > h3 {
	margin-block: 16px 8px;
}
.sf-team > .wp-block-column > p {
	margin-block: 0;
}
.sf-team > .wp-block-column > p + p {
	margin-block-start: 10px;
}

/* --- 34. About · Inside Our Factory — core Gallery block + lightbox ------
   Six white cards (card-white plate + 1px border + min-height 260px) became
   one core Gallery block; it now carries six tiles, all 800x600 = exactly
   4:3. The only fac-placeholder.webp slots (1100x733, cover-cropped) were
   dropped: six identical frames with identical alt text added nothing a
   visitor could read. The card chrome and the
   :where(.wp-block-group.has-background) 1.25em/2.375em padding that
   shrank each photo are gone, so the photo is the tile. The grid is stated here
   rather than inherited from the block's columns attribute because WP
   prints its gallery CSS after this sheet. Three columns at every width, so
   the six tiles land as 3x2 on desktop and on a 375px phone alike. Tiles
   float on the section's bg-light band — the same
   token Core Values uses, so the page reads gray / white / gray — via a
   soft resting shadow that deepens on hover, layered under the image's
   own scale(1.05): two independent effects on two elements. ------------- */
.sf-fac {
	display: grid !important;
	grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
	gap: 10px !important;
	margin-top: var(--wp--preset--spacing--60);
}
/* core's gallery sheet sizes the tiles itself —
   .wp-block-gallery.has-nested-images.columns-3 figure.wp-block-image:not(#individual-image)
   resolves to width: calc(33.33% - 16px), so the tiles would float in tracks
   they never fill. The width here has to carry !important to win over that
   rule. No max-width of our own either: the section's constrained layout is
   what keeps the grid at content width. */
.sf-fac > .wp-block-image {
	display: block !important;
	flex: none !important;
	width: 100% !important;
	max-width: 100% !important;
	margin: 0 !important;
	overflow: hidden;
	border-radius: 8px;
	aspect-ratio: 4 / 3;
	/* float: soft resting shadow, deepening on hover; the tile itself never
	   scales — that stays on the img — so the two effects compose cleanly */
	box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
	transition: box-shadow 0.25s ease;
	cursor: zoom-in;
}
.sf-fac > .wp-block-image:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
}
.sf-fac > .wp-block-image:hover,
.sf-fac > .wp-block-image:focus-visible {
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.sf-fac > .wp-block-image img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	border-radius: 8px;
	transform: scale(1);
	transition: transform 0.25s ease;
}
.sf-fac > .wp-block-image:hover img,
.sf-fac > .wp-block-image:focus-visible img,
.sf-fac > .wp-block-image.is-current img {
	transform: scale(1.05);
}
@media (max-width: 768px) {
	.sf-fac {
		grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
		gap: 8px !important;
	}
}
@media (prefers-reduced-motion: reduce) {
	.sf-fac > .wp-block-image,
	.sf-fac > .wp-block-image img {
		transition: none;
	}
	.sf-fac > .wp-block-image:hover img,
	.sf-fac > .wp-block-image:focus-visible img {
		transform: none;
	}
}

/* the lightbox: one dialog, arrows that wrap, backdrop and Escape to close.
   Built by about.js on first click only; without JS the gallery stays a
   plain grid of photos. */
html.sf-lb-open,
body.sf-lb-open {
	overflow: hidden;
}
.sf-lb {
	position: fixed;
	inset: 0;
	z-index: 99999;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 18px;
	padding: 24px;
	background-color: rgba(9, 20, 16, 0.94);
	opacity: 0;
	transition: opacity 0.25s ease, background-color 0.2s ease;
	-webkit-tap-highlight-color: transparent;
}
.sf-lb[hidden] {
	display: none;
}
.sf-lb.is-open {
	opacity: 1;
}
.sf-lb.is-closing {
	background-color: rgba(9, 20, 16, 0);
}
.sf-lb__stage {
	display: flex;
	align-items: center;
	justify-content: center;
	/* about.js sizes this per photo (largest box that fits the viewport at that
	   photo's ratio); max-width is the no-JS net. A shrink-to-fit stage would
	   leave the photo's percentage width resolving against an indefinite box. */
	max-width: 100%;
}
.sf-lb__img {
	display: block;
	width: 100%;
	height: auto;
	/* ratio comes from the photo itself (about.js writes it inline), so a
	   non-4:3 upload still opens undistorted; contain + max-height are nets */
	aspect-ratio: 4 / 3;
	max-height: calc(100vh - 180px);
	object-fit: contain;
	border-radius: 8px;
	box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.sf-lb__bar {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 14px;
}
.sf-lb__count {
	margin: 0;
	color: rgba(255, 255, 255, 0.72);
	font-size: 13px;
	letter-spacing: 0.04em;
}
.sf-lb__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 1px solid rgba(255, 255, 255, 0.28);
	border-radius: 999px;
	background-color: rgba(255, 255, 255, 0.08);
	color: #fff;
	font-family: inherit;
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
	transition: background-color 0.2s ease, border-color 0.2s ease;
}
.sf-lb__btn:hover,
.sf-lb__btn:focus-visible {
	background-color: rgba(255, 255, 255, 0.2);
	border-color: #fff;
}
.sf-lb__btn--close {
	font-size: 16px;
}
@media (min-width: 769px) {
	.sf-lb__btn--prev {
		position: absolute;
		left: 24px;
		top: 50%;
		transform: translateY(-50%);
	}
	.sf-lb__btn--next {
		position: absolute;
		right: 24px;
		top: 50%;
		transform: translateY(-50%);
	}
	.sf-lb__btn--close {
		position: absolute;
		top: 24px;
		right: 24px;
	}
}
@media (prefers-reduced-motion: reduce) {
	.sf-lb {
		transition: none;
	}
}

/* === 31. Quality & Compliance page ========================================
   Four of the page's sections are reshaped here: the certificate list, the
   in-house laboratory grid, the six QC steps and the palatability flow.
   Section 31a additionally re-aligns the FAQ accordion of EVERY page to the
   1200px text measure — that one is not Quality-only.
   ------------------------------------------------------------------------ */

/* --- 31a. FAQ accordion: back to the full 1200px text measure ------------
   The accordion read 800px wide on all 15 pages that carry one, while the
   headings above it stayed at 1200px. Two inline declarations did it: the
   `contentSize` block attribute on every .sf-faq group, and — on the FAQ hub
   — on the wrapping section too. Both land in
   `--wp--style--global--content-size`, which the core constrained rules read
   through `:where()` (specificity 0). An `!important` custom-property
   declaration in a stylesheet outranks a non-important inline one, so
   re-declaring the variable here fixes the group AND the sibling headings
   that share the section's measure, on every page, from one place — no
   template has to be touched. Answers keep their own 820px readable measure
   (.sf-faq details p), so only the question rows widen. */
.sf-faq,
section:has(> .sf-faq),
.wp-block-group:has(> .sf-faq) {
	--wp--style--global--content-size: 1200px !important;
}
.sf-faq,
.sf-faq:has(> .has-text-align-center) {
	max-width: 1200px !important;
}
/* The FAQ hub's wrapping section carries its own contentSize:800px, and core
   writes that one into the container rule as a hard-coded max-width on the
   section's children — `.wp-container-core-group-is-layout-2bb1c38a > :where(…)`
   — rather than into the variable, so the rule above cannot reach the headings
   that sit next to the accordion. Match them directly; specificity 0,1,1 beats
   that 0,1,0 selector, and its own !important only covers the margins. */
section:has(> .sf-faq) > :where(:not(.alignwide):not(.alignfull)) {
	max-width: 1200px !important;
}

/* --- 31b. Certifications: 120px thumbnail rows --------------------------
   The row already was a two-column list with hairlines instead of cards; the
   only thing keeping six of them off one screen was the media column, which
   the 4fr/6fr split stretched to 467px. Pinning it to 120px makes an 800x1100
   certificate 165px tall at 8/11 — now the copy column (~135px) is the
   shorter one, so the thumbnail sets the row rhythm. Six rows drop from
   2522px to roughly 1400px. */
.sf-certrow {
	grid-template-columns: 120px minmax(0, 1fr);
	gap: 28px;
	padding: 20px 0;
}
.sf-certrow__media a {
	display: block;
	cursor: zoom-in;
}
.sf-certrow__media img {
	display: block;
	/* border-box so the 1px frame is part of the 120px, and the height is
	   stated instead of derived: an 800x1100 scan reads 8/11 here, and it has
	   to match the placeholder box exactly or those two rows run 16px taller. */
	box-sizing: border-box;
	width: 120px;
	height: 167px;
	max-height: none;
	object-fit: cover;
	object-position: top center;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	background-color: var(--wp--preset--color--card-white);
	box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
	transition: box-shadow 0.25s ease;
}
.sf-certrow__media a:hover img,
.sf-certrow__media a:focus-visible img {
	box-shadow: 0 8px 22px rgba(0, 0, 0, 0.12);
}
.sf-certrow__media--placeholder {
	box-sizing: border-box;
	width: 120px;
	height: 167px;
	aspect-ratio: auto;
	max-height: none;
	padding: 8px;
	text-align: center;
}
.sf-certrow__media--placeholder span {
	font-size: 11px;
	line-height: 1.4;
}
.sf-certrow__name {
	font-size: 19px;
}
.sf-certrow__issuer {
	margin-top: 4px;
	font-size: 13px;
}
.sf-certrow__desc {
	margin-top: 8px;
	font-size: 14px;
	line-height: 1.65;
}
.sf-certrow__links {
	gap: 18px;
	margin-top: 10px;
	font-size: 13px;
}

/* --- 31c. In-house QC laboratory: six instruments, 3 x 2 ---------------
   Same treatment as the Factory grid (section 34): core's gallery sheet
   prints width: calc(33.333% - 16px) for .columns-3 after this file, so the
   tile width needs !important, and no max-width of our own — the section's
   constrained layout is what holds the grid at content width. Each tile is a
   flex column so the caption sits under the photo instead of the core
   default, which pins it over the photo's bottom edge. */
.sf-eq {
	display: grid !important;
	grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
	column-gap: 24px !important;
	row-gap: 30px !important;
	margin-top: var(--wp--preset--spacing--60);
}
.sf-eq > .wp-block-image {
	display: flex !important;
	flex-direction: column;
	width: 100% !important;
	max-width: 100% !important;
	margin: 0 !important;
}
.sf-eq > .wp-block-image img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	object-position: center;
	border-radius: 8px;
	box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
	transition: box-shadow 0.25s ease;
}
.sf-eq > .wp-block-image:hover img {
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.sf-eq .wp-element-caption,
.sf-eq figcaption {
	position: static !important;
	inset: auto !important;
	width: auto !important;
	margin: 12px 0 0 !important;
	padding: 0 !important;
	background: none !important;
	/* core's gallery sheet prints ".wp-block-gallery.has-nested-images
	   figure.wp-block-image figcaption { color:#fff; text-shadow:0 0 1.5px #000 }"
	   AFTER this file, which left the small print as white-on-white ghosts.
	   !important is what beats it — plain specificity does not. */
	color: #6b6b6b !important;
	text-shadow: none !important;
	font-size: 13px;
	line-height: 1.6;
	/* centred on purpose: core's gallery sheet pins captions centre-aligned at a
	   higher specificity than this block, and centred names under centred tiles
	   are what the section wants anyway — declared here so the intent is explicit */
	text-align: center;
}
.sf-eq .wp-element-caption strong,
.sf-eq figcaption strong {
	display: block;
	margin-bottom: 4px;
	font-size: 16px;
	font-weight: 600;
	color: #2a2a2a !important;
	text-shadow: none !important;
}
@media (prefers-reduced-motion: reduce) {
	.sf-eq > .wp-block-image img {
		transition: none;
	}
}

/* --- 31d. Quality control steps: three-up card grid ---------------------
   Six steps in two rows of three. The zigzag this replaces ran one step per
   row and measured 1800px of band; three-up measures 861px, so the section
   stops costing the visitor two extra screens of scrolling.
   The card is a flex column-reverse: the DOM stays copy-first (number,
   heading, copy, then the photo) for screen readers and for the no-CSS
   reading order, while the photo renders above the copy the way every other
   card on this page reads. Because the photo moved to the top, the number
   needs a top margin of its own — without one it would sit flush against the
   photo's shadow.
   No frame is drawn: .sf-qs__media img already carries this page's image
   language (8px radius, soft shadow, the same one .sf-eq uses), so the six
   cards stand on the band's own background rather than in white boxes.
   The row hairlines and their 22px lead-in go with the layout: the 01-06
   numbers are what carries the sequence now, and a separator between grid
   cells has nothing to attach to.
   The even-row selector rides along in the base rule rather than being
   dropped: both parities are identical on purpose now, and stating that at
   one specificity is what stops the zigzag from being reinstated by a later
   edit that only touches the odd rows. */
.sf-qs {
	max-width: 1014px; /* three 319px columns + two 28px gutters */
	margin: 32px auto 0;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	column-gap: 28px;
	row-gap: 28px;
}
.sf-qs__step,
.sf-qs__step:nth-child(even) {
	display: flex;
	flex-direction: column-reverse;
	column-gap: 0;
	row-gap: 0;
	align-items: stretch;
}
.sf-qs__text {
	display: flex;
	flex-direction: column;
}
.sf-qs__num {
	margin: 16px 0 8px;
	font-size: 64px;
	font-weight: 600;
	line-height: 1;
	letter-spacing: -0.03em;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--brand-green);
}
.sf-qs__text h3 {
	margin: 0 0 8px;
	font-size: 24px;
	font-weight: 600;
	line-height: 1.25;
	color: var(--wp--preset--color--accent);
}
.sf-qs__text p {
	margin: 0;
	font-size: 16px;
	line-height: 1.6;
	color: #6b6b6b;
}
.sf-qs__media {
	margin: 0;
}
.sf-qs__media img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	object-position: center;
	border-radius: 8px;
	box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}

/* --- 31e. Palatability testing: horizontal flow bar --------------------
   Four steps across one row, tied together by a horizontal connector with a
   chevron at its end. This is deliberately NOT the About page's Our Journey
   treatment (a vertical rail running behind stacked nodes) so the two never
   read as the same component: no vertical rule appears here at any width —
   when the steps stack, the connector turns vertical between them and the
   chevron follows it down. */
.sf-pal {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	column-gap: 56px;
	margin-top: var(--wp--preset--spacing--60);
}
.sf-pal__step {
	position: relative;
	text-align: center;
}
.sf-pal__dot {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: 34px;
	height: 34px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 50%;
	background-color: var(--wp--preset--color--card-white);
	color: var(--wp--preset--color--brand-green);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.02em;
}
.sf-pal__step + .sf-pal__step::before {
	content: "";
	position: absolute;
	left: -52px;
	top: 17px;
	width: 36px;
	height: 1px;
	background-color: var(--wp--preset--color--border-medium);
}
.sf-pal__step + .sf-pal__step::after {
	content: "";
	position: absolute;
	left: -16px;
	top: 13px;
	width: 8px;
	height: 8px;
	border-top: 1px solid var(--wp--preset--color--border-medium);
	border-right: 1px solid var(--wp--preset--color--border-medium);
	transform: rotate(45deg);
}
.sf-pal__step h3 {
	margin: 14px 0 8px;
	font-size: 17px;
}
.sf-pal__step p {
	margin: 0;
	font-size: 14px;
	line-height: 1.65;
	color: var(--wp--preset--color--text-secondary);
}

@media (max-width: 900px) {
	/* certificates: the thumbnail shrinks with the row, keeping the 8/11 frame */
	.sf-certrow {
		grid-template-columns: 88px minmax(0, 1fr);
		gap: 16px;
		padding: 18px 0;
	}
	.sf-certrow__media img,
	.sf-certrow__media--placeholder {
		width: 88px;
		height: 123px;
		max-height: none;
	}
	/* QC steps: the three-up grid drops to a single column. Only the base
	   grid's three tracks have to be undone — the cards themselves are flex
	   columns and need no adjustment at this width. */
	.sf-qs {
		grid-template-columns: minmax(0, 1fr);
		column-gap: 0;
	}
	.sf-qs__num {
		font-size: 44px;
		margin: 12px 0 6px;
	}
	.sf-qs__text h3 {
		font-size: 20px;
	}
	.sf-qs__text p {
		font-size: 15px;
	}
	/* palatability: one step per row, connector running down between them */
	.sf-pal {
		grid-template-columns: minmax(0, 1fr);
		column-gap: 0;
		row-gap: 30px;
		justify-items: center;
		max-width: 460px;
		margin-inline: auto;
	}
	.sf-pal__step p {
		max-width: 320px;
	}
	.sf-pal__step + .sf-pal__step::before {
		left: 50%;
		top: -26px;
		width: 1px;
		height: 16px;
		transform: translateX(-50%);
	}
	.sf-pal__step + .sf-pal__step::after {
		left: 50%;
		top: -14px;
		transform: translateX(-50%) rotate(135deg);
	}
}

@media (max-width: 768px) {
	/* QC laboratory: two tiles per row — three would leave ~89px each at 375px,
	   which the instrument captions cannot live in */
	.sf-eq {
		grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
		column-gap: 14px !important;
		row-gap: 20px !important;
	}
}

/* === 32. Bare-section page inset (<= 1240px) ==============================
   A constrained top-level group only ever gets "max-width: 1200px + auto
   margins". Above roughly 1240px that measure is narrower than the viewport,
   so the leftover gutter reads as a page inset; below it the content box IS
   the viewport and the block sits flush at x = 0.

   Sections that declare a backgroundColor escape this, because WP core ships
   :where(.wp-block-group.has-background) { padding: 1.25em 2.375em } (38px).
   Sections whose background comes from a class we wrote — or that have no
   background at all — get nothing, and flush to the edge. That is the same
   hole 27s fills for .sf-hero-inner, and it is why the QC Laboratory, the
   Certificate of Analysis, Full Traceability, the dosage configurators and
   the dosage FAQs all sat at x = 0.

   This closes it for every remaining bare top-level group in one rule.
   38px is not a new value: it is exactly what the has-background sections
   already resolve to, so a page mixing the two keeps a single left edge.

   Direct child only — a nested group must never inherit the inset.
   1240px keeps desktop (>= 1241px) untouched. ----------------------------- */
@media (max-width: 1240px) {
	.wp-site-blocks > .wp-block-group.is-layout-constrained:not(.has-background):not(.sf-hero-inner) {
		padding-inline: 38px;
	}
}

/* === 33. OEM/ODM engagement cards (services page) ========================
   The services page builds its engagement cards as plain background-coloured
   groups, so nothing stretches the card to its column: the columns themselves
   are equal (WP's stretch alignment) but the white card inside the shorter
   column used to bottom out ~26px above its neighbour. The home page gets this
   for free from `.wp-block-column > .sf-card--roomy { height: 100% }`; this is
   the same declaration for the white-card markup, now that the row carries
   four cards instead of two.

   box-sizing is not optional: a WP column is a content-box, so height:100%
   on a padded card would add the 40px inset on top of the column height and
   push the card past the band. ------------------------------------------------- */
.sf-oem .wp-block-column > .wp-block-group.has-background {
	box-sizing: border-box;
	height: 100%;
}

/* Four cards in a 1200px row leave 234px of text per card, so the ODM
   description runs to six lines and the five checklist rows inherit WP's
   24px block gap. Stacking five 22px rows 24px apart turns a scannable list
   into a column of loose lines; 8px keeps each row distinct while tying the
   checklist to its heading. Only consecutive check rows are tightened —
   heading -> description -> first row keep the section rhythm. ------------- */
.sf-oem p.has-primary-color + p.has-primary-color {
	margin-block-start: 8px;
}

/* Below this width four cards fall to ~170px of text each and the longer
   card titles ("OEM — You Bring the Formula") wrap onto three lines, which
   is the point where a row of four stops reading as a row. Two per row keeps
   every title on one or two lines. WP's own <=781px rule already stacks them
   to a single column, so this only covers the 782-1100 band.

   Two declarations need overturning, both shipped by the block library and
   both winning on cascade order (its layout CSS is printed after this file):
     flex-wrap: the library pins `nowrap !important` from 782px up, so the
       override has to carry !important too;
     flex-basis: the library's selector carries its own :not(), i.e.
       three class-level parts — matching that shape (and out-specifying it)
       keeps this rule !important-free. */
@media (max-width: 1100px) {
	.sf-oem .wp-block-columns {
		flex-wrap: wrap !important;
	}
	.sf-oem .wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column {
		flex-basis: calc(50% - 12px);
	}
}

/* === 34. Article footer: previous / next navigation =====================
   Two adjacent-post cells closing the article body, ruled off from it by a
   single hairline — the same "a hairline defines the structure" language as
   the ruled panels above. The row is inset to the 800px article measure so
   the rule and the titles line up with the text they follow.

   The grid always keeps two equal columns and each cell keeps a minimum
   height, so a post with no neighbour (first or last in the blog) leaves its
   half empty instead of collapsing the row or pulling the other half across.

   Arrows are drawn here rather than through the block's `arrow` attribute:
   that attribute rewrites the block's $format and swallows the label span,
   which would drop the Previous / Next wording that carries the meaning. The
   label takes its own line so the article title — the anchor text — stays the
   largest thing in the cell. */
.wp-block-columns.sf-prevnext {
	display: grid !important;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0;
	margin: 0;
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-prevnext > .wp-block-column {
	display: flex;
	flex-direction: column;
	justify-content: flex-start;
	margin: 0;
	padding: 22px 0 0;
	min-height: 96px;
	box-sizing: border-box;
}
.sf-prevnext > .wp-block-column:first-child { padding-right: 28px; }
.sf-prevnext > .wp-block-column:last-child {
	padding-left: 28px;
	text-align: right;
}
.sf-prevnext__cell {
	display: block;
	min-height: 74px;
}
.sf-prevnext__cell .post-navigation-link__label {
	display: block;
	margin-bottom: 8px;
	font-size: 12px;
	font-weight: 600;
	line-height: 1.4;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
.sf-prevnext__cell--prev .post-navigation-link__label::before { content: "\2190\00a0\00a0"; }
.sf-prevnext__cell--next .post-navigation-link__label::after { content: "\00a0\00a0\2192"; }
.sf-prevnext__cell a {
	font-size: 17px;
	font-weight: 600;
	line-height: 1.4;
	color: var(--wp--preset--color--primary);
	text-decoration: none;
	transition: color 0.2s ease;
}
.sf-prevnext__cell a:hover {
	color: var(--wp--preset--color--brand-green);
	text-decoration: underline;
}
/* No adjacent post: the cell's box stays (grid + min-height above), only the
   empty wrapper goes, so there is nothing to hover or tab to. */
.sf-prevnext__cell:empty { display: none; }

@media (max-width: 781px) {
	.wp-block-columns.sf-prevnext { grid-template-columns: minmax(0, 1fr); }
	.sf-prevnext > .wp-block-column {
		padding: 16px 0 0;
		min-height: 0;
	}
	.sf-prevnext > .wp-block-column:first-child { padding-right: 0; }
	.sf-prevnext > .wp-block-column:last-child {
		padding-left: 0;
		margin-top: 16px;
		border-top: 1px solid var(--wp--preset--color--border-light);
		text-align: left;
	}
	.sf-prevnext__cell { min-height: 0; }
	.sf-prevnext__cell a { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
	.sf-prevnext__cell a { transition: none; }
}

/* === 35. Single post page: cover, meta, body rhythm ======================= */
/* 35a. Cover: 900px column (was alignwide 1364px tall = 85% of viewport).
   16:9 with a 460px cap — beyond that object-fit: cover crops centred.
   Now sits inside the white content section; the grey band is gone. */
.sf-single-cover {
	margin-inline: auto;
}
.sf-single-cover img {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	max-height: 450px;
	object-fit: cover;
	object-position: center;
	border-radius: 8px;
}

/* 35b. Meta row: 13.5px translucent white on the Forest hero. */
.sf-single-meta,
.sf-single-meta > * {
	color: rgba(255, 255, 255, 0.75);
}

/* 35c. Category tags: brand green on Forest hero, Forest on white surfaces.
   Rule 5 forces post-terms anchors to inherit (accent #1B4D3E was invisible
   on the Forest hero), so restore the colour per surface here. */
.has-primary-background-color .wp-block-post-terms a {
	color: var(--wp--preset--color--brand-green);
}
.sf-related .wp-block-post-terms a {
	color: var(--wp--preset--color--accent);
}

/* 35d. Body rhythm: paragraph gap 16px, H2 48/16 (core default gap is 0). */
.sf-single-body .wp-block-post-content > p {
	margin-top: 0;
	margin-bottom: 16px;
}
.sf-single-body .wp-block-post-content > h2 {
	margin-top: 48px;
	margin-bottom: 16px;
}
.sf-single-body .wp-block-post-content > h3 {
	margin-top: 32px;
	margin-bottom: 12px;
}
.sf-single-body .wp-block-post-content > :first-child {
	margin-top: 0;
}

/* 35e. Phone: core pads has-background sections 38px per side (299px text
   at 375). 20px per side → 335px, matching the cover. */
@media (max-width: 768px) {
	.sf-single-body {
		padding-left: 20px !important;
		padding-right: 20px !important;
	}
}

/* --- 36. Blog list mobile: chip rail + horizontal compact cards --------- */
/* Scope: body.blog only (/blog/ is the posts archive). Desktop (grid 3-up
   with white cards) is untouched — every rule below sits in the phone MQ. */
@media (max-width: 781px) {
	/* 36a. Filter chips: single-row touch rail. The container is the
	   chip strip — the wp-block-buttons holding the 5 pill links (direct
	   child of the card-white section in blog-index / archive.html); the
	   header CTA lives inside <header>, so this selector cannot reach it.
	   The strip is now [sf_blog_chips] output on /blog/ AND the archive
	   templates, so the rail targets its own sf-blog-chips class (kept as
	   a plain class, not body-scoped, so both scopes share one rule).
	   2C Step2 adds .sf-fchips: the /formulas/ filter bar is the same kind
	   of strip (a row of pills, nine of them) and needs the same phone
	   behaviour — see 37c for its own geometry. */
	.sf-blog-chips,
	.sf-fchips {
		flex-wrap: nowrap !important;
		overflow-x: auto;
		justify-content: flex-start !important;
		gap: 8px !important;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
	}
	.sf-blog-chips::-webkit-scrollbar,
	.sf-fchips::-webkit-scrollbar {
		display: none;
	}
	.sf-blog-chips .wp-block-button {
		flex: 0 0 auto;
		margin: 0;
	}
	.sf-blog-chips .wp-block-button__link {
		white-space: nowrap;
	}

	/* 36b. Cards: single column, thumbnail-left compact rows separated by
	   hairlines (no card chrome). ~118px per row → 5-6 posts per screen. */
	body.blog .wp-block-post-template.columns-3 {
		grid-template-columns: minmax(0, 1fr) !important;
		gap: 0 !important;
	}
	body.blog .wp-block-post-template.columns-3 > .wp-block-post {
		padding: 14px 0;
	}
	body.blog .wp-block-post-template.columns-3 > .wp-block-post + .wp-block-post {
		border-top: 1px solid #E0E5DC;
	}
	body.blog .wp-block-post > .wp-block-group.has-card-white-background-color {
		display: grid !important;
		grid-template-columns: 120px minmax(0, 1fr);
		grid-template-rows: auto auto auto;
		column-gap: 12px;
		row-gap: 4px;
		align-items: start;
		padding: 0 !important;
		background: transparent !important;
		border-radius: 0 !important;
	}

	/* Thumbnail: fixed 120x90 (4:3) left cell spanning all text rows. */
	body.blog .wp-block-post .wp-block-post-featured-image {
		grid-column: 1;
		grid-row: 1 / 4;
		width: 120px;
		margin: 0;
	}
	body.blog .wp-block-post .wp-block-post-featured-image img {
		width: 120px !important;
		height: 90px;
		object-fit: cover;
		border-radius: 8px;
	}

	/* Text stack: title (2-line clamp) → date, both left-aligned. The
	   category chip is desktop-only; on phones the row is title+date. */
	body.blog .wp-block-post .taxonomy-category {
		display: none !important;
	}
	body.blog .wp-block-post .wp-block-post-title {
		grid-column: 2;
		grid-row: 1;
		/* !important required: WP's is-layout-constrained sets margin-left/right
		   auto !important on children, which would auto-center the cell. */
		margin: 0 !important;
		font-size: 15px !important;
		line-height: 1.3;
		text-align: left !important;
		justify-self: start;
	}
	/* The clamp lives on the inner link: on the grid item itself Chromium
	   blockifies display:-webkit-box to flow-root and refuses to clamp. */
	body.blog .wp-block-post .wp-block-post-title a {
		display: -webkit-box;
		-webkit-line-clamp: 2;
		-webkit-box-orient: vertical;
		overflow: hidden;
	}
	body.blog .wp-block-post .wp-block-post-date {
		grid-column: 2;
		grid-row: 2;
		margin: 0 !important; /* beat WP constrained margin auto !important */
		font-size: 12px;
		color: var(--wp--preset--color--text-secondary);
		text-align: left !important;
		justify-self: start;
	}

	/* Excerpt + Read More are hidden on phones. */
	body.blog .wp-block-post .wp-block-post-excerpt,
	body.blog .wp-block-post .wp-block-read-more,
	body.blog .wp-block-post .wp-block-post-excerpt__more-link {
		display: none !important;
	}
}

/* --- 37. Blog cards desktop: equal-height rows, Read More pinned -------- */
/* Card order (from blog-index): image → category → title → date → excerpt
   → read more. The date already ships in the template (text-secondary
   #5F6B65, 12px); nothing to add there. These rules are desktop-only: the
   36b phone block overrides display with grid !important below 781px. */
body.blog .wp-block-post-template.columns-3 > .wp-block-post {
	display: flex;
	flex-direction: column;
}
body.blog .wp-block-post-template.columns-3 > .wp-block-post > .wp-block-group.has-card-white-background-color {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	height: 100%;
	box-sizing: border-box; /* WP groups are content-box: 100% + padding */
}
body.blog .wp-block-post .wp-block-read-more {
	/* !important required: WP's constrained layout uses margin auto
	   !important on children; auto top margin pins the link to the bottom. */
	margin-top: auto !important;
}

/* --- 38. Contact info cards: equal height within each row --------------- */
/* Page 28 = /contact/. Selector only matches the two card rows (Email/
   Phone and Address/Working Hours), not the form columns. The column
   already stretches to the row height; the white card inside must be
   allowed to fill it. */
body.page-id-28 .wp-block-columns:has(> .wp-block-column > .wp-block-group.has-card-white-background-color > h3.wp-block-heading) > .wp-block-column {
	display: flex;
	flex-direction: column;
}
body.page-id-28 .wp-block-columns:has(> .wp-block-column > .wp-block-group.has-card-white-background-color > h3.wp-block-heading) > .wp-block-column > .wp-block-group.has-card-white-background-color {
	flex: 1 1 auto;
	width: 100%;
	box-sizing: border-box;
}


/* === 37. Formula card grid (dosage pages) ================================
   2B Stage3: the legacy <details> accordion is gone — sf_formula_grid now
   renders self-supporting cards (.sf-fcard) straight from the formula
   records. The .sf-formulas-data JSON mirror that used to ride along with
   them went in batch H6, once the configurator that was its only reader had
   been deleted. The grid carries its own columns via --sf-fgrid-cols (the
   shortcode sets it inline), so nothing here depends on wp-container-core
   layout classes. Column geometry mirrors .sf-dosage-grid: 4 × minmax(0,1fr)
   with a 24px gap on desktop.

   The .sf-formula__cta button keeps its class and its data-formula (K1 —
   formulas.js binds it and copies the name into the inquiry).

   2C Step0 adds the dosage still: .sf-fcard__media is a full-bleed 4:3
   frame (the uploads/2026/09/{form}.webp 720x720 renders, cropped by
   object-fit), and the copy moved into .sf-fcard__body. Cards whose still
   is missing keep the 2B geometry because the padding lives on __body. */
.sf-fgrid {
	display: grid;
	grid-template-columns: repeat(var(--sf-fgrid-cols, 4), minmax(0, 1fr));
	gap: 24px;
	align-items: stretch;
}
.sf-fcard {
	display: flex;
	flex-direction: column;
	box-sizing: border-box;
	/* 2C Step0: the card itself carries no padding. The dosage still bleeds
	   to the border on top and both sides (same construction as .sf-tile),
	   and .sf-fcard__body holds the copy inset — so a card whose still is
	   missing is geometrically identical to the 2B text-only card. */
	padding: 0;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 10px;
	background: var(--wp--preset--color--card-white);
}
/* Full-bleed 4:3 dosage still. The ratio lives on the IMG, not on the
   figure: WebKit mis-computes an aspect-ratio box that is also a flex item
   with a percentage-height img inside — the same reason .sf-tile__media
   puts it here. The eight source renders are 720x720 squares, so
   object-fit:cover crops top/bottom into the 4:3 band. flex:0 0 auto keeps
   the frame rigid when the row stretches a shorter card to match. */
.sf-fcard__media {
	margin: 0;
	flex: 0 0 auto;
	border-radius: 10px 10px 0 0;
	overflow: hidden;
	background: var(--wp--preset--color--bg-light);
	/* 待办17: the badge is positioned against the still's frame. relative is
	   deliberately on the figure and not on .sf-fcard — the badge belongs to
	   the image, and this is also the box that carries the radius, so the
	   frame clips it. Neither the img's aspect-ratio box nor the flex basis
	   is touched (that WebKit note above is why the ratio lives on the IMG). */
	position: relative;
}
.sf-fcard__media img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: cover;
}
/* The still is a link (待办14). display:block kills the inline-gap line box
   the anchor would otherwise add under the image. */
.sf-fcard__imagelink {
	display: block;
}
.sf-fcard__imagelink:focus-visible {
	outline: 3px solid var(--wp--preset--color--brand-green);
	outline-offset: -3px;
}
/* 待办17 — the card badge. Three values, one palette entry each; the hexes are
   theme colours that are not in theme.json (gold/red/blue are badge-only, and
   the palette deliberately holds no generic red or blue), so the ratio each
   one holds against its white label is recorded here: #8A6D1F 4.90:1,
   #B3261E 6.54:1, #1F5C99 6.89:1 — all above the 4.5:1 AA floor for text this
   size, measured 2026-09-23, not eyeballed. The unmodified base rule carries
   the Forest fallback so a badge whose modifier is somehow missing still
   paints readably instead of vanishing into the still. */
.sf-fcard__badge {
	position: absolute;
	top: 12px;
	right: 12px;
	z-index: 2;
	display: inline-block;
	padding: 4px 10px;
	border-radius: 4px;
	background: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	line-height: 1.4;
	text-transform: uppercase;
	white-space: nowrap;
}
.sf-fcard__badge--best-seller {
	background: #8A6D1F;
}
.sf-fcard__badge--hot {
	background: #B3261E;
}
.sf-fcard__badge--new {
	background: #1F5C99;
}
/* The no-still placement: static at the head of __body, shrink-wrapped rather
   than stretched (__body is a column flex container). */
.sf-fcard__badge--inline {
	position: static;
	align-self: flex-start;
	margin-bottom: 8px;
}
/* the still bleeds, so the copy block carries the padding */
.sf-fcard__body {
	display: flex;
	flex-direction: column;
	flex: 1 1 auto;
	padding: 20px 22px;
}
.sf-fcard__use {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--wp--preset--color--brand-green);
}
.sf-fcard__name {
	margin: 8px 0 6px;
	font-size: 17px;
	font-weight: 600;
	line-height: 1.3;
	color: var(--wp--preset--color--primary); /* deep ink green #1B4D3E */
}
.sf-fcard__name a {
	color: inherit;
	text-decoration: none;
}
.sf-fcard__name a:hover {
	color: var(--wp--preset--color--brand-green);
}
.sf-fcard__spec {
	margin: 0;
	font-size: 13px;
	line-height: 1.55;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fcard__actions {
	display: flex;
	align-items: center;
	gap: 12px;
	flex-wrap: wrap;
	margin-top: auto;
	padding-top: 14px;
}
.sf-fcard__more {
	font-size: 13px;
	font-weight: 600;
	color: var(--wp--preset--color--text-secondary);
	text-decoration: none;
}
.sf-fcard__more:hover {
	color: var(--wp--preset--color--brand-green);
}
.sf-formula__cta {
	padding: 9px 18px;
	border: 2px solid var(--wp--preset--color--primary);
	border-radius: 6px;
	background: transparent;
	color: var(--wp--preset--color--primary);
	font-size: 14px;
	font-weight: 600;
	cursor: pointer;
	transition: background-color 0.15s ease, color 0.15s ease;
}
.sf-formula__cta:hover {
	background: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
}
@media (max-width: 768px) {
	.sf-fgrid {
		grid-template-columns: minmax(0, 1fr);
		gap: 16px;
	}
}

/* === 37b. Formula detail page (single-sf_formula.html) ====================
   One standard formula as its own record page: Forest hero, a three-field
   specification grid, the record's own long copy when it has any, then the
   sibling formulas of the same dosage form.

   Geometry lives here rather than in the template for the same reason
   .sf-fgrid does: the blocks that could carry a wp-container-core-* layout
   class are shortcode outputs, and a block template runs do_shortcode()
   before do_blocks(), so a shortcode can never own a layout class. The two
   bands that come from PHP ([sf_formula_detail] / [sf_formula_body]) are
   plain elements for the same reason.

   The K1 contract on the CARD GRID is unchanged: the card button keeps
   class="sf-formula__cta" + data-formula, and formulas.js copies the name
   into the clipboard. data-form still rides along — it fed the
   sessionStorage key batch H6 retired, nothing reads it today, and it goes
   in the next markup pass.

   Batch H7b took the detail page's hero CTA out of that contract: it is an
   <a href="/contact/#quote" data-sf-inquiry-open> that opens the inquiry
   dialog, so the class pair stays for the paint and formulas.js skips it. */

/* 37b-i. Hero. The section carries .sf-hero-inner as well, so the band tone
   and the phone inset come from rule 25b / 27s exactly like every other
   inner-page hero — the formula page opens from a dosage page, and both are
   #2E6B54. Everything below is only the content recipe: breadcrumb, a tag,
   the title, a meta line, then the actions (same order as single.html).
   The tag is .sf-fcard__use so the card and the page it opens label the same
   term with the same type treatment; on this tone it has to go light, like
   .sf-eyebrow under .has-primary-background-color — brand-green on #1B4D3E is
   3.8:1 and #2E6B54 is no better, both failing AA at 11px even at weight 700.
   It must be a BLOCK here, not the card's inline: the constrained layout
   centres block children (max-width + auto margins) but an inline-level child
   flows in an anonymous block that spans the full band, and the tag rendered
   flush at x=0 while the breadcrumb above it sat at the content edge. No
   fit-content shrink either — the layout's auto side margins would then
   centre the shrink-wrapped box instead of leaving it at the content edge. */
.sf-formula-hero .sf-fcard__use {
	display: block;
	color: var(--wp--preset--color--card-white);
	opacity: 0.72;
}
/* Batch H5-0: this band's heading became a div — the page's one h1 now opens
   the parameters column — and the 700 it used to inherit from the h1 element
   rule in theme.json did not come with the tag, so it is declared here. Of the
   four properties that element rule supplies, size, line-height and tracking
   were already overridden below and the colour was too; weight was the only
   one that leaked. Declaring it is the whole of the visual-no-change claim. */
.sf-formula-hero__title {
	margin: 14px 0 0;
	font-size: clamp(30px, 3.4vw, 44px);
	font-weight: 700;
	line-height: 1.15;
	letter-spacing: -0.01em;
	color: var(--wp--preset--color--card-white);
}
.sf-formula-hero__meta {
	margin: 12px 0 0;
	font-size: 13.5px;
	line-height: 1.5;
	color: rgba(255, 255, 255, 0.75);
}
/* {{FORMULA_META}} is composed in PHP and can legitimately come back empty
   (a formula with no dosage term); :empty keeps that from leaving a gap. */
.sf-formula-hero__meta:empty { display: none; }
.sf-formula-hero__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 14px 22px;
	margin-top: 26px;
}
/* 37b-ii. The K1 button inverts on the dark band. The base .sf-formula__cta
   is Forest-on-white for the card grid; Forest-on-Forest would be invisible
   here, so the --solid modifier puts ink on brand-green (5.8:1) and flips to
   white-on-Forest on hover. The card grid's copy behaviour and its data
   attributes are untouched. On this band alone the element is batch H7b's
   inquiry opener and therefore an <a>, so the decoration is pinned off on both
   states: theme.json gives every link an underline on hover, and a filled
   button that grows an underline under the pointer reads as a link that lost
   its button. The fills above stay exactly as they were. */
.sf-formula-hero .sf-formula__cta--solid {
	border-color: var(--wp--preset--color--brand-green);
	background-color: var(--wp--preset--color--brand-green);
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}
.sf-formula-hero .sf-formula__cta--solid:hover {
	border-color: var(--wp--preset--color--card-white);
	background-color: var(--wp--preset--color--card-white);
	color: var(--wp--preset--color--primary);
	text-decoration: none;
}
.sf-formula-hero__build {
	font-size: 14px;
	font-weight: 600;
	color: var(--wp--preset--color--card-white);
	text-decoration: underline;
	text-underline-offset: 3px;
}
.sf-formula-hero__build:hover { color: var(--wp--preset--color--brand-green); }

/* 37b-iii. Specification: ruled cards, one per filled meta field.
   auto-fit + minmax needs no media query — the 1200px measure gives
   (1200 - 2*20) / 3 = 386 >= 280 so three tracks; a phone gives one.
   The 3px brand-green top edge echoes the .sf-cell rule used by the
   dosage spec panels, keeping the two "specification" surfaces related. */
.sf-fdetail__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	gap: 20px;
	margin-top: 28px;
}
/* 2D-S4: one card is the intended state — Ingredients and Guaranteed
   Analysis moved out because the "Formula & nutrition" band below already
   renders both. The remaining value is short, so a full-width track would
   stretch a 350px line across a 1200px measure. The cap lives on the
   --solo modifier, never on .sf-fdetail__grid, so a grid that grows back
   to several cards keeps its unconstrained auto-fit behaviour.

   The cap is expressed as the track width, not as a max-width on the box.
   A max-width plus auto margins — which is what a constrained-layout child
   gets — can only centre the box: with a 560px cap inside a 1200px measure
   the single card parked at x=440 on /formulas/ear-care-drops/ at 1440,
   while x=120 is the left edge every heading and band on the page shares.
   Forcing the auto margins off (margin-inline: 0) is a no-op — core locks
   them with !important — and past it the card lands at x=38, the section's
   has-background content-box edge. Sizing the track instead leaves the
   grid's own centring untouched and puts the card at x=120, width 560. */
.sf-fdetail__grid--solo {
	grid-template-columns: minmax(0, 560px);
}
.sf-fdetail__card {
	padding: 22px 24px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-top: 3px solid var(--wp--preset--color--brand-green);
	border-radius: 8px;
	background-color: var(--wp--preset--color--bg-light);
}
.sf-fdetail__label {
	margin: 0 0 10px;
	font-size: 12px;
	font-weight: 700;
	line-height: 1.4;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail__value {
	margin: 0;
	font-size: 15px;
	line-height: 1.6;
	color: var(--wp--preset--color--text-primary);
}

/* 37b-iv. Related formulas band: the h2 and the grid are separate wp:html
   blocks, so the gap between them is a layout guess (the block gap) rather
   than a decision. Pin it. */
.sf-fdetail-more .sf-fgrid { margin-top: 28px; }

/* 37b-v. Long copy. Emitted only when post_content is non-empty, so this
   band does not exist on any of the 21 current formulas — it is here for
   the first record that gets real copy. 800px measure and the 35d rhythm,
   which is what .sf-single-body uses for the same job.
   The 800px measure needs no inset until the viewport is narrower than the
   measure itself, so the gutter is a separate, wider breakpoint. */
.sf-fdetail-body {
	padding: 48px 0;
	background-color: var(--wp--preset--color--card-white);
}
.sf-fdetail-body__inner {
	max-width: 800px;
	margin-inline: auto;
}
@media (max-width: 840px) {
	.sf-fdetail-body__inner { padding-left: 20px; padding-right: 20px; }
}
.sf-fdetail-body__inner > p {
	margin-top: 0;
	margin-bottom: 16px;
}
.sf-fdetail-body__inner > h2 {
	margin-top: 48px;
	margin-bottom: 16px;
}
.sf-fdetail-body__inner > h3 {
	margin-top: 32px;
	margin-bottom: 12px;
}
.sf-fdetail-body__inner > :first-child { margin-top: 0; }

/* The body band above only styles p/h2/h3. Nothing resets lists, so a bulleted
   block falls back to UA defaults and drops out of the 16px rhythm the
   paragraphs set. These rules put lists back on that rhythm and keep the
   bullets, which is what the formula copy is written in. */
.sf-fdetail-body__inner > ul,
.sf-fdetail-body__inner > ol {
	margin-top: 0;
	margin-bottom: 16px;
	padding-left: 22px;
}
.sf-fdetail-body__inner > ul { list-style: disc; }
.sf-fdetail-body__inner > ol { list-style: decimal; }
.sf-fdetail-body__inner li {
	margin-bottom: 8px;
	line-height: 1.65;
}
.sf-fdetail-body__inner li:last-child { margin-bottom: 0; }
.sf-fdetail-body__inner strong { font-weight: 600; }

/* 37b-vi. Phone: core pads has-background bands 38px per side (299px of text
   at 375). 20px per side -> 335px, the same inset 35e uses for the article
   body. The hero is not in this list — it is .sf-hero-inner and 27s already
   sets that inset for it. */
@media (max-width: 768px) {
	.sf-fdetail,
	.sf-fdetail-more {
		padding-left: 20px !important;
		padding-right: 20px !important;
	}
	.sf-fdetail__card { padding: 18px 18px 20px; }
	.sf-formula-hero__actions { margin-top: 22px; }
}

/* lightweight toast (formulas.js): fixed above the bottom edge, no layout shift */
.sf-toast {
	position: fixed;
	left: 50%;
	bottom: 24px;
	transform: translateX(-50%) translateY(8px);
	z-index: 9999;
	max-width: min(90vw, 420px);
	padding: 10px 18px;
	border-radius: 6px;
	background: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
	font-size: 14px;
	line-height: 1.4;
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.2s ease, transform 0.2s ease;
}
.sf-toast.is-visible {
	opacity: 1;
	transform: translateX(-50%) translateY(0);
}

/* === 37c. Formula archive filter bar (/formulas/, formula-filter.js) ======
   Nine chips — All plus the eight dosage forms — above the 21-card grid. The
   bar narrows what is already on screen, so the chips are <button>s, not
   links: that is the honest control for "hide some of this list", and it
   keeps the row out of the tab order as nine navigations. The pressed chip is
   filled ink-green, so the state is painted as well as announced
   (aria-pressed on the button, the count in the role="status" line).

   Geometry mirrors .sf-explore__chip from the dosage pages — same 6px radius,
   same border-medium outline, same fill when current — but the type has to be
   re-declared here: a <button> does not inherit font-family the way an <a>
   does, and left alone it would fall back to the UA's 13.33px Arial.

   JavaScript-only, twice over. The bar is hidden under html:not(.sf-js) — the
   class lands via an inline head script on this template only, before the
   body paints — so a visitor without scripting reads the plain 21-card list
   instead of nine buttons that do nothing. And the cards are filtered with a
   class, never [hidden]: .sf-fcard is display:flex and an author display rule
   beats the hidden attribute's UA style, so the card would stay painted. */
.sf-fchips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 0 0 12px;
	padding: 0;
}
.sf-fchip {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	min-height: 40px;
	box-sizing: border-box;
	padding: 8px 14px;
	background-color: var(--wp--preset--color--card-white);
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 6px;
	-webkit-appearance: none;
	appearance: none;
	font-family: inherit;
	font-size: 13px;
	font-weight: 600;
	line-height: 1.4;
	color: var(--wp--preset--color--text-primary);
	white-space: nowrap;
	cursor: pointer;
	transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}
.sf-fchip:hover {
	border-color: var(--wp--preset--color--primary);
}
.sf-fchip:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
}
/* Pressed = filled ink-green, mirroring .sf-explore__chip.is-current. */
.sf-fchip.is-active {
	border-color: var(--wp--preset--color--primary);
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
}
.sf-fchips-status {
	margin: 0;
	font-size: 13px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}
/* Filtered out (formula-filter.js). */
.sf-fcard.is-sf-off {
	display: none;
}
/* No scripting: the strip has nothing to drive it, so it is not shown. Set
   before the body paints, so there is no flash of buttons. */
html:not(.sf-js) .sf-fchips-wrap {
	display: none;
}
/* Screen-reader-only text — the standard recipe, available to this bar (and
   anything else that needs to announce without painting). */
.sf-visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
}

/* === 45. Header CTA alignment (the inquiry basket's former section) =======
   The bag button that used to sit inside .sf-header__cta was removed in batch
   H7f, with its drawer, its script and this section's rules. The container
   stays because the Get a Quote button lives in it, and this rule is what
   keeps that button centred against the taller logo row. */
.sf-header .sf-header__cta {
	align-items: center;
}
/* --- Overlay: certificate dialog + inquiry dialog ------------------------- */
/* Shared by the certificate dialog (section 50) and the inquiry dialog (batch
   H4): one backdrop, one fade, one z-index stack — the two cannot drift. */
.sf-certmodal,
.sf-inquiry-modal {
	position: fixed;
	inset: 0;
	z-index: 10000;
	background-color: rgba(15, 26, 21, 0.45);
	opacity: 0;
	transition: opacity 0.3s ease;
}

.sf-certmodal.is-open,
.sf-inquiry-modal.is-open {
	opacity: 1;
}

.sf-certmodal[hidden],
.sf-inquiry-modal[hidden] {
	display: none;
}

/* Scroll lock while a dialog is open. */
body.sf-certmodal-lock,
body.sf-inquiry-lock {
	overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
	.sf-certmodal,
	.sf-certmodal__panel {
		transition: none;
	}
}
/* === 47. Contact page: inquiry band split (form left / contact cards right)
   Replaces the standalone "Get in Touch" section: its four info cards plus
   a 200px Map placeholder card now live in the right column of the green
   #inquiry-form band. Grid override of WP's post-theme flex layout needs
   !important (same as sf-prevnext). DOM order = left(title/form/points),
   right(cards+map) -> mobile stacking below 781px runs form -> points ->
   cards -> map by default. */
.wp-block-columns.sf-contact-split {
	display: grid !important;
	grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) !important;
	column-gap: 32px;
	row-gap: 28px;
	align-items: stretch; /* right column matches the left column's full height */
}

.sf-contact-split > .wp-block-column {
	flex: initial;
}

.sf-contact-split > .sf-contact-aside {
	display: flex;
	flex-direction: column;
	justify-content: space-between; /* spare height (~108px) spreads into the gaps */
	gap: 20px; /* acts as the minimum spacing */
	/* the column block carries is-vertically-aligned-center (align-self:center
	   would beat the grid stretch) and WP flow layout adds 24px margin-top to
	   stacked children (on top of every gap) - both neutralized here */
	align-self: stretch;
}

.sf-contact-split > .sf-contact-aside > * {
	margin: 0;
}

.sf-contact-card__icon {
	display: none; /* stroke icons are a mobile-only affordance */
}

.sf-contact-card {
	padding: 24px;
	background-color: rgba(255, 255, 255, 0.95);
	border-radius: 8px;
}

.sf-contact-card h3 {
	margin: 0 0 6px;
	font-size: 15px;
	color: var(--wp--preset--color--primary);
}

.sf-contact-card p {
	margin: 0;
	font-size: 14.5px;
	line-height: 1.55;
	color: var(--wp--preset--color--text-secondary);
}

.sf-contact-card a {
	color: var(--wp--preset--color--primary);
	text-decoration: underline;
	text-underline-offset: 2px;
}

.sf-contact-card a:hover {
	color: var(--wp--preset--color--text-secondary);
}

/* Map placeholder card: same family as the info cards, fixed 200px height,
   content centered (replaces the old standalone 400px Map section). */
.sf-contact-card--map {
	min-height: 200px;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	text-align: center;
}

.sf-contact-card--map p {
	margin: 0 0 4px;
}

/* ✓ selling points: now the last row INSIDE the white form card, so the
   left column is exactly the form card and the right column can align to
   it perfectly (grid stretch). */
.sf-form-card .fluentform {
	margin-bottom: 0;
}

.sf-contact-points {
	margin: 16px 0 0;
	color: var(--wp--preset--color--text-secondary);
}

@media (max-width: 781px) {
	.wp-block-columns.sf-contact-split {
		grid-template-columns: minmax(0, 1fr) !important;
		row-gap: 22px;
	}

	/* The aside itself becomes ONE merged contact card; the four info
	   cards lose their own chrome and read as rows inside it. */
	.sf-contact-split > .sf-contact-aside {
		box-sizing: border-box; /* columns are content-box: 40px padding would spill past the track */
		gap: 16px;
		padding: 20px;
		background-color: rgba(255, 255, 255, 0.95);
		border-radius: 8px;
	}

	.sf-contact-split > .sf-contact-aside > .sf-contact-card {
		padding: 0;
		background: none;
	}

	.sf-contact-card h3 {
		display: flex;
		align-items: center;
		gap: 8px;
		margin: 0 0 4px;
	}

	.sf-contact-card__icon {
		display: block;
		flex: none;
		color: var(--wp--preset--color--primary);
	}

	.sf-contact-card--map {
		min-height: 150px;
	}
}

/* === 48. Services page: Key Facts table + panel checklists ----------------
   Rebuilt page (2026-09-18): the three checklist bands merged into one
   .sf-panel--3 "What We Handle" section plus a structured <table> so the
   commercial terms render as real data (SEO/GEO), not bullet prose. */
.sf-keyfacts {
	width: 100%;
	/* 待办22 — the table capped itself at 720px and auto-centred inside a
	   1200px content box, so its two ends sat 240px inside the heading's.
	   The cap is now the SAME one the heading is held to rather than `none`:
	   the section is an `is-layout-constrained` group, so core already caps
	   its block children at `--wp--style--global--content-size` and centres
	   them — the heading measures 1200px inside a 1364px content box. Writing
	   `none` there took the table OUT of that rule and let it fill all
	   1364px, which put its ends 82px outside the heading's on each side. The
	   measured fix needs both halves: this cap, and the `auto` side margins
	   core applies with `!important`, so the table centres into the same
	   1200px the heading occupies. The 140px on the first column below stays a
	   hint, not a cap: at 1200px the Detail column simply takes what is left. */
	max-width: var(--wp--style--global--content-size);
	margin: 24px 0 0;
	border-collapse: collapse;
	font-size: 16px;
	background: var(--wp--preset--color--card-white);
}
.sf-keyfacts th,
.sf-keyfacts td {
	text-align: left;
	vertical-align: middle;
}
.sf-keyfacts thead th {
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
	font-size: 13px;
	font-weight: 600;
	letter-spacing: 1px;
	text-transform: uppercase;
	height: 44px;
	padding: 0 16px;
}
.sf-keyfacts tbody td {
	height: 44px;
	padding: 0 16px;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sf-keyfacts tbody td:first-child {
	width: 140px;
	font-weight: 600;
	color: var(--wp--preset--color--primary);
	white-space: nowrap;
}
/* Checklists inside the .sf-panel--3 columns: single column per panel cell
   (the 2-col grid variant of .sf-checklist was for the removed wide bands),
   check mark drawn by ::before so the <li> text stays machine-readable. */
.sf-oem .sf-card > * + * {
	/* WP flow drops 24px between card paragraphs; the 2-diff cards read
	   better (and shorter) at a tighter 10px rhythm */
	margin-block-start: 10px;
}
.sf-checklist--panel {
	list-style: none;
	margin: 0 0 4px;
	padding: 0;
	font-size: 15px;
}
.sf-checklist--panel li {
	position: relative;
	margin: 0 0 10px;
	padding-left: 24px;
}
.sf-checklist--panel li::before {
	content: "\2713";
	position: absolute;
	left: 0;
	top: 0;
	color: var(--wp--preset--color--accent);
	font-weight: 700;
}
.sf-checklist--panel li:last-child {
	margin-bottom: 0;
}
@media (max-width: 768px) {
	.sf-keyfacts thead th {
		height: 40px;
	}
	.sf-keyfacts tbody td {
		height: auto;
		padding: 10px 12px;
	}
	.sf-keyfacts tbody td:first-child {
		width: 96px;
	}
	/* 27q's phone 2-up grid was built for quality's six QC steps; its
	   section:has(> .sf-panel--3) selector also catches this page's
	   What We Handle panel, dissolving three semantic h3+list columns into
	   one interleaved grid. Restore the plain 1-col stack on services only. */
	body.page-id-29 section:has(> .wp-block-columns.sf-panel--3) {
		display: block;
	}
	body.page-id-29 section:has(> .wp-block-columns.sf-panel--3)
		> .wp-block-columns.sf-panel--3 {
		display: grid !important;
		/* 27n re-grids every sf-panel--3 to 2-up on phones (quality/enquiry
		   card grids); this panel is semantic lists and must stack 1-col */
		grid-template-columns: minmax(0, 1fr) !important;
	}
	body.page-id-29 section:has(> .wp-block-columns.sf-panel--3)
		> .wp-block-columns.sf-panel--3
		> .wp-block-column {
		padding: 16px 0 !important;
	}
	/* five steps in one columns block: the odd 05 closes the phone row
	   full-width (the old 07-span rule only matched the two-block markup) */
	body.page-id-29
		section:has(> .wp-block-columns > .wp-block-column > .wp-block-group.has-top-border-color)
		> .wp-block-columns
		> .wp-block-column:last-child {
		grid-column: 1 / -1;
	}
}

/* === 49. Quality page: COA trust module ------------------------------------
   Replaces the old text+image columns: the real sample PDF previewer (left
   60%) beside the pitch and actions (right 40%). The <object> embed carries
   an <img> fallback because iOS Safari cannot render inline PDFs. The
   primary CTA is stage-B wired (gated download); until then it is inert. */
.sf-coa {
	display: grid;
	grid-template-columns: 3fr 2fr;
	gap: 40px;
	align-items: start;
}
.sf-coa__preview {
	/* fr tracks default to a max(min-content) floor; the <object>'s 800px
	   fallback scan would otherwise blow the 60% track up to ~70% */
	min-width: 0;
}
.sf-coa__label {
	margin: 0 0 10px;
	font-size: 13px;
	color: var(--wp--preset--color--text-secondary);
}
.sf-coa__frame {
	background: var(--wp--preset--color--card-white);
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	padding: 10px;
}
.sf-coa__pdf {
	display: block;
	width: 100%;
	height: 560px;
	border: 0;
}
.sf-coa__info h2 {
	margin: 0 0 12px;
}
.sf-coa__lead {
	margin: 0 0 18px;
	font-size: 15px;
	color: var(--wp--preset--color--text-secondary);
}
.sf-coa__actions {
	display: flex;
	flex-direction: column;
	gap: 12px;
	margin-top: 26px;
}
.sf-coa__btn {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	height: 48px;
	padding: 0 22px;
	border-radius: 6px;
	font-size: 15px;
	font-weight: 600;
	text-align: center;
	/* B4.5: the primary button is an <a> now (see the note in section 50) */
	text-decoration: none;
	cursor: pointer;
}
.sf-coa__btn--primary {
	background: var(--wp--preset--color--cta);
	color: var(--wp--preset--color--card-white);
	border: 1px solid var(--wp--preset--color--cta);
}
.sf-coa__btn--primary:hover {
	background: var(--wp--preset--color--cta-hover);
	border-color: var(--wp--preset--color--cta-hover);
}
.sf-coa__btn--ghost {
	background: transparent;
	color: var(--wp--preset--color--primary);
	border: 1px solid var(--wp--preset--color--border-medium);
	text-decoration: none;
}
.sf-coa__btn--ghost:hover {
	border-color: var(--wp--preset--color--primary);
}
@media (max-width: 781px) {
	.sf-coa {
		grid-template-columns: 1fr;
		gap: 20px;
	}
	.sf-coa__pdf {
		height: 340px;
	}
	.sf-coa__btn {
		width: 100%;
		height: 44px;
	}
}

/* === 50. Certificate request modal (Quality page) =========================
   B4.1/B4.2 (2026-09-18): every certificate control opens Gravity Forms
   Form 5 in a centred dialog and pre-fills the hidden `certificate` key.

   Only the dialog shell lives here. The backdrop colour, its 0.3s fade, the
   z-index stack (10000 / this panel sits inside it) and the body scroll lock
   are shared with the inquiry dialog — section 45 carries both class names on
   those rules, so there is one definition and no drift. A centred dialog needs
   the
   flex centring, the 520px measure and the mobile sheet, which is all that
   follows. */
.sf-certmodal {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	/* The dialog is a top-level block in the template, so WordPress applies
	   its block gap to it (`.wp-site-blocks > * + *` → margin-block-start:
	   24px). On a fixed, inset:0 box that gap does not push it down so much
	   as shrink it: the backdrop stopped 24px short of the top of the
	   viewport and the header stayed clickable above it. */
	margin: 0;
	padding: 24px;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	/* The backdrop rules at section 45 hand out 10000. The dialog is the
	   active layer whenever it is up, so it takes its own step above that
	   stack instead of relying on document order to break the tie. */
	z-index: 10010;
}

.sf-certmodal__panel {
	position: relative;
	box-sizing: border-box;
	width: 100%;
	max-width: 520px;
	max-height: calc(100vh - 48px);
	max-height: calc(100dvh - 48px);
	display: flex;
	flex-direction: column;
	background-color: var(--wp--preset--color--card-white);
	border-radius: 8px;
	box-shadow: 0 24px 64px rgba(15, 26, 21, 0.22);
	transform: translateY(12px);
	transition: transform 0.3s ease;
}

.sf-certmodal.is-open .sf-certmodal__panel {
	transform: translateY(0);
}

/* 40px target, inset from the corner rather than padded around: the title
   keeps its own 24px rhythm and the close button never collides with it. */
.sf-certmodal__close {
	position: absolute;
	top: 10px;
	right: 10px;
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: 40px;
	height: 40px;
	padding: 0;
	background: none;
	border: none;
	border-radius: 6px;
	font-size: 26px;
	line-height: 1;
	color: var(--wp--preset--color--text-secondary);
	cursor: pointer;
}

.sf-certmodal__close:hover {
	background-color: var(--wp--preset--color--bg-light);
	color: var(--wp--preset--color--primary);
}

.sf-certmodal__head {
	padding: 24px 60px 4px 24px;
}

.sf-certmodal__title {
	margin: 0 0 6px;
	font-size: 22px;
	line-height: 1.25;
	color: var(--wp--preset--color--primary);
}

.sf-certmodal__lead {
	margin: 0;
	font-size: 14px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}

/* The form is nine fields tall on a phone: the panel stays put and the body
   scrolls, so the heading and the close button never leave the screen. */
.sf-certmodal__body {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	padding: 18px 24px 24px;
	overscroll-behavior: contain;
}

.sf-certmodal__body .fluentform {
	margin: 0;
}

.sf-certmodal__body .fluentform .ff-el-group {
	row-gap: 14px !important;
}

/* Success state (B4.3): the confirmation Gravity Forms swaps in for the form.
   No JavaScript is involved in rendering it — the download is a plain <a>
   with a one-time token in its href. */
.sf-cert-result__title {
	margin: 0 0 8px;
	font-size: 18px;
	font-weight: 700;
	color: var(--wp--preset--color--primary);
}

.sf-cert-result__note {
	margin: 0 0 16px;
	font-size: 15px;
	line-height: 1.6;
	color: var(--wp--preset--color--text-secondary);
}

.sf-cert-result__cta {
	margin: 0 0 12px;
}

.sf-cert-result__btn {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	height: 48px;
	padding: 0 22px;
	background-color: var(--wp--preset--color--cta);
	color: var(--wp--preset--color--card-white);
	border-radius: 6px;
	font-size: 15px;
	font-weight: 600;
	text-decoration: none;
}

.sf-cert-result__btn:hover {
	background-color: var(--wp--preset--color--cta-hover);
	color: var(--wp--preset--color--card-white);
}

.sf-cert-result__fine {
	margin: 0;
	font-size: 13px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}

/* --- Success state (B4.3) -------------------------------------------------
   cert-modal.js reads the payload out of the confirmation markup and builds
   this card in its place, so the dialog ends on something a visitor can act
   on rather than a paragraph. The confirmation markup stays in the DOM (it is
   the copy a visitor without JavaScript clicks) and is simply parked. */
.sf-certmodal.is-success .sf-certmodal__head {
	display: none;
}

/* The heading is gone, so the card needs its own room at the top; the close
   button stays where it is, inset in the panel's corner. */
.sf-certmodal.is-success .sf-certmodal__body {
	padding-top: 34px;
	padding-bottom: 28px;
}

.sf-certmodal__stale {
	display: none;
}

/* A postback that arrives with no JavaScript at all lands inside the dialog
   while it is still hidden, so the visitor would see nothing at all. Show it
   when the confirmation is sitting there unclaimed — the :not() keeps this
   from re-opening a dialog that was closed properly, because the script parks
   the confirmation it has already turned into a card. */
.sf-certmodal[hidden]:has(.sf-cert-result:not(.sf-certmodal__stale)) {
	display: flex;
}

.sf-certmodal__success {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
}

.sf-certmodal__success-icon {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: 48px;
	height: 48px;
	margin-bottom: 16px;
	border-radius: 50%;
	background-color: var(--wp--preset--color--bg-light);
	color: var(--wp--preset--color--primary);
}

.sf-certmodal__success-icon svg {
	display: block;
	width: 26px;
	height: 26px;
}

.sf-certmodal__success-title {
	margin: 0 0 10px;
	font-size: 22px;
	line-height: 1.25;
	font-weight: 700;
	color: var(--wp--preset--color--primary);
}

.sf-certmodal__success-note {
	margin: 0 0 22px;
	font-size: 16px;
	line-height: 1.55;
	color: var(--wp--preset--color--text-secondary);
}

/* The address is the part a visitor checks, and it can be long: let it wrap
   instead of pushing the card wider than the panel. */
.sf-certmodal__success-note strong {
	font-weight: 600;
	color: var(--wp--preset--color--text-primary);
	word-break: break-word;
}

.sf-certmodal__success-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: 100%;
	height: 48px;
	background-color: var(--wp--preset--color--cta);
	color: var(--wp--preset--color--card-white);
	border-radius: 6px;
	font-size: 15px;
	font-weight: 600;
	text-decoration: none;
}

.sf-certmodal__success-btn:hover {
	background-color: var(--wp--preset--color--cta-hover);
	color: var(--wp--preset--color--card-white);
}

.sf-certmodal__success-fine {
	margin: 8px 0 0;
	font-size: 13px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}

.sf-certmodal__success-close {
	box-sizing: border-box;
	width: 100%;
	height: 48px;
	margin-top: 20px;
	background: none;
	color: var(--wp--preset--color--primary);
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 6px;
	font: inherit;
	font-size: 15px;
	font-weight: 600;
	cursor: pointer;
}

.sf-certmodal__success-close:hover {
	background-color: var(--wp--preset--color--bg-light);
	border-color: var(--wp--preset--color--primary);
}

/* Mobile: a bottom sheet rather than a 520px card with 24px of gutter on a
   375px screen. Full width, square bottom corners, at most 92% of the
   viewport — one thumb-reachable surface, still scrollable inside. */
@media (max-width: 600px) {
	.sf-certmodal {
		align-items: flex-end;
		padding: 0;
	}

	.sf-certmodal__panel {
		max-width: none;
		max-height: 92vh;
		max-height: 92dvh;
		border-radius: 8px 8px 0 0;
		transform: translateY(100%);
	}

	.sf-certmodal.is-open .sf-certmodal__panel {
		transform: translateY(0);
	}

	.sf-certmodal__head {
		padding: 20px 60px 4px 20px;
	}

	.sf-certmodal__body {
		padding: 16px 20px 24px;
	}

	.sf-cert-result__btn {
		height: 44px;
	}

	/* the card, one notch tighter on a phone */
	.sf-certmodal.is-success .sf-certmodal__body {
		padding-top: 30px;
		padding-bottom: 22px;
	}

	.sf-certmodal__success-icon {
		width: 44px;
		height: 44px;
	}

	.sf-certmodal__success-icon svg {
		width: 24px;
		height: 24px;
	}

	.sf-certmodal__success-title {
		font-size: 20px;
	}

	.sf-certmodal__success-note {
		font-size: 15px;
		margin-bottom: 18px;
	}

	.sf-certmodal__success-btn,
	.sf-certmodal__success-close {
		height: 44px;
	}

	.sf-certmodal__success-close {
		margin-top: 16px;
	}
}

/* --- B4.5: the certificate controls are links, not buttons ---------------
   Every trigger is now <a href="/contact/#quote" data-cert="…">. Without
   JavaScript the click lands on the inquiry form — the promise the link keeps —
   and with it cert-modal.js preventDefaults and opens the dialog instead, so
   the href is only ever the fallback. .sf-coa__btn and .wp-block-button__link
   already carry the button look; a link needs the underline reset on top of it.
   The dark viewer is the one place the theme's own link colour would be
   unreadable, which is why its line is spelled out below. */
.sf-cert-trigger {
	cursor: pointer;
}

/* The HACCP and BRC rows used to carry a muted note ("Certificate copy
   available upon request"). All six rows now say "Request Certificate", so the
   note styling was dropped and those two links read exactly like the other
   four — one convention for one control. */
/* The viewer's request line, built by quality.js: last line of the lightbox,
   under the controls. */
.sf-lb__request {
	margin: 0;
	font-size: 13px;
	line-height: 1.5;
	color: rgba(255, 255, 255, 0.72);
}

.sf-lb__request-link {
	color: #fff;
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* --- 31d. Certifications: 2-up card grid ---------------------------------
   Six hairlined rows measured 1247px on a 1440 screen — half a page of scroll
   for six short entries. The rows keep their DOM exactly as-is: quality.js
   builds the lightbox gallery from `.sf-certdetail > .sf-certrow__media a` in
   document order, and the E2E suites anchor on the same hooks, so this is a
   layout change only. Numbering stays row-major (left→right, top→bottom),
   which is the order the gallery's prev/next already walks.

   The thumbnail holds at 120x167 — the 8/11 scan ratio the viewer measures
   itself against — so the media column, not the copy, still sets the card
   height: three rows of ~199px replace six of ~208px, and the section drops
   from ~1496px to ~890px.

   The old `.sf-certrow + .sf-certrow` separator (above, section 31b) needs no
   neutralising: it repaints the top edge in border-light, which is the very
   colour of the new card frame, so it is invisible either way. */
.sf-certdetail {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 20px 24px;
}
.sf-certrow {
	box-sizing: border-box;
	padding: 16px 18px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	background-color: var(--wp--preset--color--card-white);
}

@media (max-width: 768px) {
	/* One card per row. The phone layout already put the 88px thumbnail on the
	   left (the 900px rule above), so stacking only adds the frame. The padding
	   and the inner gap have to be restated here: the desktop card rule sits
	   later in this file than the 900px block and would otherwise win on a
	   phone too. Kept to 12px because the frame's horizontal inset eats into
	   the copy column, and at 16px the six cards grew ~190px taller than the
	   uncarded list — 12px trims that to ~85px. The 88px media track is set by
	   the 900px block; narrowing it here would give the copy column back its
	   full width, but the brief keeps the 88x123 thumbnail. */
	.sf-certdetail {
		grid-template-columns: minmax(0, 1fr);
		gap: 12px;
	}
	.sf-certrow {
		padding: 12px;
		gap: 12px;
	}
}

/* === 31e. Third-party testing bar (home, certifications section) ---------
   A quiet strip of accepted-lab capsules between the six seal cards and the
   "View Full Certifications" button. Text capsules on purpose: naming the
   laboratories we accept reports from, not logos claiming their endorsement
   (trademark-safe, same "accepted" framing as the factory-tour FAQ). Centered
   via flex so the row wraps cleanly once five capsules stop fitting. */
.sf-certbar {
	margin-top: 28px;
	text-align: center;
}
.sf-certbar__label {
	margin: 0;
	font-size: 14px;
	font-weight: 600;
	line-height: 1.35;
	color: #1B4D3E;
}
.sf-certbar__sub {
	margin: 4px 0 0;
	font-size: 13px;
	line-height: 1.35;
	color: #6B6B6B;
}
.sf-certbar__logos {
	display: flex;
	justify-content: center;
	flex-wrap: wrap;
	gap: 16px;
	margin-top: 16px;
}
.sf-certbar__item {
	display: inline-block;
	padding: 6px 16px;
	border: 1px solid #DCE2DF;
	border-radius: 999px;
	background: #fff;
	font-size: 13px;
	font-weight: 600;
	line-height: 1.3;
	color: #1B4D3E;
	white-space: nowrap;
}
@media (max-width: 768px) {
	.sf-certbar__item {
		font-size: 12px;
		padding: 5px 13px;
	}
	.sf-certbar__logos {
		gap: 10px;
	}
}

/* === 51. On-this-page dot-rail TOC (toc-nav.js) =====================
 * Desktop-only (>= 1101px): fixed dot rail in the right gutter (body bg is
 * white there, so the light palette is always correct). 8px dots, 16px gap,
 * hover expands a white label card to the left; the active dot scales to
 * 12px in deep forest green. Fades out above the fold (scrollY < 200) so it
 * never competes with the hero. z-index 9990 sits under the float stack
 * (9998); vertically centred, so the two never overlap. */
.sf-toc {
	position: fixed;
	right: 20px;
	top: 50%;
	transform: translateY(-50%);
	z-index: 9990;
	opacity: 1;
	transition: opacity 0.3s ease;
}
.sf-toc--hidden {
	opacity: 0;
	pointer-events: none;
}
.sf-toc ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 16px;
}
.sf-toc a {
	position: relative;
	display: block;
	width: 8px;
	height: 8px;
}
/* invisible hit-area extension around the 8px dot */
.sf-toc a::after {
	content: "";
	position: absolute;
	inset: -7px;
}
.sf-toc__dot {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: #DCE2DF;
	transition: transform 0.2s ease, background-color 0.2s ease;
}
.sf-toc a:hover .sf-toc__dot,
.sf-toc a:focus-visible .sf-toc__dot {
	transform: scale(1.5);
	background: #1B4D3E;
}
.sf-toc li.is-current .sf-toc__dot {
	transform: scale(1.5);
	background: #1B4D3E;
}
.sf-toc a:focus-visible {
	outline: 2px solid #1B4D3E;
	outline-offset: 4px;
	border-radius: 50%;
}
.sf-toc__label {
	position: absolute;
	right: 100%;
	top: 50%;
	transform: translateY(-50%) translateX(4px);
	margin-right: 12px;
	padding: 6px 12px;
	background: #fff;
	border: 1px solid #DCE2DF;
	border-radius: 6px;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
	font-size: 13px;
	line-height: 1.3;
	color: #1B4D3E;
	white-space: nowrap;
	max-width: 260px;
	overflow: hidden;
	text-overflow: ellipsis;
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.2s ease, transform 0.2s ease;
}
.sf-toc a:hover .sf-toc__label,
.sf-toc a:focus-visible .sf-toc__label {
	opacity: 1;
	transform: translateY(-50%) translateX(0);
}
/* click targets stop 96px below the sticky header (61px) + breathing room */
.sf-toc-target {
	scroll-margin-top: 96px;
}
@media (max-width: 1100px) {
	.sf-toc {
		display: none;
	}
}
@media (prefers-reduced-motion: reduce) {
	.sf-toc,
	.sf-toc__dot,
	.sf-toc__label {
		transition: none;
	}
}

/* === 52. Client Stories link cards (case-study phase 2) ==============
 * Story cards in the first marquee group are anchors now; the duplicated
 * aria-hidden loop group stays a plain div (no duplicate tab stops).
 * .sf-story already provides the flex-column card layout. */
.sf-story__more {
	margin-top: auto;
	padding-top: 14px;
	font-size: 14px;
	font-weight: 600;
	line-height: 1.3;
	color: var(--wp--preset--color--primary, #2e6b54);
}
.sf-story--link:hover .sf-story__more,
.sf-story--link:focus-visible .sf-story__more {
	text-decoration: underline;
	text-underline-offset: 3px;
}
.sf-story--link:focus-visible {
	outline: 2px solid var(--wp--preset--color--primary, #2e6b54);
	outline-offset: 4px;
	border-radius: 6px;
}

/* === 53. Article reading progress bar (toc-nav.js 2.0) ==================
 * 3px brand-green bar pinned to the viewport top; width is driven by
 * transform:scaleX from JS (no layout thrash). z-index sits above the
 * sticky header (999) and under the float stack (9998). */
.sf-progress {
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 3px;
	background: #5ab735;
	transform: scaleX(0);
	transform-origin: 0 50%;
	z-index: 9995;
	pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
	.sf-progress { transition: none; }
}

/* === 54. Article TOC — text list (>= 1280px, toc-nav.js 2.0) =============
 * Shares .sf-toc positioning with the dot rail (section 51) but resets the
 * dot sizing/hit-area: a plain left-ruled list, one line per entry with
 * ellipsis (full title on :hover via the title attribute). A per-entry copy
 * button fades in on hover/focus-within. */
.sf-toc--text {
	right: 24px;
	max-width: 252px;
}
.sf-toc--text ul { gap: 2px; }
.sf-toc--text li { position: relative; }
.sf-toc--text a.sf-toc__link {
	display: block;
	width: auto;
	height: auto;
	padding: 6px 30px 6px 12px;
	border-left: 2px solid #DCE2DF;
	border-radius: 0 6px 6px 0;
	font-size: 13px;
	line-height: 1.35;
	color: #4a5a52;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}
/* kill the dot-rail's invisible hit-area extension */
.sf-toc--text a.sf-toc__link::after { content: none; }
.sf-toc--text a.sf-toc__link:hover,
.sf-toc--text a.sf-toc__link:focus-visible {
	color: #1B4D3E;
	background: rgba(90, 183, 53, 0.07);
}
.sf-toc--text li.is-current > a.sf-toc__link {
	color: #1B4D3E;
	font-weight: 600;
	border-left-color: #5ab735;
	background: rgba(90, 183, 53, 0.09);
}
.sf-toc--text a.sf-toc__link:focus-visible {
	outline: 2px solid #1B4D3E;
	outline-offset: 2px;
}
.sf-toc--text .sf-toc__copy {
	position: absolute;
	right: 2px;
	top: 50%;
	transform: translateY(-50%);
	width: 24px;
	height: 24px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: none;
	border-radius: 4px;
	background: transparent;
	color: #6b7a72;
	cursor: pointer;
	opacity: 0;
	transition: opacity 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.sf-toc--text li:hover .sf-toc__copy,
.sf-toc--text .sf-toc__copy:focus-visible {
	opacity: 1;
}
.sf-toc--text .sf-toc__copy:hover {
	color: #1B4D3E;
	background: rgba(27, 77, 62, 0.08);
}
.sf-toc--text .sf-toc__copy:focus-visible {
	outline: 2px solid #1B4D3E;
	outline-offset: 1px;
}
/* Text list owns the right gutter from 1280px; below that the mobile
 * collapsible takes over (section 55). Base .sf-toc hides <= 1100. */
@media (max-width: 1279px) {
	.sf-toc--text { display: none; }
}

/* === 55. Article TOC — collapsible "On this page" (< 1280px) =============
 * Filled by toc-nav.js into the .sf-mobile-toc mount in single.html. Shown
 * up to 1279px so tablets get it too; the desktop text list takes over at
 * 1280px. Default collapsed. */
.sf-mobile-toc {
	margin-bottom: 28px;
}
.sf-mobile-toc__toggle {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	box-sizing: border-box;
	padding: 12px 16px;
	background: #fff;
	border: 1px solid var(--wp--preset--color--border-light, #e4e9e6);
	border-radius: 6px;
	font-size: 14px;
	font-weight: 600;
	color: #1B4D3E;
	cursor: pointer;
	transition: background-color 0.2s ease;
}
.sf-mobile-toc__toggle:hover { background: rgba(90, 183, 53, 0.05); }
.sf-mobile-toc__chev { margin-left: auto; transition: transform 0.2s ease; }
.sf-mobile-toc__toggle[aria-expanded="true"] .sf-mobile-toc__chev {
	transform: rotate(180deg);
}
.sf-mobile-toc__panel {
	background: #fff;
	border: 1px solid var(--wp--preset--color--border-light, #e4e9e6);
	border-top: 0;
	border-radius: 0 0 6px 6px;
}
.sf-mobile-toc__panel[hidden] { display: none; }
.sf-mobile-toc__panel ul {
	list-style: none;
	margin: 0;
	padding: 6px 0;
}
.sf-mobile-toc__panel a {
	display: block;
	padding: 9px 18px;
	font-size: 14px;
	line-height: 1.4;
	color: #3f4f47;
	text-decoration: none;
	transition: background-color 0.15s ease, color 0.15s ease;
}
.sf-mobile-toc__panel a:hover {
	color: #1B4D3E;
	background: rgba(90, 183, 53, 0.07);
}
.sf-mobile-toc__panel li.is-current > a {
	color: #1B4D3E;
	font-weight: 600;
}
.sf-mobile-toc__panel a:focus-visible {
	outline: 2px solid #1B4D3E;
	outline-offset: -2px;
}
@media (min-width: 1280px) {
	.sf-mobile-toc { display: none; }
}

/* === 56. Article actions: share row + feedback (toc-nav.js 2.0) ========== */
.sf-article-actions {
	max-width: 800px;
	box-sizing: border-box;
	margin: 48px auto 0;
	padding-top: 32px;
	border-top: 1px solid var(--wp--preset--color--border-light, #e4e9e6);
}
.sf-article-share {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px;
}
.sf-article-share__label,
.sf-article-feedback__q {
	font-size: 14px;
	font-weight: 600;
	color: #1B4D3E;
}
.sf-share-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 9px 16px;
	border: 1px solid #DCE2DF;
	border-radius: 6px;
	background: #fff;
	color: #1B4D3E;
	font-size: 13.5px;
	font-weight: 600;
	line-height: 1.2;
	text-decoration: none;
	cursor: pointer;
	transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.sf-share-btn:hover {
	border-color: #1B4D3E;
	background: #1B4D3E;
	color: #fff;
}
.sf-share-btn:focus-visible {
	outline: 2px solid #1B4D3E;
	outline-offset: 2px;
}
.sf-article-feedback {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px;
	margin-bottom: 24px;
}
.sf-fb-btn {
	width: 40px;
	height: 40px;
	box-sizing: border-box;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid #DCE2DF;
	border-radius: 50%;
	background: #fff;
	color: #1B4D3E;
	cursor: pointer;
	transition: border-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.sf-fb-btn:hover {
	border-color: #5ab735;
	color: #5ab735;
	transform: translateY(-1px);
}
.sf-fb-btn:focus-visible {
	outline: 2px solid #1B4D3E;
	outline-offset: 2px;
}
.sf-article-feedback.is-done .sf-fb-btn {
	opacity: 0.45;
	pointer-events: none;
}
.sf-article-feedback__thanks {
	font-size: 14px;
	font-weight: 600;
	color: #5ab735;
}
/* Down-vote mini form (optional email + description -> GF Form 6). */
.sf-fb-form {
	width: 100%;
	box-sizing: border-box;
	margin-top: 8px;
	padding: 20px;
	background: #fff;
	border: 1px solid #DCE2DF;
	border-radius: 8px;
}
.sf-fb-form[hidden] { display: none; }
.sf-fb-form__lead {
	margin: 0 0 12px;
	font-size: 14.5px;
	font-weight: 600;
	color: #1B4D3E;
}
.sf-fb-form input[type="email"],
.sf-fb-form textarea {
	width: 100%;
	box-sizing: border-box;
	padding: 10px 12px;
	margin-bottom: 10px;
	border: 1px solid #DCE2DF;
	border-radius: 6px;
	font-size: 14px;
	font-family: inherit;
	line-height: 1.5;
	color: #22332b;
	background: #fff;
}
.sf-fb-form input[type="email"]:focus,
.sf-fb-form textarea:focus {
	outline: none;
	border-color: #5ab735;
	box-shadow: 0 0 0 3px rgba(90, 183, 53, 0.15);
}
.sf-fb-form__consent {
	display: flex;
	align-items: flex-start;
	gap: 8px;
	margin-bottom: 14px;
	font-size: 13px;
	line-height: 1.5;
	color: #4a5a52;
	cursor: pointer;
}
.sf-fb-form__consent input {
	margin-top: 2px;
	accent-color: #5ab735;
}
.sf-fb-form__row {
	display: flex;
	gap: 10px;
}
.sf-fb-form__send {
	padding: 10px 22px;
	border: none;
	border-radius: 6px;
	background: #5ab735;
	color: #fff;
	font-size: 14px;
	font-weight: 600;
	cursor: pointer;
	transition: filter 0.2s ease;
}
.sf-fb-form__send:hover { filter: brightness(1.06); }
.sf-fb-form__send[disabled] { opacity: 0.6; cursor: default; }
.sf-fb-form__cancel {
	padding: 10px 18px;
	border: 1px solid #DCE2DF;
	border-radius: 6px;
	background: transparent;
	color: #4a5a52;
	font-size: 14px;
	cursor: pointer;
}
.sf-fb-form__error {
	margin: 10px 0 0;
	font-size: 13.5px;
	color: #b3261e;
}

/* === 57. Article meta extras + inline CTA (toc-nav.js 2.0) =============== */
/* {{LAST_UPDATED}} resolves to '' on posts without sf_last_reviewed — the
 * empty paragraph must not leave a ghost dot/gap in the hero meta row. */
.sf-single-meta__updated:empty { display: none; }
.sf-copy-link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	box-sizing: border-box;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.14);
	color: #fff;
	cursor: pointer;
	transition: background-color 0.2s ease, transform 0.2s ease;
}
.sf-copy-link:hover {
	background: rgba(255, 255, 255, 0.28);
	transform: translateY(-1px);
}
.sf-copy-link:focus-visible {
	outline: 2px solid #fff;
	outline-offset: 2px;
}
/* Long-article mid-content CTA — inserted by JS before the 3rd H2. */
.sf-inline-cta {
	box-sizing: border-box;
	margin: 40px 0;
	padding: 24px 28px;
	background: rgba(90, 183, 53, 0.08);
	border: 1px solid rgba(90, 183, 53, 0.35);
	border-left: 4px solid #5ab735;
	border-radius: 8px;
}
.sf-inline-cta__title {
	margin: 0 0 6px;
	font-size: 19px;
	font-weight: 700;
	line-height: 1.3;
	color: #1B4D3E;
}
.sf-inline-cta__text {
	margin: 0 0 16px;
	font-size: 15px;
	line-height: 1.6;
	color: #3f4f47;
}
.sf-inline-cta__btn {
	display: inline-block;
	padding: 11px 24px;
	border-radius: 6px;
	background: #5ab735;
	color: #fff;
	font-size: 14.5px;
	font-weight: 600;
	text-decoration: none;
	transition: filter 0.2s ease;
}
.sf-inline-cta__btn:hover { filter: brightness(1.06); }
.sf-inline-cta__btn:focus-visible {
	outline: 2px solid #1B4D3E;
	outline-offset: 2px;
}
@media (max-width: 768px) {
	.sf-inline-cta { padding: 18px; }
	.sf-inline-cta__btn {
		display: block;
		width: 100%;
		box-sizing: border-box;
		min-height: 44px;
		text-align: center;
	}
	.sf-share-btn {
		flex: 1 1 auto;
		min-height: 44px;
	}
	.sf-article-actions {
		margin-top: 36px;
		padding-top: 24px;
	}
}

/* === 58. Print styles =====================================================
 * Articles print clean black-on-white: chrome (header/footer/float stack/
 * TOC/progress), the interactive bands (share, feedback, prev/next, Related
 * grid, closing CTA) and the breadcrumb all go. toc-nav.js injects the
 * source URL + print date into the article body on beforeprint. */
@page {
	margin: 18mm 15mm;
}
@media print {
	header,
	footer,
	.sf-toc,
	.sf-toc--text,
	.sf-mobile-toc,
	.sf-progress,
	.sf-float-stack,
	.sf-cookie-banner,
	.sf-toast,
	.sf-breadcrumb,
	.sf-inline-cta,
	.sf-article-actions,
	.sf-prevnext,
	.sf-related,
	.sf-copy-link,
	body.single-post section.has-primary-background-color:last-of-type {
		display: none !important;
	}
	body {
		background: #fff !important;
		color: #000 !important;
	}
	/* The Forest hero prints white; its title/term colours are forced dark. */
	section.has-primary-background-color {
		background: #fff !important;
	}
	.sf-single-body {
		padding: 0 !important;
	}
	.sf-single-cover img {
		max-height: none;
		border-radius: 0;
	}
	h1, h2, h3, p, li, td, th,
	/* Batch H5-0: the formula hero's heading is a div now, so the element list
	   above no longer reaches it — and this rule is the only thing that turns
	   it black for print. Without this line a formula page prints its title
	   white on white. The class is in the list rather than the tag because the
	   point of the batch is that the hero's copy no longer carries an h1. */
	.sf-formula-hero__title {
		color: #000 !important;
	}
	a {
		color: #000 !important;
		text-decoration: none;
	}
	.has-primary-background-color .wp-block-post-terms a,
	.sf-single-meta,
	.sf-single-meta > * {
		color: #444 !important;
	}
	.sf-print-url {
		display: block;
		margin-top: 28px;
		padding-top: 12px;
		border-top: 1px solid #999;
		font-size: 12px;
		color: #333;
	}
}


/* Tour grid: several cards stacked in one column — cards size to their own
   content, not to the flex column (which the global .sf-card--roomy stretch
   would otherwise blow up to full column height, stacking three oversized
   cards on top of each other). */
.wp-block-column > .sf-tour-card-item {
	height: auto;
}


/* Optional-field toggle (GF forms): secondary fields carry .sf-optional and
   stay hidden until the visitor expands them. */
.gfield.sf-optional { display: none; }
.gfield.sf-optional.sf-opt-open { display: block; }
.sf-opt-toggle {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	background: none;
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 6px;
	padding: 9px 16px;
	font-size: 14px;
	font-weight: 500;
	color: var(--wp--preset--color--primary);
	cursor: pointer;
	transition: border-color 0.2s ease;
}
.sf-opt-toggle:hover { border-color: var(--wp--preset--color--accent); }
.sf-opt-toggle span { transition: transform 0.2s ease; }
.sf-opt-toggle[aria-expanded="true"] span { transform: rotate(180deg); }

/* =====================================================================
   Factory tour — photo-based tour route (.sf-route__step + .sf-photo-slot).
   Each stop card reserves the photo's frame at the final 4:3 ratio, so the
   grid never reflows when the real shot replaces the slot. The filename for
   every slot sits in the block markup right above it. ----------------- */
.sf-photo-slot {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 4 / 3;
	margin-block-end: 18px;
	border: 1px dashed var(--wp--preset--color--border-medium);
	border-radius: 6px;
	background: var(--wp--preset--color--bg-light);
}
.sf-photo-slot p {
	margin: 0 !important;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
.sf-route__step {
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 1.5px;
	color: var(--wp--preset--color--brand-green);
}
/* One card per column, so each card can fill its column: cards in a row end
   level even when a title or description wraps to a second line (longer
   translations, narrower viewports). More specific than the theme's
   .wp-block-column > .sf-tour-card-item { height: auto }. ---------------- */
.sf-eq-row > .wp-block-column > .sf-tour-card-item {
	box-sizing: border-box;
	height: 100%;
}
/* B2D Step1 — Active Ingredients & Guaranteed Analysis band on the eight
   dosage pages. One recipe per block, hairline-separated: the band sits on
   the white section, so card chrome would not read. The guaranteed-analysis
   table is the compact .sf-spec-list grid defined above (section "Compact
   8-row spec list"), reused here with <dt>/<dd> — the correct semantics for a
   term/value pair, and structurally out of reach of the dosage-page Product
   JSON-LD parser, which matches <span class="sf-spec-term">…</span>
   <span class="sf-spec-value">…</span> on the template file. ------------- */
.sf-actives__title { margin: 0; }
.sf-actives__intro {
	max-width: 720px;
	margin-top: 10px;
	font-size: 15px;
	color: var(--wp--preset--color--text-secondary);
}
.sf-actives__list { margin-top: 30px; }
.sf-actives__item + .sf-actives__item {
	margin-top: 28px;
	padding-top: 28px;
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-actives__name { margin: 0; font-size: 17px; }
.sf-actives__label {
	margin: 16px 0 0;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
/* Ingredient pills. Deliberately NOT .sf-fchip: that class carries
   cursor:pointer and a hover/.is-active state because it is a filter button —
   the wrong affordance on a static term. Outline pill mirrors the
   .sf-topbar-badges certificate pill, the site's existing pill vocabulary. */
.sf-actives__ing {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 10px 0 0;
	padding: 0;
	list-style: none;
}
.sf-actives__pill {
	padding: 3px 10px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	font-size: 13px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}
/* <dl> ships margin-block 1em and <dd> margin-inline-start 40px — the reused
   .sf-spec-list grid needs both neutralised inside this band. */
.sf-actives .sf-spec-list { margin: 10px 0 0; }
.sf-actives .sf-spec-term,
.sf-actives .sf-spec-value { margin: 0; }

/* B2D Step2 — dosage page product gallery, between the hero and #formulas.
   The section is a bg-light band because #formulas and the three bands after
   it are all white: a white gallery would have read as one five-block slab.

   The visual language is taken from .sf-fac ("Inside Our Factory",
   page-about) on purpose — same 8px radius, same object-fit/object-position
   pair, same soft resting shadow, same focus-visible token, same
   reduced-motion off-switch. The one thing deliberately NOT taken is
   .sf-fac's !important: those exist only because .sf-fac is a core Gallery
   block and core's own sheet sizes the tiles with
   `width: calc(33.33% - 16px)`. This markup is plain <figure>, so that rule
   cannot match and nothing here has to win a specificity fight. No img rule
   in this file carries !important; keep it that way.

   Batch H7a deleted the band's heading ("A Closer Look at {name}") and with it
   the one rule that styled it, .sf-gallery__title { margin: 0; }. The switch
   that took its place is styled at the end of this section. -------------- */
.sf-gallery__stage {
	position: relative;
	max-width: 680px;
	margin: 30px auto 0;
	overflow: hidden;
	border-radius: 8px;
	/* The main photo is 720x720, so a 1:1 box holds it with zero crop; the
	   three facility frames are cropped from the centre instead. The box is
	   fixed either way, so changing photo moves nothing on the page. */
	aspect-ratio: 1 / 1;
	box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
	/* Horizontal drags are ours (the swipe); vertical panning and
	   pinch-to-zoom stay with the browser. Dropping pinch-zoom would take
	   pinch-to-zoom away from anyone reading the page on a phone. */
	touch-action: pan-y pinch-zoom;
}
.sf-gallery__slide {
	position: absolute;
	inset: 0;
	margin: 0;
	opacity: 1;
	/* `display:none` cannot transition, which is why the frames are hidden
	   with this class rather than with the attribute once JS is running. */
	transition: opacity 0.28s ease;
}
/* Belt and braces: the `hidden` attribute is the no-JS switch, and any plain
   `display` declaration would outrank the UA's own [hidden] rule. */
.sf-gallery__slide[hidden] { display: none; }
.sf-gallery__slide--off {
	opacity: 0;
	pointer-events: none;
}
.sf-gallery__slide img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}
/* Built by formula-gallery.js from the frames above; absent without JS. */
.sf-gallery__thumbs {
	display: flex;
	gap: 12px;
	justify-content: center;
	margin-top: 16px;
	/* room for the selected ring and the focus outline inside the scroll box */
	padding: 4px 0;
}
.sf-gallery__thumb {
	flex: 0 0 auto;
	width: 104px;
	height: 104px;
	padding: 0;
	overflow: hidden;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
	background: none;
	cursor: pointer;
	transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.sf-gallery__thumb img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}
/* 1px border plus a 1px ring reads as the 2px accent outline without the
   border-width change that would nudge the whole strip sideways. */
.sf-gallery__thumb[aria-selected="true"] {
	border-color: var(--wp--preset--color--accent);
	box-shadow: 0 0 0 1px var(--wp--preset--color--accent);
}
.sf-gallery__thumb:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
}
/* On a phone the strip scrolls instead of shrinking: four 84px tiles plus
   three gaps come to 372px against ~343px of content width, so a sliver of
   the next photo stays in view and the strip is visibly swipeable. */
@media (max-width: 768px) {
	.sf-gallery__thumbs {
		justify-content: flex-start;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		scroll-padding-left: 4px;
		-webkit-overflow-scrolling: touch;
	}
	.sf-gallery__thumb {
		flex: 0 0 84px;
		width: 84px;
		height: 84px;
		scroll-snap-align: start;
	}
}
@media (prefers-reduced-motion: reduce) {
	.sf-gallery__slide,
	.sf-gallery__thumb,
	.sf-gallery__tab {
		transition: none;
	}
}

/* B2D Step3 — "Formula & nutrition" and "Ingredients & composition", the two
   bands between the Specification cards and the related-formulas grid on a
   formula detail page.

   Both are emitted whole by their shortcode (section + heading + body) and
   return nothing at all when the record has no data, so they are styled here
   rather than in the template: a band with no data must not leave a padded
   empty section or an orphan heading behind. Same contract as
   .sf-fdetail-body above, which emits itself for the same reason.

   White, like .sf-fdetail above them: the three read as one "this formula's
   data" surface, each run separated by its own H2. Padding sits on the
   section, not on the inner box, so the background covers the gutter at every
   width regardless of box-sizing. Pill and term/value vocabulary is the
   2D-S1 set, unchanged. ------------------------------------------------ */
.sf-fdetail-actives,
.sf-fdetail-composition {
	padding: 48px 0;
	background-color: var(--wp--preset--color--card-white);
}
.sf-fdetail-actives__inner,
.sf-fdetail-composition__inner {
	max-width: var(--wp--style--global--content-size, 1200px);
	margin-inline: auto;
}
/* Matches the 0 top margin core's layout gives the "Specification" heading
   in the band above, so all three headings sit on the same baseline. */
.sf-fdetail-actives__title,
.sf-fdetail-composition__title { margin: 0; }
/* The reused pill list and spec grid carry their own spacing; <dl> ships
   margin-block 1em and <dd> margin-inline-start 40px, which the grid needs
   neutralised inside these bands — the same pair .sf-actives sets. */
.sf-fdetail-actives .sf-spec-list,
.sf-fdetail-composition .sf-spec-list { margin: 10px 0 0; }
.sf-fdetail-actives .sf-spec-term,
.sf-fdetail-actives .sf-spec-value,
.sf-fdetail-composition .sf-spec-term,
.sf-fdetail-composition .sf-spec-value { margin: 0; }
/* Composition values are prose, not figures: the bold primary-green weight is
   the guaranteed-analysis treatment and would read as a level on a sentence. */
.sf-fdetail-composition .sf-spec-value {
	font-weight: 400;
	color: var(--wp--preset--color--text-primary);
}
@media (max-width: 1240px) {
	.sf-fdetail-actives,
	.sf-fdetail-composition {
		padding-left: 38px;
		padding-right: 38px;
	}
}
@media (max-width: 768px) {
	.sf-fdetail-actives,
	.sf-fdetail-composition {
		padding-left: 20px;
		padding-right: 20px;
	}
}

/* 38a-ii. The spec sheet — batch H7c. Twelve rows at the top of the reading
   area, before the long copy and before the Ingredients band. Emitted whole by
   its shortcode for the reason the two bands above are: a record with none of
   the fields must leave no padded empty section and no orphan heading behind.

   Two columns on a desk, one on a phone. The split into groups happens in PHP,
   over the rows that actually rendered (five and four when a record has nine),
   so each column ends where its own content ends; this file only says there
   are two of them. White, continuing the surface that starts here and runs
   unbroken to the end of the FAQ.

   The order of the four blocks below is the load-bearing part. Base rule
   first, then 1240, then 900, then 768 — a media query adds no specificity, so
   a step written ABOVE the rule it means to override is dead at every width.
   That is the mistake batch H7b shipped and had to fix (see
   .sf-fdetail2__title), and there is nothing in the file's syntax that would
   have caught it. */
.sf-fdetail-specs {
	padding: 48px 0;
	background-color: var(--wp--preset--color--card-white);
}
.sf-fdetail-specs__inner {
	max-width: var(--wp--style--global--content-size, 1200px);
	margin-inline: auto;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	column-gap: 64px;
	/* Batch H10 — three named groups (Core / Specs / Packaging) instead of one
	   list sliced into two anonymous columns; the gap joins them vertically. */
	row-gap: 36px;
}
/* <dl> ships margin-block 1em; the groups are grid items, not prose. */
.sf-fdetail-specs__group { margin: 0; }
/* Batch H10 — the small-caps group heading. Sits inside its dl, above its
   rows; the row rules below address it with the sibling selector so the
   first row after it keeps the list's own rhythm. */
.sf-fdetail-specs__gtitle {
	margin: 0 0 2px;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
/* The label column is sized to its own content and floored at 120px. The nine
   labels in the sheet run 32–105px wide, so the floor is what decides every
   row: each row is its own grid, and a floor above the widest label is the
   only thing that keeps the ten values on one x. The 40% share it replaces
   resolved to 227px on a desk — 144px of it empty on the widest row, and 164px
   between the longest label and its value. `max-content` is the ceiling, so a
   future label longer than 120px widens its own row instead of clipping. */
.sf-fdetail-specs__row {
	display: grid;
	grid-template-columns: minmax(120px, max-content) minmax(0, 1fr);
	align-items: baseline;
	column-gap: 20px;
	padding: 12px 0;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
/* No rule above the first row and none under the group's last row (the
   --last class is set by the renderer): the band's own padding is the whole
   space, and a group should not read as a boxed table. In the two-column
   state that leaves each column's last row open, which is the point — the
   columns are one list that happens to wrap. */
.sf-fdetail-specs__gtitle + .sf-fdetail-specs__row { padding-top: 0; }
.sf-fdetail-specs__row--last { border-bottom: 0; }
.sf-fdetail-specs__term {
	font-size: 13px;
	font-weight: 600;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail-specs__value {
	margin: 0;
	font-size: 15px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}
/* Outline pills, the .sf-actives__pill vocabulary: a three-item ingredient row
   stays a line instead of becoming a column of its own. */
.sf-fdetail-specs__chip {
	display: inline-block;
	margin: 0 6px 4px 0;
	padding: 1px 10px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	font-size: 13px;
	line-height: 1.6;
}
/* Batch H10 — the Factory & Trust band. Its OWN section below the spec
   sheet, not a fourth grid group: the top border is the separator the
   ruling asked for, and the band prints nothing at all when every trust
   field is off. Rows reuse the sheet's label/value grid vocabulary. */
.sf-fdetail-trust {
	padding: 40px 0 48px;
	background-color: var(--wp--preset--color--card-white);
	border-top: 1px solid var(--wp--preset--color--border-light);
}
.sf-fdetail-trust__inner {
	max-width: var(--wp--style--global--content-size, 1200px);
	margin-inline: auto;
	padding-left: 38px;
	padding-right: 38px;
}
.sf-fdetail-trust__title {
	margin: 0 0 14px;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail-trust__list { margin: 0; }
.sf-fdetail-trust__row {
	display: grid;
	grid-template-columns: minmax(120px, max-content) minmax(0, 1fr);
	align-items: baseline;
	column-gap: 20px;
	padding: 12px 0;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sf-fdetail-trust__row:first-child { padding-top: 0; }
.sf-fdetail-trust__row:last-child { border-bottom: 0; }
.sf-fdetail-trust__term {
	font-size: 13px;
	font-weight: 600;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail-trust__value {
	margin: 0;
	font-size: 15px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}
@media (max-width: 1240px) {
	.sf-fdetail-trust__inner { padding-left: 38px; padding-right: 38px; }
}
@media (max-width: 768px) {
	.sf-fdetail-trust { padding: 32px 0 40px; }
	.sf-fdetail-trust__inner { padding-left: 20px; padding-right: 20px; }
	.sf-fdetail-trust__row {
		grid-template-columns: minmax(0, 1fr);
		row-gap: 2px;
	}
}
@media (max-width: 1240px) {
	.sf-fdetail-specs {
		padding-left: 38px;
		padding-right: 38px;
	}
}
/* 900, not 768: two columns of a 150px label plus a value stop fitting before
   the phone breakpoint does — at 768 the value column is under 300px and every
   ingredient chip already wraps twice. One column from here down, the three
   groups stacking under their headings. */
@media (max-width: 900px) {
	.sf-fdetail-specs__inner {
		grid-template-columns: minmax(0, 1fr);
		column-gap: 0;
	}
	/* The groups stack now, so they are one continuous list: every group's
	   last row keeps the rule its own column would have dropped, and each
	   heading after the first re-opens the space the previous group's last
	   row never had. */
	.sf-fdetail-specs__row--last { border-bottom: 1px solid var(--wp--preset--color--border-light); }
	.sf-fdetail-specs__group:last-child > .sf-fdetail-specs__row--last { border-bottom: 0; }
	.sf-fdetail-specs__group + .sf-fdetail-specs__group > .sf-fdetail-specs__gtitle { padding-top: 12px; }
}
@media (max-width: 768px) {
	.sf-fdetail-specs {
		padding-left: 20px;
		padding-right: 20px;
	}
	/* The label cannot hold a column beside its value at this width, so each
	   row becomes a labelled stack — the trade .sf-facts makes at 767. */
	.sf-fdetail-specs__row {
		grid-template-columns: minmax(0, 1fr);
		row-gap: 2px;
	}
}
/* 38a-iii. The configurator — batch H7d. Seven rows of the right column stopped
   being text and became choices: Flavor, Piece Weight, Pack Size, Suitable For,
   Life Stage, Container Type and Quantity & Pricing. The four rows that are not
   choices stayed in .sf-fdetail2__params directly below this band, so the two
   renderers sit one above the other exactly as they print.

   Every group prints the record's OWN value (`__meta`) above its options. That
   one line does three jobs: it is the no-JS answer, it is the dialog's fallback
   when the visitor ticks nothing, and it is why the options read as pills beside
   an answer rather than as a form that does not know the answer.

   The order of the blocks below is load-bearing the same way 38a-ii's is: base
   rule first, then the phone drawer. A step written above the rule it means to
   override is dead at every width — the mistake batch H7b shipped. */
.sf-fdetail-config {
	margin: 0 0 16px;
}
.sf-fdetail-config__group {
	padding: 10px 0;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sf-fdetail-config__group:first-child { padding-top: 0; }
.sf-fdetail-config__group:last-child { border-bottom: 0; padding-bottom: 0; }
.sf-fdetail-config__row {
	display: grid;
	grid-template-columns: minmax(104px, 38%) minmax(0, 1fr);
	align-items: baseline;
	column-gap: 14px;
	margin: 0 0 7px;
}
.sf-fdetail-config__label {
	font-size: 13px;
	font-weight: 600;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail-config__meta {
	font-size: 14px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}
.sf-fdetail-config__hint {
	grid-column: 2;
	margin-top: 1px;
	font-size: 12px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail-config__options {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}
/* The pills. The input is 1px and transparent rather than display:none,
   because a hidden input cannot take focus and these inputs ARE the widget:
   the keyboard path is Tab to an input, Space to toggle it, and the ring is
   drawn on the pill beside it. */
.sf-fdetail-config__opt {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	padding: 4px 11px 4px 9px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	font-size: 13px;
	line-height: 1.6;
	color: var(--wp--preset--color--text-primary);
	cursor: pointer;
	transition: border-color .15s ease, background-color .15s ease;
}
.sf-fdetail-config__opt:hover { border-color: var(--wp--preset--color--border-medium); }
.sf-fdetail-config__input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	opacity: 0;
}
.sf-fdetail-config__box {
	position: relative;
	flex: none;
	width: 13px;
	height: 13px;
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 3px;
}
/* Shape by input type. Cosmetic, so it degrades to a square where :has() is
   not supported — the pill still toggles, because that is the input's job. */
.sf-fdetail-config__opt:has(.sf-fdetail-config__input[type="radio"]) .sf-fdetail-config__box {
	border-radius: 999px;
}
.sf-fdetail-config__input:checked + .sf-fdetail-config__box {
	background-color: var(--wp--preset--color--brand-green);
	border-color: var(--wp--preset--color--brand-green);
}
.sf-fdetail-config__input:checked + .sf-fdetail-config__box::after {
	content: "";
	position: absolute;
	left: 3px;
	top: 4px;
	width: 5px;
	height: 2px;
	border-left: 2px solid #fff;
	border-bottom: 2px solid #fff;
	transform: rotate(-45deg);
}
.sf-fdetail-config__input:focus-visible + .sf-fdetail-config__box {
	outline: 2px solid var(--wp--preset--color--brand-green);
	outline-offset: 2px;
}
.sf-fdetail-config__opt.is-on {
	border-color: var(--wp--preset--color--brand-green);
	background-color: var(--wp--preset--color--bg-light);
}
.sf-fdetail-config__img {
	flex: none;
	width: 60px;
	height: 60px;
	object-fit: contain;
	border-radius: 8px;
	background-color: var(--wp--preset--color--card-white);
}
/* A container or shape the library has no picture for yet. Dashed, not
   broken: every attachment_id in both libraries ships at 0, so the picker's
   empty state is the state it will be in on the day this deploys. The name
   lives inside the box, centred — the placeholder is the label, so an
   empty slot reads as clearly as a filled one. */
.sf-fdetail-config__img--empty {
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px dashed var(--wp--preset--color--border-medium);
	background-color: transparent;
}
.sf-fdetail-config__empty-label {
	padding: 0 4px;
	font-size: 11px;
	font-weight: 500;
	line-height: 1.25;
	text-align: center;
	color: var(--wp--preset--color--text-secondary);
	overflow-wrap: break-word;
}
.sf-fdetail-config__note {
	font-size: 12px;
	color: var(--wp--preset--color--text-secondary);
}
/* An option that carries a thumbnail is a card, not a pill: a 46px image inside
   a 999px radius reads as a banner. */
.sf-fdetail-config__opt:has(.sf-fdetail-config__img) {
	flex-direction: column;
	align-items: flex-start;
	gap: 4px;
	min-width: 92px;
	padding: 8px 10px;
	border-radius: 12px;
}

/* Batch H8a — the two image pickers slide sideways instead of wrapping.
   Eight shapes in a 472px column is two wrapped rows, and a second row is a
   shape a buyer has to go looking for; one row that slides keeps every shape
   the same distance from the finger, and the half-tile at the edge is what
   says the row continues. Nothing is hidden by it: this is a real scroll
   container, so a visitor without the script still swipes it (touch) or
   drags its track (pointer) — the arrows on a pointer device are an
   improvement on that, not the only way through. */
.sf-fdetail-config__group[data-sf-config-group="shape"] .sf-fdetail-config__options,
.sf-fdetail-config__group[data-sf-config-group="container"] .sf-fdetail-config__options {
	flex-wrap: nowrap;
	overflow-x: auto;
	scroll-snap-type: x proximity;
	-webkit-overflow-scrolling: touch;
}
.sf-fdetail-config__group[data-sf-config-group="shape"] .sf-fdetail-config__opt,
.sf-fdetail-config__group[data-sf-config-group="container"] .sf-fdetail-config__opt {
	scroll-snap-align: start;
}
/* Built by config.js, so a page with no script has no arrows to press and no
   wrapper either. --scrolls is set only when the row actually overflows. */
.sf-fdetail-config__rail {
	position: relative;
}
.sf-fdetail-config__arrow {
	display: none;
	position: absolute;
	top: 50%;
	z-index: 1;
	width: 28px;
	height: 28px;
	margin-top: -14px;
	padding: 0;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	background-color: var(--wp--preset--color--card-white);
	color: var(--wp--preset--color--text-primary);
	font-size: 13px;
	line-height: 1;
	cursor: pointer;
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
/* Outside the column, in the band's own gap: an arrow sitting ON the row
   covers the first shape's picture, which is the one thing the row is for. */
.sf-fdetail-config__arrow--prev { left: -22px; }
.sf-fdetail-config__arrow--next { right: -22px; }
.sf-fdetail-config__arrow[disabled] {
	opacity: 0.35;
	cursor: default;
}
@media (min-width: 769px) {
	.sf-fdetail-config__rail--scrolls .sf-fdetail-config__arrow {
		display: inline-flex;
	}
	/* A pointer device has the arrows, so the track is redundant; a touch
	   device keeps it. Same container either way. */
	.sf-fdetail-config__rail--scrolls .sf-fdetail-config__options {
		scrollbar-width: none;
	}
	.sf-fdetail-config__rail--scrolls .sf-fdetail-config__options::-webkit-scrollbar {
		display: none;
	}
}

/* Batch H8a — the box a Custom pick reveals. It is a SIBLING of the option
   list and ships `hidden`, so it takes no space until the pick is made:
   config.js flips the attribute and moves the focus. A visitor without the
   script never sees it, and the Custom pick then means what it says — the
   word "Custom", which is what the record's own options would have posted. */
.sf-fdetail-config__custom {
	margin-top: 8px;
}
.sf-fdetail-config__custom[hidden] {
	display: none;
}
.sf-fdetail-config__custom-input {
	width: 100%;
	max-width: 320px;
	padding: 8px 12px;
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 8px;
	background-color: var(--wp--preset--color--card-white);
	font-family: inherit;
	font-size: 14px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-primary);
}
.sf-fdetail-config__custom-input:focus-visible {
	outline: 2px solid var(--wp--preset--color--brand-green);
	outline-offset: 1px;
}
.sf-fdetail-config__summary {
	margin: 10px 0 0;
	padding: 8px 12px;
	border-radius: 8px;
	background-color: var(--wp--preset--color--bg-light);
	font-size: 13px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}
.sf-fdetail-config__summary[hidden],
.sf-fdetail-config__note[hidden] { display: none; }
/* H7g compacting — the value-preview line only matters when no script can
   speak for the group. config.js sets --js on the root; with it, each
   group's current value is the selection itself (or nothing chosen yet,
   which the summary line says honestly), so the static echo goes. Without
   the script the meta row is the only place the record's own value
   appears — which is why this is a JS-gated rule and not an unbinding. */
.sf-fdetail-config--js .sf-fdetail-config__meta { display: none; }
/* H7g — the main image area's preview mode. Absolute over the stage (the
   stage is position:relative), so the gallery's own box never moves: the
   fixed 1:1 stage and its layout are untouched, the layer only covers it
   while a Shape or Container choice is being previewed. */
.sf-gallery__preview {
	position: absolute;
	inset: 0;
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
	background-color: var(--wp--preset--color--card-white);
}
.sf-gallery__preview[hidden] { display: none; }
.sf-gallery__preview img {
	max-width: 100%;
	max-height: 100%;
	width: auto;
	height: auto;
	object-fit: contain;
}
/* Both are created by config.js, so a visitor without the script is never shown
   a button that opens nothing. */
.sf-fdetail-config__open,
.sf-fdetail-config__close { display: none; }
body.sf-config-lock { overflow: hidden; }
@media (max-width: 768px) {
	.sf-fdetail-config__open {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 100%;
		padding: 11px 16px;
		border: 1px solid var(--wp--preset--color--brand-green);
		border-radius: 999px;
		background-color: var(--wp--preset--color--card-white);
		font-size: 14px;
		font-weight: 600;
		color: var(--wp--preset--color--brand-green);
		cursor: pointer;
	}
	/* --js is set by config.js. Without it the list stays inline: the drawer
	   exists only where something can open and close it. */
	.sf-fdetail-config--js .sf-fdetail-config__list { display: none; }
	/* H7g — the library thumbnails step down one size on a phone. */
	.sf-fdetail-config__img {
		width: 48px;
		height: 48px;
	}
	/* The drawer is a full-screen overlay, so it has to outrank every fixed
	   layer on the page -- and the page is not only ours. At z-index 96
	   (batch H7d's first value) three things stood on it, measured at 420px
	   with the drawer open: the sticky header (999), the cookie banner
	   (9999), and the TranslatePress floating language switcher, which the
	   plugin pins at 99999 in its own stylesheet and which this theme cannot
	   re-order. The drawer's only exit -- the Done button at y840-880 -- was
	   under the banner for a first-time visitor: all 19 points along its own
	   centre line hit the banner instead, so the drawer could not be closed
	   by tapping. The value below is one step above the top of the stack,
	   which is the lightbox's 99999. It costs nothing: an image cannot be
	   opened from under an opaque full-screen drawer, so the two are never
	   up together. While the drawer is up the banner and the switcher are
	   covered, which is the point -- the drawer is the active layer then, and
	   it brings its own exit. The browser pass asserts the ordering against
	   the switcher's measured z, so a plugin that moves it says so. */
	.sf-fdetail-config--open .sf-fdetail-config__list {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 100000;
		margin: 0;
		padding: 20px 20px 96px;
		overflow-y: auto;
		background-color: var(--wp--preset--color--card-white);
		-webkit-overflow-scrolling: touch;
	}
	/* 97 is not a second layer and is not stale: config.js appends the exit
	   INSIDE the list, so the list's own stacking context (above) already
	   paints the button over the drawer's content. This number only orders it
	   against what is inside that context, so it stays small while the list's
	   is huge -- raising it would imply the two were siblings. */
	.sf-fdetail-config--open .sf-fdetail-config__close {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		position: fixed;
		left: 20px;
		right: 20px;
		bottom: 20px;
		z-index: 97;
		padding: 12px 16px;
		border: 0;
		border-radius: 999px;
		background-color: var(--wp--preset--color--brand-green);
		font-size: 14px;
		font-weight: 600;
		color: #fff;
		cursor: pointer;
	}
}

/* <=767px: the label column cannot hold "Production lead time" next to a
   value, so each field stacks as a labelled row. Same trade .sf-spectable
   makes — display:block drops the table semantics for assistive tech, and
   nothing here needs the hidden header row that pattern uses, because these
   labels are real <th scope="row"> cells and stack above their own value. */
@media (max-width: 767px) {
	.sf-facts {
		padding-top: 24px !important;
		padding-bottom: 28px !important;
	}
}

/* F1 — minimal core-facts row, between the hero and #formulas on the eight
   dosage pages. Three fields on one line: MOQ, lead time, certifications.
   Batch 2D-E deleted the bands that used to carry these facts; this row is
   the one-line replacement, and it is also machine-readable: every value
   span carries data-label, and sinofresh_formula_spec_cell() scopes its
   lookup to the .sf-facts-mini block to feed the formula-detail hero meta —
   the page stays the single source of truth.

   No heading on purpose: toc-nav.js builds one dot per h2, and the dot rail
   settled at 6 in 2D-E; this row must not move it.

   Voice copied from .sf-spectable / .sf-facts: hairline top and bottom
   rules, no vertical rules, 11px letterspaced muted labels, ink values.
   A new class group rather than a reuse of .sf-facts: separate names stop an
   edit to one band from silently restyling the other. No padding-top/bottom
   declaration here either — the vertical rhythm is the block markup's blank
   lines plus core's constrained-layout child margin, exactly the seam
   2D-E's gates measured; a class rule here would fight that arithmetic.
   Selectors stay inside the .sf-facts-mini namespace. ------------------ */
.sf-facts-mini {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: 10px 40px;
	border-top: 1px solid var(--wp--preset--color--border-light);
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sf-facts-mini__item {
	display: flex;
	align-items: baseline;
	gap: 10px;
	min-width: 0;
}
.sf-facts-mini__label {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
	white-space: nowrap;
}
.sf-facts-mini__value {
	font-size: 14px;
	line-height: 1.5;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--text-primary);
}
/* <=767px: three fields on one line cannot hold "FDA, cGMP, ISO 9001, FSSC
   22000, HACCP, BRC", so the row stacks — label above its own value, same
   trade .sf-facts makes. No !important needed: nothing here overrides a
   core declaration, the flex direction is this namespace's own. */
@media (max-width: 767px) {
	.sf-facts-mini {
		flex-direction: column;
		gap: 0;
	}
	.sf-facts-mini__item {
		flex-direction: column;
		align-items: flex-start;
		gap: 2px;
		padding: 10px 0;
		border-bottom: 1px solid var(--wp--preset--color--border-light);
	}
	.sf-facts-mini__item:last-child {
		border-bottom: 0;
	}
}

/* ===========================================================================
   38. Formula detail band, second revision — .sf-fdetail2 (H2a, 2.10.55)

   Replaces batch G's .sf-fdetail-media on single-sf_formula.html: the media
   column on the left (a vertical thumbnail rail beside one main frame) and
   the parameters column on the right (the ten rows [sf_formula_params]
   emits). Batch G's own rules were deleted in batch H6 (2.10.61) once no
   template referenced them, so this band is the only one the detail page
   carries.

   Mobile-first, at the four widths the brief pins down:

       <=480      one column, thumbnails in a row below the frame, 56px
       481-768    one column, thumbnails in a row below the frame, 84px
       769-1024   two columns, 32px gap
       >=1101     two columns, 48px gap, media = 72px rail + main frame

   Same two house rules batch G held itself to: no :has() anywhere (this
   site must not depend on a selector old Android/iOS WebViews lack), and no
   !important (the markup is plain <figure>/<dl>/<table>, not a core block,
   so nothing here has to out-rank a core sheet).

   The thumbnail strip is built by formula-gallery.js out of the frames. With
   no JS there is no strip at all — the grid area is simply empty, the main
   photo stands alone and nothing is left behind, which is the no-JS contract
   the gallery already had.
   ------------------------------------------------------------------------- */
.sf-fdetail2__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 32px;
	align-items: start;
}
/* core gives every .is-layout-flow child after the first a 24px block-gap
   margin, which in a grid row would drop the parameters column below the
   media one; the grid gap is what spaces these two. */
.sf-fdetail2__inner > * {
	margin-block-start: 0;
}
.sf-fdetail2__media,
.sf-fdetail2__side {
	min-width: 0;
}

/* The gallery's inner wrapper is a grid with named areas, so the strip can
   move to the stage's left at >=1101 without any DOM change and without the
   script having to know which of the two layouts it is running in. The third
   area is the [Photos][Video] switch (batch H7a), and it holds the row the
   heading had: the same slot, directly under the main photo. */
.sf-fdetail2__media .sf-gallery__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	grid-template-areas:
		"stage"
		"tabs"
		"thumbs";
	gap: 16px;
	align-items: start;
}
/* .sf-gallery__stage was written for a full-width band: 680px wide and
   auto-centred, which inside a column is a narrow box floating in the middle
   of its own space. It fills the column here; the 1:1 aspect-ratio is kept,
   so the height arithmetic is exactly what it was. */
.sf-fdetail2__media .sf-gallery__stage {
	grid-area: stage;
	max-width: none;
	margin: 0;
}
/* The strip scrolls rather than shrinking at every width where it is a row:
   at 84px tiles a six-frame record is wider than the media column long before
   the phone breakpoint, and a sliver of the next tile staying in view is what
   makes the strip read as swipeable. */
.sf-fdetail2__media .sf-gallery__thumbs {
	grid-area: thumbs;
	justify-content: flex-start;
	margin-top: 0;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-padding-left: 4px;
	-webkit-overflow-scrolling: touch;
}
.sf-fdetail2__media .sf-gallery__thumb {
	flex: 0 0 84px;
	/* This theme has no global box-sizing reset — each component declares its
	   own, as .sf-tour-card and .sf-cookie-banner__btn do. Here it matters:
	   at >=1101 the tile sits in a 72px grid track, and the 1px border on each
	   side would otherwise make the tile 74px and push it into the rail gap. */
	box-sizing: border-box;
	width: 84px;
	height: 84px;
	scroll-snap-align: start;
}
@media (max-width: 480px) {
	.sf-fdetail2__media .sf-gallery__thumb {
		flex-basis: 56px;
		width: 56px;
		height: 56px;
	}
}

/* ---- the video frame ------------------------------------------------------

   A facade, not an embed: the frame ships the poster and a real <button>,
   and formula-gallery.js injects the player only once it is asked for. With
   no JS the poster and the button are all there is — and because the control
   is a <button> and not a styled link, pressing it goes nowhere instead of
   navigating off to youtube.com. -------------------------------------------- */
.sf-gallery__play {
	display: block;
	position: relative;
	width: 100%;
	height: 100%;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
}
.sf-gallery__play img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}
.sf-gallery__play-icon {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 64px;
	height: 64px;
	margin: -32px 0 0 -32px;
	border-radius: 999px;
	background: rgba(0, 0, 0, 0.55);
	transition: background-color 0.2s ease;
}
/* The triangle is drawn with borders: no icon font, no extra request. */
.sf-gallery__play-icon::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	margin: -11px 0 0 -6px;
	border-style: solid;
	border-width: 11px 0 11px 18px;
	border-color: transparent transparent transparent #fff;
}
.sf-gallery__play:hover .sf-gallery__play-icon,
.sf-gallery__play:focus-visible .sf-gallery__play-icon {
	background: rgba(0, 0, 0, 0.75);
}
.sf-gallery__play:focus-visible {
	outline: 2px solid var(--wp--preset--color--card-white);
	outline-offset: -6px;
}
/* The button's own name for assistive tech; the circle above is the visible
   affordance, so the words do not need to sit on the poster. */
.sf-gallery__play-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}
/* The player, once injected, has to fill the same 1:1 box the poster did. */
.sf-gallery__embed,
.sf-gallery__embed iframe {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
}
/* ---- the [Photos][Video] switch ------------------------------------------
   Batch H7a. Three things about it are load-bearing:

   1. It ships hidden. `.sf-gallery--js` is put on the band by
      formula-gallery.js, so the bar has no styling at all until a script is
      there to drive it — a no-JS visitor sees the photo area and no dead
      controls, the same contract the strip already kept. The class is added
      before the strip is built, so the two appear together.
   2. The underline is a pseudo-element on the active tab rather than a
      border, because a border cannot be transitioned without moving the text
      it belongs to. It sits at the bottom of 10px of padding, which is the
      room it needs and is the same on both labels.
   3. `--accent` (#1B4D3E), not `--brand-green` (#5AB735): the accent token is
      what the strip's selected tile is drawn with, so the switch and the
      strip read as one control. The brand green is the logo's; putting it
      here would give the band two greens.

   The strip is hidden in video mode as well (.sf-gallery--video): a photo
   picker with no photo on screen is an offer the visitor cannot act on. */
.sf-gallery__tabs {
	display: none;
	justify-content: center;
	gap: 28px;
}
.sf-fdetail2__media .sf-gallery__tabs {
	grid-area: tabs;
}
.sf-gallery--js .sf-gallery__tabs {
	display: flex;
}
.sf-gallery--video .sf-gallery__thumbs {
	display: none;
}
.sf-gallery__tab {
	position: relative;
	margin: 0;
	padding: 4px 2px 10px;
	border: 0;
	background: none;
	-webkit-appearance: none;
	appearance: none;
	font-family: inherit;
	font-size: 14px;
	font-weight: 600;
	line-height: 1.4;
	color: var(--wp--preset--color--text-secondary);
	cursor: pointer;
	transition: color 0.15s ease;
}
.sf-gallery__tab::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 2px;
	background-color: var(--wp--preset--color--accent);
	opacity: 0;
	transition: opacity 0.15s ease;
}
.sf-gallery__tab:hover {
	color: var(--wp--preset--color--accent);
}
.sf-gallery__tab:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
}
.sf-gallery__tab.is-active {
	color: var(--wp--preset--color--accent);
}
.sf-gallery__tab.is-active::after {
	opacity: 1;
}

/* ---- the parameters column ----------------------------------------------- */
/* Batch H5-0: the product name, and the page's only h1. It used to be the
   hero's, above the gallery band; the brief puts it here, at the top of the
   column beside the frames, which is also where it is the first thing read in
   the right-hand half of the band.

   Batch H7b: 28px -> 32px, line-height 1.2 -> 1.3 and margin 16px -> 24px,
   plus a 26px step on a phone. The column title is the page's h1 and now reads
   as one: a step below the hero's clamp(30-44px) was the right relation when
   the same string was set at 44px a screen above it, but this is the name the
   page is about, and at 28px it read as a caption to the frames. The margin
   follows the type — 32px of ink needs more air under it than 28px did — and
   the phone step keeps the two-line names ("Wild Alaskan Salmon Oil") off the
   480px edge. */
.sf-fdetail2__title {
	margin: 0 0 24px;
	font-size: 32px;
	font-weight: 700;
	line-height: 1.3;
	letter-spacing: -0.01em;
	color: var(--wp--preset--color--text-primary);
}
/* The phone step. It has to sit BELOW the rule above, not in the 480px block
   that handles this band's thumbnails: a media query adds no specificity, so
   with the base rule later in the file the base rule wins at every width and
   the step is silently dead. The browser pass caught this — 420px still
   measured 32px — and the gate now asserts the two rules' order, not just their
   contents. 32px was sized for the two-column band; at 480px the column is the
   whole width and the longest names ("Wild Alaskan Salmon Oil") still wrap to
   two lines, so the step is for the wrap, not the measure. */
@media (max-width: 480px) {
	.sf-fdetail2__title {
		font-size: 26px;
	}
}
.sf-fdetail2__intro {
	margin: 0 0 24px;
	font-size: 15px;
	line-height: 1.6;
	color: var(--wp--preset--color--text-primary);
}
/* Two columns of a grid, not a table: the terms size to their own text, so
   every value starts on one x whatever the longest term is. Voice copied from
   .sf-facts-mini — 11px letterspaced muted label, ink value. */
.sf-fdetail2__params {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: 12px 16px;
	margin: 0 0 28px;
}
.sf-fdetail2__term {
	margin: 0;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
	white-space: nowrap;
}
.sf-fdetail2__value {
	min-width: 0;
	margin: 0;
	font-size: 14px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}
/* Multi-select rows (Flavor, Suitable For) render one chip per value. */
.sf-fdetail2__chip {
	display: inline-block;
	margin: 0 6px 6px 0;
	padding: 2px 10px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	background: var(--wp--preset--color--card-white);
	font-size: 12px;
	letter-spacing: 0.02em;
	color: var(--wp--preset--color--text-primary);
}
/* Quantity & Pricing. Two columns, the second flush right, so a tier ladder
   reads as one ladder rather than two loose lists. */
.sf-fdetail2__tiers {
	width: 100%;
	border-collapse: collapse;
	font-size: 13px;
}
.sf-fdetail2__tiers th {
	padding: 0 0 6px;
	border-bottom: 1px solid var(--wp--preset--color--border-medium);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	text-align: left;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail2__tiers td {
	padding: 6px 0;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}
.sf-fdetail2__tiers tr:last-child td {
	border-bottom: 0;
}
.sf-fdetail2__tiers th:last-child,
.sf-fdetail2__tier-price {
	text-align: right;
}
/* Certifications. sf_render_cert_badges() emits a bare .sf-cert-badge, and
   the only rule styling that class today is scoped to .sf-topbar-badges — so
   the band supplies its own pills rather than editing the header's rule. */
.sf-fdetail2__value .sf-cert-badge {
	display: inline-block;
	margin: 0 6px 6px 0;
	padding: 2px 10px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	background: var(--wp--preset--color--card-white);
	font-size: 12px;
	font-weight: 600;
	text-decoration: none;
	color: var(--wp--preset--color--text-primary);
}
.sf-fdetail2__value a.sf-cert-badge:hover,
.sf-fdetail2__value a.sf-cert-badge:focus-visible {
	border-color: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--accent);
}
/* Same orange as the page's closing "Request a Quote": two call-to-action
   buttons on one page that looked different would read as two different
   kinds of action. */
.sf-fdetail2__cta {
	display: inline-block;
	padding: 12px 24px;
	border-radius: 6px;
	background: var(--wp--preset--color--cta);
	color: var(--wp--preset--color--card-white);
	font-size: 15px;
	font-weight: 600;
	text-decoration: none;
	transition: background-color 0.2s ease;
}
.sf-fdetail2__cta:hover,
.sf-fdetail2__cta:focus-visible {
	background: var(--wp--preset--color--cta-hover);
	color: var(--wp--preset--color--card-white);
}

/* From 769px the two columns sit side by side (the site's main breakpoint is
   768px, the same one batch G used). The 3fr/2fr split is kept so the media
   column stays the wider one at every width above the phone. */
@media (min-width: 769px) {
	.sf-fdetail2__inner {
		grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
		gap: 32px;
	}
	/* Batch H8a — the main image keeps the reader's place while the parameters
	   column scrolls past it. The two conditions were already met, which is why
	   this is four lines and not a restructure: the band's grid is
	   `align-items: start` (so the media column is content-height rather than
	   stretched to the row) and nothing between here and the root clips — the
	   horizontal overflow guard at the top of this file uses `overflow-x: clip`
	   and says in its own comment that it stays out of position:sticky's way.
	   100px clears the 61px bar the header becomes past scrollY 100.
	   Desktop only: below this width the columns are a single stack, nothing
	   scrolls past anything, and a stuck image would only eat the screen. */
	.sf-fdetail2__media {
		position: sticky;
		top: 100px;
		align-self: start;
	}
}
/* From 1101px there is room for the rail: the strip turns into a 72px column
   to the left of the frame, which is the layout the desktop reference shows.
   1101 rather than 1100 because the site's own .sf-toc hides at <=1100, and
   the two bands should not change shape at visibly different widths. */
@media (min-width: 1101px) {
	.sf-fdetail2__inner {
		gap: 48px;
	}
	.sf-fdetail2__media .sf-gallery__inner {
		grid-template-columns: 72px minmax(0, 1fr);
		/* The switch takes the stage's own column, so "centred" means centred
		   under the main photo — not centred on the band, which would land it
		   half a rail to the left of the frame it labels. */
		grid-template-areas:
			"thumbs stage"
			".      tabs";
		gap: 16px 12px;
	}
	/* Column, not row: and the scroll box goes away with it, because a rail
	   that scrolls sideways would hide tiles on a desktop. */
	.sf-fdetail2__media .sf-gallery__thumbs {
		flex-direction: column;
		overflow: visible;
	}
	/* Batch H18c — a seventh tile only fits if the rail tightens. Seven 72px
	   tiles on the base 12px gap come to 584px against a 607px main photo, so
	   at 1440 it is inside by 23px but at 1237 it is already 82px over; at 8px
	   the same seven come to 560px and sit inside the photo from ~1198px up.
	   Measured 2026-09-30, ten viewports, tools/h18c_gallery_capacity_probe.js.

	   Scoped with :has() to records that actually carry a seventh tile, so the
	   four-tile rails every record on the site ships today keep the 12px
	   rhythm they have always had — the same "nothing moves until ops fills
	   it" contract batch H18 kept. Where :has() is unsupported the selector
	   simply does not match and the rail stays at 12px: a few pixels taller,
	   never wrong. (This rule needs no html.no-has fallback for that reason —
	   it degrades to today's layout by itself.) */
	.sf-fdetail2__media .sf-gallery__thumbs:has(.sf-gallery__thumb:nth-child(7)) {
		gap: 8px;
	}
	.sf-fdetail2__media .sf-gallery__thumb {
		flex-basis: 72px;
		width: 72px;
		height: 72px;
		scroll-snap-align: none;
	}
}

/* === 59. Explore more dosage forms band (dosage pages, H2b1) ==============
   Cross-sell band: a pill per dosage form plus an outline CTA to the full
   product list. Batch H2b1 moved it out of the configurator's options column
   into its own section on the eight dosage pages — between the Standard
   Formulas band and How We Work — so it spans the full content column now
   instead of 65% of it. That is why the pills are back at the site's 14px chip
   scale: the "as tight as possible so all eight fit one row" sizing only ever
   existed to survive the narrow column, and the 6px radius stays because these
   are navigation tags, not multi-select chips. */
.sf-explore {
	margin-top: 0;
	padding: 32px;
	background-color: #F5F7F2;
	border-radius: 8px;
}

.sf-explore__title {
	margin: 0 0 8px;
	font-size: 22px;
	font-weight: 700;
	color: #1B4D3E;
}

/* Outline pills generated by the [sf_explore_chips] shortcode. Hairline
   border and 6px radius, one notch tighter than the option buttons, and back
   at the 14px chip scale now that the band is full width. */
.sf-explore__chips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 0 0 20px;
}

.sf-explore__chip {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	min-height: 40px;
	box-sizing: border-box;
	padding: 10px 16px;
	background-color: var(--wp--preset--color--card-white);
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 6px;
	font-size: 14px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
	white-space: nowrap;
	transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

.sf-explore__chip:hover {
	border-color: var(--wp--preset--color--accent);
}

.sf-explore__chip:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
}

/* Current page: filled ink-green, mirroring the CTA's hover fill. */
.sf-explore__chip.is-current {
	border-color: var(--wp--preset--color--primary);
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
	font-weight: 600;
}

/* Outline CTA mirroring the "View All Articles" sf-btn-outline style. */
.sf-explore__btn {
	display: inline-block;
	padding: 12px 24px;
	border: 2px solid #1B4D3E;
	border-radius: 6px;
	background-color: transparent;
	color: #1B4D3E;
	font-weight: 600;
	font-size: 15px;
	line-height: 1.4;
	text-decoration: none;
	transition: background-color 0.15s ease, color 0.15s ease;
}

.sf-explore__btn:hover {
	background-color: #1B4D3E;
	color: #ffffff;
}

@media (max-width: 768px) {
	.sf-explore {
		padding: 24px;
	}

	.sf-explore__title {
		font-size: 20px;
	}

	/* One swipeable row: 8 wrapping pills would push the CTA far down. */
	.sf-explore__chips {
		flex-wrap: nowrap;
		overflow-x: auto;
		overscroll-behavior-x: contain;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		padding-bottom: 2px;
	}

	.sf-explore__chips::-webkit-scrollbar {
		display: none;
	}

	.sf-explore__btn {
		display: block;
		width: 100%;
		box-sizing: border-box;
		min-height: 44px;
		padding: 12px 18px;
		text-align: center;
	}
}

@media (prefers-reduced-motion: reduce) {
	.sf-explore__btn,
	.sf-explore__chip {
		transition: none;
	}
}

/* Batch H3 — "Detailed content" and "How Sampling Works", the two bands added
   to a formula detail page, between the Ingredients & composition band and the
   related-formulas grid.

   Both are emitted whole by their shortcode and styled here for the reason the
   two H2a bands above are (see the note above .sf-fdetail-actives): the content
   band has to be able to return zero bytes when its fields are still empty, so
   its section, its heading and its background cannot live in the block template.

   Background is card-white on both, continuing the surface that starts at the
   Specification cards. The brief's white/bg-light stripe was dropped: style.css
   8103 keeps that run deliberately one colour, and striping it would slice a
   surface that is drawn as a single piece. The only colour changes on the page
   stay where they already were — bg-light at the related-formulas grid, green at
   the closing CTA. ---------------------------------------------------- */

.sf-fdetail-content,
.sf-sampling {
	padding: 48px 0;
	background-color: var(--wp--preset--color--card-white);
}
.sf-fdetail-content__inner,
.sf-sampling__inner {
	max-width: var(--wp--style--global--content-size, 1200px);
	margin-inline: auto;
}
/* The brief's 48px between blocks. Only between: the first block carries no
   top margin, so the band's own 48px padding is the whole space above it. */
.sf-fdetail-content__block + .sf-fdetail-content__block { margin-top: 48px; }
.sf-fdetail-content__heading { margin: 0 0 14px; }
/* "Packaging & Specifications" is a sibling of the three prose blocks, so it
   keeps the h2 element — and its place in the outline — and takes the 20px h3
   size instead. Smaller by weight, not by level. */
.sf-fdetail-content__subtitle {
	margin: 0 0 14px;
	font-size: 20px;
	font-weight: 600;
	line-height: 1.3;
}
.sf-fdetail-content__prose {
	margin: 0;
	font-size: 16px;
	line-height: 1.6;
}
.sf-fdetail-content__list {
	margin: 0;
	padding-left: 20px;
	font-size: 16px;
	line-height: 1.6;
}
.sf-fdetail-content__item + .sf-fdetail-content__item { margin-top: 6px; }

.sf-fdetail-content__specs { display: grid; gap: 28px; }
.sf-fdetail-content__label {
	margin: 0 0 10px;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
/* Outline pills, the .sf-actives__pill vocabulary — deliberately NOT
   .sf-fchip, which is a filter button with a cursor and a hover state, the
   wrong affordance on a static value. */
.sf-fdetail-content__chips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 0;
	padding: 0;
	list-style: none;
}
.sf-fdetail-content__chip {
	padding: 3px 10px;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 999px;
	font-size: 13px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}
/* Collapsed borders with a hairline under each row, so a carton row and a
   fact row read as the same kind of record.
   A third column is the widest cell, so the trailing right padding is dropped
   or the table would end short of the text above it. */
.sf-fdetail-content__cartons {
	width: 100%;
	margin: 0;
	border-collapse: collapse;
	font-size: 14px;
	text-align: left;
}
.sf-fdetail-content__cartons th {
	padding: 0 24px 9px 0;
	border-bottom: 1px solid var(--wp--preset--color--border-medium);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail-content__cartons td {
	padding: 10px 24px 10px 0;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
	color: var(--wp--preset--color--text-primary);
}
.sf-fdetail-content__cartons th:last-child,
.sf-fdetail-content__cartons td:last-child { padding-right: 0; }

/* --- How Sampling Works -------------------------------------------------
   Four steps across one row above 768px, stacked below. The type is
   .sf-pal__step's (17px title, 14px/1.65 muted copy) so the site's two
   four-step bands speak with one voice; the marker is deliberately NOT that
   band's 34px outline dot. The brief asks for a filled 44px one, and a filled
   marker on a band whose steps are numbered rather than joined by a connector
   reads as a sequence — .sf-pal draws a flow, with a chevron on the wire.

   Forest, not brand-green: #5AB735 carries white text at 2.7:1 and fails AA at
   this size (style.css 1892 records the same measurement for the other pairing),
   while the Forest token clears it with room to spare. The numeral is real text
   in the DOM, not a CSS counter, so it survives a stylesheet failure and is
   announced with its step. */
.sf-sampling__title { margin: 0; }
.sf-sampling__steps {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 32px;
	margin: 32px 0 0;
	padding: 0;
	list-style: none;
}
.sf-sampling__num {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: 44px;
	height: 44px;
	border-radius: 50%;
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
	font-size: 16px;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}
.sf-sampling__step-title {
	margin: 16px 0 8px;
	font-size: 17px;
}
.sf-sampling__desc {
	margin: 0;
	font-size: 14px;
	line-height: 1.65;
	color: var(--wp--preset--color--text-secondary);
}
/* The one number the band states about time. Step 4's copy leaves the
   parenthetical out so this line is the only place it appears. */
.sf-sampling__note {
	margin: 32px 0 0;
	font-size: 14px;
	font-weight: 700;
	color: var(--wp--preset--color--primary);
}

@media (max-width: 1240px) {
	.sf-fdetail-content,
	.sf-sampling {
		padding-left: 38px;
		padding-right: 38px;
	}
}
@media (max-width: 768px) {
	.sf-fdetail-content,
	.sf-sampling {
		padding-left: 20px;
		padding-right: 20px;
	}
	.sf-fdetail-content__block + .sf-fdetail-content__block { margin-top: 36px; }
	.sf-sampling__steps {
		grid-template-columns: minmax(0, 1fr);
		gap: 24px;
	}
}

/* ==========================================================================
   Batch H4 — Send Inquiry: the capsule and the dialog.
   ==========================================================================
   The capsule is the first child of .sf-float-stack (parts/footer.html), which
   is bottom-anchored at 3913, so the first child is the top one and the three
   buttons below it keep the offsets appendix A.5 #7 measured (100 / 24 / 268
   / 16). The shortcode that emits it returns nothing off a detail page, so
   this whole section only has a subject on the 42 pages that carry the
   parameter band.

   The dialog follows the certificate dialog's shape rather than the basket
   drawer's (deleted in batch H7f): the outer element is the backdrop AND the
   centring box (flex, inset 0), the panel inside it is what fades and rises.
   One backdrop definition, two components. */

.sf-float-btn--inquiry {
	/* `.sf-float-stack` owns the alignment (align-items:flex-end at 3913), so
	   the capsule only has to measure itself: `width:auto` against the stack's
	   inherited `align-items:stretch` would otherwise pull it to the column's
	   full width, and the column's width is itself set by its widest child. */
	width: auto;
	min-width: 52px;
	height: 52px;
	padding: 0 20px 0 18px;
	border-radius: 26px;
	gap: 8px;
	background-color: var(--wp--preset--color--cta);
	font-size: 14px;
	font-weight: 600;
	line-height: 1;
	white-space: nowrap;
	text-decoration: none;
}

.sf-float-btn--inquiry:hover {
	background-color: var(--wp--preset--color--cta-hover);
}

/* The capsule ships with `hidden` on it (sinofresh_inquiry_button() sets the
   attribute, the script removes it) — an author display value would otherwise
   beat the UA sheet's [hidden] rule. */
.sf-float-btn--inquiry[hidden] {
	display: none;
}

.sf-float-btn__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
}

/* The brief's "slides in from the bottom, 300ms". No fill-mode on purpose:
   `forwards` would keep the final keyframe's transform applied and the
   .sf-float-btn hover scale would stop working. */
.sf-float-btn--inquiry.is-visible {
	animation: sf-inquiry-in 300ms ease-out;
}

@keyframes sf-inquiry-in {
	from {
		opacity: 0;
		transform: translateY(12px);
	}
}

/* --- The dialog ----------------------------------------------------------- */
.sf-inquiry-modal {
	display: flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	/* A top-level block would otherwise pick up .wp-site-blocks' 24px gap,
	   which on an inset:0 fixed box does not push it down so much as shrink
	   it — the backdrop stopped short of the top and the header stayed
	   clickable above it (the same defect section 50 records). */
	margin: 0;
	padding: 24px;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	/* One generous step above the backdrop's 10000: the dialog is the active
	   layer whenever it is up. */
	z-index: 10010;
}

.sf-inquiry-modal__panel {
	position: relative;
	box-sizing: border-box;
	width: 100%;
	max-width: 560px;
	max-height: calc(100vh - 48px);
	max-height: calc(100dvh - 48px);
	display: flex;
	flex-direction: column;
	background-color: var(--wp--preset--color--card-white);
	border-radius: 8px;
	box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
	transform: translateY(12px);
	transition: transform 0.3s ease;
}

.sf-inquiry-modal.is-open .sf-inquiry-modal__panel {
	transform: translateY(0);
}

.sf-inquiry-modal__head {
	position: relative;
	flex: 0 0 auto;
	padding: 24px 64px 0 24px;
}

.sf-inquiry-modal__title {
	margin: 0;
	font-size: 20px;
	font-weight: 700;
	line-height: 1.25;
	color: var(--wp--preset--color--text-primary);
}

.sf-inquiry-modal__lead {
	margin: 4px 0 0;
	font-size: 13px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-secondary);
}

/* 40px target inset from the corner, matching the certificate dialog's close
   control rather than padding a box around a 20px glyph. */
.sf-inquiry-modal__close {
	position: absolute;
	top: 16px;
	right: 16px;
	width: 40px;
	height: 40px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: none;
	border-radius: 6px;
	background-color: transparent;
	color: var(--wp--preset--color--text-secondary);
	font-size: 24px;
	line-height: 1;
	cursor: pointer;
}

.sf-inquiry-modal__close:hover {
	background-color: var(--wp--preset--color--bg-light);
	color: var(--wp--preset--color--text-primary);
}

.sf-inquiry-modal__body {
	flex: 1 1 auto;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	padding: 20px 24px 24px;
}

/* The two summary panels are the same surface: Mist, hairline, 6px. They are
   the "what we are talking about" pair and read as one block, which is why
   neither is given a band of its own. */
.sf-inquiry-modal__card {
	margin: 0 0 16px;
	padding: 14px 16px;
	background-color: var(--wp--preset--color--bg-light);
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 6px;
}

.sf-inquiry-modal__sub {
	margin: 0 0 10px;
	font-size: 12px;
	font-weight: 700;
	line-height: 1.2;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}

.sf-inquiry-modal__rows {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: 6px 16px;
	margin: 0;
}

.sf-inquiry-modal__term {
	font-size: 13px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}

.sf-inquiry-modal__value {
	margin: 0;
	font-size: 14px;
	font-weight: 600;
	line-height: 1.5;
	color: var(--wp--preset--color--text-primary);
}

.sf-inquiry-modal__steps {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px 16px;
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: sf-inquiry;
}

.sf-inquiry-modal__step {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: 13px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-primary);
}

/* Forest, not brand-green: #5AB735 carries white text at 2.7:1 and fails AA
   at this size — the same measurement the sampling band's marker records. */
.sf-inquiry-modal__num {
	flex: 0 0 auto;
	width: 22px;
	height: 22px;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--card-white);
	font-size: 12px;
	font-weight: 700;
	line-height: 1;
}

.sf-inquiry-modal__note {
	margin: 10px 0 0;
	font-size: 12px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-secondary);
}

/* --- The form ------------------------------------------------------------- */
.sf-inquiry-form__trap {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.sf-inquiry-form__grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 12px 16px;
}

.sf-inquiry-form__field {
	margin: 0;
}

.sf-inquiry-form__field--wide {
	grid-column: 1 / -1;
}

.sf-inquiry-form__field label {
	display: block;
	margin-bottom: 4px;
	font-size: 13px;
	font-weight: 600;
	line-height: 1.4;
	color: var(--wp--preset--color--text-primary);
}

.sf-inquiry-form__req {
	color: var(--wp--preset--color--cta);
}

.sf-inquiry-form__field input,
.sf-inquiry-form__field textarea {
	display: block;
	width: 100%;
	box-sizing: border-box;
	padding: 10px 12px;
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 6px;
	background-color: var(--wp--preset--color--card-white);
	color: var(--wp--preset--color--text-primary);
	font-family: inherit;
	font-size: 15px;
	line-height: 1.4;
}

.sf-inquiry-form__field textarea {
	resize: vertical;
	min-height: 76px;
}

.sf-inquiry-form__field input:focus,
.sf-inquiry-form__field textarea:focus {
	outline: 2px solid var(--wp--preset--color--cta);
	outline-offset: 1px;
	border-color: var(--wp--preset--color--cta);
}

.sf-inquiry-form__submit {
	display: block;
	width: 100%;
	margin-top: 16px;
	padding: 13px 20px;
	border: none;
	border-radius: 6px;
	background-color: var(--wp--preset--color--cta);
	color: var(--wp--preset--color--card-white);
	font-family: inherit;
	font-size: 15px;
	font-weight: 600;
	line-height: 1.2;
	cursor: pointer;
}

.sf-inquiry-form__submit:hover {
	background-color: var(--wp--preset--color--cta-hover);
}

.sf-inquiry-form__submit:disabled {
	opacity: 0.6;
	cursor: default;
}

.sf-inquiry-form__status {
	margin: 10px 0 0;
	min-height: 1.2em;
	font-size: 13px;
	line-height: 1.4;
	color: var(--wp--preset--color--cta);
}

.sf-inquiry-form[hidden],
.sf-inquiry-modal__success[hidden] {
	display: none;
}

.sf-inquiry-modal__success {
	padding: 8px 0 4px;
	text-align: center;
}

.sf-inquiry-modal__success-title {
	margin: 0;
	font-size: 17px;
	font-weight: 700;
	line-height: 1.35;
	color: var(--wp--preset--color--text-primary);
}

.sf-inquiry-modal__success-note {
	margin: 6px 0 0;
	font-size: 14px;
	line-height: 1.5;
	color: var(--wp--preset--color--text-secondary);
}

/* --- Phones: the dialog becomes the full-height sheet the brief asks for -- */
@media (max-width: 767px) {
	.sf-inquiry-modal {
		padding: 0;
		align-items: flex-end;
	}

	.sf-inquiry-modal__panel {
		max-width: none;
		max-height: 100%;
		height: 100%;
		border-radius: 0;
		transform: translateY(100%);
	}

	.sf-inquiry-modal.is-open .sf-inquiry-modal__panel {
		transform: translateY(0);
	}

	.sf-inquiry-modal__head,
	.sf-inquiry-modal__body {
		padding-left: 20px;
		padding-right: 20px;
	}

	.sf-inquiry-form__grid,
	.sf-inquiry-modal__steps {
		grid-template-columns: minmax(0, 1fr);
	}

	.sf-inquiry-modal__rows {
		grid-template-columns: minmax(0, 1fr);
		gap: 2px;
	}

	.sf-float-btn--inquiry {
		height: 44px;
		min-width: 44px;
		padding: 0 16px 0 14px;
		border-radius: 22px;
		font-size: 13px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.sf-inquiry-modal,
	.sf-inquiry-modal__panel {
		transition: none;
	}

	.sf-float-btn--inquiry.is-visible {
		animation: none;
	}
}


/* Two children of the phone pass are built by script on EVERY detail page,
   so they need a rule at every width, not just inside the phone block: the
   dots and the fold button are styled out of existence off the phone, the
   same contract the thumbnail strip and the drawer button already keep. */
.sf-fdetail2__media .sf-gallery__dots,
.sf-fdetail-config__fold {
	display: none;
}

/* ==========================================================================
   62. Batch H7h — the phone pass. Everything in this section is scoped to
   <=480px: tablets (481-768) and desktops keep every rule they had, including
   the H7d drawer, which this batch replaces ONLY at the phone width.

   The type scale follows the platform floors (body text 16px, small text
   14px, line-height >= 1.5): 13-15px body text triggers iOS Safari's
   font-boosting and reads as a print catalogue on a phone. Every size here
   overrides a base rule LATER in the cascade than that base rule, because
   this section is the last thing in the file — the H7b lesson (.sf-fdetail2__title
   died silently above its base rule) is why the whole pass lives at the end.
   ========================================================================== */
@media (max-width: 480px) {
	/* ---- the type scale ------------------------------------------------- */
	/* Parameter labels are small text: 14px is the floor, not 13. */
	.sf-fdetail-config__label {
		font-size: 14px;
	}
	/* Option text, values, intro, hero subtitle, FAQ answers, buttons: body
	   text, 16px. The 1.5 line-heights are already in the base rules; the two
	   that were tighter are restated here. */
	.sf-fdetail-config__opt {
		font-size: 16px;
	}
	.sf-fdetail-config__meta {
		font-size: 16px;
	}
	.sf-fdetail-config__note {
		font-size: 14px;
	}
	/* The placeholder name inside an 80px dashed slot is small text too. */
	.sf-fdetail-config__empty-label {
		font-size: 14px;
		line-height: 1.2;
	}
	.sf-fdetail2__intro {
		font-size: 16px;
	}
	.sf-fdetail2__term {
		font-size: 14px;
	}
	.sf-fdetail2__value {
		font-size: 16px;
	}
	.sf-fdetail2__chip {
		font-size: 16px;
	}
	/* The H7c spec sheet is the parameters table of the reading area. */
	.sf-fdetail-specs__term {
		font-size: 14px;
	}
	.sf-fdetail-specs__value {
		font-size: 16px;
	}
	.sf-fdetail-specs__chip {
		font-size: 16px;
	}
	/* The hero's subtitle line under the title on the dark band. */
	.sf-formula-hero__meta {
		font-size: 16px;
	}
	/* FAQ answers were 14px — the one body-text size below the floor. */
	.sf-faq details p {
		font-size: 16px;
	}
	.sf-fdetail-content__label {
		font-size: 14px;
	}
	.sf-formula__cta {
		font-size: 16px;
	}

	/* ---- the redundancies ------------------------------------------------ */
	/* The value-preview row is already gone under --js (H7g); the hint line
	   ("Choose one or more") is instruction the radio group carries in its
	   own semantics, so on a phone it is pure height. */
	.sf-fdetail-config__hint {
		display: none;
	}

	/* ---- the configurator: inline list, the fold replaces the drawer ----- */
	/* At 481-768 the H7d drawer still runs. At <=480 the list stands inline
	   and config.js folds it to the first three groups behind one button —
   the four rulings' "折叠". The drawer button never shows at this width. */
	.sf-fdetail-config--js .sf-fdetail-config__open {
		display: none;
	}
	.sf-fdetail-config--js .sf-fdetail-config__list {
		display: block;
		position: static;
		inset: auto;
		z-index: auto;
		padding: 0;
		overflow: visible;
		background: none;
	}
	/* The fold button is created by config.js; absent without it. */
	.sf-fdetail-config--js .sf-fdetail-config__fold {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 100%;
		margin-top: 12px;
		padding: 10px 16px;
		border: 1px solid var(--wp--preset--color--border-medium);
		border-radius: 999px;
		background: none;
		font-size: 14px;
		font-weight: 600;
		line-height: 1.5;
		color: var(--wp--preset--color--brand-green);
		cursor: pointer;
	}
	/* Folded: groups 5+ wait behind the button. Batch H8a moved the cut from 4
	   to 5 — the ladder was moved to the head of the column (H7l) and the user
	   ruled that a price is the one line a buyer scans for, so the fold now
	   keeps the ladder AND the three groups the brief names (Flavor, Piece
	   Weight, Pack Size) and hides from Shape on. Scoped to the folded class so
	   an expanded list is one class flip, and to <=480 so the desktop list is
	   never folded even if the class survives a rotation. */
	.sf-fdetail-config--js .sf-fdetail-config__list.sf-config-folded
		.sf-fdetail-config__group:nth-child(n + 5) {
		display: none;
	}

	/* ---- the image pickers: one row that slides, 1.5 tiles in view --------- */
	/* Batch H8a. Three columns of 80px was two rows of shapes behind a fold;
	   the row now slides instead, on the same rule the base width uses (the
	   containment is declared once, in 38a) — what changes here is only the
	   tile. `calc((100% - 6px) / 1.5)` puts one full tile and half of the next
	   in view at every phone width: a row that shows all of itself reads as a
	   finished list, and only the cut tile says there is more.
	   The card turns on its side for this width alone: a 219px tile with a
	   60px picture over it would be mostly empty, while picture-beside-name is
	   how a wide, short card is normally read. */
	.sf-fdetail-config__group[data-sf-config-group="shape"] .sf-fdetail-config__opt,
	.sf-fdetail-config__group[data-sf-config-group="container"] .sf-fdetail-config__opt {
		flex: 0 0 calc((100% - 6px) / 1.5);
		flex-direction: row;
		align-items: center;
		min-width: 0;
		gap: 10px;
		padding: 10px;
	}
	.sf-fdetail-config__group[data-sf-config-group="shape"] .sf-fdetail-config__img,
	.sf-fdetail-config__group[data-sf-config-group="container"] .sf-fdetail-config__img {
		width: 56px;
		height: 56px;
		/* 56 is the BOX: the dashed slot's own 1px border would otherwise
		   make the tile measure 58. */
		box-sizing: border-box;
	}

	/* ---- the media area: strip out, dots in ------------------------------ */
	/* The swipe itself already exists (formula-gallery.js 2.1.0 drags the
	   stage); what a phone needs is the strip gone and the dots visible. */
	.sf-fdetail2__media .sf-gallery__thumbs {
		display: none;
	}
	.sf-gallery__dots {
		/* Built by formula-gallery.js; absent without it, like the strip. */
		display: none;
	}
	.sf-fdetail2__media .sf-gallery__dots {
		display: flex;
		justify-content: center;
		gap: 8px;
		margin-top: 0;
	}
	.sf-gallery__dot {
		width: 10px;
		height: 10px;
		padding: 0;
		border: 0;
		border-radius: 999px;
		background-color: var(--wp--preset--color--border-medium);
		cursor: pointer;
		transition: background-color 0.15s ease;
	}
	.sf-gallery__dot.is-active {
		background-color: var(--wp--preset--color--accent);
	}

	/* ---- the bottom bar --------------------------------------------------- */
	/* The floating circles become a full-width bar: inquiry + WhatsApp, side
	   by side, never covering content (body gains the bar's height). The
	   email circle hides — its address is in the footer contact line one
   scroll away — and the back-to-top circle hides with it: a phone scrolls
	   with one finger. z-index stays 9998: the bar is part of the float
	   stack's layer, below the cookie banner's 9999, exactly as before. */
	.sf-float-stack {
		left: 0;
		right: 0;
		bottom: 0;
		flex-direction: row;
		align-items: stretch;
		gap: 10px;
		padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
		background-color: var(--wp--preset--color--card-white);
		border-top: 1px solid var(--wp--preset--color--border-light);
		box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
	}
	.sf-float-btn {
		flex: 1 1 0;
		width: auto;
		height: 48px;
		border-radius: 999px;
		font-size: 16px;
	}
	.sf-float-btn--email,
	.sf-float-btn--top {
		display: none;
	}
	/* The inquiry capsule ships `hidden` until its script reveals it past the
	   parameter band; the bar leaves the slot to it. */
	.sf-float-btn--inquiry {
		width: auto;
		min-width: 0;
		height: 48px;
		padding: 0 16px;
		border-radius: 999px;
		font-size: 16px;
	}
	/* The cookie banner overlays the bar from below, as it floated over the
	   circles; lift the bar by the banner's stacked height. */
	body.has-cookie-banner .sf-float-stack {
		bottom: 268px;
	}
	/* The switcher rides above the bar, not the circle column it replaced. The
	   bar is 69px tall where that column was 156, so clearing this layout takes
	   a far smaller lift than the 280px the 767 block hands down: 350 leaves
	   13px of air between the switcher's bottom edge and the bar's top edge,
	   and 345 is where the asked-for 8px floor lands. Two geometries, two
	   values — this block and the bar it compensates share one breakpoint, the
	   same way the stack's own 268px does. Above 480 nothing moves, and that
	   placement is the point: the 767 block's 280 is what keeps the column band
	   clean, where a 350 lift would cost the WhatsApp button 3 of its 11 sample
	   points at 481 (measured). */
	body.has-cookie-banner .trp-floating-switcher {
		bottom: 350px;
	}
	/* The bar owns the strip the circles used to share with the page edge. */
	body {
		padding-bottom: 78px;
	}
}


/* =========================================================================
   63. Quantity & Pricing, the ladder — batch H7i (the order's task 3), plus
   the two compacting rules the same order asks for (task 11).
   -------------------------------------------------------------------------
   The group stops being a row of pills. A price list is read DOWN a column,
   not across a sentence, so each tier becomes its own card: the unit price is
   the headline, the quantity range and the unit qualify it, and the radio at
   the foot keeps it a real choice. Everything the other groups rely on is
   untouched — the pill's own class, the input, the is-on toggle config.js
   writes, the data-sf-config-opt hook — so the summary, the dialog carrier
   and the no-JS line all keep working without knowing this style exists.

   Written at the end of the file for the reason 38a-iii gives: a rule that
   overrides the pill base (display, border-radius, padding) has to come after
   it, or it is dead at every width — the mistake batch H7b shipped. */

.sf-fdetail-config__tiers {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 8px;
}
/* The card. Grid rather than the pill's inline-flex, so the three lines stack
   in one column with no wrapper element per line; the dot is absolutely
   placed, so the price stays the visual centre of the card. */
.sf-fdetail-config__opt.sf-tier {
	position: relative;
	display: grid;
	justify-items: center;
	gap: 1px;
	padding: 11px 8px 27px;
	border-radius: 10px;
	text-align: center;
}
.sf-tier__price {
	font-size: 17px;
	font-weight: 700;
	line-height: 1.25;
	color: var(--wp--preset--color--text-primary);
}
.sf-tier__range {
	font-size: 12px;
	line-height: 1.3;
	color: var(--wp--preset--color--text-primary);
}
.sf-tier__unit {
	font-size: 11px;
	line-height: 1.3;
	color: var(--wp--preset--color--text-secondary);
}
.sf-tier__dot {
	position: absolute;
	left: 50%;
	bottom: 9px;
	width: 14px;
	height: 14px;
	margin-left: -7px;
	border: 1px solid var(--wp--preset--color--border-medium);
	border-radius: 999px;
	background-color: var(--wp--preset--color--card-white);
}
/* The dot follows the input, and the sibling selector is ~, not +: the dot is
   the fourth child of the label, and + only reaches the element immediately
   after the input. */
.sf-fdetail-config__input:checked ~ .sf-tier__dot {
	border-color: var(--wp--preset--color--brand-green);
	background-color: var(--wp--preset--color--brand-green);
	box-shadow: inset 0 0 0 3px var(--wp--preset--color--card-white);
}
.sf-fdetail-config__input:focus-visible ~ .sf-tier__dot {
	outline: 2px solid var(--wp--preset--color--brand-green);
	outline-offset: 2px;
}
.sf-fdetail-config__opt.sf-tier.is-on {
	border-color: var(--wp--preset--color--brand-green);
	background-color: var(--wp--preset--color--bg-light);
}

/* The sample row, inside the ladder's own block: the divider is what says it
   is a different question from the three breaks above it. */
.sf-fdetail-config__sample {
	display: flex;
	align-items: center;
	gap: 7px;
	margin-top: 10px;
	padding-top: 10px;
	border-top: 1px solid var(--wp--preset--color--border-light);
	font-size: 13px;
	line-height: 1.4;
	color: var(--wp--preset--color--text-primary);
}
.sf-fdetail-config__sample-icon {
	display: inline-flex;
	flex: none;
	color: var(--wp--preset--color--text-secondary);
}
.sf-fdetail-config__sample-price { font-weight: 700; }
.sf-fdetail-config__sample-cta {
	flex: none;
	margin-left: auto;
	padding: 6px 14px;
	border: 1px solid var(--wp--preset--color--cta);
	border-radius: 999px;
	color: var(--wp--preset--color--cta);
	font-size: 13px;
	font-weight: 600;
	line-height: 1.4;
	text-decoration: none;
	transition: background-color .15s ease, color .15s ease;
}
.sf-fdetail-config__sample-cta:hover,
.sf-fdetail-config__sample-cta:focus-visible {
	background-color: var(--wp--preset--color--cta);
	color: var(--wp--preset--color--card-white);
}

/* Task 11, the second half. H7g gated the value-preview line on --js; the
   hint is the same kind of echo — "Choose one or more" is advice about a
   control that is already on screen — so it goes the same way. Without the
   script the hint stays, because it is then the only thing telling the
   visitor that the pills beside it are pickable. */
.sf-fdetail-config--js .sf-fdetail-config__hint { display: none; }

/* The ladder at phone width: three columns are kept — the brief reads as a
   comparison, and stacking three prices would break the comparison it exists
   for — but the cards tighten and the sample row wraps its button under the
   price rather than squeezing it. */
@media (max-width: 480px) {
	.sf-fdetail-config__tiers { gap: 6px; }
	.sf-fdetail-config__opt.sf-tier { padding: 9px 5px 25px; border-radius: 8px; }
	.sf-tier__price { font-size: 16px; }
	.sf-fdetail-config__sample { flex-wrap: wrap; row-gap: 8px; }
	.sf-fdetail-config__sample-cta { margin-left: 0; padding: 8px 16px; font-size: 14px; min-height: 36px; }
}

/* ==========================================================================
   63. Batch H7j — the core-facts band joins the content gutter, and the main
   menu finally says where you are.

   63a. .sf-facts-mini was the one band on a dosage page sitting on the
   viewport edge. Measured at 1440px on /products/soft-chews/: its first item
   began at x = 0 and the first line's last item ended at x = 1440, so the band
   hugged BOTH edges, while every neighbouring band's content sat at x = 120 —
   the constrained layout's own auto-margin around the 1200px measure. The
   band's BOX has to stay full width: it draws the two hairlines that separate
   the hero from the page, and those are meant to run edge to edge. Only its
   content moves in.

   The inset is the expression rule 25c already puts on the header bar and
   rule 32 puts on the bare section groups: max(38px, (100% - 1200px)/2)
   resolves to the content column's left edge at every width above the measure
   and to 38px below it — with one deliberate exception at <=1024px, where it
   drops to 20px to match the hero directly above it. (The other body bands
   stay at 38px there; the ladder from the brief is 120 / 38 / 20 and 20 is the
   step the hero and the phone pass take.)

   justify-content: space-between stays — the four facts are meant to spread
   across the row rather than stack at the left. A narrower line wraps sooner,
   so between 1025px and 1215px the four facts go from two lines to three.
   That is the measured cost of the inset, and it is the only width band whose
   row count moves.

   63b. The six ways to mark the current menu item (task 20). The class
   contract is fixed: the header carries sf-header--nav-<slug>, the current
   link carries .sf-nav__link.is-active. WHICH link that is, is decided in
   functions.php — never one of the nine links inside the Products dropdown,
   which sit on the light popup while every mark below is drawn white or
   brand-green for the dark Forest bar.

   Four states per variant, in this order, because Normal/Hover and
   Active/Active+Hover overlap:
     Normal        .sf-nav__link:not(.is-active)
     Hover         .sf-nav__link:not(.is-active):hover
     Active        .sf-nav__link.is-active
     Active+Hover  .sf-nav__link.is-active:hover

   The Normal/Hover pair is scoped with a child combinator
   (.wp-block-navigation__container > .wp-block-navigation-item > ...) rather
   than with a marker class, so a dropdown link can never match it and the
   active pair keeps the exact selector the brief names.

   Every mark that occupies space — the thicker line, the wash, the left rule,
   the pill — reserves that space on ALL seven top-level links, not only the
   active one, or the bar would reflow each time the visitor moved between
   pages. The active value is also what Active+Hover shows: hover already has
   the visitor's attention, and inventing a seventh colour for a state the
   brief did not name is how a palette drifts.

   63c. The same marks on a phone, and the three defects this batch's own
   measurement turned up. Four of the six marks were invisible inside the phone
   drawer, because every rule in 63b is three or four classes deep and the
   drawer's row rule is five; and `underline` and `thick-line` — the two that
   necessarily reserve a bottom border and then hide it — were hiding the
   drawer's own row separator with it. Both are repaired in 63c. The third,
   `left-line` painting a 3px rule of nothing at every width and in both
   builds, was a specificity conflict inside 63b and is repaired there, on the
   section it belongs to. Each repair is written at the depth the rule it has to
   beat is written at, and each one is named by an assertion in
   tools/b2d_h7j_e2e.py so it cannot come back unseen.
   ========================================================================== */

/* --- 63a. The core-facts band joins the content gutter ------------------- */
.sf-facts-mini {
	padding-left: max(38px, calc((100% - var(--wp--style--global--content-size, 1200px)) / 2));
	padding-right: max(38px, calc((100% - var(--wp--style--global--content-size, 1200px)) / 2));
}
@media (max-width: 1024px) {
	.sf-facts-mini {
		padding-left: 20px;
		padding-right: 20px;
	}
}

/* --- 63b. Navigation: the current item, six ways ------------------------- */

/* 1. underline — the shipped default. The line is drawn with the bottom
   border 21c already reserves (1px solid transparent, padding-bottom 2px) so
   nothing shifts: only the width and the colour move. */
.sf-header--nav-underline .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link {
	border-bottom-width: 2px;
}
.sf-header--nav-underline .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active) {
	border-bottom-color: transparent;
}
.sf-header--nav-underline .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active):hover {
	color: var(--wp--preset--color--card-white);
	border-bottom-color: rgba(255, 255, 255, 0.45);
}
.sf-header--nav-underline .sf-nav__link.is-active {
	color: var(--wp--preset--color--card-white);
	font-weight: 600;
	border-bottom-color: var(--wp--preset--color--card-white);
}
.sf-header--nav-underline .sf-nav__link.is-active:hover {
	color: var(--wp--preset--color--card-white);
	border-bottom-color: var(--wp--preset--color--card-white);
}

/* 2. bg — a translucent block behind the label. */
.sf-header--nav-bg .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link {
	border-bottom-color: transparent;
	padding: 6px 10px;
	border-radius: 4px;
}
.sf-header--nav-bg .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active) {
	background-color: transparent;
}
.sf-header--nav-bg .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active):hover {
	color: var(--wp--preset--color--card-white);
	background-color: rgba(255, 255, 255, 0.1);
}
.sf-header--nav-bg .sf-nav__link.is-active {
	color: var(--wp--preset--color--card-white);
	background-color: rgba(255, 255, 255, 0.15);
}
.sf-header--nav-bg .sf-nav__link.is-active:hover {
	color: var(--wp--preset--color--card-white);
	background-color: rgba(255, 255, 255, 0.15);
}

/* 3. thick-line — the same border, 4px, so the bar reads as a ruled strip. */
.sf-header--nav-thick-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link {
	border-bottom-width: 4px;
}
.sf-header--nav-thick-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active) {
	border-bottom-color: transparent;
}
.sf-header--nav-thick-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active):hover {
	color: var(--wp--preset--color--card-white);
	border-bottom-color: rgba(255, 255, 255, 0.45);
}
.sf-header--nav-thick-line .sf-nav__link.is-active {
	color: var(--wp--preset--color--card-white);
	border-bottom-color: var(--wp--preset--color--card-white);
}
.sf-header--nav-thick-line .sf-nav__link.is-active:hover {
	color: var(--wp--preset--color--card-white);
	border-bottom-color: var(--wp--preset--color--card-white);
}

/* 4. color — the brand green in the label, no rule and no block. Contrast of
   #5AB735 on the Forest bar (#1B4D3E) is 3.79:1, which is under the 4.5:1
   WCAG AA floor for 15px text; it clears the 3:1 floor for large text only.
   The brief names the colour, so it ships as named — recorded here rather
   than silently darkened. */
.sf-header--nav-color .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link {
	border-bottom-color: transparent;
}
.sf-header--nav-color .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active) {
	color: var(--wp--preset--color--card-white);
}
.sf-header--nav-color .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active):hover {
	color: var(--wp--preset--color--brand-green);
}
.sf-header--nav-color .sf-nav__link.is-active {
	color: var(--wp--preset--color--brand-green);
	font-weight: 600;
}
.sf-header--nav-color .sf-nav__link.is-active:hover {
	color: var(--wp--preset--color--brand-green);
}

/* 5. left-line — a 3px rule at the left of the item, the label 8px clear of
   it. The rule and the 8px are reserved on every top-level link.
   The active rules below are written at the SAME depth as the base rule above
   them, on purpose. Written the short way — `.sf-header--nav-left-line
   .sf-nav__link.is-active` — they are (0,3,0) against the base rule's (0,4,0),
   so the base rule's `border-left: 3px solid transparent` won and the mark
   painted a 3px rule of nothing: measured at 1440px, `border-left-width: 3px`
   with `border-left-color: rgba(0, 0, 0, 0)`, at every width and in both
   builds. The only state that ever showed the rule was :hover, because that
   rule happens to be (0,4,0) and comes later in the sheet. */
.sf-header--nav-left-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link {
	border-bottom-color: transparent;
	border-left: 3px solid transparent;
	padding-left: 8px;
}
.sf-header--nav-left-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active) {
	border-left-color: transparent;
}
.sf-header--nav-left-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active):hover {
	color: var(--wp--preset--color--card-white);
	border-left-color: rgba(255, 255, 255, 0.45);
}
.sf-header--nav-left-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link.is-active {
	color: var(--wp--preset--color--card-white);
	border-left-color: var(--wp--preset--color--card-white);
}
.sf-header--nav-left-line .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link.is-active:hover {
	color: var(--wp--preset--color--card-white);
	border-left-color: var(--wp--preset--color--card-white);
}

/* 6. pill — the brand green as a capsule, white label. */
.sf-header--nav-pill .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link {
	border-bottom-color: transparent;
	padding: 6px 14px;
	border-radius: 999px;
}
.sf-header--nav-pill .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active) {
	background-color: transparent;
}
.sf-header--nav-pill .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active):hover {
	color: var(--wp--preset--color--card-white);
	background-color: rgba(90, 183, 53, 0.7);
}
.sf-header--nav-pill .sf-nav__link.is-active {
	color: var(--wp--preset--color--card-white);
	background-color: rgba(90, 183, 53, 0.9);
}
.sf-header--nav-pill .sf-nav__link.is-active:hover {
	color: var(--wp--preset--color--card-white);
	background-color: rgba(90, 183, 53, 0.9);
}

/* --- 63c. ...and the same marks inside the phone drawer -------------------- */

/* The drawer draws its own rows, and it draws them deeper than the marks do.
   Three things measured on the preflight copy at 375px with the drawer open,
   the first two of which are repairs and the third a repair of this batch's own
   collateral damage:

   1. TWO MARKS ERASED BY DEPTH. The drawer's row rule
        .wp-block-navigation__responsive-container.is-menu-open
          .wp-block-navigation__container > .wp-block-navigation-item
          > .wp-block-navigation-item__content { min-height: 48px;
          font-size: 16px; font-weight: 600; border-bottom: 1px solid
          rgba(255, 255, 255, 0.14); }                  [max-width: 768px]
      is (0,5,0), and the ≤781px modal rule
        .wp-block-navigation .wp-block-navigation__responsive-container
          .has-modal-open .wp-block-navigation-item__content { color: <white> }
      is (0,4,0). Every mark in 63b is (0,3,0) or (0,4,0), so inside the drawer
      `underline` painted a 1px 0.14-white hairline instead of its declared 2px
      white, `thick-line` 1px instead of 4px, and `color` white instead of the
      brand green. Measured: the current row was INDISTINGUISHABLE from an
      unmarked row for four of the six marks — including `underline`, which is
      the shipped default — so the mark was simply absent on a phone. The three
      below restate those marks at (0,7,0) with the property and the colour
      already recorded in 63b, so the drawer says what the bar says. `bg` and
      `pill` are deliberately absent: they draw with background-color, which the
      drawer rule does not touch, and both were measured still doing their job.
      `left-line` is absent too, but for a different reason — its repair was a
      specificity conflict inside 63b itself (see the note on mark 5), which is
      why it was fixed there and not here.

   2. THE ROW SEPARATOR WIPED BY THIS BATCH. `underline` and `thick-line` are
      the two marks that draw with the bottom border, so both necessarily carry
      a `:not(.is-active) { border-bottom-color: transparent }` to hide the
      2px/4px they reserve on the other links. That rule is (0,5,0) — the same
      as the drawer's row rule above — and it comes later in the sheet, so it
      won on order and took the drawer's hairline off every row that was not
      current. Measured: `rgba(0, 0, 0, 0)` under `underline` and `thick-line`,
      the drawer's own `rgba(255, 255, 255, 0.14)` under the other four. One
      rule, `:where()` over the six marks so it is (0,7,0) without naming them
      six times, and the drawer's furniture comes back. */
@media (max-width: 768px) {
	.sf-header--nav-underline .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link.is-active {
		border-bottom-width: 2px;
		border-bottom-color: var(--wp--preset--color--card-white);
	}
	.sf-header--nav-thick-line .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link.is-active {
		border-bottom-width: 4px;
		border-bottom-color: var(--wp--preset--color--card-white);
	}
	.sf-header--nav-color .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link.is-active {
		color: var(--wp--preset--color--brand-green);
	}
	.sf-header:where(.sf-header--nav-underline, .sf-header--nav-bg, .sf-header--nav-thick-line,
		.sf-header--nav-color, .sf-header--nav-left-line, .sf-header--nav-pill)
		.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item > .sf-nav__link:not(.is-active) {
		border-bottom-color: rgba(255, 255, 255, 0.14);
	}
}
