/*
Theme Name: Ordercube Replica
Theme URI: https://example.com/ordercube-replica
Author: Claude
Author URI: https://example.com
Description: A full-site-editing block theme that reproduces the measured LAYOUT, TOKEN SYSTEM and MOTION BEHAVIOUR of ordercube.de as one scrolling page of seven anchored sections (#home #scenarios #features #references #impact #faq #contact) plus four real routes (/privacy/ /terms/ /imprint/ /customer-map/). All copy, artwork and marks are original placeholders for a fictional demo brand ("Lumen Table Call"); no third-party content, imagery, icon set, trademark or photography is included. The original's transform-based scroll-jacking is reproduced as BEHAVIOUR over native document scroll rather than cloned, because the original cannot be scrolled by keyboard at all.
Requires at least: 6.5
Tested up to: 6.8
Requires PHP: 7.4
Version: 2.5.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: ordercube-replica
Tags: full-site-editing, block-patterns, one-column, custom-colors, custom-logo, accessibility-ready, dark-mode
*/

/* ==========================================================================
   0. Tokens — these mirror theme.json 1:1. theme.json is the source of truth;
   these aliases exist so hand-written CSS below reads like the spec's tables.
   ========================================================================== */

:root {
	/* [DEFECT 8 FIX] The page surface is #0E0F13. Without `color-scheme: dark` the
	   UA paints native controls (the calculator checkbox, date/number spinners,
	   scrollbars, form field chrome) with LIGHT chrome on a dark page. Declared on
	   :root so it applies document-wide; the light bands (footer, references, the
	   mobile overlay) set their own explicit colours and are unaffected. */
	color-scheme: dark;

	/* Color (spec "Color table") */
	--oc-bg: var(--wp--preset--color--bg, #0E0F13);
	--oc-ink: var(--wp--preset--color--ink, #FFFFFF);
	--oc-ink-dim: var(--wp--preset--color--ink-dim, rgba(255, 255, 255, 0.85));
	--oc-ink-muted: var(--wp--preset--color--ink-muted, rgba(255, 255, 255, 0.6));
	--oc-black: var(--wp--preset--color--black, #000000);
	--oc-accent-start: var(--wp--preset--color--accent-start, #B89B83);
	/* REVERTED under the replication brief. This was an invented lighter tint
	   (#D8C7BA) introduced so the features pull-quote cleared 4.5:1 over the scene's
	   radial gradient. The original has no such token — it paints accent text in the
	   measured #B89B83 and measures 3.16:1 there. We now do the same. */
	--oc-accent-tint: var(--wp--preset--color--accent-tint, #B89B83);
	--oc-accent-end: var(--wp--preset--color--accent-end, #B77B38);
	/* [A11Y-2026-10-03] CORRECTNESS DEVIATION, deliberate. The measured original is
	   `.card-value.positive { color: #4CAF50 }` (ordercube_styles.css:2380), which is
	   2.780:1 on the white `.oc-stat` surface — it fails the 4.5:1 body bar AND the
	   3.0:1 large-text bar. An earlier pass tried #43A047 (3.303:1) and documented it
	   at section 27 as an accepted deviation; that still fails 4.5:1, and a later pass
	   reverted even that under the replication brief. Contrast is a CORRECTNESS
	   finding, so the original being wrong is not a licence to be wrong: the hue
	   (122.4deg) and saturation (0.394) of #4CAF50 are held and only the HSL lightness
	   is lowered until the white surface clears 4.5:1.
	     #4CAF50  L=0.327756  on #FFFFFF 2.780:1  FAIL
	     #43A047  L=0.267898  on #FFFFFF 3.303:1  FAIL 4.5 (passes 3.0 only)
	     #3A863D  L=0.182690  on #FFFFFF 4.509:1  PASS
	   EVERY SURFACE CHECKED before the token moved, because a value that fixes white
	   can break dark. --oc-positive is used in exactly two live places:
	     style.css:1615  .oc-stat__delta             36px/600 on #FFFFFF  -> 4.509:1 PASS
	     style.css:2410  .oc-notice--ok .oc-notice__icon  SVG stroke on rgba(255,255,255,0.05)
	                     over --oc-bg (= #191A1E)    -> 3.856:1 PASS (1.4.11 graphic bar 3:1)
	   The dark surface keeps 3.86:1, so the darkening costs nothing there. README
	   deviation 60. */
	--oc-positive: var(--wp--preset--color--positive, #3A863D);
	--oc-gray-600: var(--wp--preset--color--gray-600, #666666);
	/* [A11Y-2026-10-03] CORRECTNESS DEVIATION, deliberate. The measured original is
	   `.copyright { color: #999 }` (ordercube_styles.css:3428) and an earlier pass
	   reverted a #666666 fix back to it under the replication brief. #999999 is
	   2.849:1 on the white footer band — a real AA failure on the one line a
	   regulator reads. Neutral grey, so there is no hue to preserve; the lightness
	   drops to the first value that clears 4.5:1 with margin.
	     #999999  2.849:1 FAIL   #767676  4.542:1 PASS (thin)   #757575  4.608:1 PASS
	   Used in exactly one place: style.css:1927 .oc-footer__bottom, 14px/400 on
	   --oc-ink (#FFFFFF). README deviation 61. */
	--oc-gray-400: var(--wp--preset--color--gray-400, #757575);
	/* MEASURED. ordercube_styles.css:4011 — the FAQ scene's base background is the
	   espresso #2d1810 (route-map.md colour census). An earlier pass invented
	   #2A1D15 because one spec part recorded no hex; the census had measured it.
	   REVERTED to the measured value. */
	--oc-faq-bg: var(--wp--preset--color--faq-bg, #2d1810);

	/* Gradients */
	--oc-gradient-angle: 135deg;
	--oc-accent: linear-gradient(var(--oc-gradient-angle), var(--oc-accent-start) 0%, var(--oc-accent-end) 100%);
	/* [A11Y-2026-10-03] CORRECTNESS DEVIATION, deliberate. `--oc-accent-aa` is the
	   variant used wherever WHITE ink sits on the accent surface. It had been
	   reverted to the measured stops, which fail AA with white ink at BOTH ends:
	     #B89B83 (0% stop)   L=0.352721  white on it 2.607:1  FAIL
	     #B77B38 (100% stop) L=0.245193  white on it 3.557:1  FAIL   <- the detector's
	                                                                   "#ffffff on #b77b38"
	   White 14px/300 is NORMAL text, so the bar is 4.5:1, which caps the surface at
	   L <= 1.05/4.5 - 0.05 = 0.183333. Each stop is darkened in HSL with its hue and
	   saturation HELD, so the bronze family is unchanged:
	   FIRST ATTEMPT, AND WHY IT WAS NOT ENOUGH — solving each ENDPOINT to exactly
	   4.5:1 gave #927053 / #A06B31 (4.502:1 / 4.524:1). Both endpoints passed and the
	   detector passed it, but a composited-pixel read of the deployed button found the
	   worst pixel at 4.47:1. Sampling the sRGB-interpolated 135deg ramp at 101 points
	   confirmed it independently: the ramp DIPS BELOW BOTH ENDPOINTS, worst interior
	   point #937052 = 4.487:1. A gradient is not safe because its ends are safe.
	   So the solve target is 4.8:1 on the endpoints, which puts the whole ramp clear:
	     #B89B83 -> #8C6B50  hsl(27.6, 0.272, 0.438)  endpoint 4.845:1
	     #B77B38 -> #9A672F  hsl(31.5, 0.531, 0.396)  endpoint 4.822:1
	     worst interior point of the ramp: #986835 = 4.809:1  PASS with margin
	   `--oc-accent` (the decorative gradient: swatches, hairlines, the wordmark
	   accent) is NOT touched and keeps the measured stops — nothing puts text on it.
	   README deviation 62. */
	--oc-accent-aa-start: #8C6B50;
	--oc-accent-aa-end: #9A672F;
	--oc-accent-aa: linear-gradient(var(--oc-gradient-angle), var(--oc-accent-aa-start) 0%, var(--oc-accent-aa-end) 100%);

	/* Spacing / layout (spec ":root custom properties") */
	--oc-grid-max: 1280px;
	--oc-frame: 1.5rem;         /* 24px desktop gutter */
	--oc-frame-mobile: 0.375rem; /* 6px mobile gutter */
	/* 20.2 — the #impact stat cards' standoff from the frame's inner edge.
	   One token, so the value cannot be set twice and drift. Equal to the
	   grid's own `gap: 16px` (== --oc-space-md), which is what makes the outer
	   gap read as the same gutter the cards already have between them. */
	--oc-stats-standoff: 16px;
	--oc-gutter: var(--oc-frame);
	--oc-blur: 24px;

	/* Component geometry */
	--oc-pill: 999px;
	--oc-card-radius: 0px;      /* strict binary: cards are sharp, pills are round */
	--oc-header-h: 136px;
	--oc-header-pad: 48px;
	--oc-hairline: 1px solid rgba(255, 255, 255, 0.14);
	--oc-hairline-dark: 1px solid rgba(0, 0, 0, 0.12);

	/* Type */
	--oc-font-system: var(--wp--preset--font-family--system, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif);
	--oc-font-arial: var(--wp--preset--font-family--arial, Arial, Helvetica, sans-serif);
	--oc-fs-button: 14px;
	--oc-fs-link: 16px;
	--oc-fs-card: 18px;
	--oc-fs-body: 22px;

	/* Motion */
	--oc-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
	--oc-dur: 220ms;
}

/* The 6px/24px gutter pair cannot be expressed as a media query inside theme.json,
   so the mobile half of the --frame/--frame-mobile pair is set here by overriding the
   root-padding custom properties core emits for useRootPaddingAwareAlignments. */
@media (max-width: 768px) {
	body {
		--wp--style--root--padding-left: var(--oc-frame-mobile);
		--wp--style--root--padding-right: var(--oc-frame-mobile);
		--oc-gutter: var(--oc-frame-mobile);
		--oc-header-h: 72px;
		--oc-header-pad: 12px;
	}
}

/* ==========================================================================
   1. Base
   ========================================================================== */

html {
	scroll-behavior: smooth;
	/* the fixed header must not cover an anchored scene */
	scroll-padding-top: var(--oc-header-h);
}


body {
	background-color: var(--oc-bg);
	color: var(--oc-ink);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
	overflow-x: hidden;
}

/* The mobile "bezel": the 6px gutter reads as a thin dark frame around every scene. */
.oc-scene {
	position: relative;
	isolation: isolate;
}

:where(.oc-scene) > :where(.wp-block-group__inner-container, .wp-block-cover__inner-container) {
	position: relative;
	z-index: 1;
}

/* FOCUS — REVERTED under the replication brief.
   MEASURED on the original: `focus-visible` appears ZERO times; `outline` of any
   kind appears exactly ONCE in 5,795 lines of CSS (`outline: none` on the FAQ
   search input, ordercube_styles.css:2480) and zero times in home.html. Not one
   rule anywhere ADDS a focus indicator. Below 768px all 21 of its `:focus` rules
   actively pin the control to its resting appearance with `!important`
   (ordercube_styles.css:4601-4619 and 5651-5778).
   An earlier pass added a 3px accent ring site-wide. That was a self-invented
   improvement and it is removed: this theme now ships no focus ring, which is the
   original's measured behaviour, and the mobile suppression below mirrors it.
   The ONE survivor is the hero video's pause button (section 31) — see README
   deviation 59, which the client asked for by name. */

/* The `.oc-skip-link` rules that used to live here are gone with the markup they
   styled: the original ships no skip link, so the theme's was removed and core's
   auto-injected one (`.skip-link.screen-reader-text`) is now the only one. */

.oc-visually-hidden {
	position: absolute !important;
	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;
}

/* ==========================================================================
   2. Components — buttons (spec "Buttons (all variants observed)")
   Strict binary: every interactive pill/circle is 999px; every card is 0 radius,
   no shadow. No outline-only variant, no intermediate radius, no shadow anywhere.
   ========================================================================== */

.oc-label,
.wp-block-button__link,
.oc-btn,
.oc-nav__link {
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	line-height: 1.1;
	letter-spacing: 0.56px;
	text-transform: uppercase;
}

/* Base pill.
   [DEFECT 6 FIX] 14px/1.1 + 12px padding computed 39.4px tall — under the 44px
   target size. The type scale is measured and stays; the TARGET is grown with
   min-block-size instead, which costs 4.6px of height and nothing else. */
.wp-block-button__link,
.oc-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	min-block-size: 44px;
	border: 0;
	border-radius: var(--oc-pill);
	padding: 12px 18px;
	color: var(--oc-ink);
	text-decoration: none;
	cursor: pointer;
	transition: transform var(--oc-dur) var(--oc-ease), background-color var(--oc-dur) var(--oc-ease), opacity var(--oc-dur) var(--oc-ease);
}

/* Primary CTA — the 135deg accent gradient, 12px 18px (spec table row 2).
   [A11Y-2026-10-03] background-color is the no-gradient fallback and must clear
   4.5:1 with white ink on its own, so it tracks --oc-accent-aa-end, not the
   measured #B77B38 (3.557:1). */
.oc-btn--primary,
.wp-block-button.oc-btn--primary > .wp-block-button__link {
	background-image: var(--oc-accent-aa);
	background-color: var(--oc-accent-aa-end);
	color: var(--oc-ink);
	padding: 12px 18px;
	border-radius: var(--oc-pill);
}

.oc-btn--primary:hover,
.wp-block-button.oc-btn--primary > .wp-block-button__link:hover {
	transform: translateY(-1px);
	filter: saturate(1.08);
}

/* Ghost pill — translucent dark, used for "watch a video" / carousel controls. */
.oc-btn--ghost,
.wp-block-button.oc-btn--ghost > .wp-block-button__link {
	background-color: rgba(0, 0, 0, 0.5);
	color: var(--oc-ink);
	border-radius: var(--oc-pill);
	padding: 12px 18px;
	backdrop-filter: blur(8px);
}

.oc-btn--ghost:hover,
.wp-block-button.oc-btn--ghost > .wp-block-button__link:hover {
	background-color: rgba(0, 0, 0, 0.68);
}

/* On white bands the ghost pill inverts so its label keeps >= 4.5:1. */
.oc-band--light .oc-btn--ghost,
.oc-band--light .wp-block-button.oc-btn--ghost > .wp-block-button__link {
	background-color: rgba(0, 0, 0, 0.82);
	color: var(--oc-ink);
}

/* Ghost circular icon button — rgba(0,0,0,.5), perfect circle, icon only. */
.oc-icon-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	inline-size: 44px;
	block-size: 44px;
	padding: 0;
	border: 0;
	border-radius: var(--oc-pill);
	background-color: rgba(0, 0, 0, 0.5);
	color: var(--oc-ink);
	cursor: pointer;
	transition: background-color var(--oc-dur) var(--oc-ease), transform var(--oc-dur) var(--oc-ease);
}

.oc-icon-btn:hover { background-color: rgba(0, 0, 0, 0.72); }
.oc-icon-btn:active { transform: scale(0.96); }
.oc-icon-btn svg { inline-size: 18px; block-size: 18px; fill: currentColor; }

.oc-band--light .oc-icon-btn {
	background-color: rgba(0, 0, 0, 0.82);
}

/* Plain text + circular icon badge link (no chrome) — "View Scenarios" etc. */
.oc-textlink {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	min-block-size: 44px;   /* [DEFECT 6 FIX] WCAG 2.5.8 target size */
	padding: 0;
	background: none;
	border: 0;
	color: inherit;
	text-decoration: none;
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
}

.oc-textlink::after {
	content: "";
	inline-size: 32px;
	block-size: 32px;
	flex: 0 0 auto;
	border-radius: var(--oc-pill);
	background-color: rgba(0, 0, 0, 0.5);
	background-image: linear-gradient(transparent, transparent);
	transition: transform var(--oc-dur) var(--oc-ease);
}

.oc-band--light .oc-textlink { color: var(--oc-black); }
.oc-band--light .oc-textlink::after { background-color: rgba(0, 0, 0, 0.82); }
.oc-textlink:hover::after { transform: translateX(4px); }

/* Cards — sharp, flat, never a shadow. */
.oc-card {
	border-radius: var(--oc-card-radius);
	box-shadow: none;
	background-color: var(--oc-ink);
	color: var(--oc-black);
	padding: 24px;
}

.oc-card :where(h1, h2, h3, h4) { color: var(--oc-black); }

/* ==========================================================================
   3. Header / nav (spec "Header / nav")
   position: fixed, header itself fully transparent; the six nav items live in ONE
   translucent dark capsule with backdrop-filter: blur(24px); CTA sits outside it.
   ========================================================================== */

.oc-header {
	position: fixed;
	inset-block-start: 0;
	inset-inline: 0;
	z-index: 100;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 24px;
	min-block-size: var(--oc-header-h);
	padding: 0 var(--oc-header-pad);
	background-color: rgba(0, 0, 0, 0); /* transparent at the header level */
	pointer-events: none;
}

.oc-header > * { pointer-events: auto; }

/* [DEVIATION 24] Scroll scrim on the fixed header.
   The ORIGINAL site's header is fully transparent (measured), which is safe THERE because the
   page is scroll-jacked: scenes are pinned, so content never travels under the header. This
   rebuild deliberately uses native scrolling (spec Build Notes), so light content — the white
   Scenarios caption card — scrolls beneath a transparent header and renders the white wordmark
   at 1.00:1, i.e. invisible. Measured at scrollY ~1350/1500 @1440 and ~1266 @390.
   Fix: stay transparent at rest (preserving the hero's intended look), fade in a blurred scrim
   once scrolled. The scrim is a ::before so the header keeps `pointer-events: none` and its own
   transparent background-color; `.oc-header > *` only matches elements, never this pseudo. */
.oc-header::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	opacity: 0;
	background-color: rgba(14, 15, 19, 0.88);
	backdrop-filter: blur(var(--oc-blur, 24px));
	-webkit-backdrop-filter: blur(var(--oc-blur, 24px));
	border-block-end: 1px solid rgba(255, 255, 255, 0.08);
	transition: opacity 180ms ease-out;
	pointer-events: none;
}

.oc-header.is-scrolled::before { opacity: 1; }


.oc-wordmark {
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;   /* [DEFECT 6 FIX] it is a link; it needs the target */
	gap: 10px;
	color: var(--oc-ink);
	text-decoration: none;
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-card);
	font-weight: 300;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	white-space: nowrap;
}

.oc-wordmark__glyph {
	inline-size: 26px;
	block-size: 26px;
	flex: 0 0 auto;
}

.oc-wordmark__glyph rect { fill: none; stroke: currentColor; stroke-width: 2; }
.oc-wordmark__glyph circle { fill: url(#oc-wordmark-accent); }
.oc-wordmark b { font-weight: 400; }

/* The one shared glassy capsule holding all six nav labels. */
.oc-nav {
	display: none; /* desktop only; mobile uses the overlay */
}

@media (min-width: 769px) {
	.oc-nav {
		display: block;
	}
}

.oc-nav__list {
	display: flex;
	align-items: center;
	gap: 2px;
	margin: 0;
	padding: 6px;
	list-style: none;
	border-radius: var(--oc-pill);
	background-color: rgba(14, 15, 19, 0.56);
	backdrop-filter: blur(var(--oc-blur));
	-webkit-backdrop-filter: blur(var(--oc-blur));
}

.oc-nav__link {
	/* [DEFECT 6 FIX] was display:inline-block at 39.4px tall. inline-flex + a
	   44px min-block-size keeps the measured 12/14 padding and the 14px label. */
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;
	padding: 12px 14px;       /* nav pill inactive: transparent, 12px 14px */
	border-radius: var(--oc-pill);
	background-color: transparent;
	color: var(--oc-ink);
	text-decoration: none;
	transition: background-color var(--oc-dur) var(--oc-ease);
}

.oc-nav__link:hover { background-color: rgba(255, 255, 255, 0.1); }

/* Active item gets the thin underline indicator.
   [ONE-PAGE, 2026-10-03] "Active" is the SECTION THE READER IS LOOKING AT,
   which only the client can know, so assets/js/scroll.js sets both hooks:
   `.is-active` on the link and `aria-current="true"` (a section is not a page,
   so "page" would be the wrong token). The `aria-current="page"` selector is
   kept because the four inner pages' own links legitimately use it, and
   `.oc-nav__item.is-active` is kept as the historical class hook. With
   JavaScript off, no item is marked — which is honest: nothing has scrolled. */
.oc-nav__item.is-active .oc-nav__link,
.oc-nav__link.is-active,
.oc-nav__link[aria-current="true"],
.oc-nav__link[aria-current="page"] {
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 6px;
}

.oc-header__cta { display: none; }

@media (min-width: 769px) {
	.oc-header__cta { display: inline-flex; }
}

/* Mobile header: wordmark + "HOME" pill + hamburger. */
.oc-burger {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0;
	inline-size: 44px;
	block-size: 44px;
	padding: 0;
	border: 0;
	border-radius: var(--oc-pill);
	background-color: rgba(14, 15, 19, 0.56);
	backdrop-filter: blur(var(--oc-blur));
	-webkit-backdrop-filter: blur(var(--oc-blur));
	color: var(--oc-ink);
	cursor: pointer;
}

.oc-burger__bars {
	position: relative;
	display: block;
	inline-size: 18px;
	block-size: 2px;
	background: currentColor;
}

.oc-burger__bars::before,
.oc-burger__bars::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	block-size: 2px;
	background: currentColor;
}

.oc-burger__bars::before { inset-block-start: -6px; }
.oc-burger__bars::after { inset-block-start: 6px; }

.oc-header__mobile {
	display: flex;
	align-items: center;
	gap: 8px;
}

@media (min-width: 769px) {
	.oc-header__mobile { display: none; }
}

/* [DEFECT 1 FIX] At 320px the whole mobile control group sat OFF-SCREEN
   (measured rect left 345.2 -> right 389.2 on a 320px viewport, header is
   position:fixed so maxScrollX was 0 and the burger — the only mobile nav —
   could not be tapped). Three causes, all fixed here:
     a) the static "HOME" pill duplicated the wordmark's own home link and ate
        ~70px; it is REMOVED from parts/header.html (see README deviation 27);
     b) the 24px header gap is 12px on mobile;
     c) the wordmark could not shrink — a flex item defaults to min-width:auto,
        so its nowrap text set an unshrinkable floor. min-inline-size:0 plus an
        ellipsis on the label makes it the flexible item, and the 44px burger is
        pinned flex:0 0 auto so it can never be the thing pushed out.
   Budget at 320px: 12 pad + 44 burger + 12 gap + 12 pad = 80px of chrome, so the
   wordmark gets 240px and needs ~180px. Verified at 320 / 360 / 390. */
@media (max-width: 768px) {
	.oc-header { gap: 12px; }

	.oc-wordmark {
		flex: 0 1 auto;
		min-inline-size: 0;
		/* [DEFECT 6 FIX] Shrinking the wordmark for defect 1 took the home link
		   itself down to 22px tall, which is below the WCAG 2.5.8 floor. It is
		   a link, so it needs the target too — 44px fits inside the 72px mobile
		   header without moving anything. */
		min-block-size: 44px;
		gap: 8px;
		font-size: var(--oc-fs-button);
		letter-spacing: 0.6px;
	}

	.oc-wordmark > span {
		min-inline-size: 0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.oc-wordmark__glyph { inline-size: 22px; block-size: 22px; }

	.oc-header__mobile { flex: 0 0 auto; }
}

.oc-pill-static {
	display: inline-block;
	padding: 12px 14px;
	border-radius: var(--oc-pill);
	background-color: rgba(14, 15, 19, 0.56);
	backdrop-filter: blur(var(--oc-blur));
	-webkit-backdrop-filter: blur(var(--oc-blur));
	color: var(--oc-ink);
	text-decoration: none;
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
}

/* Full-screen white overlay menu (spec OBSERVED at 390px). */
.oc-overlay {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: flex;
	flex-direction: column;
	gap: 24px;
	padding: 16px var(--oc-frame) 32px;
	background-color: var(--oc-ink);
	color: var(--oc-black);
	overflow-y: auto;
}

.oc-overlay[hidden] { display: none; }

.oc-overlay__top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
}

/* The EN/DE switcher was REMOVED (README deviation 28): both options pointed at
   #home, there is no German page, and a control that cannot keep its promise is
   worse than no control. `.oc-overlay__top` now holds only the close button, so
   it right-aligns it. */
.oc-overlay__top { justify-content: flex-end; }

.oc-overlay__close {
	inline-size: 44px;
	block-size: 44px;
	border: 0;
	border-radius: var(--oc-pill);
	background-color: rgba(0, 0, 0, 0.82);
	color: var(--oc-ink);
	font-size: 20px;
	line-height: 1;
	cursor: pointer;
}

.oc-overlay__list {
	display: flex;
	flex-direction: column;
	gap: 4px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.oc-overlay__list a {
	display: block;
	padding: 6px 0;
	color: var(--oc-black);
	text-decoration: none;
	font-family: var(--oc-font-system);
	font-size: clamp(30px, 8vw, 40px);
	font-weight: 500;
	line-height: 1.1;
	text-transform: uppercase;
	letter-spacing: -0.5px;
}

.oc-overlay__list .is-active a { font-weight: 700; }

.oc-overlay__rule {
	block-size: 0;
	margin: 8px 0;
	border: 0;
	border-block-start: var(--oc-hairline-dark);
}

.oc-overlay__phone {
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;   /* [DEFECT 6 FIX] WCAG 2.5.8 target size */
	color: var(--oc-black);
	font-size: var(--oc-fs-card);
	text-decoration: none;
}

.oc-overlay__social {
	display: flex;
	gap: 12px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.oc-overlay__social a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	inline-size: 44px;
	block-size: 44px;
	border-radius: var(--oc-pill);
	background-color: rgba(0, 0, 0, 0.82);
	color: var(--oc-ink);
	text-decoration: none;
	font-family: var(--oc-font-arial);
	font-size: 12px;
	letter-spacing: 0.56px;
}

body.oc-menu-open { overflow: hidden; }

/* ==========================================================================
   4. Scene scaffolding
   ========================================================================== */

.oc-scene {
	min-block-size: calc( var( --oc-vh, 1vh ) * 100 );
	display: flex;
	align-items: center;
	padding-block: calc(var(--oc-header-h) + 24px) 64px;
	scroll-margin-block-start: 0;
}

@media (max-width: 768px) {
	.oc-scene {
		/* `min-block-size: auto` WAS here. See assets/js/preloader.js for why it had
		   to go: it let #scenarios settle at 659px and #features at 684px against an
		   844px viewport, and the next scene then rendered inside the fold. The floor
		   is one viewport at every width — the original's contract is `height:
		   calc(var(--vh,1vh)*100)` EXACTLY (harvest/ordercube_styles.css:212), one
		   viewport, not "at most one". */
		min-block-size: calc( var( --oc-vh, 1vh ) * 100 );
		padding-block: calc(var(--oc-header-h) + 24px) 48px;
	}
}

.oc-scene--dark { background-color: var(--oc-bg); color: var(--oc-ink); }
.oc-scene--faq { background-color: var(--oc-faq-bg); color: var(--oc-ink); }

.oc-eyebrow {
	display: block;
	margin: 0 0 12px;
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
	color: var(--oc-ink-muted);
}

.oc-band--light .oc-eyebrow { color: var(--oc-gray-600); }

.oc-quote {
	font-style: italic;
	color: var(--oc-accent-start);
}

.oc-lead {
	font-size: var(--oc-fs-body);
	line-height: 1.45;
	color: var(--oc-ink-dim);
	max-inline-size: 58ch;
}

@media (max-width: 768px) {
	/* Spec: the type scale is nearly FLAT (22px -> 21.1px). Mobile adaptation is
	   layout reflow, not font shrinkage. This is the only size step down. */
	.oc-lead { font-size: 21.1px; line-height: 29.54px; }
}

/* ==========================================================================
   5. Media placeholders — every "photo" slot is a CSS gradient panel with an
   authored inline-SVG motif. No raster asset, no remote URL, anywhere.
   ========================================================================== */

.oc-media {
	position: relative;
	display: block;
	overflow: hidden;
	border-radius: var(--oc-card-radius);
	background-color: #15161c;
	/* [A11Y-2026-10-03] INVENTED DECORATION REMOVED — two accent radial "spotlight
	   glows" (rgba(184,155,131,0.34) and rgba(183,123,56,0.3), both fading to
	   transparent) were the detector's 4 flagged `radial-spotlight-glow` instances on
	   this one rule (16 .oc-media elements exist; 4 were large enough to flag).
	   ORIGINAL'S CORRESPONDING COUNT: ZERO. The counterpart element is the original's
	   image container, `.feature-image-container`
	   (harvest/ordercube_styles.css:252-264), which declares NO background property at
	   all and holds a real <img> (harvest/home.html:738). Same for `.hero-media`
	   (harvest/ordercube_styles.css:213). Removed, not restyled, per the
	   replicate-exactly-and-add-nothing brief. The 160deg linear ramp is left in place:
	   it is the placeholder surface itself, not an added glow, and removing it would
	   leave an empty box where the original has a photograph (logged as a separate
	   replication gap, not worked here). */
	background-image:
		linear-gradient(160deg, #23242c 0%, #121319 58%, #0b0c10 100%);
}

.oc-media--3x2 { aspect-ratio: 3 / 2; }
.oc-media--16x9 { aspect-ratio: 16 / 9; }
.oc-media--4x3 { aspect-ratio: 4 / 3; }
.oc-media--1x1 { aspect-ratio: 1 / 1; }
.oc-media--3x4 { aspect-ratio: 3 / 4; }

.oc-media__motif {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	opacity: 0.6;
}

/* .oc-media__caption — DELETED 1.5.0.
   It stamped "artwork placeholder - 16:9" over fifteen finished original
   illustrations. The SVGs are the artwork, not a stand-in for artwork, and
   labelling them as pending is the single loudest reason the page read as
   unfinished. Provenance now lives in ONE footer line and in each image's own
   `alt`, which describes what the picture DEPICTS. The rule is removed, not
   merely unused, so nobody re-attaches it. */

.oc-media--studio {
	background-color: #4a4a50;
	/* [A11Y-2026-10-03] INVENTED DECORATION REMOVED — the white spotlight radial
	   (rgba(255,255,255,0.34) -> transparent). ORIGINAL'S CORRESPONDING COUNT: ZERO,
	   same citation as .oc-media above. The second radial is kept: it is the
	   placeholder's own body shading (opaque stops, no fade to transparent, so not a
	   glow), the direct analogue of the 160deg ramp on .oc-media. */
	background-image:
		radial-gradient(90% 90% at 50% 50%, #6a6a72 0%, #3a3a42 56%, #24242a 100%);
}

/* Abstract, wordless partner placeholder marks. Never a real company's logo. */
.oc-marks {
	display: flex;
	flex-wrap: wrap;
	gap: 16px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.oc-marks__item {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	inline-size: 140px;
	block-size: 50px;
	padding: 8px;
	border: var(--oc-hairline);
	border-radius: var(--oc-card-radius);
	color: var(--oc-ink-muted);
	font-family: var(--oc-font-arial);
	font-size: 12px;
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
}

.oc-marks__item svg { inline-size: 26px; block-size: 26px; flex: 0 0 auto; }

.oc-band--light .oc-marks__item {
	border: var(--oc-hairline-dark);
	color: var(--oc-gray-600);
	filter: grayscale(1);
}

/* Recurring small "made locally" trust badge, bottom-right of several scenes. */
.oc-badge {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 6px 12px;
	border: var(--oc-hairline);
	border-radius: var(--oc-pill);
	font-family: var(--oc-font-arial);
	font-size: 12px;
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
	color: var(--oc-ink-muted);
}

.oc-badge svg { inline-size: 18px; block-size: 12px; }
.oc-band--light .oc-badge { border: var(--oc-hairline-dark); color: var(--oc-gray-600); }

/* ==========================================================================
   6. Scene 1 — HERO
   [DEVIATION] The spec's distortion #1 states the original's giant display
   headline is an image/SVG graphic in front of a visually-hidden 32px <h1>, and
   that its true font/size/weight could NOT be extracted. So this is NOT a
   measured match: it is a deliberate CSS re-interpretation as real, selectable,
   responsive text (clamp 40px -> 104px) with the white card-cutout behind each
   line, which keeps the visual read while staying accessible and translatable.
   ========================================================================== */

.oc-hero__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 40px;
	inline-size: 100%;
}

@media (min-width: 1024px) {
	.oc-hero__inner {
		grid-template-columns: minmax(0, 1fr) 200px;
		align-items: end;
	}
}

/* [DEFECT 3 FIX] The lockup WAS a <div> holding an <h1> ("Table call") and an
   <h2>. Under the replication brief the lockup is now a DECORATIVE <div> with
   `aria-hidden="true"`, and the heading is a separate visually hidden <h1>
   duplicate beside it — the original's own shape (home.html:514-530). Not one
   visual property changed: the knockout slabs, the ragged widths, the 6px line
   gap and the 28px trailing space all come from the rules below, which match on
   the class, not the tag. */
.oc-hero-lockup {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 6px;
	margin-block: 0 28px;
	font-size: inherit;
	font-weight: inherit;
}

.oc-hero-lockup .oc-hero-line,
.oc-404 .oc-hero-line {
	margin: 0;
	font-family: var(--oc-font-system);
	font-size: clamp(40px, 2rem + 7.2vw, 104px);
	font-weight: 800;
	line-height: 0.96;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	text-wrap: balance;
}

/* The white card-cutout treatment: each line sits in its own flat white slab,
   knocking the type out to the scene background. Sharp corners, no shadow. */
.oc-hero-line--cut {
	display: inline-block;
	padding: 0.06em 0.16em 0.1em;
	border-radius: var(--oc-card-radius);
	background-color: var(--oc-ink);
	color: var(--oc-bg);
}

.oc-hero-line--open { color: var(--oc-ink); padding-inline: 0.02em; }

/* ==========================================================================
   THE HERO SLABS BLEED LEFT INTO THE WHITE FRAME.  Added 1.8.5.

   Client, verbatim: "the white box should slide in from the left and the white
   box touches the white frames."

   HOW THE ORIGINAL COMPOSES IT, measured, not inferred. Its headline is an SVG
   knockout: a white `<rect>` masked by the type (home.html:520-537), and the
   rect is placed at `x="0"` of a `0 0 1440 480` viewBox (js:589). The wrap that
   carries the SVG is then pulled OUT of the content column:

     ordercube_styles.css:396   .headline-wrap  { position: relative; left: -3.5rem; }
     ordercube_styles.css:397   .headline-cutout{ width: calc(100% - 1.5rem); }
     ordercube_styles.css:389   .hero-content   { padding: var(--frame); }   = 24px
     ordercube_styles.css:393   .container      { margin-left: 1.5rem; }     = 24px

   So its slab's left edge lands at 24 + 24 - 56 = -8px: EIGHT PIXELS PAST the
   viewport's left edge, running underneath the 24px white frame border
   (`.frame { border: var(--frame) solid #fff; z-index: 5 }`, css:32-38). The
   frame sits ON TOP of the slab and both are #fff, so the two whites are
   continuous and there is no dark seam. That is what "touches the white frames"
   means — it underlaps, it does not merely abut. Its knockout text still starts
   at viewBox x=20, i.e. ~11px in, so the LETTERS never reach the frame.

   WHAT THIS THEME HAD. The slab is a padded inline-block, not an SVG rect, and
   it sat at the content edge of `.oc-scene > .oc-hero__inner`, which is
   `max-inline-size: var(--oc-grid-max)` (1280px) centred with
   `padding-inline: var(--oc-gutter)`. MEASURED at 1440: (1440-1280)/2 + 24 =
   104px, against a frame inner edge at 24px — an 80px dark gap, exactly as
   reported.

   THE FIX reproduces the RELATIONSHIP rather than the -3.5rem, because -3.5rem
   is only correct for the original's own container chain. `--oc-hero-bleed` is
   the distance from the slab's left edge back to 8px past the viewport edge,
   written in terms of the chain that actually places it:

     centring gutter   max(0px, (100vw - grid-max) / 2)
     column padding    var(--oc-gutter)
     overshoot         8px   <- the original's own measured -8px landing

   The lockup takes that as a negative inline-start margin so the slab moves
   left, and the slab takes the SAME amount back as inline-start padding so the
   TYPE does not move with it. Only the white extends. That resolves to a left
   edge of -8px at every breakpoint — 1440, 1024 and 390 alike — which is the
   original's number at all three.

   `100vw` is safe here: `body` is `overflow: hidden` on the one-pager
   (.oc-onepage-shell), so there is no scrollbar for 100vw to disagree with.

   The slab's RIGHT edge does not move: it gains `bleed` of left padding while
   its box starts `bleed` further left, so its width grows and its right edge
   stays put. That is why this cannot introduce right-side overflow.
   ========================================================================== */
.oc-hero .oc-hero-lockup {
	--oc-hero-bleed: calc(
		max(0px, (100vw - var(--oc-grid-max)) / 2) + var(--oc-gutter) + 8px
	);
	margin-inline-start: calc(-1 * var(--oc-hero-bleed));
}

.oc-hero .oc-hero-lockup .oc-hero-line--cut {
	/* The bleed, plus the 0.16em the shorthand above already set. */
	padding-inline-start: calc(var(--oc-hero-bleed) + 0.16em);
}

/* --- and they slide in from the left on lock-in -------------------------

   MEASURED on the original, which drives this from JS on its hero entry:

     ordercube_script.js:2604   // "Slide white SVG rectangles from left to
                                //  right (no fade)"
     ordercube_script.js:2615   r1 -> 'slideInFromLeftSVG 0.8s ease forwards'
     ordercube_script.js:2621   r2 -> 'slideInFromLeftSVG 0.8s ease 0.1s forwards'
     ordercube_styles.css:3796  @keyframes slideInFromLeftSVG
                                  from { translateX(-110%) } to { translateX(0) }
     ordercube_styles.css:3815  #r1,#r2 { transform: translateX(-110%) }

   So: 800ms, `ease`, no fade, and the second slab is 0.1s behind the first.
   NOTE the stagger is the original's 0.1s, NOT the 0.2s + 0.08s/item of the
   generic reveal — js:2621 states it outright for these two elements, and the
   generic figure (js:3526) belongs to the impact cards.

   The trigger is the reveal layer already wired and verified: motion.js adds
   `.is-revealed` on `oc:section-enter`, which scroll.js dispatches
   SETTLE_DELAY_MS (100ms) after the hero settles. So this fires on lock-in,
   never on a scroll threshold.

   The lockup itself opts OUT of the generic reveal. That reveal fades from
   `opacity: 0` and slides from `translateX(20px)` — the wrong direction and a
   fade the original explicitly does not do ("no fade", js:2604). Taking the
   wrapper out of it also stops the two transforms from composing. */
html.oc-js [data-oc-reveal] > .oc-hero-lockup,
html.oc-js [data-oc-reveal].is-revealed > .oc-hero-lockup {
	opacity: 1;
	transform: none;
	transition: none;
}

html.oc-js .oc-hero .oc-hero-lockup .oc-hero-line--cut {
	transform: translateX(-110%);          /* ordercube_styles.css:3815 */
}

html.oc-js [data-oc-reveal].is-revealed .oc-hero-lockup .oc-hero-line--cut {
	animation: ocHeroSlabIn var(--oc-dur-hero-slab) ease forwards;
}

/* js:2621 — the second slab trails the first by 0.1s. */
html.oc-js [data-oc-reveal].is-revealed .oc-hero-lockup .oc-hero-line--cut:nth-child(2) {
	animation-delay: 0.1s;
}

@keyframes ocHeroSlabIn {
	from { transform: translateX(-110%); }
	to   { transform: translateX(0); }
}

/* REDUCED MOTION: end state, never the start state.
   Scoped to these two slabs on purpose. assets/js/motion.js pins its
   `reduced` flag to FALSE as a deliberate replication decision (the original
   has zero `prefers-reduced-motion` anywhere), and that standing call is not
   mine to reverse from inside a hero fix. What IS mine is the animation added
   here: a reader who asked for less motion gets the slab at rest, in place and
   opaque, with no slide and no chance of being left off-frame at -110%. */
/* REDUCED MOTION, background video. CSS cannot stop `autoplay`, so
   assets/js/motion.js does the actual gating; this is the visual half, and it
   is what guarantees a still even if the script never runs. `object-fit` keeps
   the poster framed exactly as the video was. Scoped to these two elements:
   motion.js pins its own `reduced` flag to false as a standing replication
   decision and that is not reversed here. */
@media (prefers-reduced-motion: reduce) {
	.oc-scene__media-video {
		/* Chrome paints `poster` only until the first frame is decoded; with
		   playback never started that is the whole time. */
		background-position: center;
		background-size: cover;
		background-repeat: no-repeat;
	}
}

@media (prefers-reduced-motion: reduce) {
	html.oc-js .oc-hero .oc-hero-lockup .oc-hero-line--cut,
	html.oc-js [data-oc-reveal].is-revealed .oc-hero-lockup .oc-hero-line--cut {
		transform: none;
		animation: none;
		opacity: 1;
	}
}

.oc-hero__cues {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 24px;
	margin-block-start: 32px;
}

.oc-hero__scroll-cue {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	color: var(--oc-ink-muted);
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
	text-decoration: none;
}

.oc-hero__trust {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.oc-hero__trust .oc-marks {
	flex-direction: column;
}

/* Mobile: the 5 trust marks become a horizontal 3-up scroll strip under the
   headline instead of a right-hand column (spec, scene 1 "Mobile"). */
@media (max-width: 1023px) {
	.oc-hero__trust .oc-marks {
		flex-direction: row;
		flex-wrap: nowrap;
		overflow-x: auto;
		scroll-snap-type: x proximity;
		padding-block-end: 8px;
	}
	.oc-hero__trust .oc-marks__item {
		flex: 0 0 auto;
		inline-size: calc((100% - 32px) / 3);
		min-inline-size: 104px;
		scroll-snap-align: start;
	}
}

/* ==========================================================================
   7. Scene 2 — SCENARIOS (5-slide problem storytelling)
   Full-bleed media panel + bottom-left dark scrim + white caption card +
   right-aligned vertical list of 5 selector chips.
   ========================================================================== */

/* [DEFECT 1 FIX] .oc-scene is `display:flex; flex-direction:row`, so a scene with
   TWO element children splits the row between them. Every other scene avoids this
   by giving the section exactly ONE child — a 1-column grid that gains columns at
   >=1024px (.oc-hero__inner, .oc-features__inner). Scenarios emitted the stage and
   the cues row as two siblings, so at 390px they sat side by side (188px / 190px).
   This wrapper applies the same established mechanism: one grid child, one column
   below 1024px, so the scene stacks instead of splitting. */
.oc-scenarios__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 32px;
	inline-size: 100%;
}

/* [DEFECT 1 FIX] Scenarios no longer borrows the hero's .oc-hero__cues. It owns its
   own class so the two scenes cannot drift into each other; the margin the hero
   needs (it sits in flow) is dropped here because the grid `gap` already spaces it. */
.oc-scenarios__cues {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 24px;
}

.oc-scenarios__stage {
	position: relative;
	inline-size: 100%;
	min-block-size: min(72vh, 620px);
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	align-items: end;
	overflow: hidden;
}

.oc-scenarios__stage::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 0;
	background: linear-gradient(to top right, rgba(14, 15, 19, 0.92) 0%, rgba(14, 15, 19, 0.45) 42%, rgba(14, 15, 19, 0) 72%);
	pointer-events: none;
}

.oc-scenarios__stage > .oc-media {
	position: absolute;
	inset: 0;
	aspect-ratio: auto;
	z-index: -1;
}

.oc-scenarios__layer {
	/* [DEFECT 1 FIX] content-box + `inline-size:100%` + 24px padding each side made
	   this 48px WIDER than its grid area (measured 236px inside a 188px parent), which
	   is what the stage's overflow:hidden was clipping. border-box makes the declared
	   100% include the padding. */
	box-sizing: border-box;
	position: relative;
	z-index: 1;
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 24px;
	padding: 24px;
	inline-size: 100%;
}

@media (min-width: 1024px) {
	.oc-scenarios__layer {
		grid-template-columns: minmax(0, 420px) minmax(0, 1fr) minmax(0, 260px);
		align-items: end;
	}
	.oc-scenarios__spacer { display: block; }
}

.oc-scenarios__caption {
	/* [DEFECT 1 FIX] Same defect class as .oc-scenarios__layer: the <=1023px rule
	   below sets `inline-size:100%`, and under content-box the 24px padding was
	   added ON TOP of that 100%, pushing the card 48px past its grid area (measured
	   362px inside a 314px column, 24px of it clipped to the right by the stage's
	   overflow:hidden). border-box makes the 100% inclusive. */
	box-sizing: border-box;
	background-color: var(--oc-ink);
	color: var(--oc-black);
	border-radius: var(--oc-card-radius);
	box-shadow: none;
	padding: 24px;
}

.oc-scenarios__caption p { color: var(--oc-black); max-inline-size: 42ch; }

.oc-chips {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 8px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.oc-chips a {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	min-block-size: 44px;   /* [DEFECT 6 FIX] WCAG 2.5.8 target size */
	padding: 12px 14px;
	border-radius: var(--oc-pill);
	background-color: rgba(0, 0, 0, 0.5);
	backdrop-filter: blur(8px);
	color: var(--oc-ink);
	text-decoration: none;
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
	white-space: nowrap;
}

.oc-chips .is-active a {
	background-image: var(--oc-accent-aa);
	/* [A11Y-2026-10-03] tracks --oc-accent-aa-end for the same reason as the CTA. */
	background-color: var(--oc-accent-aa-end);
}

.oc-chips__swatch {
	inline-size: 22px;
	block-size: 22px;
	border-radius: var(--oc-pill);
	background-image: var(--oc-accent);
	flex: 0 0 auto;
}

/* Mobile: the 5-tab vertical list collapses to an "n / 5" counter + one active
   chip, placed top-right of the media panel (spec, scene 2 "Mobile"). */
.oc-scenarios__counter {
	position: absolute;
	inset-block-start: 16px;
	inset-inline-end: 16px;
	z-index: 2;
	display: flex;
	align-items: center;
	gap: 8px;
}

@media (min-width: 1024px) {
	.oc-scenarios__counter { display: none; }
}

@media (max-width: 1023px) {
	.oc-chips { display: none; }
	.oc-scenarios__caption { inline-size: 100%; }
}

/* ==========================================================================
   8. Scene 3 — FEATURES (6 features, studio render centre)
   3-part row: left feature list / centre render / right quote + paragraph.
   Background: neutral gray radial gradient, no photography.
   ========================================================================== */

.oc-features {
	background-color: #3c3c42;
	/* [A11Y-2026-10-03] INVENTED DECORATION REMOVED, and it was also the cause of two
	   P0 contrast failures. ORIGINAL'S CORRESPONDING COUNT: ZERO — the original's
	   features section is a FLAT fill, `#features { background: #dddddd }`
	   (harvest/ordercube_styles.css:242-245). The word "radial" in the original's own
	   comment there refers to the JS-injected scrim, which this theme already
	   reproduces faithfully at `.oc-features .oc-scene__scrim` (see
	   harvest/ordercube_script.js:2391) — not to any section background.
	   WHY IT WAS ALSO A CONTRAST BUG: the radial's 0% stop is #6e6e76 (L=0.157750),
	   a LIGHT centre under light text, which is the surface the detector resolved:
	     .oc-quote           #B89B83 on #6e6e76  1.962:1  FAIL (28px large, bar 3.0)
	     .oc-features__copy  ink-dim on #6e6e76  4.2:1    FAIL (18px normal, bar 4.5)
	   On the flat #3c3c42 that the removal exposes:
	     .oc-quote           #B89B83 on #3c3c42  4.201:1  PASS
	     .oc-features__copy  rgba(255,255,255,0.85) composites to #E2E2E3  8.461:1  PASS
	   NOTE the direction: the original is LIGHTER than this theme here, not darker.
	   Restoring #dddddd is a full re-valuing of the scene (every text colour in it
	   inverts) and is NOT done in this pass — logged as an open replication gap. */
}

.oc-features__inner {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 32px;
	inline-size: 100%;
}

/* [DEFECT 4 FIX] The measured track set was 260 / 624 / 300 with a 48px gap. At
   22px/400 the 300px copy column held ~29 characters per line — roughly half the
   45-75 cpl readability band, so the paragraph read as a ragged ribbon. The copy
   column is widened to 424px and the render column gives up the difference
   (1280 - 240 - 424 - 96 = 520). Combined with the 18px/1.6 body below this is
   ~48 cpl. The list column loses 20px, which it has: its longest label
   ("Pay at table", 14px uppercase) measures well under 240px minus its chrome. */
@media (min-width: 1024px) {
	.oc-features__inner {
		grid-template-columns: minmax(0, 240px) minmax(0, 1fr) minmax(0, 424px);
		align-items: center;
		gap: 48px;
	}
}

.oc-features__list {
	display: flex;
	flex-direction: column;
	gap: 4px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.oc-features__list a {
	display: flex;
	align-items: center;
	gap: 12px;
	min-block-size: 44px;   /* [DEFECT 6 FIX] WCAG 2.5.8 target size */
	padding: 12px 14px;
	border-radius: var(--oc-pill);
	color: var(--oc-ink);
	text-decoration: none;
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
}

.oc-features__list .is-active a {
	background-color: rgba(0, 0, 0, 0.5);
}

.oc-features__icon {
	inline-size: 22px;
	block-size: 22px;
	flex: 0 0 auto;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.6;
}

/* Mobile: the left list becomes a horizontal icon strip above the render. */
@media (max-width: 1023px) {
	.oc-features__list {
		flex-direction: row;
		flex-wrap: nowrap;
		gap: 8px;
		overflow-x: auto;
		padding-block-end: 8px;
	}
	.oc-features__list li { flex: 0 0 auto; }
}

/* [DEFECT 4 FIX] 22px body in a narrow column is what produced the 29 cpl. The
   body drops to 18px/1.6 — the `card` step of the measured scale, not an
   invented size — which is what takes the widened 424px column to ~48 cpl. */
.oc-features__copy p {
	margin: 0;
	color: var(--oc-ink-dim);
	font-size: var(--oc-fs-card);
	line-height: 1.6;
}
/* [DEFECT 3 FIX] 22px/400 is NORMAL text => 4.5:1. The measured accent-start
   #B89B83 over this scene's radial gradient ran 3.16:1 (worst, the quote box's
   top-left) to 4.73:1 (bottom-right) — failing across most of its extent. The
   `accent-tint` token is the same bronze hue (27deg) and saturation, lightened to
   relative luminance 0.5899, which puts the WORST point at 5.02:1. Scoped to this
   scene only; the contact-scene .oc-quote sits on flat #0E0F13 and already passes. */
/* [DEFECT 4 FIX] With the body at 18px the pull-quote at 22px no longer read as
   a separate register — the two blocks merged. 28px/1.25 italic restores the
   contrast (1.56x the body size) without touching the measured colour decision
   in deviation 22: `accent-tint` was computed for LARGE text here only after it
   was computed to pass at 22px NORMAL text, so raising the size only loosens the
   bar it already clears (5.02:1 at the worst point vs the 3.0:1 large-text
   floor). */
.oc-features__copy .oc-quote {
	display: block;
	margin-block-end: 16px;
	font-size: 28px;
	line-height: 1.25;
	font-style: italic;
	color: var(--oc-accent-tint);
}

/* ==========================================================================
   9. Scene 4 — REFERENCES (black band over white 3-col band)
   ========================================================================== */

.oc-references { display: block; padding-block: 0; }

.oc-references__top {
	background-color: var(--oc-bg);
	min-block-size: 45vh;
	display: flex;
	align-items: center;
	padding-block: calc(var(--oc-header-h) + 24px) 32px;
}

.oc-references__top-inner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	inline-size: 100%;
}

.oc-dots {
	display: flex;
	gap: 8px;
	margin: 0;
	padding: 0;
	list-style: none;
}

/* [DEFECT 6 FIX] The dot STAYS 10px visually; the target grows to 24x24 by
   painting the dot with a background-image on a transparent 24px box, so the
   measured indicator size is unchanged and the hit area clears WCAG 2.5.8. */
.oc-dots button {
	inline-size: 24px;
	block-size: 24px;
	padding: 0;
	border: 0;
	border-radius: var(--oc-pill);
	background-color: transparent;
	background-image: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.35) 0 5px, transparent 5px);
	cursor: pointer;
}

.oc-dots .is-active button {
	background-color: transparent;
	background-image: radial-gradient(circle at 50% 50%, var(--oc-ink) 0 5px, transparent 5px);
}

.oc-references__band {
	background-color: var(--oc-ink);
	color: var(--oc-black);
	min-block-size: 55vh;
	display: flex;
	align-items: stretch;
}

.oc-references__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0;
	inline-size: 100%;
	align-items: stretch;
}

@media (min-width: 1024px) {
	.oc-references__grid {
		grid-template-columns: 40% 20% 40%;
	}
}

.oc-references__photo { position: relative; }
.oc-references__photo .oc-media { block-size: 100%; aspect-ratio: 4 / 3; }

.oc-references__photo .oc-icon-btn {
	position: absolute;
	inset-block-end: 12px;
	inset-inline-end: 12px;
}

.oc-references__logo {
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
	background-color: #ece7e1;
}

.oc-references__quote {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 16px;
	padding: 32px 24px;
	margin: 0;
}

.oc-references__quote blockquote {
	margin: 0;
	border: 0;
	padding: 0;
	font-style: italic;
	font-size: var(--oc-fs-body);
	color: var(--oc-black);
}

.oc-references__attrib {
	margin: 0;
	font-size: var(--oc-fs-link);
	line-height: 1.5;
	color: var(--oc-gray-600);
}

.oc-references__arrows { display: flex; gap: 8px; }

/* ==========================================================================
   10. Scene 5 — IMPACT (ROI calculator)
   near-black + warm bronze radial glow from right-of-centre.
   ========================================================================== */

.oc-impact {
	background-color: var(--oc-bg);
	/* [A11Y-2026-10-03] INVENTED DECORATION REMOVED — this was the detector's 5th
	   `radial-spotlight-glow` ("impact", #b89b83 a0.28 -> transparent) and the cause of
	   FOUR P0 contrast failures. ORIGINAL'S CORRESPONDING COUNT: ZERO. The original's
	   impact section background is a real PHOTOGRAPH,
	   `<img src="Assets/example_growth.jpg">` (harvest/home.html:1179-1182); the
	   section itself gets only `overflow-x: visible`
	   (harvest/ordercube_styles.css:5428-5430) and no background declaration at all.
	   The `scene-glow` preset is removed from theme.json in the same change — the
	   var() here is the FALLBACK, so deleting only the fallback would have changed
	   nothing.
	   WHAT IT COST IN CONTRAST (the glow resolves to a light accent haze under light
	   text; after removal the surface is the flat --oc-bg #0E0F13, L=0.004821):
	     .oc-impact__title     #FFFFFF        2.6:1 -> 19.153:1  PASS
	     .oc-calc__title       #FFFFFF        2.6:1 -> 19.153:1  PASS
	     .oc-calc__disclaimer  ink-muted 0.6  1.8:1 ->  7.24:1   PASS (measured composited)
	     .oc-impact__note      ink-muted 0.6  1.8:1 ->  7.24:1   PASS (measured composited) */
}

.oc-calc {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 40px;
	inline-size: 100%;
}

@media (min-width: 1024px) {
	.oc-calc { grid-template-columns: minmax(0, 420px) minmax(0, 1fr); gap: 64px; align-items: start; }
}

.oc-calc__title { margin: 0 0 24px; max-inline-size: 22ch; }

.oc-calc__controls {
	display: flex;
	flex-direction: column;
	gap: 12px;
	margin: 0;
	padding: 0;
	border: 0;
}

/* [A11Y-2026-10-03] The 1.0:1 finding — INVISIBLE TEXT, the same colour as its
   background. The <legend> is `.oc-visually-hidden`, so a sighted reader never sees
   it, but it INHERITS the dark page's white ink while the surface resolved behind
   its text box is #FFFFFF — measured two independent ways, both agreeing:
     * the detector reports 1.0:1 "via solid-background" with fg #ffffff;
     * a composited-pixel read of the text box returns bg #FFFFFF, worst 1.000:1.
   White on white is a real same-colour condition even behind a clip, and it is one
   stylesheet change away from being visible. The legend keeps its accessible
   grouping role for the fieldset and gets an ink that contrasts with the surface it
   actually sits over: --oc-black on #FFFFFF is 21.000:1.
   Scoped to this legend rather than put on `.oc-visually-hidden` globally, because
   no single ink clears 4.5:1 against BOTH #FFFFFF and this theme's mid surfaces —
   a universal value would just move the failure.
   The opaque `background-color` is belt-and-braces: it paints nothing (the element
   is clipped by `clip-path: inset(50%)`), but it makes the pair DECLARED rather than
   inherited, so any reader that resolves a background by walking ancestors gets
   #000000 on #FFFFFF = 21.000:1 deterministically instead of guessing. */
.oc-calc__controls legend {
	padding: 0;
	color: var(--oc-black);
	background-color: var(--oc-ink);
}

.oc-stepper {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	padding: 8px 8px 8px 20px;
	border-radius: var(--oc-pill);
	background-color: rgba(255, 255, 255, 0.07);
}

.oc-stepper__label {
	font-size: var(--oc-fs-link);
	color: var(--oc-ink-dim);
}

.oc-stepper__group {
	display: flex;
	align-items: center;
	gap: 8px;
	flex: 0 0 auto;
}

.oc-stepper__value {
	min-inline-size: 4ch;
	text-align: center;
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-card);
	font-weight: 300;
	letter-spacing: 0.56px;
	color: var(--oc-ink);
	font-variant-numeric: tabular-nums;
}

/* [DEFECT 6 FIX] CSS-only target growth. The calculator's logic, its markup and
   its live regions are deliberately NOT touched: the label gains a 44px floor and
   the native checkbox goes 20px -> 24px (the WCAG 2.5.8 minimum), nothing else. */
.oc-calc__optin {
	display: flex;
	align-items: center;
	gap: 12px;
	min-block-size: 44px;
	padding: 14px 20px;
	border-radius: var(--oc-pill);
	background-color: rgba(255, 255, 255, 0.07);
	font-size: var(--oc-fs-link);
	color: var(--oc-ink-dim);
	cursor: pointer;
}

.oc-calc__optin input {
	inline-size: 24px;
	block-size: 24px;
	margin: 0;
	accent-color: var(--oc-accent-end);
	flex: 0 0 auto;
}

/* Mobile: the opt-in row loses its pill chrome and renders as plain text +
   checkbox (spec, scene 5 "Mobile"). */
@media (max-width: 768px) {
	.oc-calc__optin {
		padding: 0;
		border-radius: 0;
		background-color: transparent;
		align-items: center;
	}
	.oc-stepper { border-radius: var(--oc-pill); padding-inline-start: 16px; }
}

/* 3x2 grid of flat, sharp-cornered white stat cards. 24px padding, 16px gap. */
.oc-stats {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 16px;
}

@media (max-width: 1023px) {
	.oc-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 599px) {
	.oc-stats { grid-template-columns: minmax(0, 1fr); }
}

.oc-stat {
	display: flex;
	flex-direction: column;
	gap: 6px;
	min-block-size: 189px;
	padding: 24px;
	border-radius: 0;        /* sharp corners, measured */
	box-shadow: none;        /* no shadow anywhere in this design system */
	background-color: var(--oc-ink);
	color: var(--oc-black);
}

.oc-stat__audience {
	font-family: var(--oc-font-arial);
	font-size: 12px;
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
	color: var(--oc-gray-600);
}

.oc-stat__metric {
	margin: 0;
	font-size: var(--oc-fs-card);
	font-weight: 600;
	color: var(--oc-black);
}

.oc-stat__delta {
	font-size: 36px;
	line-height: 1.1;
	font-weight: 600;
	color: var(--oc-positive);
	font-variant-numeric: tabular-nums;
}

.oc-stat__caveat {
	margin: 0;
	font-size: 14px;
	line-height: 1.5;
	color: var(--oc-gray-600);
}

.oc-calc__disclaimer {
	margin: 16px 0 0;
	font-size: 14px;
	color: var(--oc-ink-muted);
}

/* ==========================================================================
   11. Scene 6 — FAQ (warm dark brown, centred 1000px column)
   ========================================================================== */

.oc-faq__inner {
	inline-size: 100%;
	max-inline-size: 1000px;
	margin-inline: auto;
}

.oc-faq__head {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 320px;
	gap: 24px;
	align-items: end;
	margin-block-end: 32px;
}

.oc-faq__title {
	margin: 0 0 12px;
	/* css:2438 — `clamp(16.8px, 2.4vw, 28.8px)` / 400 / 1.2, measured. */
	font-size: clamp(16.8px, 2.4vw, 28.8px);
	font-weight: var(--oc-fw-regular);
	line-height: 1.2;
	letter-spacing: normal;
	text-transform: none;
	color: var(--oc-ink);
}

.oc-faq__subtitle {
	margin: 0;
	/* css:2446 — `clamp(16px, 1.6vw, 20px)` / 400 / 1.5, measured. */
	font-size: clamp(16px, 1.6vw, 20px);
	line-height: 1.5;
	color: var(--oc-ink-dim);
}

/* THE SEARCH PILL — restored. css:2463-2471 is the measured treatment: a pill
   with a hairline border over a translucent surface and a backdrop blur. */
.oc-faq__search-container {
	display: flex;
	align-items: center;
	border: var(--oc-hairline);
	border-radius: var(--oc-pill);
	background: rgba(255, 255, 255, 0.06);
	backdrop-filter: blur(var(--oc-blur));
	overflow: hidden;
}

/* `outline: none` with NO replacement ring is the original's own rule
   (ordercube_styles.css:2480) and the only `outline` declaration in its whole
   stylesheet. Replicated. */
.oc-faq__search {
	flex: 1 1 auto;
	min-inline-size: 0;
	border: none;
	background: transparent;
	padding: 1rem 1.5rem;
	font-family: inherit;
	font-size: 16px;
	color: #fff;
	outline: none;
}

.oc-faq__search::placeholder { color: rgba(255, 255, 255, 0.6); }

.oc-faq__search-btn {
	flex: 0 0 auto;
	display: grid;
	place-items: center;
	inline-size: 3rem;
	block-size: 3rem;
	border: none;
	background: transparent;
	color: var(--oc-ink);
	cursor: pointer;
}

/* A row the search filtered out. The original sets `display: none` on the item
   (ordercube_script.js filterFAQs). */
.oc-faq-item[hidden] { display: none; }

/* THE PAGE INDICATOR — restored as the original's chrome. See the pattern
   header: its page-break maths cannot exist on a natively scrolling document,
   so the arrows are inert. It is shipped because the original ships it. */
.oc-faq__pagination {
	display: flex;
	justify-content: center;
	margin-block-start: 24px;
}

.oc-faq__pagination-controls {
	display: flex;
	align-items: center;
	gap: 16px;
	color: var(--oc-ink-dim);
}

.oc-faq__nav-btn {
	display: grid;
	place-items: center;
	inline-size: 2.5rem;
	block-size: 2.5rem;
	padding: 0;
	border: var(--oc-hairline);
	border-radius: var(--oc-pill);
	background: transparent;
	color: inherit;
	cursor: pointer;
}

.oc-faq__page-info { font-size: 14px; font-variant-numeric: tabular-nums; }

/* The head is a 2-track grid again — heading left, 320px search pill right — as
   the original lays it out (home.html:1365-1378). An earlier pass deleted the
   pill as a dead control and dropped the second track with it; the pill is back
   (and, measured, it was never dead) so the track is back. */

.oc-faq__list { display: flex; flex-direction: column; }
/* The `core/details` fallback rules that used to live here are gone with the
   <details> markup they styled. The FAQ rows are the original's div pair now
   (see patterns/scene-6-faq.php). */

/* [DEFECT 7] The `.oc-pager` rule is gone too: the "Page 1 / 4" nav it styled
   claimed four pages beside six rows that were all already on screen. */

/* ==========================================================================
   12. Scene 7 — CONTACT (25% dark card column / 75% portrait)
   ========================================================================== */

.oc-contact { display: block; padding-block: 0; }

.oc-contact__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0;
	min-block-size: 100vh;
	align-items: center;
}

@media (min-width: 1024px) {
	.oc-contact__grid { grid-template-columns: 25% 75%; }
	.oc-contact__col {
		position: relative;
		z-index: 2;
		/* the info card visually overlaps the portrait's left edge */
		margin-inline-end: -64px;
	}
}

@media (max-width: 1023px) {
	.oc-contact__grid { min-block-size: auto; }
}

.oc-contact__col {
	padding: calc(var(--oc-header-h) + 24px) var(--oc-gutter) 48px;
	background-color: var(--oc-bg);
}

.oc-contact__card {
	background-color: var(--oc-ink);
	color: var(--oc-black);
	border-radius: var(--oc-card-radius);
	box-shadow: none;
	padding: 24px;
	display: flex;
	flex-direction: column;
	gap: 16px;
}

/* [A11Y-2026-10-03] `h2` joins the selector: the card's company name is promoted
   from h3 to h2 to repair the skipped level (see patterns/scene-7-contact.php), and
   the visual treatment must not change with the tag. */
.oc-contact__card h2,
.oc-contact__card h3 { margin: 0 0 4px; font-size: var(--oc-fs-card); font-weight: 600; }
.oc-contact__card p { margin: 0; font-size: var(--oc-fs-link); line-height: 1.6; color: var(--oc-black); }

/* [DEFECT 6 FIX] The phone and email links measured 20px tall inside a <p> of
   <br>-separated lines. They are now their own 44px rows in a plain list; the
   postal address is no longer wrapped in a link at all, because it pointed at
   #contact and went nowhere. */
.oc-contact__lines {
	margin: 8px 0 0;
	padding: 0;
	list-style: none;
	font-size: var(--oc-fs-link);
}

.oc-contact__lines a {
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;
	color: var(--oc-black);
}

.oc-contact__photo { position: relative; min-block-size: 56vh; }
.oc-contact__photo .oc-media { position: absolute; inset: 0; aspect-ratio: auto; }

@media (max-width: 1023px) {
	.oc-contact__photo { min-block-size: 46vh; }
}

.wp-block-social-links.oc-social { margin-block-start: 8px; }

.wp-block-social-links.oc-social .wp-social-link {
	background-color: rgba(0, 0, 0, 0.5);
	border-radius: var(--oc-pill);
}

/* ==========================================================================
   13. Scene 8 — FOOTER (full-bleed white band, 4-col 356/178/178/344, 64px gap)
   ========================================================================== */

.oc-footer {
	max-width: none;         /* explicit: the footer escapes the 1280px cap */
	inline-size: 100%;
	padding: 32px 48px;
	background-color: var(--oc-ink);
	color: var(--oc-black);
}

.oc-footer a { color: var(--oc-black); }
.oc-footer h2,
.oc-footer h3 { margin: 0 0 12px; font-size: var(--oc-fs-card); font-weight: 600; }

/* [DEFECT 5 FIX] The measured track set was FIXED pixels — 356 178 178 344 with
   a 64px gap. That is 1056px of track + 192px of gap = 1248px, and with the
   footer's own 48px side padding the last column's right edge landed at 1296px.
   The 2-up fallback stopped at max-width:1199px, so every viewport from 1200px
   to 1295px painted a 1248px row into a 1104-1199px box and silently clipped up
   to 96px of the Partners column — content with no scrollbar and no overflow
   indicator. Fixed pixel tracks cannot be made safe by adding another
   breakpoint, only by removing the pixels: the four tracks keep their MEASURED
   PROPORTIONS as fr units (356/178 = 2.0, 344/178 = 1.93) and the gap becomes
   fluid, so the row is exactly as wide as its container at every width.
   minmax(0, Nfr) is required — the default `auto` minimum would let a long word
   re-inflate a track past its share. Verified at 1200 / 1250 / 1295 / 1440. */
.oc-footer__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 32px;
}

@media (min-width: 769px) {
	.oc-footer__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: clamp(32px, 4vw, 64px);
		row-gap: 32px;
	}
}

@media (min-width: 1024px) {
	.oc-footer__grid {
		grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.93fr);
	}
}

.oc-footer__col p {
	margin: 0 0 12px;
	font-size: var(--oc-fs-link);
	line-height: 1.6;
	color: var(--oc-gray-600);
}

/* [DEFECT 6 FIX] Footer links measured 20px tall. Each is now a 44px flex row;
   the 8px list gap is dropped because the rows carry their own breathing space,
   so the column's overall height barely moves. */
.oc-footer__links {
	display: flex;
	flex-direction: column;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--oc-fs-link);
}

.oc-footer__links a {
	display: flex;
	align-items: center;
	min-block-size: 44px;
}

.oc-footer__rule {
	block-size: 0;
	margin: 24px 0 16px;
	border: 0;
	border-block-start: var(--oc-hairline-dark);
}

.oc-footer__bottom {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	font-size: 14px;
	/* MEASURED. ordercube_styles.css:3428 — `.copyright { color: #999 }`. An earlier
	   pass raised this to #666666 because #999999 is 2.85:1 on white; REVERTED. */
	color: var(--oc-gray-400);
}

/* [DEFECT 6 FIX] The three legal links measured 17px tall — the smallest
   targets on the page, and the ones a regulator cares about most. */
.oc-footer__legal {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0 20px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.oc-footer__legal a {
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;
}

/* The spec OBSERVED display:none on this grid at 390px, with a simplified stack
   in the mobile DOM and no Partners row. [DEVIATION] We reproduce the simplified
   stack as a real single-column layout instead of hiding the grid and leaving
   nothing: the 4-column grid becomes one column, the Partners column alone is
   display:none (matching the OBSERVED mobile absence of the partner marks), and
   every link list, paragraph, legal link and the copyright survive. Shipping a
   bare display:none would delete the sitemap from mobile. */
@media (max-width: 768px) {
	.oc-footer { padding: 32px var(--oc-frame); }
	.oc-footer__col--partners { display: none; }
	.oc-footer__bottom { flex-direction: column; align-items: flex-start; }
}

/* ==========================================================================
   14. (was: persistent floating chat FAB) — DELETED, see README deviation 29.
   The button was inert, its own aria-label said "placeholder, does nothing", it
   was tab stop 10 (ahead of every piece of page content), and elementFromPoint
   proved its 56px disc covered the footer "Terms" link, making a legal link
   unclickable. Markup, CSS and the rule that styled it are all gone; there is no
   JS to remove because it never had any.
   ========================================================================== */

/* ==========================================================================
   15. Editor-side + singular templates
   ========================================================================== */

.oc-article {
	padding-block: calc(var(--oc-header-h) + 32px) 64px;
}

.oc-article :where(h1, h2, h3) { text-wrap: balance; }

.oc-prose p { color: var(--oc-ink-dim); max-inline-size: 70ch; }

.oc-404 { text-align: center; }
.oc-404 .oc-hero-line { font-size: clamp(56px, 14vw, 140px); }

/* Entry/listing cards stay sharp and shadowless like every other card. */
.oc-entry {
	border-block-end: var(--oc-hairline);
	padding-block: 24px;
}

.oc-entry:first-child { border-block-start: var(--oc-hairline); }

/* ==========================================================================
   16. Gutters for the flow-layout scenes
   Scenes using a constrained inner layout inherit the 1280px cap + the
   6px/24px root gutter from core. The three scenes that need their own
   full-bleed internal geometry use a flow layout, so they take the gutter here.
   ========================================================================== */

.oc-scenarios,
.oc-references__top,
.oc-features {
	padding-inline: var(--oc-gutter);
}

.oc-scenarios > *,
.oc-references__top-inner {
	max-inline-size: var(--oc-grid-max);
	margin-inline: auto;
}

.oc-references__band > .wp-block-columns {
	inline-size: 100%;
	margin-block: 0;
	gap: 0;
}

.oc-references__band > .wp-block-columns > .wp-block-column {
	padding: 0;
}

.oc-main > .oc-scene:first-child { margin-block-start: 0; }

/* The calculator's left-hand panel is a plain flow column inside .oc-calc. */
.oc-calc__panel { display: block; }

/* ==========================================================================
   17. MULTIPAGE — page scenes, home teaser bands, and the contact form.
   Added when the one-pager became a real multipage site. Everything below
   reuses the existing binary: pills are 999px, every other surface is 0px, and
   nothing casts a shadow. No new colour token is introduced.
   ========================================================================== */

/* --- 17.1 The page masthead every inner page opens with -------------------- */

.oc-page {
	padding-block: calc(var(--oc-header-h) + 48px) 72px;
}

.oc-page__inner {
	inline-size: 100%;
	max-inline-size: var(--oc-grid-max);
	margin-inline: auto;
}

.oc-page__head {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 24px;
	max-inline-size: 72ch;
}

/* The hero keeps the white knockout slabs; inner pages use the same geometry at
   a smaller step and leave the type open, so the slab stays the front page's
   signature rather than becoming wallpaper. */
.oc-page__title {
	margin: 0;
	font-family: var(--oc-font-system);
	font-size: clamp(32px, 1.6rem + 3.4vw, 64px);
	font-weight: 800;
	line-height: 1.02;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	text-wrap: balance;
	color: var(--oc-ink);
}

.oc-band--light .oc-page__title { color: var(--oc-black); }

/* --- 17.2 Generic placeholder blocks the inner pages are built from -------- */

.oc-section {
	margin-block-start: 64px;
}

.oc-section__title {
	margin: 0 0 20px;
	font-size: var(--wp--preset--font-size--display-m, clamp(27px, 1.35rem + 1.5vw, 36px));
	font-weight: 600;
	line-height: 1.15;
	letter-spacing: -0.01em;
	text-wrap: balance;
}

.oc-section > p {
	margin: 0 0 16px;
	max-inline-size: 70ch;
	font-size: var(--oc-fs-card);
	line-height: 1.6;
	color: var(--oc-ink-dim);
}

/* A flat, sharp, hairline tile — the dark-scene counterpart of the white
   .oc-stat card, same 0 radius and same absence of shadow. */
.oc-tiles {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 16px;
	margin: 0;
	padding: 0;
	list-style: none;
}

@media (min-width: 769px) {
	.oc-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.oc-tiles--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.oc-tile {
	display: flex;
	flex-direction: column;
	gap: 8px;
	padding: 24px;
	border: var(--oc-hairline);
	border-radius: var(--oc-card-radius);
	background-color: rgba(255, 255, 255, 0.04);
}

.oc-tile h3 {
	margin: 0;
	font-size: var(--oc-fs-card);
	font-weight: 600;
	color: var(--oc-ink);
}

.oc-tile p {
	margin: 0;
	font-size: var(--oc-fs-link);
	line-height: 1.6;
	color: var(--oc-ink-dim);
}

.oc-tile__index {
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	text-transform: uppercase;
	color: var(--oc-accent-start);
}

.oc-band--light .oc-tile {
	border: var(--oc-hairline-dark);
	background-color: rgba(0, 0, 0, 0.03);
}

.oc-band--light .oc-tile h3 { color: var(--oc-black); }
.oc-band--light .oc-tile p { color: var(--oc-gray-600); }

/* A definition-style placeholder list for the legal scaffolding pages. */
.oc-deflist { margin: 0; }
.oc-deflist dt {
	margin-block-start: 24px;
	font-size: var(--oc-fs-card);
	font-weight: 600;
	color: var(--oc-ink);
}
.oc-deflist dd {
	margin: 8px 0 0;
	max-inline-size: 70ch;
	font-size: var(--oc-fs-link);
	line-height: 1.6;
	color: var(--oc-ink-dim);
}

/* The "this is placeholder text, not legal advice" banner the three legal pages
   open with. Hairline, flat, sharp — a card, so 0 radius. */
.oc-placeholder-note {
	display: block;
	margin-block: 24px 0;
	padding: 20px 24px;
	border: var(--oc-hairline);
	/* [A11Y-2026-10-03] INVENTED SIDE TAB REMOVED (`border-inline-start: 4px solid
	   var(--oc-accent-start)`). ORIGINAL'S CORRESPONDING COUNT: ZERO, on four probes
	   over harvest/ordercube_styles.css — `border-inline-start`: 0 matches;
	   `4px solid` (any side): 0 matches; narrow ::before/::after bars (width <= 6px):
	   0 matches; `border-left`: 1 match, harvest/ordercube_styles.css:471, which is
	   `.triangle{width:0;height:0;border-left:10px solid #fff;...}` — the CSS-triangle
	   idiom for a play glyph, not an edge rail. There is also no counterpart ELEMENT:
	   the three original legal pages are one byte-identical SPA shell with no
	   disclaimer/placeholder banner in it. */
	border-radius: var(--oc-card-radius);
	background-color: rgba(255, 255, 255, 0.04);
	font-size: var(--oc-fs-link);
	line-height: 1.6;
	color: var(--oc-ink-dim);
}

.oc-placeholder-note strong { color: var(--oc-ink); }

/* The closing CTA row every inner page ends with. */
.oc-page-cta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 24px;
	margin-block-start: 64px;
	padding-block-start: 32px;
	border-block-start: var(--oc-hairline);
}

.oc-page-cta p {
	margin: 0;
	flex: 1 1 320px;
	font-size: var(--oc-fs-body);
	line-height: 1.45;
	color: var(--oc-ink-dim);
}

/* Anchor jump-list used on /faq/ in place of the removed search + fake pager. */
.oc-jumplist {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 32px 0 0;
	padding: 0;
	list-style: none;
}

.oc-jumplist a {
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;
	padding: 10px 18px;
	border-radius: var(--oc-pill);
	background-color: rgba(255, 255, 255, 0.07);
	color: var(--oc-ink-dim);
	text-decoration: none;
	font-size: var(--oc-fs-link);
}

.oc-jumplist a:hover { background-color: rgba(255, 255, 255, 0.12); color: var(--oc-ink); }

/* --- 17.3 Home teaser bands ------------------------------------------------ */

/* Home is no longer the seven scenes. It keeps the hero in full and then runs
   one compact band per section: heading, one short paragraph, one link. */
.oc-teasers {
	padding-block: 96px;
}

.oc-teasers__inner {
	inline-size: 100%;
	max-inline-size: var(--oc-grid-max);
	margin-inline: auto;
}

.oc-teaser {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 12px;
	padding-block: 32px;
	border-block-start: var(--oc-hairline);
}

.oc-teaser:last-of-type { border-block-end: var(--oc-hairline); }

@media (min-width: 769px) {
	.oc-teaser {
		grid-template-columns: minmax(0, 3fr) minmax(0, 5fr) minmax(0, 2fr);
		align-items: center;
		gap: 48px;
	}
}

.oc-teaser h2 {
	margin: 0;
	font-size: var(--wp--preset--font-size--display-s, clamp(24px, 1.2rem + 1.1vw, 28.8px));
	font-weight: 600;
	line-height: 1.15;
	letter-spacing: -0.01em;
}

.oc-teaser p {
	margin: 0;
	font-size: var(--oc-fs-card);
	line-height: 1.6;
	color: var(--oc-ink-dim);
}

@media (min-width: 769px) {
	.oc-teaser .oc-textlink { justify-self: end; }
}

/* --- 17.4 The contact form ------------------------------------------------- */

/* Inputs are SURFACES, not controls-shaped-like-pills, so they take the card
   radius (0) exactly like every other surface in the system; only the submit
   button is a pill. That is the existing binary applied, not an exception. */
.oc-form {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 24px;
	max-inline-size: 720px;
	margin-block-start: 40px;
}

@media (min-width: 769px) {
	.oc-form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.oc-form__field--wide { grid-column: 1 / -1; }
}

.oc-form__field {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.oc-form__label {
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-button);
	font-weight: 300;
	letter-spacing: 0.56px;
	/* [A11Y-2026-10-03] INVENTED UPPERCASE REMOVED. It turned two sentence-length
	   labels into all-caps body text — "Restaurant or room name (optional)" (34 chars,
	   patterns/scene-7-contact.php:100) and "What would you like to know? (required)"
	   (39 chars, :111) — the detector's two `all-caps-body` findings.
	   ORIGINAL'S CORRESPONDING COUNT: ZERO.
	     * the original has no <form> at all (`rg -c '<form' harvest/home.html` -> no
	       match); its contact section (harvest/home.html:1708-1760) is an info card.
	     * its only <label> elements are the ROI calculator's `.control-label`
	       (harvest/home.html:1194, :1203, :1212), and `.control-label`
	       (harvest/ordercube_styles.css:2083-2094) declares NO text-transform —
	       nor does its mobile override at :4824-4832.
	     * all 14 `text-transform: uppercase` rules in the original are on short
	       chrome (buttons, pills, nav items, counters); the longest real string
	       among them is "WAITERCALL" (harvest/home.html:744).
	   The 14px/300 size, the Arial stack and the 0.56px tracking are the measured
	   label treatment and are kept — only the uppercase is removed. */
	color: var(--oc-ink);
}

/* WCAG 1.4.1: "required" is carried by the WORD, never by the colour alone. */
.oc-form__req {
	font-size: 12px;
	letter-spacing: 0.4px;
	color: var(--oc-ink-muted);
}

.oc-form__hint {
	font-size: 14px;
	line-height: 1.5;
	color: var(--oc-ink-muted);
}

.oc-form input[type="text"],
.oc-form input[type="email"],
.oc-form input[type="number"],
.oc-form textarea {
	inline-size: 100%;
	box-sizing: border-box;
	min-block-size: 48px;
	padding: 12px 16px;
	border: 0;
	border-radius: var(--oc-card-radius);
	background-color: var(--oc-ink);
	color: var(--oc-black);
	font-family: var(--oc-font-system);
	font-size: var(--oc-fs-link);
	line-height: 1.5;
}

.oc-form textarea { min-block-size: 160px; resize: vertical; }

.oc-form input::placeholder,
.oc-form textarea::placeholder { color: var(--oc-gray-600); }

.oc-form__actions {
	grid-column: 1 / -1;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 24px;
}

.oc-form__actions button { font-family: var(--oc-font-arial); }

.oc-form__smallprint {
	margin: 0;
	flex: 1 1 280px;
	font-size: 14px;
	line-height: 1.5;
	color: var(--oc-ink-muted);
}

/* The honeypot. Off-screen rather than display:none so a bot that filters on
   display still finds it; aria-hidden + tabindex="-1" keep it away from humans. */
.oc-form__hp {
	position: absolute !important;
	inline-size: 1px;
	block-size: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
	padding: 0;
	margin: -1px;
}

/* --- 17.5 Form result notices --------------------------------------------- */

.oc-notice {
	display: flex;
	gap: 12px;
	margin-block: 32px 0;
	padding: 20px 24px;
	border: var(--oc-hairline);
	/* [A11Y-2026-10-03] INVENTED SIDE TAB REMOVED — same four probes and the same
	   zero result as .oc-placeholder-note above. There is no counterpart element
	   either: `rg -c '<form' harvest/home.html` -> no <form> anywhere in the harvest,
	   so a form-result notice has nothing to replicate. */
	border-radius: var(--oc-card-radius);
	background-color: rgba(255, 255, 255, 0.05);
	font-size: var(--oc-fs-link);
	line-height: 1.6;
	color: var(--oc-ink-dim);
}

.oc-notice strong { color: var(--oc-ink); }

/* The state is carried by the word in <strong> AND by the icon — never by colour
   alone. [A11Y-2026-10-03] The third cue, the rail colour, went with the invented
   side tab above; the two remaining cues are both non-colour, so the
   1.4.1 Use-of-Colour position is unchanged. */

.oc-notice__icon {
	flex: 0 0 auto;
	inline-size: 24px;
	block-size: 24px;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
}

.oc-notice--ok .oc-notice__icon { color: var(--oc-positive); }
.oc-notice--error .oc-notice__icon { color: var(--oc-accent-start); }

/* The contact card's postal address: a real <address>, no longer a dead link. */
.oc-contact__address {
	margin: 0;
	font-size: var(--oc-fs-link);
	line-height: 1.6;
	font-style: normal;
	color: var(--oc-black);
}

/* --- 17.6 Scenes embedded in a page --------------------------------------- */

/* A scene is a 100vh slide because on the one-pager it WAS the viewport. On an
   inner page it is one block among several, under a masthead that already
   announced the page — so every scene that follows an .oc-page masthead gives
   up the 100vh floor and the duplicate header offset, and becomes content
   height. The front page is untouched: it has no .oc-page masthead, so the
   sibling combinator never matches there. */
.oc-page ~ .oc-scene {
	min-block-size: auto;
	padding-block: 64px;
}

.oc-page ~ .oc-references,
.oc-page ~ .oc-contact {
	padding-block: 0;   /* these two carry their own full-bleed bands */
}

.oc-page + .oc-scene { padding-block-start: 0; }

/* Explicit gutters for the multipage scenes. These use a flow layout rather
   than core's constrained layout, so they take the 24/6px frame here instead of
   depending on useRootPaddingAwareAlignments resolving for an alignfull child. */
.oc-page,
.oc-page-body,
.oc-teasers {
	padding-inline: var(--oc-gutter);
}

.oc-form__heading { margin-block-start: 48px; scroll-margin-block-start: var(--oc-header-h); }
#oc-contact-result { scroll-margin-block-start: var(--oc-header-h); }

/* ==========================================================================
   18. THE ORIGINAL'S OWN NINE :root PROPERTIES, VERBATIM
   -------------------------------------------------------------------------
   route-map.md §3.1: the original defines EXACTLY nine custom properties, all
   on :root at ordercube_styles.css:1-10, with exactly one override anywhere
   (`--frame: var(--frame-mobile)` in a mobile @media block at :4186).

   They are carried here under their original names, byte-for-byte, so the
   measured token set is auditable against the source without translating
   through this theme's `--oc-*` aliases. The aliases above remain the thing
   the stylesheet actually consumes; these are the provenance record, and the
   two agree by construction (each `--oc-*` fallback is the value below).

   NOTE ON --pill: the original uses one property for BOTH the CTA gradient and
   the 999px pill radius vocabulary; only the gradient is a property. Nothing
   in the original tokenises type, spacing or radius — see section 19.
   ========================================================================== */

:root {
	--bg: #0e0f13;
	--ink: #ffffff;
	--ink-dim: rgba(255, 255, 255, .85);
	--gradient-angle: 135deg;
	--pill: linear-gradient(var(--gradient-angle, 135deg), #b89b83 0%, #b77b38 100%);
	--grid-max: 1280px;
	--frame: 1.5rem;
	--frame-mobile: 0.375rem;
	--blur: 24px;
}

/* ordercube_styles.css:4186 — the one and only override in the whole file. */
@media screen and (max-width: 768px) {
	:root { --frame: var(--frame-mobile); }
}

/* ==========================================================================
   19. TOKENS THE ORIGINAL LACKS — type scale, spacing, radius
   -------------------------------------------------------------------------
   route-map.md checklist item 8: "Tokenise the type scale, spacing and radii —
   only 9 custom properties exist today, none for type or spacing." Every value
   below is lifted from the per-role census in route-map.md §2.2 or the
   radius/breakpoint census in §4; the only new thing is that it has a NAME.

   Where the census records a value, the citation is the stylesheet line it was
   measured at. Where this theme had to choose (a step the original simply does
   not have), the line is marked [NEW].
   ========================================================================== */

:root {
	/* --- Type scale. Sizes are the measured ones; the names are new. --- */
	--oc-fs-badge: 10px;        /* SVG badge micro-label      home.html:988, :1770 */
	--oc-fs-caption: 12px;      /* [NEW] media captions */
	--oc-fs-14: 14px;           /* button/nav/stat label      styles.css:73, :2364 */
	--oc-fs-16: 16px;           /* FAQ answer, nav inherit     styles.css:2600, :95 */
	--oc-fs-18: 18px;           /* FAQ question, card heading  styles.css:2545, :3075 */
	--oc-fs-20: 1.25rem;        /* footer phone                styles.css:3346 */
	--oc-fs-22: 22px;           /* body / hero sub cap         styles.css:398 */
	--oc-fs-stat: 2.5rem;       /* .card-value                 styles.css:2373 */
	--oc-fs-sub: clamp(16px, 1.6vw, 22px);        /* .sub          styles.css:398 */
	--oc-fs-impact: clamp(24px, 3vw, 36px);       /* .impact-title styles.css:2062 */
	--oc-fs-faq-title: clamp(16.8px, 2.4vw, 28.8px); /* .faq-main-title styles.css:2438 */
	--oc-fs-faq-sub: clamp(16px, 1.6vw, 20px);    /* .faq-subtitle styles.css:2446 */

	/* --- Weights. Global frequencies are in route-map.md §2.2. --- */
	--oc-fw-light: 300;         /* 12 uses — every button label */
	--oc-fw-regular: 400;       /* 5 uses */
	--oc-fw-medium: 500;        /* 6 uses */
	--oc-fw-semibold: 600;      /* 15 uses — the most common weight in the file */
	--oc-fw-bold: 700;          /* 5 uses — .card-value */
	--oc-fw-heavy: 800;         /* 1 use  — SVG sidebar labels */
	--oc-fw-black: 900;         /* 1 use  — SVG display text */

	/* --- Tracking. `.04em` x8 is the dominant value. --- */
	--oc-ls-tight: 0.02em;      /* .card-metric    styles.css:2364 */
	--oc-ls-button: 0.04em;     /* 8 uses          styles.css:73 */
	--oc-ls-wide: 0.08em;       /* 1 use */
	--oc-ls-caps: 0.1em;        /* 2 uses */
	--oc-ls-badge: 0.22em;      /* SVG micro-label home.html:988 */

	/* --- Line heights, all measured. --- */
	--oc-lh-1: 1;               /* .card-value      styles.css:2373 */
	--oc-lh-tight: 1.2;         /* .faq-main-title  styles.css:2438 */
	--oc-lh-snug: 1.3;          /* .impact-title    styles.css:2062 */
	--oc-lh-copy: 1.4;          /* .scenario-content p styles.css:592 */
	--oc-lh-lead: 1.45;         /* .sub             styles.css:398 */
	--oc-lh-answer: 1.5;        /* .faq-a-text      styles.css:2600 */
	--oc-lh-body: 1.6;          /* .feature-description styles.css:1987 */

	/* --- Spacing. A 4px-based ramp over the values the original hard-codes
	       at the use site (1rem / 1.5rem / 2rem / 3rem recur). [NEW names] --- */
	--oc-space-2xs: 4px;
	--oc-space-xs: 8px;
	--oc-space-sm: 12px;
	--oc-space-md: 16px;        /* 1rem */
	--oc-space-lg: 24px;        /* 1.5rem, == --frame */
	--oc-space-xl: 32px;        /* 2rem */
	--oc-space-2xl: 48px;       /* 3rem, == the header's own padding */
	--oc-space-3xl: 64px;
	--oc-space-4xl: 96px;

	/* --- Radius. route-map.md §4: the system is BINARY plus circles.
	       0 x19 (+8 !important), 999px x6, 50% x14 (+6 !important). The 2rem /
	       2px / 1.5rem outliers are 5 declarations between them and are NOT
	       promoted to tokens — they are noise, not a scale. --- */
	--oc-radius-card: 0;        /* cards, panels, inputs, media */
	--oc-radius-pill: 999px;    /* pills and buttons only */
	--oc-radius-circle: 50%;    /* icon buttons, dots, the spinner */

	/* --- Elevation. route-map.md §4: exactly ONE real box-shadow value in the
	       entire stylesheet, used 3 times. Everything else is `none !important`.
	       This is a near-flat design and this token is the whole vocabulary. --- */
	--oc-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);

	/* --- Breakpoints, as documentation. CSS cannot use a custom property in a
	       media query, so these are the single recorded source of the numbers
	       the @media blocks below use literally, and the same 768/769 boundary
	       assets/js/scroll.js reads via matchMedia. See README deviation 52. --- */
	--oc-bp-mobile-max: 768px;
	--oc-bp-desktop-min: 769px;

	/* --- Motion, from the measured JS. Cited where each one came from. --- */
	--oc-dur-advance: 800ms;    /* scrollToSection      ordercube_script.js:4042 */
	--oc-dur-count: 400ms;      /* animateCount         ordercube_script.js:1421 */
	--oc-dur-reveal: 600ms;     /* fadeIn               ordercube_script.js:3501 */
	--oc-dur-slide: 500ms;      /* microSlideIn         ordercube_script.js:3527 */
	--oc-dur-hero-slab: 800ms;  /* slideInFromLeftSVG   ordercube_script.js:2615 */
	--oc-dur-exit: 300ms;       /* fadeOut / modal fade ordercube_script.js:3540 */
	--oc-dur-faq: 200ms;        /* height transition    ordercube_styles.css:2577 */
	--oc-dur-curtain: 800ms;    /* preloader panels     home.html:92, :213 */
	--oc-ease-curtain: cubic-bezier(0.77, 0, 0.175, 1);  /* home.html:213 */
	--oc-ease-inout-quad: cubic-bezier(0.45, 0, 0.55, 1); /* approximates js:4050-4052 */
}

/* ==========================================================================
   20. ONE-PAGE SHELL — snap baseline, scene scrim
   ========================================================================== */

/* ==========================================================================
   THE SCROLL ENGINE'S OWN BOX — true transform jacking, as the original does it
   --------------------------------------------------------------------------
   REPLICATED, not approximated. `ordercube_styles.css:15` puts
   `overflow: hidden` on `body` so `pageYOffset` is ALWAYS 0, and the content is
   moved by `transform: translateY(-visualPosition)` on `#scrollContent`
   (`ordercube_script.js:3755`). An earlier pass shipped a hybrid — the measured
   engine layered over native document scroll plus
   `scroll-snap-type: y proximity`. The client reported the result as "choppy",
   and the hybrid is why: the browser's own scroll and the engine were competing
   for the same gesture, so every wheel tick was resolved twice. The snap is
   GONE and the engine owns the gesture outright.

   `html.oc-js` gates the lock. If the theme's JavaScript is blocked, 404s or
   throws before `preloader.js` sets that class, the document scrolls normally
   instead of showing only section 1 with no way out. The original has that
   failure mode; this is the one place it is deliberately not reproduced, for
   the same reason as the reveals' start state (README §4 N3).
   ========================================================================== */

html.oc-js body.oc-onepage-shell {
	overflow: hidden;           /* ordercube_styles.css:15 */
	overscroll-behavior: none;
}

/* The viewport the track moves inside. */
html.oc-js .oc-scroll-container {
	position: fixed;
	inset: 0;
	overflow: hidden;
	z-index: 1;
	/* ASSERTED, not assumed. CSS scroll snapping competing with a transform
	   engine for the same wheel gesture is the mechanism behind the choppiness
	   the client reported, so neither of these two boxes may ever carry a snap
	   again — including by inheritance from a future rule. The only
	   `scroll-snap` left in this stylesheet is `x proximity` on the mobile
	   partner-mark strip (a HORIZONTAL overflow box inside the hero), which
	   cannot act on a vertical gesture. */
	scroll-snap-type: none;
}

/* The track. `will-change` + `translate3d` keep it on the compositor, which is
   what makes a 7×130vh column move without repainting every frame — the other
   half of the answer to "choppy". `transform-style: flat` stops a stacking
   context inside a scene from forcing a slow path. */
html.oc-js .oc-scroll-track {
	will-change: transform;
	transform: translate3d(0, 0, 0);
	backface-visibility: hidden;
	scroll-snap-type: none;
}

/* NO BACKDROP BLUR WHILE THE TRACK IS MOVING.  Added 1.8.2.

   MEASURED with CDP Performance.getMetrics over an identical 4929px nav
   advance on this page, one variable at a time:

     as-is                          mean 30.6ms  max 83.3ms  18/42 frames >33ms
     + per-frame scrim write gone   mean 20.5ms  max 50.1ms  13/61 frames >33ms
     + backdrop-filter dropped      mean 16.7ms  max 16.7ms   0/75 frames >33ms

   The middle row is the tell: main-thread TaskDuration was already down to
   5.46ms/frame there, so the residual 4ms of jitter was not main-thread work at
   all. It was the compositor — a `backdrop-filter` element must re-read and
   re-blur the pixels behind it on every frame those pixels move, and the whole
   7-scene track moving underneath invalidates all of them, every frame.

   So the blur is a SETTLED-STATE effect here. `assets/js/scroll.js` puts
   `.oc-in-motion` on <html> for exactly as long as something is driving the
   track and takes it off the moment the track stops, which is one style recalc
   per gesture instead of a compositor re-blur per frame. Nothing is removed
   from the design: during those ~800ms the backdrop is a wall of content
   travelling past at up to 1100px/frame, which no blur radius could resolve
   anyway, and the frosted look is back before the user can read it.

   The universal selector is deliberate, and so is `!important`. This is a
   blanket STATE override, and it has to beat whatever specificity the ~12
   individual blur rules in this file happen to carry (several are two classes
   deep, which would outrank a plain `html.oc-in-motion .thing`). Scoped to the
   state class it is inert for all but the duration of a movement. */
html.oc-in-motion,
html.oc-in-motion *,
html.oc-in-motion *::before,
html.oc-in-motion *::after {
	backdrop-filter: none !important;
	-webkit-backdrop-filter: none !important;
}

/* PROMOTE THE SCRIM WHILE THE TRACK IS MOVING.  Added 1.8.3.

   1.8.2 moved the scrim off the inherited `--oc-scrim` and onto the scrim
   leaf's own `opacity`, which killed the per-frame full-document style recalc
   (4.82ms/frame -> 1.68ms/frame, measured). It did NOT kill the per-frame
   PAINT: the scrim is a full-viewport gradient, and changing the opacity of an
   unpromoted element repaints it. Two scrims change on every frame of a
   movement (the scene leaving and the scene arriving), so that was two
   1440x900 gradient repaints per frame, which is compositor and raster work the
   main-thread metrics never showed.

   MEASURED on the deployed 1.8.2, same 4929px advance, this rule the only
   variable:

     1.8.2 as deployed      mean 30.0ms  max 100.0ms  17/40 frames >33ms  task 19.13ms/frame
     + this rule            mean 21.9ms  max  50.0ms  16/54 frames >33ms  task  7.56ms/frame

   `will-change: opacity` gives each scrim its own compositor layer, so the
   opacity change becomes a compositor property update instead of a repaint.

   It is scoped to `.oc-in-motion` on purpose. A standing `will-change` on all
   seven scrims would hold seven full-viewport layers in GPU memory
   (~5MB each at 1440x900) for the whole life of the page to serve an effect
   that only animates during a movement. This way the layers exist for the
   ~800ms they are needed and are released on settle.

   WHAT THIS DID NOT FIX, recorded so the next pass does not re-measure it:
   at 21.9ms mean the main thread is only 7.56ms/frame, so the remaining ~14ms
   is off-main-thread raster of the 7-scene track itself. That is a content
   cost (full-bleed imagery moving at up to 1150px/frame), not an engine cost,
   and it is the next thing to attack if 16.7ms is required. */
html.oc-in-motion .oc-scene__scrim {
	will-change: opacity;
}

/* With JS absent neither box does anything: the container is static and the
   document scrolls, so every section and the footer are reachable. */
.oc-scroll-container,
.oc-scroll-track { position: static; }

/* THE WHITE FRAME — `ordercube_styles.css:32-46` verbatim in behaviour.
   A fixed full-viewport element whose BORDER is the inset white page frame, so
   it never moves while the track slides underneath it: there is nothing to
   jitter, because the frame is not in the scrolled subtree at all. It shows
   only while a section is settled at its own top, which is the "locks into a
   section and the white border frames it" the client described. */
/* MEASURED DEFECT, found on the live render.
   WordPress emits `:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--30) }`
   from theme.json's block gap, which here is 24px. `:where()` contributes ZERO
   specificity, so that rule is effectively `*` — but these two boxes set no
   margin of their own, so it applied unopposed. For a `position: fixed` box with
   `top: 0; bottom: 0`, a top margin SHIFTS IT DOWN and shortens it: the frame
   measured `top 24, height 876` in a 900px viewport, leaving the top 24px of the
   page unframed, and the scroll container was 24px short at the bottom, clipping
   the track. Zeroing the margin at one class of specificity fixes both. */
.oc-frame,
.oc-scroll-container { margin: 0; }

.oc-frame {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	border-style: solid;
	border-color: #fff;
	/* [A11Y-2026-10-03] The frame used to START at `border-width: 0` and TRANSITION
	   the border-width up to the gutter, which the detector flagged as
	   `transition: width` (its matcher sees the `width` in `border-width`; confirmed
	   on the live DOM, one element, computed transition-property `border-width`).
	   The border-width is now CONSTANT at the gutter and the reveal is carried by
	   `opacity` instead — compositor-only, no layout property animated at all.
	   This makes the 0.000px geometry invariant STRONGER, not weaker:
	     * the border box was already exactly the viewport at every border-width
	       (position: fixed + all four insets 0 + box-sizing: border-box), so only
	       the empty content box ever moved;
	     * now nothing moves, and the frame's INNER edge is a stable 24px desktop /
	       6px mobile at every moment of the transition rather than only at its ends,
	       which is what the hero-slab check measures against.
	   Nothing reads this element's geometry from script — scroll.js:479-482 and
	   navigation.js:60-63 touch its classList only. It stays `pointer-events: none`
	   with no children, so a constant border costs no layout anywhere. */
	border-width: var(--oc-gutter);
	opacity: 0;
	/* MEASURED DEFECT, found on the live render: without this the frame's border
	   box was 1440x876 with its top edge 24px DOWN the viewport, so the top
	   24px of the page had no frame at all. The document's default is
	   `content-box`, under which `inset: 0` sizes the CONTENT box and the border
	   is laid outside it. `border-box` makes the border box exactly the viewport
	   and draws the 24px inside it, which is what `border: var(--frame) solid`
	   means on the original (its stylesheet has a `* { box-sizing: border-box }`
	   reset that this theme deliberately does not). */
	box-sizing: border-box;
	pointer-events: none;
	z-index: 5;
	transition: opacity 0.4s ease;   /* css:37, re-expressed as a paint-only property */
}

/* css:34 — `border: var(--frame) solid #fff`, i.e. 24px desktop / 6px mobile,
   the same token as the page gutter so the frame and the content agree. The
   WIDTH is now declared once on the base rule; the state classes carry opacity. */
.oc-frame.is-visible { opacity: 1; }

/* css:40 / :44 — the frame retracts while an overlay is open, and whenever the
   section is not settled. Both must beat `.is-visible`, hence the pairing. */
.oc-frame.is-visible.is-menu-open,
.oc-frame.is-hidden { opacity: 0; }

/* A scene is focusable by script only (scroll.js sets tabindex="-1" on the
   destination so the keyboard follows a nav jump). It must never show a focus
   ring for that — it is a programmatic focus target, not a control. */
.oc-scene:focus { outline: none; }
.oc-scene:focus-visible { outline: none; }


/* THE SECTION SCRIM.
   ordercube_script.js:2384-2397 appends a `.section-overlay-div` to every
   scene's media and drives its opacity from scroll position: 0.5 when settled,
   up to 1.0 as the scene leaves, and 1.0 -> 0.5 as the next one arrives
   (:3830-3855). `--oc-scrim` is written by assets/js/scroll.js.

   The default is the SETTLED value, not the hidden value. That is deliberate:
   if the engine never runs — no JS, reduced motion, a script error — the scene
   is left in its readable state, never dimmed out. This is the exact trap the
   original falls into everywhere else (`prefers-reduced-motion` has zero
   occurrences in its CSS and its initial states are `opacity:0`, so disabling
   its animations leaves content permanently invisible). */
.oc-scene {
	--oc-scrim: 0.5;
}

/* ==========================================================================
   THE BACKGROUND VIDEO LAYER.  Added 1.8.6.

   MEASURED on the original, which puts exactly TWO of these on the page — the
   hero (home.html:486) and #references (home.html:1012) — both in a
   `div.hero-media`, and composes them with ONE rule:

     ordercube_styles.css:212  .hero       { position: relative; overflow: hidden }
     ordercube_styles.css:213  .hero-media { position: absolute; inset: 0;
                                             z-index: 0; pointer-events: none }
     ordercube_styles.css:343  .hero-media video, .hero-media img
                                           { position: absolute;
                                             width: 100%; height: 100%;
                                             object-fit: cover;
                                             object-position: center;
                                             top: 0; left: 0; z-index: -1 }
     ordercube_styles.css:389  .hero-content { position: relative; z-index: 1 }

   WHAT IS REPRODUCED IS THE RELATIONSHIP, NOT `z-index: -1`. That value is
   safe on the original because its `.hero` paints no background of its own.
   Here `.oc-scene--dark` sets `background-color: var(--oc-bg)` and `.oc-scene`
   sets `isolation: isolate`, so a `z-index: -1` child would be painted BEHIND
   the scene's own background colour and be invisible. The ordering is
   therefore done by DOM position instead, which needs no z-index at all:

     1. .oc-scene__media   first child   -> painted first
     2. .oc-scene__scrim   next sibling  -> painted over the video
     3. the scene's content              -> painted over both

   `overflow: hidden` on the layer (not on `.oc-scene`, which must not clip the
   bleeding hero slabs) keeps a `cover`-scaled video inside the scene box.
   ========================================================================== */
/* INVARIANT A, 1.8.9 — a background video spans exactly the SNAP-IN.
 *
 * The snap-in is the frame's interior: the viewport inset by the frame's own
 * border on all four sides (1392x852 at 1440x900, derived below, never
 * hardcoded). That is the whole of what a reader can ever see at rest, because
 * lock-in lands the track exactly on a scene's top — flushError 0.000px,
 * measured.
 *
 * 1.8.6-1.8.8 sized this layer with `inset: 0` on the SCENE, and our scenes
 * are content-sized rather than viewport-sized, so the videos were rastered at
 * the scene box: 1440x1389 (home) and 1440x1220 (references), overshooting the
 * snap-in bottom by 513px and 344px. That is decode and raster work for pixels
 * no reader ever sees at rest.
 *
 * Anchored to the snap-in instead. At rest a settled scene's top IS the
 * viewport top, so the snap-in in scene coordinates is the frame border down
 * from the scene's top, by (100vh - 2 x border) tall. `var(--oc-gutter)` is
 * the same custom property the frame's own border-width uses, so this tracks
 * it at every breakpoint (24px desktop, 6px mobile) without a second source of
 * truth.
 *
 * `position: fixed` is not available: this sits inside `.oc-scroll-track`,
 * which is transformed, and a fixed descendant of a transformed ancestor is
 * positioned against that ancestor. Hence the vh arithmetic. */
.oc-scene__media {
	position: absolute;
	inset-block-start: var(--oc-gutter);
	inset-inline: var(--oc-gutter);
	block-size: calc(100vh - 2 * var(--oc-gutter));
	inset-block-end: auto;
	overflow: hidden;
	/* Decoration only. The pause control is a SIBLING, never a descendant —
	   an operable control in here would be unclickable and hidden from AT. */
	pointer-events: none;
	background-color: #0e0f13;   /* what shows before the first decoded frame */
}

.oc-scene__media-video {
	position: absolute;
	top: 0;
	left: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
	object-position: center;      /* ordercube_styles.css:343 */
	display: block;
	pointer-events: none;
}

.oc-scene__scrim {
	position: absolute;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	/* ordercube_script.js:2389-2391 — linear for every scene except #features,
	   which uses a centre-out radial. */
	background: linear-gradient(180deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.55));
	opacity: calc(1 - var(--oc-scrim));
}

.oc-features .oc-scene__scrim {
	background: radial-gradient(
		circle at center,
		transparent 0%,
		transparent 15%,
		rgba(0, 0, 0, 0.5) 40%,
		rgba(0, 0, 0, 0.8) 70%,
		rgba(0, 0, 0, 0.95) 100%
	);
}

/* MEASURED DEFECT, 1.8.7. This rule lifts every direct child of a scene above
   the scrim, and its exemption list has to name every child that belongs
   BELOW it. 1.8.6 added two new direct children without joining the list, and
   both broke silently:

     .oc-scene__media      got `position: relative`, so it stopped being an
                           absolutely-positioned layer and became a FLEX ITEM of
                           `.oc-scene` (which is `display: flex`). With a
                           0x0-resolving child it collapsed: MEASURED
                           getBoundingClientRect 0x0 at 1440x900, i.e. the
                           background video was not rendering at all even though
                           the element reported readyState 4, paused false and
                           currentTime 2.58.
     .oc-video-toggle      same cause. `.oc-scene > *:not(...)` is specificity
                           (0,2,0) and beats `.oc-video-toggle`'s (0,1,0), so
                           the pause button's `position: absolute` lost and it
                           laid out as a flex item beside the hero copy.

   Neither threw and both still "looked" plausible, which is why this is called
   out here rather than just fixed: the next direct child added to a scene must
   be added to this list too, or it will be lifted out of its layer. */
.oc-scene > *:not(.oc-scene__scrim):not(.oc-scene__media):not(.oc-video-toggle):not(.oc-features__hand) {
	position: relative;
	z-index: 1;
}

/* THE SECTION HEADING ROLE — route-map.md checklist item 6.
   The original has NO h2-level section-heading role at all: its section titles
   are decorative SVG knockout text (route-map.md §2.2, "h2 — section heading:
   does not exist for any marketing section"), which means three sections have
   no accessible name and the document outline is h1 -> h3. One real h2 per
   section is introduced here, sized from the measured display-m step.
   README deviation 45. */
.oc-section-title {
	margin: 0 0 var(--oc-space-lg);
	max-inline-size: 28ch;
	font-size: clamp(27px, 1.35rem + 1.5vw, 36px);
	font-weight: var(--oc-fw-regular);
	line-height: var(--oc-lh-snug);
	letter-spacing: normal;
	text-transform: none;
	text-wrap: balance;
	color: var(--oc-ink);
}

.oc-band--light .oc-section-title,
.oc-references .oc-section-title { color: var(--oc-ink); }

/* FOCUS SUPPRESSION AT MOBILE — replicated, deliberately.
   The original's mobile sheet neutralises `:hover`, `:active` AND `:focus` in
   lockstep across seven selector groups with `!important`
   (ordercube_styles.css:5651-5778), pinning every primary CTA to its resting
   appearance when focused. The spec calls this "the single most consequential
   accessibility defect in the file" (WCAG 2.4.7 fail). The replication brief says
   match the original's focus behaviour, so it is matched. */
@media screen and (max-width: 768px) {
	:where(.oc-btn, .oc-nav__link, .oc-chips__btn, .oc-features__btn, .oc-dots__btn, .oc-icon-btn, .oc-textlink):focus {
		outline: none !important;
	}
}

/* Media slots. Every <img> is an authored SVG in assets/img/ with explicit
   width/height on the element AND an aspect-ratio on the frame, so there is no
   layout shift whether or not the file has loaded. */
.oc-media__art {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
	display: block;
}

.oc-media--studio .oc-media__art,
.oc-features__device .oc-media__art,
.oc-impact__aside .oc-media__art { object-fit: contain; }

/* Invented wordmark plates. These replace the original's wall of real
   third-party trademarks — set as type, never drawn to imitate a mark. */
.oc-marks--wordmarks { gap: var(--oc-space-lg); }

.oc-marks--wordmarks .oc-marks__item {
	display: inline-flex;
	align-items: center;
	gap: var(--oc-space-xs);
	font-size: var(--oc-fs-14);
	font-weight: var(--oc-fw-semibold);
	letter-spacing: var(--oc-ls-caps);
	text-transform: uppercase;
	color: var(--oc-ink-dim);
}

.oc-band--light .oc-marks--wordmarks .oc-marks__item { color: var(--oc-gray-600); }

.oc-wordmark-plate {
	display: inline-grid;
	place-items: center;
	inline-size: 26px;
	block-size: 26px;
	flex: 0 0 auto;
	border: 1px solid currentColor;
	border-radius: var(--oc-radius-card);
	font-size: var(--oc-fs-14);
	font-weight: var(--oc-fw-bold);
	letter-spacing: 0;
}

.oc-wordmark-plate--lg {
	inline-size: 56px;
	block-size: 56px;
	font-size: 24px;
}

.oc-marks__note {
	margin-block-start: var(--oc-space-sm);
	font-size: var(--oc-fs-caption);
	color: var(--oc-ink-muted);
}

.oc-band--light .oc-marks__note { color: var(--oc-gray-600); }

.oc-u-visually-hidden {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ==========================================================================
   21. PRELOADER — the four-panel "barn door" reveal
   -------------------------------------------------------------------------
   Every value is from the original's inline <head> block, cited per rule.
   Timings and the exit trigger live in assets/js/preloader.js.
   ========================================================================== */

/* THE NO-JS GATE.
   `oc-loading` is printed on <body> by the server (body_class in functions.php),
   and `oc-js` is set on <html> by the FIRST statement of assets/js/preloader.js.
   Both are required before the curtain is shown or the page is locked, so if the
   script never runs there is no curtain to remove and no `overflow:hidden` to
   undo — the page is simply visible and scrollable.

   This is the inverse of the original, which prints `class="loading"` with
   `overflow:hidden` server-side and relies on JavaScript to take it off
   (home.html:144-147, :276). With JS blocked, the original's page is a frozen
   white rectangle. */
html.oc-js body.oc-loading { overflow: hidden; }   /* cf. home.html:145-147 */
.oc-preloader { display: none; }
html.oc-js body.oc-loading .oc-preloader { display: block; }

.oc-preloader {
	position: fixed;
	inset: 0;
	z-index: 999999;                              /* home.html:168 */
	pointer-events: none;
}

/* Spinner: 60px box, 4px ring, 1s linear infinite — home.html:173-186. */
.oc-preloader__spinner {
	position: absolute;
	inset-block-start: 50%;
	inset-inline-start: 50%;
	transform: translate(-50%, -50%);
	inline-size: 60px;
	block-size: 60px;
	border: 4px solid rgba(183, 123, 56, 0.2);
	border-top-color: #b77b38;
	border-radius: var(--oc-radius-circle);
	animation: oc-preloader-spin 1s linear infinite;
	z-index: 2;
}

@keyframes oc-preloader-spin {
	to { transform: translate(-50%, -50%) rotate(360deg); }
}

/* 24px mark, centred in the spinner — home.html:192-200. */
.oc-preloader__mark {
	position: absolute;
	inset-block-start: 50%;
	inset-inline-start: 50%;
	transform: translate(-50%, -50%);
	inline-size: 24px;
	block-size: 24px;
	z-index: 3;
}

.oc-preloader__mark svg { inline-size: 100%; block-size: 100%; display: block; }

/* The four white panels — home.html:209-263. Note the geometry: top and bottom
   are each 50% TALL and left and right are each 50% WIDE, so the screen is
   covered twice over. That is the original's own arrangement and it is what
   gives the exit its layered "barn door" read; it is reproduced, not tidied. */
.oc-preloader__panel {
	position: absolute;
	background: #fff;
	z-index: 1;
	transition: transform var(--oc-dur-curtain) var(--oc-ease-curtain); /* home.html:213 */
}

.oc-preloader__panel--top    { inset: 0 0 auto 0; block-size: 50%; transform-origin: top; }
.oc-preloader__panel--bottom { inset: auto 0 0 0; block-size: 50%; transform-origin: bottom; }
.oc-preloader__panel--left   { inset: 0 auto 0 0; inline-size: 50%; transform-origin: left; }
.oc-preloader__panel--right  { inset: 0 0 0 auto; inline-size: 50%; transform-origin: right; }

.oc-preloader.is-loaded .oc-preloader__panel--top    { transform: translateY(-100%); }
.oc-preloader.is-loaded .oc-preloader__panel--bottom { transform: translateY(100%); }
.oc-preloader.is-loaded .oc-preloader__panel--left   { transform: translateX(-100%); }
.oc-preloader.is-loaded .oc-preloader__panel--right  { transform: translateX(100%); }

/* home.html:266-270 */
.oc-preloader.is-loaded .oc-preloader__spinner,
.oc-preloader.is-loaded .oc-preloader__mark {
	opacity: 0;
	transition: opacity var(--oc-dur-exit) ease;
}



/* ==========================================================================
   22. SCROLL-PROGRESS RING
   -------------------------------------------------------------------------
   NOT a custom cursor. `cursor:none` has ZERO matches in the original
   stylesheet (probed), so the system cursor is never replaced anywhere. This is
   a 2rem indicator (ordercube_styles.css:49-57) that reports how much more
   input the current section will absorb before it advances; r=16 gives the
   100.53 circumference the JS uses (ordercube_script.js:2535).
   ========================================================================== */

.oc-progress-ring {
	position: fixed;
	inset-block-start: 0;
	inset-inline-start: 0;
	inline-size: 2rem;
	block-size: 2rem;
	pointer-events: none;
	z-index: 9999;
	transform: translate(-50%, -50%);
	opacity: 0;
	transition: opacity 0.2s ease;   /* ordercube_styles.css:57 */
}

.oc-progress-ring.is-visible { opacity: 1; }
.oc-progress-ring svg { inline-size: 100%; block-size: 100%; display: block; }
.oc-progress-ring__arc { transition: stroke-dashoffset 80ms linear; }


/* Touch and coarse pointers never get it: there is no pointer to anchor it to. */
@media not all and (hover: hover) and (pointer: fine) {
	.oc-progress-ring { display: none; }
}

/* ==========================================================================
   23. MODALS — video, map, fullscreen image
   -------------------------------------------------------------------------
   Timings from ordercube_styles.css:4070-4114 (video), :3468-3546 (map) and
   :1191-1215 (image): backdrop rgba(0,0,0,.95) + blur(10px), panel
   scale(0.95) -> scale(1) over 0.3s ease, container opacity 0 -> 1 over 0.3s.

   TWO OPEN MECHANISMS, ON PURPOSE:
     1. `:target` — the no-JS path. Every trigger is a real `<a href="#id">`, so
        the overlay genuinely opens and closes by fragment navigation.
     2. `.is-open` — the JS path. assets/js/modals.js adds `.oc-modal--js` on
        load, which DISABLES the `:target` rule so a stale fragment cannot wedge
        a modal open, and takes over with `hidden` + a focus trap.
   ========================================================================== */

.oc-modal {
	position: fixed;
	inset: 0;
	z-index: 100000;                 /* ordercube_styles.css:3474 */
	display: none;
	align-items: center;
	justify-content: center;
	padding: var(--oc-space-xl);
	opacity: 0;
	transition: opacity var(--oc-dur-exit) ease;
}

.oc-modal[hidden] { display: none; }

/* 1. the :target fallback — only while JS has not claimed the modal */
.oc-modal:not(.oc-modal--js):target {
	display: flex;
	opacity: 1;
}

/* 2. the JS path */
.oc-modal--js.is-open {
	display: flex;
	opacity: 1;
}

.oc-modal__scrim {
	position: absolute;
	inset: 0;
	background: rgba(0, 0, 0, 0.95);  /* :3489 */
	backdrop-filter: blur(10px);      /* :3490 */
}

.oc-modal__panel {
	position: relative;
	z-index: 1;
	inline-size: min(80vw, 1400px);   /* :3496-3498 */
	max-block-size: 90vh;             /* :3497 */
	overflow: auto;
	padding: var(--oc-space-xl);
	background: var(--oc-bg);
	border-radius: var(--oc-radius-card);
	transform: scale(0.95);           /* :3500 */
	transition: transform var(--oc-dur-exit) ease;
}

.oc-modal:not(.oc-modal--js):target .oc-modal__panel,
.oc-modal--js.is-open .oc-modal__panel { transform: scale(1); }   /* :3507-3509 */

.oc-modal__title {
	margin: 0 0 var(--oc-space-md);
	font-size: var(--oc-fs-18);
	font-weight: var(--oc-fw-semibold);
	color: var(--oc-ink);
}

.oc-modal__note {
	margin-block-start: var(--oc-space-md);
	margin-block-end: 0;
	font-size: var(--oc-fs-caption);
	line-height: var(--oc-lh-answer);
	color: var(--oc-ink-muted);
}

.oc-modal__frame {
	inline-size: 100%;
	/* The map modal's panel is 80vw x 90vh on the original (:3496-3497); the
	   frame takes an explicit ratio so the iframe reserves its box before the
	   src is even set, 350ms after open. */
	aspect-ratio: 4 / 3;
	background: #fff;
	overflow: hidden;
}

.oc-modal__frame iframe {
	inline-size: 100%;
	block-size: 100%;
	border: 0;
	display: block;
}

/* 48px disc, top-right — ordercube_styles.css:4116-4138. */
.oc-modal__close {
	position: absolute;
	inset-block-start: var(--oc-space-md);
	inset-inline-end: var(--oc-space-md);
	display: grid;
	place-items: center;
	inline-size: 48px;
	block-size: 48px;
	border-radius: var(--oc-radius-circle);
	background: rgba(0, 0, 0, 0.5);
	backdrop-filter: blur(10px);
	color: var(--oc-ink);
	z-index: 2;
	transition: transform 0.2s ease, background-color 0.2s ease;
}

.oc-modal__close svg { inline-size: 24px; block-size: 24px; fill: currentColor; }
.oc-modal__close:hover { transform: scale(1.1); background: rgba(0, 0, 0, 0.7); }

/* The image overlay has no chrome of its own — :1211-1215. */
.oc-modal--image .oc-modal__panel {
	inline-size: auto;
	padding: 0;
	background: transparent;
}

.oc-modal--image img {
	max-inline-size: 90vw;
	max-block-size: 90vh;
	object-fit: contain;
	display: block;
}

body.oc-modal-open { overflow: hidden; }   /* ordercube_script.js:4370 */


@media screen and (max-width: 768px) {
	.oc-modal { padding: var(--oc-space-sm); }
	.oc-modal__panel { inline-size: 100%; padding: var(--oc-space-lg) var(--oc-space-md); }
	.oc-modal__close { inline-size: 44px; block-size: 44px; }
}

/* ==========================================================================
   24. ENTRY REVEALS — and the trap this avoids
   -------------------------------------------------------------------------
   THE TRAP, stated first because it is the single most dangerous thing about
   porting this site's motion: in the original, the pre-animation state of
   almost everything is `opacity: 0` in CSS, and `prefers-reduced-motion` has
   ZERO occurrences in its stylesheet and its JavaScript. So the obvious
   reduced-motion implementation — `animation: none` — leaves the content
   PERMANENTLY INVISIBLE. Disabling the animation is not the same as applying
   its end state.

   The hidden start state is therefore gated TWICE, and both gates must pass:

     1. `@media (prefers-reduced-motion: no-preference)` — so a reader who has
        asked for less motion never meets the hidden state at all.
     2. `html.oc-js` — set by the first statement of assets/js/preloader.js. If
        JavaScript is blocked, 404s or throws before that line, nothing is ever
        hidden, because the thing that would un-hide it is also missing.

   MEASURED, and this is why gate 2 exists. With only gate 1 in place, a
   headless Chromium at 1440x900 with no motion preference and the theme's JS
   withheld reported `opacity: 0` on the hero copy — i.e. this build had
   reproduced the original's own bug. With gate 2 it reports `opacity: 1`.
   See README deviation 49 for the full before/after table.

   assets/js/motion.js additionally adds `.is-revealed` to every group
   immediately when the preference is set, so nothing depends on the cascade
   alone, and `[data-oc-reveal]` groups that the IntersectionObserver never
   reaches are revealed rather than left blank.

   Stagger values are the original's: base 0.2s plus 0.08s per item
   (ordercube_script.js:3526), handed to CSS as `--oc-stagger`.
   ========================================================================== */

html.oc-js [data-oc-reveal] > * {
	opacity: 0;
	transform: translateX(20px);           /* ordercube_script.js:3525 */
	transition:
		opacity var(--oc-dur-reveal) ease var(--oc-stagger, 0s),
		transform var(--oc-dur-slide) ease var(--oc-stagger, 0s);
}

html.oc-js [data-oc-reveal].is-revealed > * {
	opacity: 1;
	transform: none;
}

/* Belt and braces: an explicit end state that wins wherever the start state
   was applied, so a group that somehow never receives `.is-revealed` after
   being hidden still becomes readable. */

/* ==========================================================================
   25. CAROUSELS — scenarios (5, click only), features (4, click only),
       references (3, auto-advancing at 5000ms)
   -------------------------------------------------------------------------
   `setInterval` has ZERO matches in the original's 7,054 lines (probed): the
   references rotation is a requestAnimationFrame loop against
   `autoAdvanceDuration = 5000` (ordercube_script.js:776, :790-812). Scenarios
   and features advance on click only, so neither carries `data-oc-autoplay`
   and neither gets a timer.
   ========================================================================== */

/* An inactive slide carries the `hidden` attribute (set by motion.js), which
   takes it out of the layout AND out of the tab order. That is the whole
   mechanism — there is no opacity cross-fade to get stuck half-way. */
[data-oc-slide][hidden] { display: none; }

.oc-chips__btn,
.oc-features__btn {
	display: flex;
	align-items: center;
	gap: var(--oc-space-xs);
	inline-size: 100%;
	min-block-size: 44px;
	padding: var(--oc-space-xs) var(--oc-space-md);
	border: 0;
	border-radius: var(--oc-radius-pill);
	background: transparent;
	color: var(--oc-ink-dim);
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-14);
	font-weight: var(--oc-fw-light);
	letter-spacing: var(--oc-ls-button);
	text-transform: uppercase;
	text-align: start;
	cursor: pointer;
	transition: color var(--oc-dur) var(--oc-ease), background-color var(--oc-dur) var(--oc-ease);
}

.oc-chips__btn:hover,
.oc-features__btn:hover { color: var(--oc-ink); }

.oc-chips__btn.is-active,
.oc-features__btn.is-active {
	color: var(--oc-ink);
	background: rgba(255, 255, 255, 0.1);
}

.oc-features__btn .oc-features__icon {
	inline-size: 20px;
	block-size: 20px;
	flex: 0 0 auto;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
}

.oc-dots__btn {
	inline-size: 24px;                /* 24px hit area around a 10px dot */
	block-size: 24px;
	padding: 0;
	border: 0;
	border-radius: var(--oc-radius-circle);
	cursor: pointer;
	/* The measured dot is 10px. Painting it as a radial-gradient on a
	   transparent 24px box keeps the VISUAL size while giving the control a
	   WCAG 2.5.8 hit area. README deviation 35. */
	background: radial-gradient(circle at center, rgba(255, 255, 255, 0.45) 0 5px, transparent 5px);
}

.oc-dots__btn.is-active {
	background: radial-gradient(circle at center, var(--oc-accent-start) 0 5px, transparent 5px);
}

.oc-band--light .oc-dots__btn {
	background: radial-gradient(circle at center, rgba(0, 0, 0, 0.3) 0 5px, transparent 5px);
}

/* The progress ring on the "next" control — r=18, circumference 113.1
   (ordercube_script.js:788). dasharray/dashoffset are written by motion.js. */
.oc-icon-btn--ring { position: relative; }

.oc-icon-btn--ring .oc-ring {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	pointer-events: none;
}

.oc-icon-btn--ring .oc-icon-btn__glyph {
	position: relative;
	inline-size: 24px;
	block-size: 24px;
}


.oc-references__disclaimer,
.oc-impact__note {
	margin-block-start: var(--oc-space-md);
	font-size: var(--oc-fs-caption);
	line-height: var(--oc-lh-answer);
	max-inline-size: 60ch;
}

.oc-references__disclaimer { color: var(--oc-gray-600); }
.oc-impact__note { color: var(--oc-ink-muted); }

.oc-references__mark {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--oc-space-sm);
	color: var(--oc-gray-600);
}

.oc-references__mark-name {
	font-size: var(--oc-fs-14);
	font-weight: var(--oc-fw-semibold);
	letter-spacing: var(--oc-ls-tight);
}

/* ==========================================================================
   26. FAQ ACCORDION
   -------------------------------------------------------------------------
   Native <details>/<summary>, so with JavaScript off the rows still open —
   instantly, which is what <details> does. assets/js/motion.js layers the
   measured height animation on top: `height: scrollHeight + 32` to open,
   `0px` to close, `transition: height 0.2s linear` (ordercube_script.js:6263,
   :6258; ordercube_styles.css:2577), and ONE row open at a time (:6246-6253).
   ========================================================================== */

.oc-faq-item {
	border-block-end: 1px solid rgba(255, 255, 255, 0.14);
}

/* The CLICK TARGET. ordercube_styles.css + behavior-spec.md B.1: the listener is
   on this row <div>, not on the chevron button inside it. */
.oc-faq-item__question {
	display: flex;
	align-items: flex-start;
	gap: var(--oc-space-md);
	min-block-size: 44px;
	padding: var(--oc-space-md) 0;
	cursor: pointer;
}

.oc-faq-item__q-wrapper,
.oc-faq-item__a-wrapper {
	display: flex;
	align-items: flex-start;
	gap: var(--oc-space-md);
	flex: 1 1 auto;
}

.oc-faq-item__q-label,
.oc-faq-item__a-label {
	flex: 0 0 auto;
	font-weight: var(--oc-fw-semibold);
	color: var(--oc-accent-start);
}

.oc-faq-item__q-label { font-size: var(--oc-fs-18); }   /* css:2538 — 18px/600 */
.oc-faq-item__a-label { font-size: var(--oc-fs-16); }   /* css:2593 — 16px/600 */

.oc-faq-item__q-text {
	flex: 1 1 auto;
	font-size: var(--oc-fs-18);                          /* css:2545 — 18px/500 */
	font-weight: var(--oc-fw-medium);
	line-height: var(--oc-lh-copy);
}

/* `pointer-events: none` is MEASURED (ordercube_styles.css:2552ff): the chevron
   only looks like the control. The row behind it is what you click. It keeps its
   `aria-label` because the original's does too (home.html:1389). */
.oc-faq-item__toggle {
	flex: 0 0 auto;
	inline-size: 24px;
	block-size: 24px;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	pointer-events: none;
	transition: transform var(--oc-dur-faq) linear;
}

.oc-faq-item__toggle svg { inline-size: 100%; block-size: 100%; display: block; }
.oc-faq-item.is-active .oc-faq-item__toggle { transform: rotate(180deg); }

/* The PANEL. [A11Y-2026-10-03] LAYOUT-THRASH TRANSITION REWRITTEN. This rule used
   to read `transition: height ..., padding ...` with the pixel height written from
   JS, which the detector flagged SIX times — once per .oc-faq-item__answer element,
   six FAQ rows sharing one rule (confirmed on the live DOM: 6 elements whose
   computed transition-property names height+padding). Animating height and padding
   dirties layout on every frame of the open/close, on a page whose frame budget is
   already measured at ~23.1ms against a 16.7ms benchmark.
   It is now `grid-template-rows: 0fr -> 1fr`, which the compositor resolves without
   a layout pass per frame, and the two JS height writes are deleted
   (assets/js/motion.js collapse()/expand()) — an inline style.height would have
   beaten this stylesheet unconditionally, so CSS alone could not have done it.
   THREE THINGS THIS DEPENDS ON, each one load-bearing:
     1. `min-block-size: 0` on the panel's children. `.oc-faq-item__a-wrapper` is
        `display: flex; flex: 1 1 auto` (style.css:3600-3605); a flex item in a
        `0fr` row will not collapse below its min-content height without it, and
        the panel would never close.
     2. `overflow: hidden` STAYS on this element and is NOT moved to a new inner
        wrapper. `.oc-faq-item.is-active .oc-faq-item__a-wrapper` runs
        `oc-focus-pull`, which starts at `translateY(6px)` with `blur(10px)`; a
        fresh clipping wrapper would clip that entry slide and its blur halo.
     3. The bottom padding moves off this element onto `.oc-faq-item__a-wrapper`.
        Left here it would be a `padding` transition on the grid container — the
        exact thing being removed — and it would animate against the row size.
   The measured bonus: expand() read `answer.scrollHeight` AFTER adding `.is-active`,
   a read-after-write forced reflow. That read is gone with the line. */
.oc-faq-item__answer {
	display: grid;
	grid-template-rows: 0fr;
	overflow: hidden;
	transition: grid-template-rows var(--oc-dur-faq) linear;
}

.oc-faq-item__answer > * { min-block-size: 0; }

.oc-faq-item.is-active .oc-faq-item__answer { grid-template-rows: 1fr; }

/* The 32px that used to be the panel's own `padding-block-end` (and the +32 that
   motion.js added to scrollHeight to pay for it). Both are gone; the space is now
   inside the grid item, so it is measured by the 1fr row instead of fighting it. */
.oc-faq-item.is-active .oc-faq-item__a-wrapper { padding-block-end: 32px; }

.oc-faq-item__a-text {
	margin: 0;
	font-size: var(--oc-fs-16);                          /* css:2600 — 16px/400 */
	line-height: var(--oc-lh-answer);
	color: var(--oc-ink-dim);
}

/* ==========================================================================
   27. THE NEGATIVE STAT — route-map.md checklist item 9
   -------------------------------------------------------------------------
   The original paints BOTH the positive and the negative delta in the same
   #4CAF50 green (ordercube_styles.css:2380 and :2384), so "wait went down 27%"
   and "tips went up 12%" are visually identical and the colour carries no
   information at all. The negative card gets its own token.

   COLOUR CHOICE, with the arithmetic recomputed from first principles, because
   the delta is 36px/600 on a WHITE card (`.oc-stat` background is #FFFFFF) —
   LARGE text, so 3.0:1 is the bar. `L = 0.2126R + 0.7152G + 0.0722B` over
   linearised sRGB channels, contrast = (L_hi + 0.05) / (L_lo + 0.05):

     | role              | hex      | L        | on #FFFFFF  | verdict        |
     |-------------------|----------|----------|-------------|----------------|
     | measured green    | #4CAF50  | 0.327756 | 2.780:1     | FAILS 3.0      |
     | shipped positive  | #43A047  | 0.267898 | 3.303:1     | passes (dev 21)|
     | shipped negative  | #B5541F  | 0.162633 | 4.938:1     | passes         |

   #B5541F is a burnt orange inside the brand's own tan -> bronze -> espresso
   family (hue 21.2 deg against accent-end #B77B38's 31.7 deg), NOT a generic
   alert red: a shorter wait is a GOOD outcome expressed as a downward number,
   and painting it red would say the opposite. It separates from the positive
   green by hue AND by luminance (0.163 vs 0.268, a 1.65x gap), so it survives
   deuteranopia and a greyscale print. #A33F0E (6.41:1) and #C45A1B (4.35:1)
   were the neighbours considered; the first reads brown rather than orange and
   the second clears the bar by less.
   ========================================================================== */

:root {
	/* [A11Y-2026-10-03] The fallback here read #4CAF50 and theme.json's `negative`
	   preset ALSO read #4CAF50 — so the "negative" delta painted the SAME GREEN as the
	   positive one (2.780:1 on the white card), which is both an AA failure and a
	   state that colour no longer distinguishes. The value section 27 below documents
	   as shipped is #B5541F (4.938:1 on #FFFFFF). The preset was the thing out of
	   step, not the prose; both now read #B5541F. README deviation 63. */
	--oc-negative: var(--wp--preset--color--negative, #B5541F);
}

.oc-stat--negative .oc-stat__delta { color: var(--oc-negative); }

/* The direction is also stated in the metric's own caveat text, so the colour
   is never the only carrier of the distinction (WCAG 1.4.1). */

/* ==========================================================================
   28. ONE-PAGE SCENE LAYOUTS
   -------------------------------------------------------------------------
   Grid only, one column at mobile. The hard rule learned in README deviation
   19: a `.oc-scene` is `display:flex` over its children, so EVERY scene gets
   exactly ONE element child (plus the scrim) — a one-column grid that gains
   columns at the desktop breakpoint. Give a scene two children and the flex
   row splits between them at 390px and the content clips.
   ========================================================================== */

.oc-scene > .oc-hero__inner,
.oc-scene > .oc-scenarios__inner,
.oc-scene > .oc-features__inner,
.oc-scene > .oc-references__inner,
.oc-scene > .oc-impact__inner,
.oc-scene > .oc-faq__inner,
.oc-scene > .oc-contact__grid {
	display: grid;
	/* [390px OVERFLOW FIX, 2026-10-03] minmax(0, 1fr), NOT 1fr.
	   A bare `1fr` is `minmax(auto, 1fr)`, and a grid item's automatic minimum
	   is its min-content. `.oc-hero__trust` is a flex column holding the trust
	   strip, which is `flex-wrap:nowrap` BY DESIGN below 1024px (it is meant to
	   be an internally scrollable 3-up strip), so its min-content is ~910px.
	   MEASURED on the live site at 390x844: documentElement.scrollWidth 916 vs
	   clientWidth 390, and the page really panned — scrollTo(9999,y) reached
	   scrollLeft 359. That also blew the hero media frame out to 910px wide,
	   i.e. the hero artwork was partly off-screen at 390px regardless of its
	   own crop. With minmax(0, 1fr): scrollWidth 390, scrollLeft 0, hero frame
	   378px, and the trust strip keeps its own horizontal scroll.
	   The desktop tracks below already use this idiom. */
	grid-template-columns: minmax(0, 1fr);
	gap: var(--oc-space-xl);
	inline-size: 100%;
	max-inline-size: var(--oc-grid-max);
	margin-inline: auto;
	padding-inline: var(--oc-gutter);
	/* Every child is inside the grid, so none of them can be laid into the
	   scene's own flex row. */
	box-sizing: border-box;
}

@media screen and (min-width: 769px) {
	/* The features tracks are the measured fix from README deviation 33:
	   240 / <remainder> / 424, which gives the copy column 57 characters per
	   line instead of 29. The hero's own columns are set with its grid areas
	   below. */
	.oc-scene > .oc-features__inner { grid-template-columns: minmax(0, 240px) minmax(0, 1fr) minmax(0, 424px); }
	.oc-scene > .oc-contact__grid   { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
}

/* --- Scene 1: hero --- */
@media screen and (min-width: 769px) {
	/* SINGLE COLUMN as of 1.8.6. The two-column "copy | stage" hero existed to
	   hold the 16:9 video panel; that panel was a replica artifact and the
	   video is now the full-bleed background, so the second column had nothing
	   left in it. This also matches the original, whose hero is one
	   left-aligned column over the video (`.center-rail` + `.container`,
	   ordercube_styles.css:392-393) with no second track at all.

	   The copy column's left content edge is unchanged by this — it was and is
	   the inner's padding edge — which is why the hero slabs' -8px landing is
	   unaffected. Re-measured after the change, not assumed. */
	.oc-scene > .oc-hero__inner {
		grid-template-columns: minmax(0, 1fr);
		grid-template-areas: "copy" "trust";
		align-items: center;
	}

	.oc-hero__copy  { grid-area: copy; }
	.oc-hero__trust { grid-area: trust; }
}

.oc-hero__cues {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--oc-space-md);
	margin-block-start: var(--oc-space-xl);
}

/* --- Scene 2: scenarios --- */
.oc-scenarios__stage { position: relative; }
.oc-scenarios__slide { position: relative; }

.oc-scenarios__layer {
	box-sizing: border-box;
	inline-size: 100%;
}

@media screen and (min-width: 769px) {
	.oc-scenarios__layer {
		position: absolute;
		inset-block-end: 0;
		inset-inline-start: 0;
		inline-size: min(52%, 560px);
	}
}

.oc-scenarios__caption {
	box-sizing: border-box;
	inline-size: 100%;
	padding: var(--oc-space-lg);
	background: var(--oc-ink);
	color: var(--oc-bg);
	border-radius: var(--oc-radius-card);
}

.oc-scenarios__caption h3 {
	margin: 0 0 var(--oc-space-xs);
	font-size: var(--oc-fs-18);
	font-weight: var(--oc-fw-semibold);
}

.oc-scenarios__caption p {
	margin: 0 0 var(--oc-space-sm);
	font-size: var(--oc-fs-14);
	line-height: var(--oc-lh-copy);
}

.oc-scenarios__counter {
	position: absolute;
	inset-block-start: var(--oc-space-sm);
	inset-inline-end: var(--oc-space-sm);
}

.oc-scenarios__controls {
	display: grid;
	gap: var(--oc-space-md);
}

@media screen and (min-width: 769px) {
	.oc-scenarios__controls { grid-template-columns: minmax(0, 1fr) minmax(0, auto); align-items: start; }
}

.oc-scenarios__cues,
.oc-features__cues,
.oc-impact__cues,
.oc-faq__cues {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--oc-space-md);
}

/* --- Scene 3: features --- */
.oc-features__list { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--oc-space-2xs); }
.oc-features__panel h3 { margin: 0 0 var(--oc-space-xs); font-size: var(--oc-fs-18); font-weight: var(--oc-fw-semibold); }
.oc-features__panel p  { margin: 0 0 var(--oc-space-md); font-size: 18px; line-height: var(--oc-lh-body); }
.oc-features__panel-art { max-inline-size: 420px; }
.oc-features__copy { display: grid; gap: var(--oc-space-md); align-content: start; }

/* --- Scene 4: references --- */
.oc-references__inner { display: grid; gap: 0; }
.oc-references__top-inner { display: grid; gap: var(--oc-space-md); }
.oc-references__actions { display: flex; flex-wrap: wrap; gap: var(--oc-space-md); }
.oc-references__band { padding: var(--oc-space-2xl) var(--oc-gutter); }
.oc-references__grid { display: grid; gap: var(--oc-space-xl); max-inline-size: var(--oc-grid-max); margin-inline: auto; }
.oc-references__slide { display: grid; gap: var(--oc-space-xl); }
.oc-references__photo { position: relative; }

.oc-references__expand {
	position: absolute;
	inset-block-start: var(--oc-space-sm);
	inset-inline-end: var(--oc-space-sm);
}

@media screen and (min-width: 769px) {
	.oc-references__slide { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 2fr); align-items: center; }
}

.oc-references__quote blockquote {
	margin: 0 0 var(--oc-space-md);
	font-style: italic;
	font-size: var(--oc-fs-22);
	line-height: var(--oc-lh-copy);
}

.oc-references__attrib { font-size: var(--oc-fs-14); color: var(--oc-gray-600); font-style: normal; }
.oc-references__arrows { display: flex; gap: var(--oc-space-xs); }
.oc-references__cues { margin-block-start: var(--oc-space-md); }

/* --- Scene 5: impact --- */
.oc-impact__inner { display: grid; gap: var(--oc-space-xl); }
.oc-impact__aside { display: grid; gap: var(--oc-space-sm); }

/* --- Scene 6: FAQ --- */
.oc-faq__inner { max-inline-size: 1000px; }
.oc-faq__head { margin-block-end: var(--oc-space-xl); }
.oc-faq__list { display: grid; }
.oc-faq__cues { margin-block-start: var(--oc-space-xl); }

/* --- Scene 7: contact --- */
.oc-contact__social {
	display: flex;
	gap: var(--oc-space-xs);
	margin: var(--oc-space-xl) 0 0;
	padding: 0;
	list-style: none;
}

.oc-contact__social a {
	display: grid;
	place-items: center;
	inline-size: 44px;
	block-size: 44px;
	border: 1px solid rgba(255, 255, 255, 0.24);
	border-radius: var(--oc-radius-circle);
	font-size: var(--oc-fs-caption);
	font-weight: var(--oc-fw-semibold);
	text-transform: uppercase;
	text-decoration: none;
	color: var(--oc-ink-dim);
}

.oc-contact__social a:hover { color: var(--oc-ink); border-color: var(--oc-accent-start); }
.oc-form__intro { max-inline-size: 60ch; color: var(--oc-ink-dim); }

/* The chain CTA — the original's ghost button with the circled down-arrow
   (home.html:566). Shape and label are measured; the disc is 44px here so it
   clears WCAG 2.5.8, which the original's does not. */
.oc-btn--chain { display: inline-flex; align-items: center; gap: var(--oc-space-sm); }

.oc-btn__disc {
	display: grid;
	place-items: center;
	inline-size: 32px;
	block-size: 32px;
	flex: 0 0 auto;
	border: 1px solid currentColor;
	border-radius: var(--oc-radius-circle);
}

.oc-btn__disc svg {
	inline-size: 16px;
	block-size: 16px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.4;
}

/* ==========================================================================
   29. /customer-map/ — the fifth route
   -------------------------------------------------------------------------
   No tile provider, no third-party map styling, no real locations. The
   authoritative form of the data is the <ol>; the drawing is `aria-hidden`
   decoration beside it, because a decorative map a screen reader cannot use is
   not an accessible map. The page also renders inside the references modal's
   iframe, so it stands entirely on its own.
   ========================================================================== */

.oc-map-page {
	display: grid;
	gap: var(--oc-space-xl);
	max-inline-size: var(--oc-grid-max);
	margin-inline: auto;
	padding: calc(var(--oc-header-h) + var(--oc-space-lg)) var(--oc-gutter) var(--oc-space-3xl);
}

.oc-map-page__head h1 { margin: 0 0 var(--oc-space-md); }
.oc-map-page__count { font-size: var(--oc-fs-14); color: var(--oc-ink-muted); }
.oc-map-page__layout { display: grid; gap: var(--oc-space-xl); }

@media screen and (min-width: 769px) {
	.oc-map-page__layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.oc-map-page__surface { border: 1px solid rgba(255, 255, 255, 0.14); }

.oc-map-page__svg {
	display: block;
	inline-size: 100%;
	block-size: auto;
	aspect-ratio: 1 / 1;   /* no layout shift: the box is reserved either way */
}

.oc-map-page__list { display: grid; gap: var(--oc-space-md); align-content: start; }

.oc-map-page__filter {
	display: grid;
	gap: var(--oc-space-xs);
	padding-block-end: var(--oc-space-md);
	border-block-end: 1px solid rgba(255, 255, 255, 0.14);
}

.oc-map-page__filter input {
	min-block-size: 44px;
	padding: var(--oc-space-xs) var(--oc-space-sm);
	border: 1px solid rgba(255, 255, 255, 0.24);
	border-radius: var(--oc-radius-card);   /* an input is a surface, not a pill */
	background: transparent;
	color: var(--oc-ink);
	font-size: var(--oc-fs-16);
}

.oc-map-page__filter .oc-btn { justify-self: start; }
.oc-map-page__pins { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--oc-space-xs); }

.oc-map-page__pins li {
	display: grid;
	grid-template-columns: 5ch minmax(0, 1fr) auto;
	gap: var(--oc-space-sm);
	align-items: baseline;
	min-block-size: 32px;
	padding-block: var(--oc-space-2xs);
	border-block-end: 1px solid rgba(255, 255, 255, 0.08);
	font-size: var(--oc-fs-14);
}

.oc-map-page__plz { color: var(--oc-accent-start); font-variant-numeric: tabular-nums; }
.oc-map-page__name { color: var(--oc-ink); }
.oc-map-page__city { color: var(--oc-ink-muted); }
.oc-map-page__empty { color: var(--oc-ink-dim); }
.oc-map-page__legend { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: var(--oc-space-md); font-size: var(--oc-fs-caption); color: var(--oc-ink-muted); }
.oc-map-page__legend li { display: inline-flex; align-items: center; gap: var(--oc-space-2xs); }

.oc-map-page__key {
	inline-size: 10px;
	block-size: 10px;
	border-radius: var(--oc-radius-circle);
}

.oc-map-page__key--active { background: #B89B83; }
.oc-map-page__key--hq { background: #B77B38; }
.oc-badge--hq { font-size: var(--oc-fs-caption); }
.oc-map-page__back { font-size: var(--oc-fs-14); }


/* ==========================================================================
   30. AUTHORED MOTION — the modal film, and one moment per scene
   -------------------------------------------------------------------------
   WHY THIS SECTION EXISTS. Up to 1.3.1 this stylesheet contained exactly ONE
   `@keyframes` (the preloader spinner), which is why a geometrically faithful
   page still read as dead: every other "animation" was a transition fired by a
   class change, and transitions do not give a room a pulse. The reference
   site's own motion inventory (behavior-spec §9b) is the opposite shape — its
   ambient layer is a handful of very slow infinite loops, and its scene layer
   is a different choreography per section, not one entrance repeated.

   SO THE RULE HERE IS: each scene gets ONE authored idea, and no two scenes get
   the same one. Scattering the same fade over seven sections is what makes a
   template feel like a template.

     scene 1  hero         a 14s routed-call loop, authored in the SVG itself
                           (assets/img/hero-motion.svg — not driven from here)
     scene 2  scenarios    the slide change performs the original's signature
                           cover-hold-reveal, re-authored as a clip-path wipe
     scene 3  features     a single specular sheen crosses the device render
                           once every 19s — a mask-shaped effect, not a fade
     scene 4  references   the quote arrives OUT OF FOCUS and resolves (blur),
                           which is the original's `fadeInBlur` idea moved from
                           its mobile-only per-word use onto the quote itself
     scene 5  impact       already had its moment: motion.js counts the six
                           figures up on entry with a cubic ease-out
                           (1 - (1-p)^3). Deliberately NOT added to.
     scene 6  faq          the room itself drifts — three radial blobs on
                           48s / 60s / 42s, which are the original's own only
                           infinite animations; answers arrive out of focus
     scene 7  contact      a bronze signal crosses the card's top edge once
                           every 11s, and is dark the rest of the time

   TIMINGS are the reference's where the reference had one (420ms cover-hold
   with the hold at 45-55%, 48s/60s/42s blobs, 0.2/0.3/0.6s transition tokens),
   and authored where it did not.

   EASING. The reference is almost entirely the `ease` keyword. Where this
   section wants a release that starts fast and settles — the craft brief's
   "exponential ease-out" — it uses `--oc-ease-expo`, declared below, rather
   than inventing a new curve per rule.

   THE REDUCED-MOTION CONTRACT, and the trap it avoids. Section 24 states it in
   full: in the original, initial states are `opacity: 0`, so `animation: none`
   under `reduce` leaves content PERMANENTLY INVISIBLE. Nothing in this section
   can reproduce that, by construction:

     * every rule below lives inside `@media (prefers-reduced-motion:
       no-preference)`, so under `reduce` not one of them is applied;
     * every element these rules animate is ALREADY in its finished state in the
       markup — the film's drawing shows the completed story, the scenario
       caption is un-clipped, the quote is in focus;
     * the decorative surfaces (blobs, sheen, signal) get their `content` inside
       the gate too, so under `reduce` they do not exist at all rather than
       existing frozen.

   Verified, not assumed: with `reduce` emulated the page reports ZERO elements
   that contain text and compute to `opacity: 0`, and the hero frame-differs
   1-of-6 (static); with `no-preference` it frame-differs 6-of-6.
   ========================================================================== */

:root {
	/* Starts fast, settles long — the "already arrived, now resolving" feel.
	   Used wherever this section wants a release rather than a travel. */
	--oc-ease-expo: cubic-bezier(0.16, 1, 0.3, 1);

	/* The reference's signature text-swap envelope: cover 0-45%, HOLD 45-55%
	   (the swap happens at the 210ms midpoint), reveal 55-100%. */
	--oc-dur-swap: 420ms;
	/* The two ambient blob periods the reference actually used, plus the
	   lemniscate drift its third blob ran from JS at ~42s. */
	--oc-dur-blob-a: 48s;
	--oc-dur-blob-b: 60s;
	--oc-dur-blob-c: 42s;
}

/* --------------------------------------------------------------------------
   30a. THE MODAL FILM
   The drawing lives in patterns/site-overlays.php as inline SVG, so that THIS
   stylesheet can gate it — a `<img src>`-referenced SVG cannot be gated, which
   is measured and recorded in patterns/scene-1-hero.php.

   The animations are ATTACHED by the open selectors, not merely play-state
   toggled. Two consequences, both wanted: the loop starts at step 1 for every
   reader who opens it rather than wherever the document clock had got to, and a
   closed modal costs nothing.
   -------------------------------------------------------------------------- */

.oc-film-stage { background: #0b0c10; background-image: none; }

.oc-film {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	display: block;
}

/* Everything that scales does so about its OWN centre. */
.oc-film .f-pop { transform-box: fill-box; transform-origin: center; }


/* 24s loop. 1s = 4.1667%.
     0.5-4.0s  step 1  the guest taps, the table lights, rings leave
     4.0-8.0s  step 2  the ticket is raised and its timer starts
     8.0-13.5s step 3  the call routes past two busy hands to the free one
     13.5-18s  step 4  that hand acknowledges; the ticket logs the ack
     18-21.5s  step 5  the ticket clears and the table goes quiet
     21.5-24s          pause, so the loop has a beat before it restarts */

:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-tap    { animation: oc-film-tap 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-table  { animation: oc-film-table 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-glow   { animation: oc-film-glow 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-ring   { animation: oc-film-ring 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-ring--2 { animation-delay: 1s; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-ticket { animation: oc-film-ticket 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-timer  { animation: oc-film-timer 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-row--raised { animation: oc-film-row-raised 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-row--ack    { animation: oc-film-row-ack 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-route  { animation: oc-film-route 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-route-pulse { animation: oc-film-crawl 6s linear infinite, oc-film-pulse 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-way    { animation: oc-film-way 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-way--2 { animation-delay: 1.1s; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-staff  { animation: oc-film-staff 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-ack    { animation: oc-film-ack 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-bar    { animation: oc-film-bar 24s linear infinite; }

/* The five steps read as a legend when still and as a playhead when moving:
   each one lifts from 0.55 to 1 for its own act. One keyframe set, five
   delays — the act starts, not a uniform stagger. */
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-step { animation: oc-film-step 24s linear infinite; }
:is(.oc-modal:not(.oc-modal--js):target, .oc-modal--js.is-open) .f-pip  { animation: oc-film-pip 24s linear infinite; }
.oc-film .f-step--1, .oc-film .f-pip--1 { animation-delay: 0.5s; }
.oc-film .f-step--2, .oc-film .f-pip--2 { animation-delay: 4s; }
.oc-film .f-step--3, .oc-film .f-pip--3 { animation-delay: 8s; }
.oc-film .f-step--4, .oc-film .f-pip--4 { animation-delay: 13.5s; }
.oc-film .f-step--5, .oc-film .f-pip--5 { animation-delay: 18s; }

@keyframes oc-film-tap {
	0%      { opacity: .18; transform: scale(2.4); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	6.25%   { opacity: 1;   transform: scale(1); }
	9%      { opacity: .45; transform: scale(1); }
	87.5%   { opacity: .45; transform: scale(1); }
	100%    { opacity: .18; transform: scale(2.4); }
}
@keyframes oc-film-table {
	0%, 4.167% { opacity: .18; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	6.25%      { opacity: 1; }
	75%        { opacity: 1; }
	83.333%    { opacity: .18; }
	100%       { opacity: .18; }
}
@keyframes oc-film-glow {
	0%, 4.167% { opacity: 0; transform: scale(.6); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	6.25%      { opacity: 1; transform: scale(1.06); }
	12.5%      { opacity: .82; transform: scale(1); }
	75%        { opacity: .82; transform: scale(1); }
	83.333%    { opacity: 0; transform: scale(.6); }
	100%       { opacity: 0; transform: scale(.6); }
}
@keyframes oc-film-ring {
	0%      { transform: scale(.5); opacity: 0; }
	6.25%   { transform: scale(.6); opacity: .9; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	18.75%  { transform: scale(2.6); opacity: 0; }
	100%    { transform: scale(2.6); opacity: 0; }
}
@keyframes oc-film-ticket {
	0%, 16.667% { transform: translateX(70px); opacity: 0; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	20.833%     { transform: translateX(0); opacity: 1; }
	81.25%      { transform: translateX(0); opacity: 1; }
	85.417%     { transform: translateX(44px); opacity: 0; }
	100%        { transform: translateX(70px); opacity: 0; }
}
@keyframes oc-film-timer {
	0%, 20.833% { stroke-dashoffset: 100; }
	58.333%     { stroke-dashoffset: 0; }
	85.417%     { stroke-dashoffset: 0; }
	85.418%     { stroke-dashoffset: 100; }
	100%        { stroke-dashoffset: 100; }
}
@keyframes oc-film-row-raised {
	0%, 18.75% { opacity: 0; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	22.917%    { opacity: 1; }
	58.333%    { opacity: 1; }
	62.5%      { opacity: .4; }
	81.25%     { opacity: .4; }
	85.417%    { opacity: 0; }
	100%       { opacity: 0; }
}
@keyframes oc-film-row-ack {
	0%, 56.25% { opacity: 0; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	60.417%    { opacity: 1; }
	81.25%     { opacity: 1; }
	85.417%    { opacity: 0; }
	100%       { opacity: 0; }
}
@keyframes oc-film-route {
	0%, 33.333% { stroke-dashoffset: 1000; opacity: 1; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	54.167%     { stroke-dashoffset: 0; opacity: 1; }
	81.25%      { stroke-dashoffset: 0; opacity: 1; }
	87.5%       { stroke-dashoffset: 0; opacity: 0; }
	87.501%     { stroke-dashoffset: 1000; opacity: 0; }
	100%        { stroke-dashoffset: 1000; opacity: 0; }
}
@keyframes oc-film-crawl { to { stroke-dashoffset: -96; } }
@keyframes oc-film-pulse {
	0%, 54.167% { opacity: 0; }
	58.333%     { opacity: .8; }
	81.25%      { opacity: .8; }
	85.417%     { opacity: 0; }
	100%        { opacity: 0; }
}
@keyframes oc-film-way {
	0%, 37.5% { transform: scale(.5); opacity: .2; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	41.667%   { transform: scale(2.2); opacity: 1; }
	45.833%   { transform: scale(1); opacity: 1; }
	81.25%    { transform: scale(1); opacity: 1; }
	87.5%     { transform: scale(.5); opacity: .2; }
	100%      { transform: scale(.5); opacity: .2; }
}
@keyframes oc-film-staff {
	0%, 33.333% { opacity: .42; transform: scale(.96); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	56.25%      { opacity: 1; transform: scale(1.07); }
	58.333%     { opacity: 1; transform: scale(1); }
	81.25%      { opacity: 1; transform: scale(1); }
	87.5%       { opacity: .42; transform: scale(.96); }
	100%        { opacity: .42; transform: scale(.96); }
}
@keyframes oc-film-ack {
	0%, 56.25% { transform: scale(.5); opacity: 0; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	58.333%    { transform: scale(.72); opacity: .95; }
	66.667%    { transform: scale(1.8); opacity: 0; }
	100%       { transform: scale(1.8); opacity: 0; }
}
@keyframes oc-film-bar {
	0%   { stroke-dashoffset: 1000; }
	100% { stroke-dashoffset: 0; }
}
@keyframes oc-film-step {
	0%      { opacity: .55; }
	1%      { opacity: 1; }
	16.667% { opacity: 1; }
	18.75%  { opacity: .55; }
	100%    { opacity: .55; }
}
@keyframes oc-film-pip {
	0%      { transform: scale(.72); opacity: .4; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
	1.5%    { transform: scale(1.3); opacity: 1; }
	4%      { transform: scale(1); opacity: 1; }
	16.667% { transform: scale(1); opacity: 1; }
	18.75%  { transform: scale(.72); opacity: .4; }
	100%    { transform: scale(.72); opacity: .4; }
}

/* --------------------------------------------------------------------------
   30b. ONE MOMENT PER SCENE
   -------------------------------------------------------------------------- */


/* --- scene 2, SCENARIOS: the signature cover-hold-reveal ---------------
   ordercube_script.js swaps scenario text behind a cover that wipes across,
   HOLDS for 10% of the envelope while the text changes underneath, then
   wipes back. Here the cover is the caption's own clip-path, so there is no
   extra element to get stuck half-way. Slides are swapped by the `hidden`
   attribute (motion.js), so un-hiding re-runs this — which is the point.
   `both` leaves it at inset(0), i.e. fully visible, never clipped. */
.oc-scenarios__slide:not([hidden]) .oc-scenarios__caption {
	animation: oc-cover-hold-reveal var(--oc-dur-swap) ease both;
}

/* --- scene 3, FEATURES: the specular sheen is GONE --------------------
   [A11Y-2026-10-03] INVENTED DECORATION REMOVED. `.oc-features__device::after`
   ran `oc-sheen` forever — translateX(-62%) -> translateX(62%), a 124%-wide
   horizontal sweep on an infinite loop, which is why the detector called it a
   marquee. ORIGINAL'S CORRESPONDING COUNT: ZERO.
     * `rg -in 'sheen|shine|shimmer|gloss|specular|marquee|ticker'` over
       harvest/ordercube_styles.css and harvest/home.html -> no matches.
     * the counterpart element is `.feature-image-container > img.feature-image`
       (harvest/home.html:737-740), styled at harvest/ordercube_styles.css:252-280,
       whose ONLY motion is a one-shot state change:
       `transition: opacity 0.6s ease, filter 0.6s ease` (:274) with
       blur(20px) -> blur(0px) on `.active` (:277-280). No `animation`, no
       `infinite`, no horizontal travel.
   Removed, not restyled. `@keyframes oc-sheen` goes with it. */

/* --- scene 4, REFERENCES: the quote resolves out of focus --------------
   The original has a `fadeInBlur` but spends it on per-word mobile text.
   It is a better fit on the thing that actually changes: the quote. Blur is
   the point — this is the one place on the page where the motion is not
   transform or opacity. 480ms so the focus pull is readable, not a flicker. */
.oc-references__slide:not([hidden]) .oc-references__quote {
	animation: oc-focus-pull 480ms var(--oc-ease-expo) both;
}

/* --- scene 6, FAQ: the room drifts -------------------------------------
   48s and 60s are the reference's own two blob periods and its only
   `infinite` CSS animations; 42s is the period its third blob ran at from
   requestAnimationFrame (a lemniscate with a 1 +/- 0.12 breath), re-authored
   here as CSS so it is not frame-rate dependent and stops with the tab.
   `overflow: clip` rather than `hidden`: clip does not create a scroll
   container, so the scene's `scroll-snap-align` is untouched. */
.oc-faq { position: relative; overflow: clip; }

.oc-faq::before,
.oc-faq::after,
.oc-faq__inner::before {
	content: "";
	position: absolute;
	z-index: 0;
	pointer-events: none;
	border-radius: 50%;
	filter: blur(10px);
}

.oc-faq::before {
	inline-size: 46vmax;
	block-size: 46vmax;
	inset-block-start: -14vmax;
	inset-inline-start: -12vmax;
	background: radial-gradient(circle at 50% 50%, rgba(184, 155, 131, 0.20) 0%, rgba(184, 155, 131, 0) 68%);
	opacity: .8;
	animation: oc-blob-a var(--oc-dur-blob-a) ease-in-out infinite;
}

.oc-faq::after {
	inline-size: 54vmax;
	block-size: 54vmax;
	inset-block-end: -20vmax;
	inset-inline-end: -16vmax;
	background: radial-gradient(circle at 50% 50%, rgba(183, 123, 56, 0.18) 0%, rgba(183, 123, 56, 0) 66%);
	opacity: .7;
	animation: oc-blob-b var(--oc-dur-blob-b) ease-in-out infinite;
}

/* The lemniscate. `.oc-faq__inner` is already positioned by section 20. */
.oc-faq__inner::before {
	inline-size: 26vmax;
	block-size: 26vmax;
	inset-block-start: 12%;
	inset-inline-start: 58%;
	background: radial-gradient(circle at 50% 50%, rgba(139, 115, 85, 0.38) 0%, rgba(139, 115, 85, 0) 70%);
	opacity: .6;
	animation: oc-blob-c var(--oc-dur-blob-c) linear infinite;
}

/* ...and the answer arrives out of focus, same idea as the quote, half the
   distance because the row is already close to the reader's eye. */
.oc-faq-item.is-active .oc-faq-item__a-wrapper {
	animation: oc-focus-pull 320ms var(--oc-ease-expo) both;
}

/* --- scene 7, CONTACT: the edge signal is GONE ------------------------
   [A11Y-2026-10-03] INVENTED DECORATION REMOVED. `.oc-contact__card::after` ran
   `oc-edge-signal` forever — translateX(-110%) -> translateX(320%), a 430%-wide
   traverse of the card's top edge on an infinite loop: a true scanline marquee.
   ORIGINAL'S CORRESPONDING COUNT: ZERO.
     * `rg -n 'animation' harvest/ordercube_styles.css | rg -i 'contact|card'`
       -> no matches.
     * the counterpart elements are `.contact-cards`, `.contact-header-card`,
       `.contact-info-card` (harvest/home.html:1716-1724). None carries an
       `animation`. The nearest decorative pseudo-element,
       `.contact-hero-media::before` (harvest/ordercube_styles.css:2941), is a
       STATIC overlay.
   Removed, not restyled. `@keyframes oc-edge-signal` goes with it.
   `position: relative; overflow: hidden` stays on the card: the clip is now
   doing nothing for this pseudo-element, but it is also what keeps any future
   bleed inside the card, and `position: relative` is the containing block for
   the card's other positioned content. */
.oc-contact__card { position: relative; overflow: hidden; }

@keyframes oc-cover-hold-reveal {
	0%   { clip-path: inset(0 0 0 0); }
	45%  { clip-path: inset(0 0 0 100%); }   /* covered */
	55%  { clip-path: inset(0 100% 0 0); }   /* held, now re-entering */
	100% { clip-path: inset(0 0 0 0); }
}

@keyframes oc-focus-pull {
	0%   { opacity: 0; filter: blur(10px); transform: translateY(6px); }
	100% { opacity: 1; filter: blur(0); transform: none; }
}

@keyframes oc-blob-a {
	0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
	33%      { transform: translate3d(7%, 5%, 0) scale(1.08); }
	66%      { transform: translate3d(-4%, 9%, 0) scale(.94); }
}

@keyframes oc-blob-b {
	0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
	40%      { transform: translate3d(-8%, -6%, 0) scale(1.1); }
	70%      { transform: translate3d(5%, -3%, 0) scale(.92); }
}

/* The figure-of-eight, sampled at eight points of x = a*sin(t),
   y = a*sin(t)cos(t) with a = 15%, plus the 1 +/- 0.12 breath. */
@keyframes oc-blob-c {
	0%    { transform: translate3d(0, 0, 0) scale(1); }
	12.5% { transform: translate3d(10.6%, 7.5%, 0) scale(1.08); }
	25%   { transform: translate3d(15%, 0, 0) scale(1.12); }
	37.5% { transform: translate3d(10.6%, -7.5%, 0) scale(1.04); }
	50%   { transform: translate3d(0, 0, 0) scale(.94); }
	62.5% { transform: translate3d(-10.6%, 7.5%, 0) scale(.88); }
	75%   { transform: translate3d(-15%, 0, 0) scale(.92); }
	87.5% { transform: translate3d(-10.6%, -7.5%, 0) scale(.98); }
	100%  { transform: translate3d(0, 0, 0) scale(1); }
}


/* ==========================================================================
   31. SHAPE + DEPTH VOCABULARY — the two things this build had read as absent
   -------------------------------------------------------------------------
   MEASURED against the original (harvest/ordercube_styles.css). Two findings,
   and one of them corrects an earlier reading in THIS stylesheet.

   A. THE 32px FAMILY WAS INVISIBLE TO GREP. The original never writes `32px`
      in a border-radius — it writes `2rem`, and sets no root font-size, so 2rem
      computes to exactly 32px. A replica audit grepping `32px` finds nothing
      and concludes the value does not exist. It does, in two places:

        :2109  .control-input          border-radius: 2rem;          3 elements
        :2468  .faq-search-container   border-radius: 2rem;          1 element
        :2090  .control-label          border-radius: 0 2rem 2rem 0; 3 elements
        :4830  #impact .control-label  (mobile restatement, same value)

      Both 2rem users are the same "frosted pill" idiom, written twice:
      rgba(255,255,255,0.1) + backdrop-filter blur + 0.5rem padding + 2rem.

   B. THE NOTCH IS A SHAPE *PLUS* A BLEED, and half of it is useless alone.
      `.control-label` is a real <label> with `0 2rem 2rem 0`. It only reads as
      a notch because a negative-margin chain drags it off the left edge so its
      two SQUARE corners are clipped out of sight:
        :2070 .calculator-controls  margin-left: -2rem
        :2075 .control-group        margin-left: -4rem
        :2083 .control-label        margin-left: 0 + text-align: right
      Reproduce the radius without the bleed and you get a free-floating
      lozenge with two visibly square corners — a different shape.

      This build bleeds to the CONTROL GROUP'S own clip box rather than to the
      viewport. Same read, and it cannot contribute a pixel of horizontal page
      overflow, which the viewport-edge version can.

   WHAT CHANGED HERE. Until 1.4.0 this build put the frosted pill on the stepper
   ROW and left the label and the control cluster unstyled — an inversion of the
   original, where the row carries nothing, the LABEL is the white notch and the
   CLUSTER is the frosted pill. Corrected below.

   THE FOURTH 32px ELEMENT. The original's was `.faq-search-container`, a search
   pill that searched nothing; this build deleted it as a dead control (section
   11, DEFECT 7) and that decision stands — re-adding a dead search box to make
   a shape census add up would be the same defect class as the video modal that
   opened onto nothing. The idiom is kept on the live control that sits in the
   same cluster, `.oc-calc__optin`, so the vocabulary is four elements of 32px
   and three of the notch, as measured, without reinstating dead furniture.
   ========================================================================== */

:root {
	/* The original's 2rem, named for what it is rather than restated as a
	   magic number at four call sites. */
	--oc-radius-frost: 32px;
	--oc-radius-notch: 0 32px 32px 0;
}

/* The row is `.control-group`: a bare flex line that carries no surface of its
   own. The negative start margin is the bleed; `.oc-calc__controls` clips it. */
.oc-calc__controls { overflow: clip; }

.oc-stepper {
	background-color: transparent;
	border-radius: 0;
	padding: 0;
	gap: 16px;
	margin-inline-start: calc(var(--oc-radius-frost) * -1);
}

/* `.control-label`: white, black text, right-aligned, notch open to the left,
   and flexing to eat the space so the frosted cluster is pushed to the end. */
.oc-stepper__label {
	flex: 1 1 auto;
	min-block-size: 44px;
	display: flex;
	align-items: center;
	justify-content: flex-end;
	padding: 12px 24px 12px calc(var(--oc-radius-frost) + 24px);
	border-radius: var(--oc-radius-notch);
	background: var(--oc-ink);
	color: var(--oc-black);
	font-size: var(--oc-fs-link);
	font-weight: var(--oc-fw-light, 300);
	text-align: end;
}

/* `.control-input`: the frosted pill holding the two icon buttons and the
   value. 0.5rem padding and 2rem radius, verbatim from :2109. */
.oc-stepper__group,
.oc-calc__optin {
	border-radius: var(--oc-radius-frost);
	background: rgba(255, 255, 255, 0.1);
	padding: 8px;
	backdrop-filter: blur(var(--oc-blur));
	-webkit-backdrop-filter: blur(var(--oc-blur));
}

.oc-stepper__group { gap: 8px; flex: 0 0 auto; }

.oc-calc__optin {
	padding: 8px 20px 8px 8px;
	background-color: rgba(255, 255, 255, 0.1);
}

@media (max-width: 768px) {
	/* The original narrows the bleed on mobile (:4810-4822) rather than
	   dropping it, and cuts the label's start padding to 0. */
	.oc-stepper { margin-inline-start: -16px; }
	.oc-stepper__label { padding: 12px 16px 12px 24px; }
	.oc-calc__optin {
		padding: 8px 16px 8px 8px;
		border-radius: var(--oc-radius-frost);
		background-color: rgba(255, 255, 255, 0.1);
	}
}

/* --------------------------------------------------------------------------
   31b. THE ONE SHADOW — and the reading it corrects
   -------------------------------------------------------------------------
   `.oc-stat` carried `box-shadow: none` with the comment "no shadow anywhere in
   this design system". That was wrong, and wrong in a specific way worth
   recording: the original DOES have exactly one shadow value, and a census of
   the RENDERED page at rest finds zero elements wearing it, which is how the
   earlier audit concluded the design had none.

     :2261  .result-card:hover   (inside @media (hover: hover))
     :2292  .result-card.active
     :4884  #impact .result-card.active  (mobile restatement)

   THREE RULES, NOT THREE ELEMENTS. Six `.result-card`s exist; the shadow is
   purely interactive, `.active` is single-select (ordercube_script.js:1518-1525
   clears it from every card before setting it), so the count is:
     at rest .......... 0
     hovering one ..... 1
     peak ............. 2 (one hovered + one active)
   Painting it statically on three cards would NOT match the original — it would
   invent a resting elevation the original does not have. So it is reproduced as
   what it is: a lift that answers the pointer.

   `0 8px 25px rgba(0,0,0,0.15)` is the file's ONLY non-`none` box-shadow — no
   `0px` or spaced-rgba variants exist. Nothing else in this build may add one.
   -------------------------------------------------------------------------- */

.oc-stat {
	/* :2230 — the transition is what makes the lift read as a lift. */
	transition: transform var(--oc-dur-exit) var(--oc-ease), box-shadow var(--oc-dur-exit) var(--oc-ease);
}

@media (hover: hover) {
	.oc-stat:hover {
		transform: translateY(-2px);
		box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
	}
}

/* Keyboard parity the original never had: a card reached by Tab gets the same
   affordance as one reached by pointer. Costs nothing and is not a new value. */
.oc-stat:focus-within {
	transform: translateY(-2px);
	box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}



/* ==========================================================================
   32. VIDEO SURFACES
   -------------------------------------------------------------------------
   Both files under assets/video/ are encodes of this theme's OWN artwork —
   hero-motion.svg and the overlay's product-loop SVG, inlined in a capture
   page, seeked frame by frame through one period with the Web Animations API,
   and run through ffmpeg. No footage, no third-party frame, nothing imported.

   The loops close on themselves: the hero's ambient layers were retuned from
   9s/11s/6s to 14s/7s/7s so every animation divides the 14s capture, and the
   film's 6s crawl already divided its 24s. Without that the splice pops.

   STALE NOTE CORRECTED 1.8.6. This block used to say "`autoplay` appears in
   NEITHER element ... the hero is started by assets/js/motion.js only under
   `no-preference`". Neither half had been true since README deviation 59: the
   hero markup carries the original's `autoplay` verbatim and motion.js started
   it unconditionally. As of 1.8.6 the background layer IS gated on
   `prefers-reduced-motion` again — by script, which strips the attribute
   before first play and leaves the poster up — so the behaviour the old note
   described is finally the behaviour that ships, for a different reason and by
   a different mechanism. The modal still waits for a press.
   ========================================================================== */

.oc-media__video {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
	display: block;
	background-color: #0e0f13;
	/* It is decoration behind a headline: never a pointer target, never a
	   focus stop. `aria-hidden` + `tabindex="-1"` do the rest in the markup. */
	pointer-events: none;
}

.oc-film-video {
	inline-size: 100%;
	block-size: 100%;
	display: block;
	object-fit: contain;
	background-color: #0b0c10;
}

/* The in-element SVG fallback only renders where <video> is unsupported, so it
   must fill the same box when it does appear. */
.oc-film-video > .oc-film {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
}


/* ==========================================================================
   33. THE VIDEO TOGGLE
   -------------------------------------------------------------------------
   Rendered in both preference states, and that is the whole point of it.

   Gating PLAYBACK on `prefers-reduced-motion` — which the first cut of this
   did — means a reader with reduce set (a common OS default, and disproportion-
   ately someone using other assistive settings too) gets a silent poster with
   nothing to indicate a video exists. The preference is about motion starting
   WITHOUT being asked for; it is not a request to be denied the content.

   It is also what makes the `no-preference` path conform: WCAG 2.2.2 requires a
   pause mechanism for automatically-playing motion lasting more than five
   seconds, and this loop is fourteen. So the control is load-bearing twice over
   and is never hidden by CSS in either state.

   It reuses the system as it already stands: bronze accent, 999px pill, 14px /
   300 / uppercase with the button letter-spacing, a 44px minimum target, and no
   shadow (section 31b: the one shadow in this design belongs to the stat card
   lift and nothing else may borrow it).
   ========================================================================== */

/* `.oc-hero__stage` is gone as of 1.8.6 — see patterns/scene-1-hero.php. The
   toggle is positioned against the SCENE now (`.oc-scene` is already
   `position: relative`), and sits inside the white frame rather than inside a
   media panel's padding box. */
/* THE PAUSE CONTROL IS FIXED TO THE VIEWPORT, 1.8.9.

   It is rendered by patterns/site-overlays.php as a direct child of <body>,
   OUTSIDE `.oc-scroll-track`, which is the only place `position: fixed` can
   actually mean "fixed to the viewport" on this page — inside the track a
   fixed element is positioned against the transformed ancestor instead.

   The two earlier attempts and what each measured, so neither is retried:
     1.8.6  anchored to the media panel  -> y=1309, 409px below the fold
     1.8.8  anchored to the scene's first viewport-height
                                         -> correct at the hero only; y=-569 at
                                            #scenarios, y=-3986 at #impact

   Inset by the frame border PLUS 12px, so it sits inside the snap-in rather
   than tucking under the white border as the 1.8.8 version did (its bottom
   12px overlapped it). `z-index` clears the frame's own 5. */
/* TWO CLASSES, 1.8.10, and the reason is a measured bug not a style choice.
   `.oc-video-toggle--global` alone is specificity (0,1,0) — an exact tie with
   the base `.oc-video-toggle` block BELOW, which sets `inset-block-end: 12px;
   inset-inline-start: 12px`. On a tie the later rule wins, so the base won and
   the button sat 12px from the viewport edge, i.e. HALF UNDER the 24px white
   frame border. That is the "visibly half-swallowed" defect, and it was not
   the custom property failing as first suspected: MEASURED x=12, y=844, which
   is the base rule's 12px exactly, not `gutter + 12`.

   `--oc-frame` rather than `--oc-gutter`: `--oc-gutter` is redefined on `body`
   inside the <=768px query, so its value depends on where it is read from, and
   this element is a direct child of <body>. `--oc-frame` is set once on
   `:root` and never overridden, and the explicit fallback means this is still
   inside the frame even if the custom property never arrives. 36px at every
   width, against a frame border of 24px desktop / 6px mobile — always clear of
   it, never clipped by it. */
.oc-video-toggle.oc-video-toggle--global {
	position: fixed;
	z-index: 6;
	inset-block-end: calc(var(--oc-frame, 1.5rem) + 12px);
	inset-inline-start: calc(var(--oc-frame, 1.5rem) + 12px);
	inset-block-start: auto;
	inset-inline-end: auto;
}

.oc-video-toggle {
	position: absolute;
	z-index: 3;
	inset-block-end: 12px;
	inset-inline-start: 12px;
	display: inline-flex;
	align-items: center;
	gap: var(--oc-space-xs, 8px);
	min-block-size: 44px;
	padding: 8px 18px 8px 10px;
	border: 1px solid rgba(255, 255, 255, 0.28);
	border-radius: var(--oc-radius-pill, 999px);
	background: rgba(14, 15, 19, 0.72);
	-webkit-backdrop-filter: blur(10px);
	backdrop-filter: blur(10px);
	color: var(--oc-ink, #fff);
	font-family: var(--oc-font-arial);
	font-size: var(--oc-fs-14, 14px);
	font-weight: 300;
	letter-spacing: var(--oc-ls-button, 0.56px);
	text-transform: uppercase;
	cursor: pointer;
	transition: background-color var(--oc-dur) var(--oc-ease), border-color var(--oc-dur) var(--oc-ease);
}

.oc-video-toggle:hover {
	background: rgba(14, 15, 19, 0.88);
	border-color: var(--oc-accent-start, #b89b83);
}

/* A real ring, not the UA default that the dark surface swallows. */
.oc-video-toggle:focus-visible {
	outline: 2px solid var(--oc-accent-start, #b89b83);
	outline-offset: 3px;
}

.oc-video-toggle__glyph {
	display: grid;
	place-items: center;
	inline-size: 26px;
	block-size: 26px;
	flex: 0 0 auto;
	border-radius: var(--oc-radius-circle, 50%);
	background: var(--oc-accent-end, #b77b38);
	color: #15161c;
}

.oc-video-toggle__glyph svg { inline-size: 14px; block-size: 14px; display: block; }

/* One glyph at a time; `.is-playing` is written by assets/js/motion.js from the
   element's own play/pause events, never from the last click. */
.oc-video-toggle__pause { display: none; }
.oc-video-toggle.is-playing .oc-video-toggle__play { display: none; }
.oc-video-toggle.is-playing .oc-video-toggle__pause { display: block; }

@media (max-width: 768px) {
	/* The label is the accessible name, so it must survive; only the padding
	   tightens. Never `display:none` on it. */
	.oc-video-toggle { padding: 8px 14px 8px 8px; font-size: 13px; }
}


/* ==========================================================================
   30. SCENE FIT — 1.9.2, 2026-10-03
   -------------------------------------------------------------------------
   THE DEFECT: every scene had interactive controls below the fold, unreachable
   at rest. Lock-in lands exactly at a scene's top (flushError 0.000px), so a
   control below the frame's inner bottom edge could only be clicked by parking
   between locks. Measured baseline on 1.9.1: 23 of 28 scene x viewport
   combinations failed.

   THE BAR. A control is reachable iff
       bottom <= innerHeight - frameBottom      (the frame's inner bottom edge)
   and top >= the header's bottom edge. An earlier bar of
   `innerHeight - 2*frame - header` was DISCARDED because it double-subtracts
   the frame top (already inside the header's box) and treats a strip HEIGHT as
   a strip BOTTOM: measured against the harvested original, that bar fails
   28/28 by exactly 136px at every desktop viewport, which indicts the bar and
   not the product. The original scores 0/28 against the bar used here.

   THE METHOD. Every reduction below is derived from the original's own CSS or
   markup and cited file:line. No copy is invented, no type is shrunk below the
   original's measured sizes, no scene gets `overflow: hidden` and no scene
   height is pinned. Where an element had no counterpart in the original it is
   removed; where the original positions something out of flow, so does this.

   THE BIG ONE. The original renders EVERY section image as an absolutely
   positioned full-bleed background: `.hero-media { position: absolute; inset: 0;
   width: 100%; height: 100% }` (harvest/ordercube_styles.css:213) with
   `.hero-media video, .hero-media img { position: absolute; width: 100%;
   height: 100%; object-fit: cover }` (:343) — ZERO layout height. This theme
   had five in-flow `.oc-media` placeholder blocks instead, 203-693px each,
   which was the single largest source of overflow. They are now background
   layers on the existing `.oc-scene__media` / `.oc-scene__scrim` pair.
   ========================================================================== */

.oc-scene__media-art {
	position: absolute;
	top: 0;
	left: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;        /* harvest/ordercube_styles.css:343 */
	object-position: center;
	display: block;
	pointer-events: none;
}

/* --- Scene 1, HOME ------------------------------------------------------
   Six `.oc-marks__item` stacked into a 448-528px column. The original's trust
   block is a horizontal strip (harvest/home.html:555-556 `SEIT 2016` /
   `VERTRAUEN UNS` beside the logo marks), never a column. */
.oc-hero__trust {
	flex-direction: row;
	align-items: center;
	column-gap: 24px;
	flex-wrap: nowrap;
}
.oc-hero__trust .oc-eyebrow { margin: 0; flex: 0 0 auto; max-inline-size: 18ch; }
.oc-hero__trust .oc-marks--wordmarks {
	flex: 1 1 auto;
	flex-direction: row;
	flex-wrap: nowrap;
	max-inline-size: none;
	row-gap: 0;
	column-gap: 16px;
}
.oc-hero__trust .oc-marks__note { margin: 0; flex: 0 0 auto; max-inline-size: 22ch; }

@media (max-width: 1023px) {
	.oc-hero__trust { flex-wrap: wrap; row-gap: 8px; }
	.oc-hero__trust .oc-marks--wordmarks { flex-wrap: wrap; row-gap: 8px; }
}

.oc-hero-lockup { margin-block-end: 12px; }
.oc-hero__inner { row-gap: 16px; }
.oc-hero__cues  { margin-block-start: 16px; }
.oc-lead        { margin-block: 12px; }

/* --- Scene 2, SCENARIOS -------------------------------------------------
   The scenario art is `div.hero-media.no-parallax > img` (harvest/home.html:
   579-581), swapped per scenario from the section's own `data-solution-image`
   attribute (:578). Ours rendered a 666px in-flow 16:9 block inside every
   slide; the art is now the scene background and assets/js/motion.js swaps it
   in `show()`. `.oc-scenarios__stage` also carried its own 72vh floor, which
   survived the media removal — the stage is caption-sized now. */
.oc-scenarios__stage { min-block-size: 0; }

/* --- Scene 3, FEATURES -------------------------------------------------
   `.feature-image-container` (harvest/ordercube_styles.css:252-264) and
   `.feature-hand-container` (:282-294) are BOTH `position: absolute`, 40vw x
   60vh, centred, differing only in z-index (1 vs 10) — wholly out of flow.
   Ours had a 456px in-flow 1:1 device block plus a 315px in-flow 4:3 panel
   art. The per-feature art is now the swapped background; the constant device
   render becomes the hand overlay at the original's own box. */
.oc-features__panel { min-block-size: 0; }
.oc-features__copy  { row-gap: 12px; }
.oc-quote           { max-inline-size: none; }

/* `.oc-scene > *:not(...)` (section 19 above) sets `position: relative` at a
   higher specificity, so this overlay has to match it to stay out of flow. */
.oc-scene > .oc-features__hand {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	inline-size: 40vw;         /* harvest/ordercube_styles.css:262-263 */
	block-size: 60vh;
	/* z-index 0, NOT 1. At 1 this overlay tied with `.oc-features__inner` (also 1 via
	   `.oc-scene > *:not(...)`) and, being LATER in DOM order, painted OVER the copy
	   from x=432 — the device render covering "Quiet, then handled." and the panel
	   paragraph. It measured 1.05:1 and 1.10:1 on composited pixels.

	   It was missed for a whole pass because the overlay is `pointer-events: none`:
	   `elementFromPoint` returns the TEXT, so a hit-test says "copy is on top" while
	   the paint says otherwise. pointer-events affects hit-testing, never painting.

	   The original's own hand sits at z-index 10 (harvest/ordercube_styles.css:285)
	   above `.hero-content` (z-index 1, :389) — but its copy is an `aside` pushed
	   clear of the centred 40vw box, so nothing is covered. Ours shares the column,
	   so the overlay belongs with the media plane: above `.oc-scene__media` and the
	   scrim, below the content. */
	z-index: 0;
	pointer-events: none;
	display: flex;
	align-items: center;
	justify-content: center;
}
.oc-features__hand img {
	max-inline-size: 100%;
	max-block-size: 100%;
	inline-size: auto;
	block-size: auto;
	object-fit: contain;       /* :5645 */
}

@media (max-width: 768px) and (orientation: portrait) {
	.oc-scene > .oc-features__hand {
		top: 35vh;             /* :5633-5639 */
		left: 75%;
		inline-size: 90vw;
		block-size: 50vh;
	}
}

/* --- Scene 4, REFERENCES ------------------------------------------------
   `.oc-references__grid` declared `40% 20% 40%` (section 9 above) while all
   six children were auto-placed, so the disclaimer was crushed into the 20%
   track, `.oc-references__arrows` was handed a whole 40% track and
   `.oc-references__cues` was orphaned onto row 2. The three-column intent
   belongs to the SLIDE, which has its own `2fr 1fr 2fr` (section 19 above).
   The outer grid collapses to one explicit column with named rows.

   The original's band is an absolutely positioned, bottom-pinned two-row grid,
   `.references-bottom-wrapper { position: absolute; bottom: 0; width: 100vw;
   display: grid; grid-template-rows: 40vh 5.5rem; gap: 1.5rem }`
   (harvest/ordercube_styles.css:5469-5478), and the band itself is a PINNED
   `height: 40vh` (:1075, duplicated at :5476). There is no `min-height`
   anywhere in that section. A long quote still cannot move it, because
   `.reference-quote-container { flex: 1; align-items: center }` (:1350-1363)
   absorbs the slack and `.reference-attribution { margin-top: auto }`
   (:1443-1454) is welded to the bottom. This theme keeps the band
   CONTENT-SIZED rather than pinned — a pin can crop, and cropping is what the
   acceptance test forbids — and replicates the slack-absorbing flex instead. */
@media (min-width: 1024px) {
	.oc-references__grid {
		grid-template-columns: minmax(0, 1fr);
		grid-template-areas: "slide" "disc" "nav";
		row-gap: 12px;
	}
	.oc-references__slide      { grid-area: slide; }
	.oc-references__disclaimer { grid-area: disc; margin: 0; }
	.oc-references__arrows     { grid-area: nav; justify-self: end; }
	.oc-references__cues       { grid-area: nav; justify-self: start; margin-block-start: 0; }
}

.oc-references__band {
	min-block-size: 0;
	block-size: auto;
	padding-block: 24px;
}
.oc-references__top {
	padding-block: 0 12px;
	min-block-size: 0;
}
/* The header reservation was duplicated INSIDE the scene as
   `.oc-references__top { padding-block-start: 160px }` because the scene itself
   was padded 0/0. One rule owns the offset now, the same one every other scene
   uses. */
[data-oc-scene="references"] {
	padding-block: calc(var(--oc-header-h) + 24px) 64px;
}
.oc-references__photo .oc-media {
	block-size: auto;
	max-block-size: 168px;
	aspect-ratio: 4 / 3;
}
.oc-references__quote { padding: 16px 24px; }
.oc-references__quote blockquote { min-block-size: 0; }

@media (max-width: 768px) {
	/* The original hides the logo tile outright below 768:
	   `.reference-logo-container { display: none }`
	   (harvest/ordercube_styles.css:1624-1626). */
	.oc-references__mark { display: none; }
	.oc-references__photo .oc-media { max-block-size: 72px; }
	.oc-references__slide { row-gap: 8px; }
	.oc-references__band { padding-block: 12px; }
	.oc-references__quote { padding: 8px 16px; }
	.oc-references__inner { row-gap: 12px; }
	.oc-references__top-inner { row-gap: 8px; }
	.oc-references__attrib { line-height: 1.3; }
	[data-oc-scene="references"] { padding-block: calc(var(--oc-header-h) + 24px) 16px; }
}

/* --- Scene 5, IMPACT ----------------------------------------------------
   TRAP, AND IT IS REFUTED BY MEASUREMENT: the original's impact section is NOT
   simpler than ours. It holds the same inventory — a six-tile
   `div.results-grid > div.result-card` grid (harvest/home.html:1246-1330, tiles
   at :1247 :1261 :1275 :1289 :1303 :1317), three stepper groups / six
   `.control-btn` (:1196 :1198, :1205 :1207, :1214 :1216), the
   `#waiter-agreement` checkbox (:1223) and six `.card-disclaimer` lines
   (:1258 :1272 :1286 :1300 :1314 :1328). Removing the card grid would DEPART
   from the replica. What differs is only CSS:

     `.result-card  { min-height: 140px }`  harvest/ordercube_styles.css:2222-2232
     `.card-disclaimer { position: absolute; opacity: 0 }`              :2315-2325

   so the original's disclaimers carry ZERO layout height, where ours were
   in-flow paragraphs forcing a 189px floor to render at 237-251px. Ours are now
   positioned the same way but revealed on hover AND focus-within, so unlike the
   original they stay reachable from the keyboard.

   The layout also follows the original: `.impact-layout { display: grid;
   grid-template-columns: 1fr 1fr; align-items: center }` (:2048-2055) with the
   heading and the CTA INSIDE the calculator column (`h1.impact-title`
   harvest/home.html:1190, `div.investigate-faq` :1229-1240), and
   `.results-grid { grid-template-columns: repeat(3, 1fr); grid-template-rows:
   repeat(2, 1fr) }` (:2213-2220) in the other. `.oc-calc { display: contents }`
   lets the pattern keep its DOM order while the heading, panel and CTA stack in
   column one and the cards fill column two. */
.oc-stat {
	min-block-size: 140px;     /* harvest:2222 — was 189px here */
	position: relative;
}
.oc-stat__caveat {
	position: absolute;        /* harvest:2315-2325 — zero layout height */
	inset-inline: 24px;
	inset-block-end: 24px;
	opacity: 0;
	margin: 0;
	transition: opacity 0.3s ease;
}
.oc-stat:hover .oc-stat__caveat,
.oc-stat:focus-within .oc-stat__caveat { opacity: 1; }

.oc-impact__inner { row-gap: 16px; }
.oc-impact__title { margin-block-end: 8px; }
.oc-impact__cues  { margin-block-start: 8px; }
.oc-calc__controls { row-gap: 8px; }
.oc-calc__disclaimer { margin-block-start: 8px; }
.oc-stepper { padding-block: 2px; }
.oc-calc__optin { min-block-size: 36px; padding-block: 6px; }

@media (min-width: 1024px) {
	.oc-impact__inner {
		display: grid;
		grid-template-columns: minmax(0, 560px) minmax(0, 1fr);
		grid-template-areas: "title stats" "panel stats" "cues stats";
		align-content: center;
		column-gap: 40px;
	}
	.oc-calc { display: contents; }
	.oc-impact__title { grid-area: title; }
	.oc-calc__panel   { grid-area: panel; }
	.oc-impact__cues  { grid-area: cues; }
	.oc-stats         { grid-area: stats; align-self: center; }
}

@media (max-width: 1023px) { .oc-calc { row-gap: 24px; } }

/* Below 768 the original turns the results grid into a horizontally scrolling
   card strip rather than a six-high stack — `#impact .results-grid`
   (harvest/ordercube_styles.css:4849-4861, with its own
   `::-webkit-scrollbar` rule) plus `.result-card:nth-child(n+4)` at :4904. */
@media (max-width: 768px) {
	.oc-stats {
		grid-template-columns: none;
		grid-auto-flow: column;
		grid-auto-columns: 78%;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		padding-block-end: 8px;
	}
	.oc-stat { scroll-snap-align: start; }
}

/* --- Scene 6, FAQ -------------------------------------------------------
   TRAP, AND IT HOLDS: faq is already SHORTER than the original (271 words vs
   374) and still overflowed, so the fix is rhythm and pagination, never copy.
   Row height is viewport-driven in the original too:
   `.faq-question { padding: clamp(1rem, 2vh, 2rem) 1rem }`
   (harvest/ordercube_styles.css:2527).

   The original does NOT paginate at a fixed seven per page — there is no page
   size constant anywhere. `getAvailableHeight()`
   (harvest/ordercube_script.js:6123-6158) measures the available PIXEL height
   and `calculatePageBreaks()` (:6161-6215) fits as many rows as fit, pushing a
   break when `currentHeight + itemHeight > availableHeight` (:6196-6203). Page
   size therefore varies by viewport — and that mechanism is exactly what this
   gate needs, because the list can then never exceed its box. Wired in
   assets/js/motion.js. The pager buttons were previously DEAD: `#oc-faq-prev`
   and `#oc-faq-next` (patterns/scene-6-faq.php:138, :146) had no listener. */
.oc-faq-item__question { padding-block: clamp(8px, 1.4vh, 16px); }

@media (max-width: 768px) {
	.oc-faq__head { row-gap: 8px; margin-block-end: 8px; }
	.oc-faq__title { margin-block-end: 8px; }
	.oc-faq__inner { row-gap: 12px; }
	.oc-faq__pagination { margin-block-start: 8px; }
	[data-oc-scene="faq"] { padding-block: calc(var(--oc-header-h) + 24px) 16px; }
}

/* --- Scene 7, CONTACT ---------------------------------------------------
   The original's contact imagery is absolutely positioned:
   `.contact-hero-media { position: absolute; inset: 0; width: 100%;
   height: 100% }` (harvest/ordercube_styles.css:2932-2939) and
   `.contact-hero-media img { position: absolute; width: 100%; height: 100%;
   object-fit: cover }` (:2949-2957), with `.contact-bg-bottom { display: none }`
   on desktop (:2959) and shown as a 60/40 split below 768 (:5003-5013). Ours was
   a 403px in-flow column. It is now the scene background, which frees the grid's
   two tracks to carry the info CARD and the FORM side by side instead of
   stacking them.

   The form itself has NO counterpart — there is no `<form>` anywhere in the
   original (zero matches across harvest/home.html:577-2002); it is a declared
   bucket-B deviation. Its rhythm is tightened rather than its copy cut, and the
   invented-brand disclosure on `.oc-form__intro` is kept. */
@media (min-width: 1024px) {
	.oc-contact__grid {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
		align-items: start;
		column-gap: 32px;
	}
	.oc-contact__col--form { padding-block: calc(var(--oc-header-h) + 24px) 48px; }
}

.oc-form { row-gap: 12px; }
.oc-form__field { row-gap: 4px; }
.oc-form__field textarea { min-block-size: 0; block-size: 72px; }
.oc-form__heading { margin-block: 24px 8px; }
.oc-form__intro { margin-block: 0 16px; }
.oc-form__actions { row-gap: 8px; }
.oc-contact__card { padding: 16px; row-gap: 8px; }
.oc-contact__social { margin-block-start: 16px; }

@media (min-width: 1024px) {
	.oc-form { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
	.oc-form__field--wide,
	.oc-form__actions { grid-column: 1 / -1; }
}

@media (max-width: 768px) {
	.oc-form { row-gap: 12px; }
	.oc-form__field textarea { block-size: 60px; }
	.oc-contact__social { margin-block-start: 8px; }
	.oc-form__heading { margin-block: 12px 4px; }
	.oc-contact__col { padding-block: calc(var(--oc-header-h) + 24px) 16px; }
	.oc-contact__col--form { padding-block: 0 16px; }
}

/* --- Scene 3 follow-up, measured on the deployed 1.9.2 render ------------
   With the two in-flow media blocks gone the copy column became the tallest
   thing in the scene (640px at 1280x720). The original's counterpart is a
   single `p.feature-description` whose text is swapped in place
   (harvest/home.html:975-979) inside `aside.features-content`; it carries no
   second paragraph and no quote above it. Capping the measure and the rhythm
   brings the column under the fold without touching a font size. */
.oc-features__copy { align-content: center; row-gap: 8px; }
.oc-features__panel h3 { margin-block-end: 4px; }
.oc-features__panel p { margin-block-end: 8px; }
.oc-features__cues { margin-block-start: 8px; }

@media (max-width: 1023px) {
	.oc-features__inner { row-gap: 12px; }
	.oc-features__list { row-gap: 2px; }
}

/* --- Detector regressions introduced by this pass, both closed -----------
   1. `.oc-scenarios__stage { overflow: hidden }` (section 8 above) existed to
      contain the IN-FLOW 16:9 media's corners. That media is now the scene
      background, so the clip no longer contains anything except the
      absolutely-positioned `.oc-scenarios__counter` — which is precisely what
      `clipped-overflow-container` flags. The clip is dead weight now, so it
      goes rather than being suppressed. `.oc-media` carries its own
      `overflow: hidden` (section 7), so nothing else relied on this one.
   2. The impact h2 is restored as `.oc-visually-hidden` in
      blocks/impact-calculator/render.php — see the note there. */
.oc-scenarios__stage { overflow: visible; }

/* --- The media's OWN baseline scrim -------------------------------------
   MEASURED REGRESSION, found by composited-pixel sampling on the deployed
   1.9.2 render: putting an image behind four scenes left five text elements
   failing WCAG 1.4.3 — `p.oc-quote` 1.206:1, the features panel `h3` 2.855:1
   and its `p` 2.816:1, `.oc-calc__disclaimer` 1.866:1 and the opt-in label
   2.421:1. The cause is that `.oc-scene__scrim` is the SCROLL-DRIVEN layer:
   its `opacity: calc(1 - var(--oc-scrim))` resolves to 0 for any scene that is
   not the active one, so at rest the text sat on the raw image with no
   darkening at all. Before this pass those scenes had no image behind them —
   the surface was the flat `--oc-bg` — so the scroll-driven layer going
   transparent cost nothing.

   The original has a SECOND, always-on layer for exactly this. It injects
   `div.section-overlay-div` INTO the media div with a fixed baseline
   `opacity: 0.5` (harvest/ordercube_script.js:2386-2395, cssText at :2394),
   flat black for every section and a centre-transparent radial for #features
   (:2390-2392). `.hero-media img/video` carry `z-index: -1` (:343) precisely so
   that overlay can sit above them. That layer is reproduced here as an
   `::after` on the media, so it is independent of the scroll-driven scrim and
   can never resolve away. Zero layout cost — it is absolutely positioned. */
.oc-scene__media::after {
	content: '';
	position: absolute;
	inset: 0;
	/* ordercube_script.js:2391 — flat black for every section but #features. */
	background: linear-gradient(180deg, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1));
	opacity: 0.5;                /* :2394 — the original's baseline */
	pointer-events: none;
	z-index: 1;
}

/* ordercube_script.js:2390 — #features gets the centre-out radial instead, so
   the product render stays visible through the middle. Its own text sits off
   to the sides, where the ramp is at its darkest. */
.oc-features .oc-scene__media::after {
	background: radial-gradient(
		circle at center,
		transparent 0%,
		transparent 15%,
		rgba(0, 0, 0, 0.5) 40%,
		rgba(0, 0, 0, 0.8) 70%,
		rgba(0, 0, 0, 0.95) 100%
	);
	opacity: 0.85;
}

/* ==========================================================================
   31. VISUAL DEFECTS — 1.9.3, 2026-10-03
   -------------------------------------------------------------------------
   Everything in this section was found by LOOKING at the rendered page. Not one
   of these was reported by the fold probe, the detector, or the contrast
   sampler: a scene can satisfy every numeric gate and still be visibly broken.
   Two of the items are regressions THIS theme's own 1.9.2 pass introduced.
   ========================================================================== */

/* --- P1-1. The pause/play toggle covered content at 390 ------------------
   MEASURED: the toggle is `position: fixed` at (36,764) 177.5x44 and overlapped
   FIVE elements of the hero's trust strip (the NORTHWIND and ATLAS GROUP
   wordmarks), and the contact form's first field label. Making it fixed solved
   reachability in 1.9.1 and created an overlap.

   Below 768 it becomes icon-only — the original's own control is a bare 2rem
   glyph button with no label at all (`.menu-btn`, harvest/ordercube_styles.css
   :176-186) — and every scene reserves the strip it occupies, so no content can
   land beneath it at any scroll position. */
@media (max-width: 768px) {
	.oc-video-toggle__label {
		position: absolute;
		inline-size: 1px;
		block-size: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}
	.oc-video-toggle {
		inline-size: 44px;
		block-size: 44px;
		padding: 0;
		justify-content: center;
	}
	/* The reserved strip: toggle height + its inset + a gutter. */
	.oc-scene { padding-block-end: 84px; }
}

/* --- P1-2. "Next scenario" collided with the chip row -------------------
   SEEN on the render: the textlink's dark backing shape overlapped the
   "THE EMPTY GLASS" chip. A getBoundingClientRect test reported NO overlap —
   the chip ends at x=712 and the link box starts at x=725 — because the paint
   extends beyond the element's own border box. Rect tests cannot see this
   class of defect; only the render can.

   `.oc-scenarios__controls` placed the chip column and the cue row side by side
   with 16px between them. They stack now, which is also how the original reads:
   its scenario list (`div.scenario-items`, harvest/home.html:622-694) is a
   column and the next/prev ghost buttons are a separate wrapper
   (`.ghost-wrapper`, harvest/ordercube_styles.css:500-505), never beside it. */
.oc-scenarios__controls {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	grid-auto-flow: row;
	align-items: start;
	row-gap: 16px;
}
.oc-scenarios__cues { justify-content: flex-start; }

/* --- P1-5. INVARIANT B: the white boxes must reach the frame -------------
   MEASURED against the frame's inner edge, before: at 1440 the scenarios card
   sat 80px short, the references band 80px short on BOTH sides, the contact
   card 104px short. At 1024 and 390 they already touched, which is why this
   only ever showed on wide viewports.

   This reuses the hero slab's proven relationship verbatim (section "the slab
   bleed" above): a negative inline margin moves the WHITE outward, and the same
   amount comes back as inline padding so the TYPE does not move. Only the white
   extends. `--oc-bleed` is the generalised form of `--oc-hero-bleed`:

     centring gutter   max(0px, (100vw - grid-max) / 2)
     column padding    var(--oc-gutter)
     overshoot         8px     <- the original's measured -8px landing

   Each box's opposite edges are unaffected: a box that gains `bleed` of padding
   while its border box starts `bleed` further out keeps its far edge exactly
   where it was, which is why this cannot create horizontal overflow. */
.oc-scene {
	--oc-bleed: calc(max(0px, (100vw - var(--oc-grid-max)) / 2) + var(--oc-gutter) + 8px);
}

/* The references band is full-bleed on BOTH sides. Its inner grid keeps its own
   max-inline-size and auto margins, so it stays centred on the same axis and no
   type moves. */
.oc-references__band {
	margin-inline: calc(-1 * var(--oc-bleed));
	padding-inline: var(--oc-bleed);
}

/* The scenarios caption and the contact card reach the LEFT border only — they
   are left-column boxes, and their right edges stay inside the snap-in. */
/* SEEN on the render, and it is why this needs the width term: the hero slab is an
   `inline-block` with AUTO width, so padding grows its box. `.oc-scenarios__caption`
   is `inline-size: 100%` + `box-sizing: border-box` (section 8), so the added
   padding ate the CONTENT box instead — the card was dragged left and its copy
   squeezed to one word per line, over the header. A fixed-width box has to gain the
   bleed as WIDTH as well, which keeps the content box exactly where it was and moves
   only the white edge. */
/* MEASURED, and the chain is NOT the hero's. The hero slab is a direct child of
   `.oc-scene > *`, so `--oc-bleed` describes its whole offset. The caption sits two
   boxes deeper — `.oc-scenarios__stage` adds a gutter and `.oc-scenarios__layer`
   adds 24px of padding — and the inner's own left edge is max(centring, gutter),
   not centring + gutter. Measured natural left edges: 128 at 1440, 72 at 1024,
   36 at 390; the target is -8 at all three, so the bleed must be 136 / 80 / 44.
   The form below yields exactly those:
     1440: max(80,24) + 24 + 24 + 8 = 136
     1024: max( 0,24) + 24 + 24 + 8 =  80
      390: max( 0, 6) +  6 + 24 + 8 =  44 */
.oc-scenarios__caption {
	--oc-caption-bleed: calc(
		max(max(0px, (100vw - var(--oc-grid-max)) / 2), var(--oc-gutter))
		+ var(--oc-gutter) + 24px + 8px
	);
	margin-inline-start: calc(-1 * var(--oc-caption-bleed));
	padding-inline-start: calc(var(--oc-caption-bleed) + 24px);
	inline-size: calc(100% + var(--oc-caption-bleed));
}

@media (min-width: 1024px) {
	.oc-contact__card {
		margin-inline-start: calc(-1 * var(--oc-bleed));
		padding-inline-start: calc(var(--oc-bleed) + 16px);
		inline-size: calc(100% + var(--oc-bleed));
	}
}

/* --- P2-7. The reference controls were orphaned in a floating panel ------
   SEEN on the render: "Watch the film" / "View the customer map" and the pager
   dots sat in a dark slab ABOVE the white band, reading as unattached to the
   quote they belong to.

   The original attaches each group to what it controls. The two actions are
   absolutely positioned over the video — `#references .cta-row.desktop-watch-video
   { position: absolute; top: 27%; left: 3rem }` (harvest/ordercube_styles.css
   :5494-5500) and `.desktop-view-map { ... right: 3rem }` (:5503-5509) — and the
   pager rides the quote band itself, `.quote-indicators { position: absolute;
   top: -1.5rem; left: 0 }` inside `.reference-quote-section` (:1405-1412).
   The dots moved onto the band in the markup; the actions go out of flow here,
   which also removes the panel's height from the scene. */
.oc-scene > .oc-references__inner .oc-references__top {
	position: absolute;
	inset-inline: var(--oc-gutter);
	/* MEASURED after the first attempt: `top: 27%` put the actions at y=240 while
	   the band occupies y=184-518, so the band painted straight over them — the
	   controls were not orphaned any more, they were INVISIBLE. The original can
	   use 27% because its band is pinned to the bottom 40vh
	   (`.references-bottom-wrapper { bottom: 0 }`, harvest/ordercube_styles.css
	   :5469-5478) leaving the whole upper section free; ours centres the band, so
	   the free video area is BELOW it. They go there — still over the video, where
	   a ghost button reads, and now adjacent to the band they belong to. */
	/* MEASURED, second correction: `.oc-references__inner` IS the band — 334px tall
	   at y=184 — so a percentage inset resolves inside it and the actions landed at
	   y=430, on top of "Check impact". The free dark area is below the inner, so the
	   offset is taken from the inner's own bottom edge instead of a percentage. */
	inset-block-start: calc(100% + 52px);
	inset-block-end: auto;
	padding: 0;
	min-block-size: 0;
	background: none;
	pointer-events: none;
	z-index: 2;
}
.oc-references__top .oc-references__actions { pointer-events: auto; }
.oc-references__top-inner { display: block; }

.oc-references__band { position: relative; }
.oc-references__band > .oc-dots {
	position: absolute;
	inset-block-start: -28px;      /* :1407 — top: -1.5rem */
	inset-inline-start: var(--oc-bleed);
	margin: 0;
	z-index: 3;
}

/* MEASURED: the dots inherited `color: rgb(0,0,0)` from `.oc-band--light` and were
   therefore BLACK ON THE DARK VIDEO — present, on top, and invisible. The original's
   indicators sitting in this same spot are LIGHT: `.quote-indicator { background:
   rgba(255,255,255,0.3) }` with `.active { background: #fff }`
   (harvest/ordercube_styles.css:1414-1424 and :1435-1437). */
.oc-references__band > .oc-dots .oc-dots__btn {
	background-color: rgba(255, 255, 255, 0.3);
	border-color: rgba(255, 255, 255, 0.5);
}
.oc-references__band > .oc-dots .oc-dots__btn.is-active {
	background-color: #ffffff;
	border-color: #ffffff;
}

/* --- P2-8. Baked-in type in the background videos was sliced -------------
   SEEN on the render: the hero loop showed "FLOOR STAFF" running out of frame
   and #references showed "...S MEASUR" cut at the right edge. The assets are
   1280x720; the snap-in box is 1392x852, a 1.634 ratio against the video's
   1.778, so `object-fit: cover` scales to fill the height and crops 122px of
   WIDTH — straight through type that is baked into our own footage.
   `contain` keeps the whole frame, and the media's background colour fills the
   letterbox, so nothing is sliced at any width. */
.oc-scene__media-video {
	object-fit: contain;
	background-color: #0e0f13;
}

/* --- FAQ: a 3-row floor, and the headline comes down at short viewports --
   The fit-to-height pager (section 30) degenerated to ONE row per page at
   1280x720 and 390x844 — six pages of one row, which is not a pager, it is a
   slideshow. The floor is three rows (assets/js/motion.js).

   The headline is what made the budget collapse: `h1.oc-faq__title` is 262px
   tall at 390. Trap 2 protects the FAQ BODY — already 0.7x the original's word
   count — not the headline, and the ORIGINAL drops this exact element to
   `font-size: 0.9rem` below 768 (`.faq-main-title`,
   harvest/ordercube_styles.css:2716-2719) from `clamp(16.8px, 2.4vw, 28.8px)`
   at desktop (:2438-2444). Matching that is replicating a measured value, not
   shrinking type below one. No FAQ copy is cut. */
@media (max-width: 768px) {
	.oc-faq__title { font-size: clamp(1rem, 2.4vh, 1.35rem); line-height: 1.25; }
	.oc-faq__subtitle { font-size: 0.9rem; line-height: 1.35; }
}

/* --- Scene 2 follow-up: the caption escaped the collapsed stage -----------
   MEASURED on the deployed render: `.oc-scenarios__caption` sat at y=54 at 1440
   and y=-9 at 1024 — ABOVE the header, and above the viewport. Cause: at >=769px
   `.oc-scenarios__layer` is `position: absolute; inset-block-end: 0` (section 19),
   anchored to the BOTTOM of `.oc-scenarios__stage`; 1.9.2 set the stage's
   `min-block-size` to 0 once its in-flow media became the scene background, so the
   layer anchored to a zero-height box and extended upward out of the scene. The
   fold probe could not see it — it measures BOTTOM edges, and the caption's bottom
   was still inside the fold.

   The fix is the original's own composition rather than a restored magic height:
   `div.container.scenario-layout` (harvest/home.html:606-607) holds the copy
   (`.scenario-content-wrapper`, :609) and the five-row list (`.scenario-items`,
   :622-694) SIDE BY SIDE. Two columns means the scene's content height is the
   TALLER of the two rather than their sum, which is also what keeps this scene
   inside the fold at 720px — stacking them needs 611px of content and the budget
   is 536px. */
@media (min-width: 1024px) {
	.oc-scenarios__layer {
		position: static;
		inline-size: 100%;
	}
	.oc-scene > .oc-scenarios__inner {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
		align-items: center;
		column-gap: 48px;
	}
	.oc-scenarios__stage { min-block-size: 0; }
	.oc-scenarios__controls { justify-self: end; }
}

/* --- The bleeding edge carries no corner radius --------------------------
   MEASURED by the continuity prover at 1024: the scenarios caption reached the
   frame (L = -8) but its strip read 11 distinct colours, 32 non-white pixels and
   a per-column minimum luminance of 159 — the top-left CORNER ARC, showing the
   dark scene through the curve. A rounded corner on an edge that is supposed to
   meet the frame is the same defect this invariant exists to catch, just smaller.
   The edges that bleed are squared; every other corner keeps its radius. */
.oc-scenarios__caption {
	border-start-start-radius: 0;
	border-end-start-radius: 0;
}
.oc-references__band {
	border-radius: 0;              /* bleeds on both inline edges */
}
@media (min-width: 1024px) {
	.oc-contact__card {
		border-start-start-radius: 0;
		border-end-start-radius: 0;
	}
}

/* MEASURED at 390: taking the actions out of flow with an offset from the inner's
   bottom edge put "View the customer map" at y=852 in a 732px scene — outside it
   entirely, 14px past the fold. The desktop convention it replicates is explicitly
   a DESKTOP one: the original's own rules are `#references .cta-row.desktop-watch-video`
   inside `@media screen and (min-width: 769px)` (harvest/ordercube_styles.css
   :5481-5510), and below 768 the same row returns to flow as grid row 1 —
   `.cta-row.desktop-watch-video { display: flex !important; grid-row: 1; order: 1 }`
   (:1557-1568). Ours does the same. */
@media (max-width: 768px) {
	.oc-scene > .oc-references__inner .oc-references__top {
		position: static;
		inset-block-start: auto;
		padding-block-end: 12px;
	}
	.oc-references__band > .oc-dots { position: static; margin-block-end: 8px; }
}

/* ==========================================================================
   32. THE TOGGLE'S NO-FLY ZONE — 1.9.4, 2026-10-03
   -------------------------------------------------------------------------
   The 1.9.3 pass checked the pause control for collisions at 390 ONLY and
   reported it clear. It is not: MEASURED at 1440x900 the toggle
   {left 36, top 820, 193x44} overlapped `button.oc-btn--chain "Get in touch"`
   in #faq by 112x10px, and at 1440x768 it hit three scenes. A fixed control
   with `z-index: 3` painting over a real clickable control is the same defect
   class as a control below the fold: the user cannot operate what is covered.

   THE DECISION WAS EXPLICIT: reserve a no-fly zone, do NOT auto-hide during
   motion. Auto-hiding would break WCAG 2.2.2, which requires the pause
   mechanism to be available WHILE the motion plays — precisely when it is
   needed — and returning the control to flow would lose the global
   reachability the 1.9.1 pass established.

   The strip it occupies, measured from the viewport bottom, is a constant 80px
   at all four viewports (900-820, 768-688, 720-640, 844-764): the frame border
   plus its own 12px inset plus its 44px minimum block size. Every scene
   reserves it, so nothing is laid out in the control's footprint. */
:root { --oc-toggle-reserve: 80px; }

/* `[data-oc-scene]` matches the same specificity as the earlier
   `[data-oc-scene="faq"]`-style overrides and comes later, so it wins over all
   of them — including the <=768 rules that had cut the bottom padding to 16px. */
[data-oc-scene] { padding-block-end: var(--oc-toggle-reserve); }

/* #contact carries its padding on the columns, not on the scene. */
.oc-contact__col,
.oc-contact__col--form { padding-block-end: var(--oc-toggle-reserve); }

@media (max-width: 768px) {
	.oc-contact__col,
	.oc-contact__col--form { padding-block-end: var(--oc-toggle-reserve); }
}

/* --- "Assembled locally" was flush to the card's padding edge ------------
   The rect test said NOT clipped — and it was right, arithmetically: the pill's
   border box was 144..672 and the card's padding box was 144..672, identical to
   two decimal places, so nothing "overflowed". The PIXELS disagree: a 26px strip
   taken at the pill's right end is ink in all 26 columns, right up to the last,
   because a `border-radius: 999px` cap landing exactly on the padding edge of a
   card with `overflow: hidden` has no room to close. It reads as sliced.
   This is the same lesson as the "Next scenario" collision: where the defect is
   visual rather than geometric, the pixels are the instrument.

   A badge should hug its text rather than stretch to the column. */
.oc-contact__card .oc-badge {
	inline-size: fit-content;
	max-inline-size: 100%;
	align-self: flex-start;
}

/* --- Fold residuals, structural only ------------------------------------
   impact: the `h1` wrapped to two lines inside the 560px calculator column.
   The original's own split is `grid-template-columns: 1fr 1fr`
   (harvest/ordercube_styles.css:2048-2055), which is wider and lets the heading
   set on one line. No type is shrunk and no copy is cut. */
@media (min-width: 1024px) {
	.oc-calc { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
	.oc-impact__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* contact: the form column is the binding one; this is rhythm, not content. */
.oc-form { row-gap: 8px; }
.oc-form__field textarea { block-size: 60px; }
.oc-form__actions { row-gap: 4px; }

/* home: the trust strip is the last thing in the scene. */
.oc-hero__trust { column-gap: 16px; }
.oc-hero__inner { row-gap: 12px; }

/* Scenes that still exceed one viewport put content inside the control's strip,
   so the strip is only truly reserved once the scene fits. These are the last
   rhythm reductions available without touching copy or type. */
.oc-faq__inner { row-gap: 12px; }
.oc-faq__head { margin-block-end: 8px; }
.oc-faq__search-container { margin-block-start: 4px; }
.oc-impact__inner { row-gap: 12px; }
.oc-calc__controls { row-gap: 6px; }
.oc-stepper { padding-block: 0; }
.oc-hero__trust { row-gap: 4px; }
.oc-hero-lockup { margin-block-end: 8px; }

/* ==========================================================================
   33. LEGIBILITY OVER A NON-UNIFORM BACKDROP — 1.9.5, 2026-10-03
   -------------------------------------------------------------------------
   The copy in #features crosses TWO backdrops: scene-dark on the left, lit
   device art on the right. A single representative contrast sample passes while
   half the line is unreadable, which is exactly how 4.96:1 and 9.05:1 were
   reported for strings that measure 1.05:1 and 1.10:1 per-third.

   The first cause was z-order: `.oc-features__hand` tied with
   `.oc-features__inner` at z-index 1 and, being later in DOM order, painted over
   the copy. Fixed in section 31. The second is this: with the overlay now in the
   media plane, its LIT pixels still sit under the right third of the panel
   paragraph, measured `rgb(134,113,87)` giving 4.01:1 against a 4.5 bar.

   The original darkens its whole media plane with one always-on overlay —
   `ordercube_script.js:2386-2395`, appended INTO the media div, flat black at
   `opacity: 0.5` (:2394) and a centre-transparent radial for #features (:2390).
   `.oc-scene__media::after` already reproduces it, but the hand overlay sits
   ABOVE that layer, so it needs the same treatment rather than a darker asset:
   the art keeps its own values and only the composite is toned. */
.oc-features__hand { position: absolute; }
.oc-features__hand::after {
	content: '';
	position: absolute;
	inset: 0;
	/* FLAT, not the radial. The radial is `transparent 0% -> transparent 15%` at its
	   centre, which is exactly where our copy column crosses the device render: with
	   it the right third of the panel paragraph measured 2.82:1 against a lit
	   `rgb(183,132,77)`. The radial is the original's treatment for the SECTION media
	   (ordercube_script.js:2390) and it stays on `.oc-features .oc-scene__media::after`,
	   where it keeps the product visible. This overlay is a layer the original does not
	   have in this position — it is our promotion of the device render — so it takes
	   the original's DEFAULT treatment instead: flat black (`:2391`) at the same
	   baseline opacity (`:2394`). The art keeps its own values; only the composite is
	   toned, so nothing is darkened into mud. */
	background: linear-gradient(180deg, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1));
	opacity: 0.5;                   /* :2394 — the original's baseline */
	pointer-events: none;
}

/* --- Mobile hero, two sweep findings ------------------------------------
   1. The lead paragraph's last line ran to within 5px of the frame's inner
      edge (measured: widest line right 379 against an inner edge of 384) while
      every other block clears it. The gutter goes on the paragraph rather than
      on `.oc-hero__inner`, because the inner's inline padding is a term in
      `--oc-hero-bleed` and changing it would move the hero slabs off -8px.
   2. The wordmark grid wrapped to THREE rows whose last bottom was 933 against
      a 844px viewport — a half-row sliced by the bottom edge, which reads as
      broken rather than as "more below". Dropping the row is preferred to
      showing half of it, so below 768 the strip carries the four marks that
      fit two whole rows. No copy is cut: the marks are decorative plates and
      the "trusted daily" disclosure line is untouched. */
@media (max-width: 768px) {
	.oc-lead { padding-inline-end: 16px; }
	.oc-hero__trust .oc-marks__item:nth-child(n + 5) { display: none; }
}

/* ==========================================================================
   34. COLUMNS NARROWER THAN A WORD — 1.9.7, 2026-10-03
   -------------------------------------------------------------------------
   P0 on #faq at 390x844: the headline set ONE WORD PER LINE over 13 lines and
   the intro over 17, with "walkthrough" clipped, pushing the accordion entirely
   below the fold.

   MEASURED CAUSE: `.oc-faq__head` is a two-column grid whose SECOND track is a
   fixed `320px` (section 11). At a 378px content width that leaves the first
   track 378 - 320 - 24(gap) = 34px, so the title and subtitle were laid out in a
   34px column while the search box occupied the 320px one. A fixed track cannot
   shrink; that is the whole defect.

   THE ORIGINAL COLLAPSES THIS EXACT ELEMENT. Its `.faq-header` is
   `grid-template-columns: 2fr 1fr` at desktop
   (harvest/ordercube_styles.css:2425-2430) and `grid-template-columns: 1fr` with
   `gap: 1rem` below 768 (:2698-2707). Note its `max-height` rules only ever touch
   `max-width` on `.faq-layout` (:2407-2423), so they are not the mechanism.

   Two changes: collapse to one column below the breakpoint as the original does,
   and make the desktop track FLEXIBLE so a fixed track can never again starve its
   neighbour at an intermediate width. */
@media (min-width: 769px) {
	.oc-faq__head { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
}

@media (max-width: 768px) {
	.oc-faq__head {
		grid-template-columns: 1fr;   /* harvest/ordercube_styles.css:2699 */
		gap: 16px;                    /* :2700 — 1rem */
		align-items: stretch;         /* :2705 */
	}
}

/* --- #references: the band was inset ~96px from the left frame -----------
   Same class, and it was mine: the 1.9.3 pass moved the pager dots onto the band
   and then, at <=768, set them `position: static` so they would not sit off the
   band's top edge. That made them a FLEX ITEM in the band's row — measured
   `flex: 88 | 290` — so the white band started 88px in from the frame while still
   meeting the right edge, breaking Invariant B asymmetrically.

   The original's own mobile rule puts the indicators back out of flow, centred at
   the top of the quote area: `.quote-indicators { position: absolute; top: 1rem;
   left: 50%; transform: translateX(-50%) }`
   (harvest/ordercube_styles.css:1673-1682). */
@media (max-width: 768px) {
	.oc-references__band > .oc-dots {
		position: absolute;
		inset-block-start: 8px;
		inset-inline-start: 50%;
		transform: translateX(-50%);
		margin: 0;
		z-index: 3;
	}
	.oc-references__band { padding-block-start: 32px; }
}

/* ==========================================================================
   35. #impact — THE LEFT COLUMN REACHES THE FRAME, THE GRID GOES RIGHT, AND
       THE HOVER REVEAL STOPS OVERLAPPING — 1.9.8, 2026-10-03
   -------------------------------------------------------------------------
   Four client-reported items, all scoped to scene 5. Everything below was
   measured on the deployed 1.9.7 render first; the numbers are in README
   section 18.

   --- 35a. THE STEPPER PILLS REACH THE LEFT FRAME ------------------------

   MEASURED BEFORE, deployed 1.9.7: `.oc-stepper` border box left 72 at 1440
   against a frame inner edge of 24 — but its WHITE was clipped at 104, because
   `.oc-calc__controls` carries `overflow: clip` (section 31) and the -32px
   start margin only ever bled the pill to that clip box. So the pill's visible
   white started at 104 / 24 / 6 at 1440 / 1024 / 390: an 80px dark gap at 1440
   and a hairline abut, never an underlap, at the other two.

   THE ORIGINAL BLEEDS THIS EXACT ELEMENT, and off the viewport, not to a clip
   box. Its negative-margin chain totals -6rem:
     harvest/ordercube_styles.css:2072   .calculator-controls  margin-left: -2rem
     harvest/ordercube_styles.css:2080   .control-group        margin-left: -4rem
     harvest/ordercube_styles.css:2090   .control-label        border-radius: 0 2rem 2rem 0
   The radius is open to the LEFT precisely because the pill is meant to run off
   that edge; the two square corners are supposed to be out of sight under the
   frame. `.frame { border: 1.5rem solid #fff; z-index: 5 }` (:32-38) paints OVER
   it and both are #fff, so the two whites are continuous with no seam. Target
   left edge -8px — the hero slab's own measured landing (section "the slab
   bleed").

   THE BLEED IS DERIVED FROM THIS ELEMENT'S OWN CONTAINER CHAIN, not copied.
   MEASURED natural left edges with the bleed zeroed: 104 / 24 / 6, so the bleed
   must be 112 / 32 / 14. `.oc-calc__panel` and `.oc-calc__controls` both measure
   `padding-left: 0px`, so the stepper's natural left edge IS
   `.oc-impact__inner`'s padding edge — one box, no intermediate inset. That makes
   this chain the HERO's, not the scenarios caption's: `--oc-bleed`
   (`centring + gutter + 8`, section "INVARIANT B") already resolves to exactly
   112 / 32 / 14 and is used verbatim.

     1440: max(0,(1440-1280)/2)=80 + 24 + 8 = 112   natural 104 -> -8
     1024: max(0,(1024-1280)/2)= 0 + 24 + 8 =  32   natural  24 -> -8
      390: max(0,( 390-1280)/2)= 0 +  6 + 8 =  14   natural   6 -> -8

   The caption's `max(max(0,centring), gutter) + gutter + 24 + 8` form would
   give 136 / 80 / 44 here and overshoot to -32 / -56 / -38. It is correct only
   for a box sitting two containers deeper, which is the whole lesson: the
   nested-element left edge is `max(centring, gutter)` rather than their sum ONLY
   when an intermediate box adds its own gutter. This one does not.

   `.oc-calc__controls` takes the same bleed as a negative start margin with the
   amount back as start padding, so its `overflow: clip` box now starts at -8
   while its content box — and therefore the opt-in and the disclaimer — does not
   move by a pixel. The clip is KEPT: it is what guarantees this cannot
   contribute horizontal page overflow, and a negative start margin whose far
   edge is unchanged cannot either. MEASURED after:
   `documentElement.scrollWidth == clientWidth` at all three widths. */

.oc-calc__controls {
	margin-inline-start: calc(-1 * var(--oc-bleed));
	padding-inline-start: var(--oc-bleed);
	/* `overflow: clip` from section 31 still applies and now clips at -8px. */
}

.oc-stepper {
	margin-inline-start: calc(-1 * var(--oc-bleed));
}

/* --- 35b. THE LABELS ARE LEFT-ALIGNED ----------------------------------
   DEVIATION, requested by the client in these words: "services per day ..etc
   are not supposed to be in the center ... they are on the left side". The
   original right-aligns them — `.control-label { text-align: right }`
   (harvest/ordercube_styles.css:2092), restated at :4828 for mobile — and this
   build replicated that faithfully (`justify-content: flex-end`, section 31).
   In a pill that now runs from -8px to past the middle of the viewport, a
   right-aligned string reads as centred, which is the defect reported.

   THE TYPE DOES NOT MOVE OUTWARD WITH THE WHITE. The start padding is the bleed
   plus the label's own inset, so the first glyph lands at
   `natural content edge + inset` exactly as it would have with no bleed at all:
   MEASURED 128 / 48 / 22 at 1440 / 1024 / 390 against content edges 104 / 24 / 6.
   Only the white extends. The mobile inset is 16px, matching that breakpoint's
   own end padding (and recovering the 2.3px of scene height the 24px inset cost
   at 390 — measured both ways). */
.oc-stepper__label {
	justify-content: flex-start;
	text-align: start;
	padding-inline-start: calc(var(--oc-bleed) + 24px);
}

@media (max-width: 768px) {
	.oc-stepper__label { padding-inline-start: calc(var(--oc-bleed) + 16px); }
}

/* --- 35c. AND THEY SLIDE IN FROM THE LEFT ON LOCK-IN -------------------

   MEASURED ON THE ORIGINAL, for THESE elements specifically — not the hero's
   figure and not the impact cards' figure, both of which were checked and are
   different:

     harvest/ordercube_script.js:3455   const delay = 0.4            section base
     harvest/ordercube_script.js:3464   labelDelay = delay + 0.1 + (index * 0.1)
     harvest/ordercube_script.js:3469   label.style.transform = 'translateX(-100%)'
     harvest/ordercube_script.js:3471   label.style.color = 'transparent'
     harvest/ordercube_script.js:3480   animation: slideInFromLeftSVG 0.3s ease
                                                  <labelDelay>s forwards
     harvest/ordercube_script.js:3483-92  then color -> #000 over 0.6s, started
                                          (labelDelay + 0.3)s in, i.e. AFTER the
                                          bar has landed
     harvest/ordercube_styles.css:3796-3803 @keyframes slideInFromLeftSVG
                                            from { translateX(-110%) } to { 0 }

   So: 300ms, `ease`, NO fade, travel -110% of the pill's own width, and a 0.1s
   stagger on a 0.1s offset. The 0.1s happens to match the hero slabs' stagger
   and is NOT assumed from it — js:3464 states it for these three rows, against
   the impact cards' own 0.08s at js:3524 and the generic reveal's 0.2s + 0.08s
   at js:3526. The original's 0.4s section base is dropped for the same reason
   the hero slabs drop theirs: this build's engine already fires
   `oc:section-enter` SETTLE_DELAY_MS (100ms) after lock-in, which is what that
   base was for. MEASURED on the deployed render at 1440: the animated element is
   the LABEL, 435.97px wide, so -110% is a travel of 479.57px; sampled duration
   283ms against 300ms declared, and the per-row stagger came back 100.8ms and
   99.3ms. `oc:section-enter` at 36672.2ms, `.is-revealed` at 36688.1ms — one
   frame later — and no pill was ever at rest before that class arrived.

   THE TRIGGER IS THE PATH THAT ALREADY DRIVES THE HERO, not a second mechanism.
   `.oc-calc__controls` carries `data-oc-reveal` (blocks/impact-calculator/
   render.php), motion.js adds `.is-revealed` on `oc:section-enter`, and
   scroll.js dispatches that 100ms after the scene settles. So this fires on
   lock-in, never on a scroll threshold, and it replays on re-entry because
   motion.js drops the class and forces a reflow first.

   The three pills opt OUT of the generic reveal exactly as the hero lockup does
   (section "the slab bleed"): that reveal fades from `opacity: 0` and slides
   from `translateX(20px)` — the wrong direction and a fade the original
   explicitly does not do. The opt-in checkbox below them KEEPS the generic
   reveal, which is a fade, as the original's `waiter-agreement` is
   (`fadeIn 0.6s ease 0.8s`, js:3510). */

html.oc-js [data-oc-reveal] > .oc-stepper,
html.oc-js [data-oc-reveal].is-revealed > .oc-stepper {
	opacity: 1;
	transform: none;
	transition: none;
}

html.oc-js .oc-impact [data-oc-reveal] > .oc-stepper > .oc-stepper__label {
	transform: translateX(-110%);          /* harvest/ordercube_styles.css:3798 */
	color: transparent;                    /* harvest/ordercube_script.js:3471 */
}

html.oc-js .oc-impact [data-oc-reveal].is-revealed > .oc-stepper > .oc-stepper__label {
	animation: ocStepperBarIn 0.3s ease forwards;   /* js:3480 */
	/* js:3483-3492 — the ink arrives after the bar lands, over 0.6s. */
	color: var(--oc-black);
	transition: color 0.6s ease 0.4s;
}

html.oc-js .oc-impact [data-oc-reveal].is-revealed > .oc-stepper:nth-of-type(2) > .oc-stepper__label {
	animation-delay: 0.1s;                 /* js:3464 — index * 0.1 */
	transition-delay: 0.5s;
}

html.oc-js .oc-impact [data-oc-reveal].is-revealed > .oc-stepper:nth-of-type(3) > .oc-stepper__label {
	animation-delay: 0.2s;
	transition-delay: 0.6s;
}

@keyframes ocStepperBarIn {
	from { transform: translateX(-110%); }
	to   { transform: translateX(0); }
}

/* REDUCED MOTION: the END state, never the start state — the same call the hero
   slabs make and for the same reason. A reader who asked for less motion gets
   the three pills at rest, in place, with their ink on, and no chance of one
   being stranded off-frame at -110%. */
@media (prefers-reduced-motion: reduce) {
	html.oc-js .oc-impact [data-oc-reveal] > .oc-stepper > .oc-stepper__label,
	html.oc-js .oc-impact [data-oc-reveal].is-revealed > .oc-stepper > .oc-stepper__label {
		transform: none;
		animation: none;
		color: var(--oc-black);
		transition: none;
	}
}

/* --- 35d. BREATHING ROOM IN THE CENTRE; THE GRID GOES RIGHT -------------

   MEASURED BEFORE: `.oc-impact__inner` resolved to `422.078px 777.922px` at
   1440 and `166.078px 777.922px` at 1024 — a 1fr track beside an AUTO one, not
   the 50/50 the stylesheet appears to declare. Cause: `grid-template-areas`
   (section 29) names two columns while `.oc-scene > .oc-impact__inner`
   (specificity 0-2-0) supplies ONE track, so column 2 fell back to `auto` and
   content-sized itself; the `minmax(0,1fr) minmax(0,1fr)` written for this
   element in the 1.9.4 fold pass is a bare `.oc-impact__inner` (0-1-0) and has
   never applied. The visible result was a 32px centre gap that reads as a
   leftover, the stats pinned to the inner's content edge at 1336 — 80px short
   of the frame — and, at 1024, a 166px panel column that wrapped the steppers
   badly enough to make the scene 1082.2px against a 900px viewport.

   THE ORIGINAL'S RELATIONSHIP, read off both halves as instructed:
     harvest/ordercube_styles.css:2048-2055  .impact-layout    1fr 1fr, padding 0 2rem
     harvest/ordercube_styles.css:2057-2060  .impact-calculator max-width: 500px
     harvest/ordercube_styles.css:2207-2211  .impact-results   flex, justify-content: flex-end
     harvest/ordercube_styles.css:2213-2220  .results-grid     repeat(3,1fr)/repeat(2,1fr),
                                                               width 100%, max-width 600px
   The split is EQUAL HALVES; each column's content is CAPPED and pinned to its
   own outer edge; and the centre breathing room is what the two caps leave over.
   That is the mechanism — the air is a consequence of the caps, not a gap value.
   Reproduced with the original's own two numbers, 500 and 600. At 1440 the
   measured card width that falls out is 189.33px, which is the original's own
   card width at 1440 to the pixel ((600-32)/3).

   DOES THE RIGHT-HAND GRID REACH THE FRAME? MEASURED ON THE ORIGINAL: no, it
   stops 8px short. Its page padding is `2rem` while its frame border is
   `1.5rem`, so at 1440 the grid's right edge lands at 1408 against a frame inner
   edge of 1416. CHOSEN HERE: reach the frame inner edge exactly — right edge
   1416 at 1440, 1000 at 1024 — and that is a DEVIATION, recorded in README
   section 18. Three reasons. The original's 8px is the arithmetic residue of a
   2rem/1.5rem mismatch, not a designed inset; this build's 1024 render ALREADY
   lands exactly on the frame inner edge and pulling it back in would be a
   visible regression there; and Invariant B (P1-5 above) is this design
   language's standing rule for white boxes, which these cards are. It reaches
   the frame and does NOT underlap it, because the original has no right-side
   bleed anywhere — the -8px underlap is a LEFT-edge mechanism in the original
   (:2072-2090) and is not invented for the right.

   The right-side reach is `centring`, derived, not a constant:
     1440: (1440-24) - 1336 = 80   = max(0,(1440-1280)/2)
     1024: (1024-24) - 1000 =  0   = max(0,(1024-1280)/2)
      390: ( 390- 6) -  384 =  0   = max(0,( 390-1280)/2)
   Zero below 1280 by construction, so nothing moves at 1024 or 390 and no
   mobile rule is touched. Only the grid's RIGHT edge moves; its left edge is
   repositioned by the `auto` start margin inside its own track, so this cannot
   produce horizontal overflow either. */
@media (min-width: 1024px) {
	/* 0-2-0 so it beats `.oc-scene > .oc-impact__inner`'s single track, which is
	   what left column 2 sizing itself `auto` off the grid areas. */
	.oc-scene > .oc-impact__inner {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	}

	/* harvest:2057-2060 — the calculator column is capped at 500px and the three
	   grid areas that live in it are capped with it. */
	.oc-impact__title,
	.oc-calc__panel,
	.oc-impact__cues {
		max-inline-size: 500px;
	}

	/* harvest:2207-2220 — capped at 600 and pushed to the END of its own half,
	   plus the reach to the frame's inner edge. */
	.oc-stats {
		max-inline-size: 600px;
		margin-inline-start: auto;
		margin-inline-end: calc(-1 * max(0px, (100vw - var(--oc-grid-max)) / 2));
	}
}

/* --- 35e. THE HOVER FROST ----------------------------------------------

   THE DEFECT, as reported: "when we mouse on hover the detail texts OVERLAP the
   percentages". MEASURED on 1.9.7 at 1440, card 1: `.oc-stat__delta` occupies
   y 382.8..422.4 and `.oc-stat__caveat` y 386..470 in the same x range —
   overlapping by 36.4px, with nothing between them, the caveat fading from
   `opacity: 0` to 1 straight over a live 36px/600 figure.

   WHAT THE ORIGINAL DOES — and it does have a counterpart, so this was checked
   before anything was designed. `harvest/ordercube_styles.css:2254-2283`, inside
   `@media (hover: hover)`:
     :2255-2257  .result-card:hover::before            opacity: 1
                 (:2235-2247 — a full-card `var(--pill)` layer at z-index 0,
                  with `.result-card > * { z-index: 1 }` at :2249-2252)
     :2275-2278  .result-card:hover .card-metric,
                 .result-card:hover .card-value        opacity: 0
     :2280-2282  .result-card:hover .card-disclaimer   opacity: 1
     :2311-2313  the same three, restated for `.active` (tap/click)
   So the original REPLACES: the card fills with the bronze gradient and the
   figure is removed outright, which is why it never overlaps. It does not dim,
   it does not blur, and it has no frosted layer anywhere in this interaction.

   THIS BUILD HAD REPRODUCED NEITHER HALF — no overlay and no figure removal,
   only `:5120-5121`'s `opacity: 1` on the caveat. That omission is the defect.

   WHAT IS BUILT INSTEAD, AND WHY IT IS A DEVIATION. The client asked, verbatim,
   for the percentage to "'Frost up' ... so the text is readable and not
   overlap" — frost the figure, do not delete it. So the figure stays and a
   frosted pane goes BETWEEN it and the caveat. Recorded as a deliberate
   departure in README section 18, with the original's own rule cited above.
   A second reason not to reproduce the overlay literally: `var(--pill)`
   (#b89b83 -> #b77b38) carries white 14px ink at 2.607:1 and 3.557:1, which
   fails 4.5:1 — this build's AA substitute for that ramp already exists
   (`--oc-accent-aa`, README B3.3) and would have been needed regardless.

   WHY A LIGHT FROST RATHER THAN A DARK ONE. The pane's job is to be the caveat's
   immediate backdrop. Over the card's own #FFFFFF a 0.82-alpha white pane
   composites back to #FFFFFF, so the caveat keeps exactly the contrast it has at
   rest (#666666 on #FFFFFF = 5.74:1); anywhere a figure glyph lies behind, the
   backdrop gets DARKER, which raises the ratio for dark ink rather than lowering
   it. The worst case is therefore the plain white pixel, not the glyph — the
   opposite of the dark-frost case, where every glyph would be a new worst third.

   Z-ORDER IS THE WHOLE TRICK — and the FIRST version of it was wrong, caught by
   LOOKING at the render after every geometric and contrast probe had passed
   (section 15, for the sixth time). It put the metric heading ABOVE the pane
   "for context", on my own initiative. The caveat then ran straight through
   `Average wait` in bold black: the reported defect, moved from the figure to
   the heading, with the contrast probe still reporting 5.231:1 because it asks
   "is the caveat's ink readable against its backdrop", never "is the caveat
   lying on top of other text".

   The original had already answered it and I had quoted the answer without
   following it: `:2275-2277` takes `.card-metric` AND `.card-value` to
   `opacity: 0` — BOTH. Only `.card-category` survives the hover, restyled rather
   than hidden (`:2264-2269`). So the metric belongs UNDER the pane with the
   figure, and the audience label is the one thing that stays crisp:
     .oc-stat__delta     z 0   frosted   <- harvest:2276 `.card-value`
     .oc-stat__metric    z 0   frosted   <- harvest:2275 `.card-metric`
     .oc-stat::after     z 1   the pane
     .oc-stat__caveat    z 2   reads on the pane
     .oc-stat__audience  z 3   crisp     <- harvest:2264 `.card-category` survives

   PERFORMANCE. `backdrop-filter` is compositor work and this page's scroll
   engine re-blurs any such element on every frame the pixels behind it move
   (section "NO BACKDROP BLUR WHILE THE TRACK IS MOVING": 30.6ms -> 16.7ms mean
   frame, 18/42 -> 0/75 frames over 33ms, measured). That precedent is followed
   rather than re-invented: `html.oc-in-motion *::after { backdrop-filter: none
   !important }` already covers this pseudo-element, so the frost cannot be live
   during a scroll. Belt and braces, the `blur()` is declared only in the hover
   and focus states, so the compositor has no backdrop to maintain at rest
   either — `opacity: 0` alone would still have promoted the layer. */

.oc-stat__delta { position: relative; z-index: 0; }

.oc-stat::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background-color: rgba(255, 255, 255, 0.82);
	/* No `backdrop-filter` at rest, on purpose — see PERFORMANCE above. */
	opacity: 0;
	transition: opacity 0.3s ease;   /* harvest:2324 — the caveat's own 0.3s */
}

.oc-stat__metric { position: relative; z-index: 0; }

.oc-stat__caveat { z-index: 2; }

.oc-stat__audience { position: relative; z-index: 3; }

/* THE FROST MIRRORS THE CAVEAT'S OWN PREDICATE, EXACTLY — and this was MEASURED,
   because the first cut of this rule did not. It was wrapped in
   `@media (hover: hover)` by analogy with the lift above, while the reveal it has
   to cover (`:5120-5121`) carries NO media guard at all. Headless Chromium made
   it visible: it reports `(hover: hover)` FALSE and `(hover: none)` TRUE, yet
   `.oc-stat.matches(':hover')` returns TRUE and the caveat composited at
   `opacity: 1` — the caveat revealed over a live figure with no frost between
   them, which is the reported defect reproduced exactly. A real touch device does
   the same thing with sticky hover after a tap.

   The lesson is the file's own: a layer that guards a defect must share the
   predicate of the thing that creates it, never a narrower one. The LIFT may
   stay behind `(hover: hover)` — it is decoration. Legibility may not. */
.oc-stat:hover::after,
.oc-stat:focus-within::after {
	opacity: 1;
	backdrop-filter: blur(10px);
	-webkit-backdrop-filter: blur(10px);
}

/* Reduced motion keeps the frost (it is legibility, not decoration) and drops
   only the cross-fade. */
@media (prefers-reduced-motion: reduce) {
	.oc-stat::after { transition: none; }
}

/* ======================================================================
   1.9.9 — CONTRAST, TOGGLE NO-FLY, MOBILE GUTTER AND MOBILE FOLD
   (2026-10-04). Every number below was measured on deployed 1.9.8 before
   the change and re-measured after. See README section 19.
   ====================================================================== */

/* --- 19.1 P0 CONTRAST: the scenarios caption was UNDER an invented scrim ---
   MEASURED on deployed 1.9.8 at 1440x900, per-third composited pixels:
     a.oc-textlink  "See the fix"            2.34:1   (bar 4.5)
     p              "The glass has been..."  3.30:1   (bar 4.5)
   The declared chain is innocent and that is exactly why six earlier passes
   missed it: `.oc-scenarios__caption` computes `background-color:
   rgb(255,255,255)`, `opacity: 1`, `backdrop-filter: none`, text
   `rgb(14,15,19)` — an opaque white card with near-black ink, ~18:1 on paper.
   getComputedStyle over the ANCESTOR CHAIN therefore reports nothing wrong.

   The compositing source is a PSEUDO-ELEMENT that no element walk can see:

     .oc-scenarios__stage::after  (style.css:1182)
       position: absolute; inset: 0; z-index: 0;
       background: linear-gradient(to top right,
         rgba(14,15,19,0.92) 0%, rgba(14,15,19,0.45) 42%, rgba(14,15,19,0) 72%);
       pointer-events: none;

   `.oc-scenarios__caption` is `position: static; z-index: auto`, so inside
   `.oc-scenarios__inner`'s stacking context a POSITIONED z-index:0 box paints
   ABOVE it. The 0.92-alpha end of that ramp lands on the caption's lower-left
   — exactly where the body copy and the "See the fix" link sit — turning the
   white card into a mid-grey (sampled surface rgb(78,79,82) and rgb(99,100,102))
   under unchanged near-black ink. `pointer-events: none` is why
   `elementFromPoint` reported a clean stack: it removes the layer from
   hit-testing and NEVER from painting (section 15, corollary 1).

   THE ORIGINAL HAS NO SUCH SCRIM. `.scenario-content` is
   `background: #fff; color: #000` (harvest/ordercube_styles.css:575-585) with
   nothing over it, and the whole original stylesheet contains exactly ONE
   gradient scrim (`:2945`), which is not on this stage. The ramp is ours.

   Fix: put the card in front of the ramp rather than deleting the ramp, so the
   video still darkens where the card is NOT — which is the only thing the ramp
   was ever doing for us. */
.oc-scenarios__caption {
	position: relative;
	z-index: 1;
}

/* --- 19.2 The pause toggle's mobile corner ---------------------------------
   MEASURED on deployed 1.9.8: the global toggle is NOT occluded anywhere —
   0 of 28 viewport x scene combinations, by a paint-difference probe whose
   planted-occluder control fires 14/14. The reported "20 of 28" does not
   reproduce. Target size is 180x44 at desktop and 44x44 at 390 — WCAG 2.5.8
   is met at every one of the 28.

   What IS real, and what this rule fixes: at 390x844 the fixed toggle sits at
   {36, 764, 44x44} and OVERLAPS two controls of its own in #references —
   `button.oc-icon-btn[data-oc-prev]` {6, 732, 44x44} and
   `button.oc-icon-btn[data-oc-next]` {58, 732, 44x44}. Two 44px targets in one
   44px square is a 2.5.8 failure whichever of them is on top, and it is the
   toggle that is on top.

   Moved rather than hidden: WCAG 2.2.2 wants the pause mechanism available
   WHILE the motion plays, so auto-hiding is not available to us (section 14.1).
   Below 768 every scene's content and every chain CTA is left-aligned, so the
   bottom-left is the one busy corner and the bottom-right is the free one. */
@media (max-width: 768px) {
	.oc-video-toggle.oc-video-toggle--global {
		inset-inline-start: auto;
		inset-inline-end: calc(var(--oc-frame, 1.5rem) + 12px);
	}
}

/* --- 19.3 A real text gutter at narrow viewports ---------------------------
   MEASURED at 390x844, glyph left edge against the frame's INNER edge (x=6):
     p.oc-lead                     gapL = 0
     p.oc-eyebrow ("Since 2019")   gapL = 0
     p.oc-references__disclaimer   gapL = 0   gapR = 16.5
     h1.oc-impact__title           gapL = 0   gapR =  8.2
     p.oc-calc__disclaimer         gapL = 0   gapR =  6.1
     h1.oc-faq__title              gapL = 0   gapR =  3.5
     p.oc-faq__subtitle            gapL = 0
     span.oc-faq-item__q-label     gapL = 0
   Zero. The glyphs start ON the frame border, which is what "cramped" means
   here; desktop has 76px. The client's rule is that the WHITE BOXES still
   touch the frame and only the TEXT needs the gutter, so this pads the text
   elements themselves and never their containers — no white box moves. */
@media (max-width: 768px) {
	:root { --oc-text-gutter: 16px; }

	.oc-hero__copy .oc-lead,
	.oc-hero__trust .oc-eyebrow,
	.oc-references__disclaimer,
	.oc-impact__title,
	.oc-calc__disclaimer,
	.oc-faq__title,
	.oc-faq__subtitle,
	.oc-faq-item {
		padding-inline: var(--oc-text-gutter);
	}

	/* `.oc-lead` carried `padding-inline-end: 16px` from the 1.9.7 pass
	   (style.css:5737); the shorthand above already supplies both edges, so
	   this only records that the old value is intentionally superseded. */
}

/* --- 19.4 Two chain CTAs were below the fold at 390 ------------------------
   MEASURED at 390x844 (fold = 844):
     #references  button.oc-btn "Check impact"        824.5 .. 882.5   38.5 over
     #impact      button.oc-btn "Investigate the FAQ" 864.4 .. 922.4   78.4 over
   Both are the scene's forward control, so below the fold means unreachable
   without scrolling past the scene the engine has locked onto. The height is
   recovered from inter-block gaps and card padding at <=768 only; no desktop
   value is touched and no type size changes. */
@media (max-width: 768px) {
	/* #references: grid gap 32 -> 16 across three boundaries = -48px */
	.oc-references__grid { gap: 16px; }
	.oc-references__inner { gap: 20px; }
	.oc-references__band { padding-block-start: 24px; }

	/* #impact: -40 (inner gap, two boundaries), -12 (calc margin),
	   -12 (calc gap), -20 (stat padding-block), -8 (cues/title margins) */
	.oc-scene > .oc-impact__inner { gap: 12px; }
	.oc-impact__title { margin-block-end: 4px; }
	.oc-calc { margin-block-start: 12px; gap: 12px; }
	.oc-calc__disclaimer { margin-block-start: 4px; }
	.oc-stat { padding-block: 14px; }
	.oc-impact__cues { margin-block-start: 4px; }
}

/* =========================================================================
   2.0.0 — THE MOBILE PASS (2026-10-04)
   ========================================================================= */

/* --- 20.1 The remaining 14 text runs at 6.0px ------------------------------
   README 19.8 item 2 recorded these as sitting "inside their own white cards,
   where 6px is the card's own padding". MEASURED on deployed 1.9.9 at 390x844,
   that is NOT what they are. A white-background sweep of all three scenes finds
   exactly ONE white box among them — `.oc-contact__card` at left 12.1 — and it
   contains NONE of the 14 runs. Every one of them sits DIRECTLY ON THE SCENE,
   the same class the 19.3 block already fixed; they were simply not on that
   block's selector list.

   What actually supplies the 6px is the scene's own inner wrapper:
     .oc-scenarios__inner   padding-inline: 6px   -> its content starts at x=12
     .oc-features__inner    padding-inline: 6px   -> x=12
     .oc-contact__col       padding-inline: 6px   -> x=12
   i.e. exactly `--oc-frame-mobile`, and the glyph then starts 6px from the
   frame's inner edge instead of the 16px the 19.3 runs get.

   So the text needs only the REMAINDER, not another full gutter: a flat
   `padding-inline: var(--oc-text-gutter)` here would land these at 22px while
   the 19.3 runs sit at 16px. The difference is expressed rather than hard-coded
   so the two blocks cannot drift apart.

   NO BOX MOVES: padding never moves an element's own border box, and every
   selector below is a transparent wrapper or a bare text element — checked
   against the white-background sweep. `.oc-contact__card` is deliberately NOT
   in this list. */
@media (max-width: 768px) {
	.oc-scenarios__cues,
	.oc-features__cues,
	.oc-features__panel,
	.oc-features__copy > .oc-quote,
	.oc-form__heading,
	.oc-form__intro {
		padding-inline: calc(var(--oc-text-gutter) - var(--oc-frame-mobile));
	}
}

/* --- 20.2 The #impact stat cards stand off the frame -----------------------
   Client instruction, verbatim: "the Percentage boxes should not TOUCH the
   white snapin frame there needs to be some spacing".

   MEASURED on deployed 1.9.9: `.oc-stats` right edge == frame inner edge at
   every width — 1416/1416 at 1440x900, 1000/1000 at 1024x900, 384/384 at
   390x844. Flush, which is the defect: the cards are white and the frame is
   white, so the outer column loses its right edge entirely.

   THE ORIGINAL DOES INSET ITS RESULTS GRID, so this is a replication, not an
   invention. `.impact-layout { padding: 0 2rem }`
   (harvest/ordercube_styles.css:2048-2055) against `--frame: 1.5rem`
   (harvest/ordercube_styles.css:8, applied :32-38) leaves its grid 32-24 = 8px
   inside the frame's inner edge. Deviation 65 ("reach the frame inner edge
   exactly") is therefore RETIRED — the original's own answer is a standoff.

   THE VALUE IS 16px, NOT THE ORIGINAL'S 8px, AND THAT IS A DEVIATION.
   Rendered and LOOKED at side by side at 1440 (8 / 16 / 24 / 32): this grid
   carries `gap: 16px` between its own cards, so an 8px outer standoff reads
   TIGHTER than the gutters inside the grid and lands as a seam rather than as
   spacing. 16px makes the outer gap identical to the inter-card gutter, which
   is the self-consistent value and is `--oc-space-md` / theme.json spacing step
   "20 — 16px". The original's 8px is the arithmetic residue of 2rem against
   1.5rem (README 18.4 established that), not a designed figure.

   TWO RULES, AND THEY MUST NOT COLLAPSE INTO ONE. The design language's
   standing rule is still "white boxes touch the snap-in frame", and the stepper
   pills on the LEFT keep their deliberate -8px underlap (deviation 64, client's
   own instruction). The carve-out is the stat cards on the RIGHT only. Nothing
   below touches a shared container, so the pills cannot move with the cards —
   `.oc-stepper` is asserted at left -8 after this change at all three widths. */
@media (min-width: 1280px) {
	.oc-stats {
		/* was: -1 * max(...) — the deviation-65 reach. The standoff is the
		   same expression plus one gutter, so the >=1280 reach and the
		   <1280 natural edge both end up 16px inside the frame. */
		margin-inline-end: calc(
			var(--oc-stats-standoff) - max(0px, (100vw - var(--oc-grid-max)) / 2)
		);
	}
}

@media (min-width: 769px) and (max-width: 1279px) {
	/* Below the 1280 cap `.oc-stats` already ends on the inner content edge,
	   which IS the frame inner edge here, so the standoff is a plain margin. */
	.oc-stats { margin-inline-end: var(--oc-stats-standoff); }
}

/* At <=768 the cards are a horizontal snap carousel. The standoff has to come
   from the SCROLL CONTAINER'S OWN BOX, not from padding: overflow clips at the
   padding box, so padding would let a card paint right through the standoff
   while it scrolls. Margin shrinks the clip box itself, so cards are cut 16px
   before the frame on both sides. The next-card peek is preserved — the strip
   still overflows (scrollWidth >> clientWidth) and `grid-auto-columns: 78%` is
   untouched, so the following card is still half in view. */
@media (max-width: 768px) {
	.oc-stats { margin-inline: var(--oc-stats-standoff); }
}

/* --- 20.3 The header must be OPAQUE where a scene scrolls under it ---------
   A CONSEQUENCE OF 20.4 (the engine's mobile reveal hold), found by LOOKING at
   the render and then proved by PAINT DIFFERENCE rather than by hit-testing.

   Once a tall mobile scene stays VISIBLE while it scrolls within itself, its
   content travels under the fixed header — and the header does not stop it.
   MEASURED at 390x844 with `#contact` scrolled to inSection 972: of the 28,080
   pixels in the header's 390x72 box, **19,361 CHANGE** when `#contact` is set
   to `visibility: hidden`, i.e. the scene is what is reaching the screen there.
   The form's white name field painted over the brand wordmark at a flat
   rgb(255,255,255); with the scene hidden the same pixels read rgb(14,15,19).
   An A/A' pair taken in the same state was byte-identical first, so the
   backdrop was proved static before any verdict was taken.

   `elementFromPoint(100, 36)` returned the wordmark's own `<b>` throughout —
   the header wins the HIT TEST and loses the PAINT. README section 15,
   corollary 1, for the seventh time on this project.

   THE FIX IS THE ELEMENT'S OWN BACKGROUND, and that is a measured choice
   between three candidates, not a guess. Differing pixels in the header box,
   same instrument, same scroll position:
     baseline                                        18592
     .oc-header::before { z-index: 0 }                18592   (the -1 is NOT the mechanism)
     .oc-header::before { z-index: 0; opaque }        17306
     .oc-header { background-color: var(--oc-bg) }        0   <- shipped
   Only the element's OWN background paints above both its negative-z
   pseudo-element and everything below it in the stacking order.

   SCOPED TO <=768 because that is the only place a scene scrolls under the
   header. Above it the engine still un-reveals during travel, so nothing can
   paint there and desktop keeps its 0.88 translucency untouched. Recorded as
   deviation 72: on a phone the header loses the 12% of video that showed
   through it. */
@media (max-width: 768px) {
	.oc-header { background-color: var(--oc-bg); }
}

/* ============================================================================
 * 2.1.0 — THE CLIENT-EDITABLE FRONT PAGE: two cascade repairs it requires
 * ============================================================================
 *
 * The hero's copy now lives in the database as real blocks so the client can
 * edit it. That means the hero's structural divs are `core/group` blocks, and
 * `wp:post-content` wraps the whole hero in `.entry-content`. Both of those
 * bring core layout CSS with them, and both are repaired here rather than
 * worked around in markup.
 *
 * REPAIR 1 — core's flow-layout gap, and the specificity I got WRONG first.
 *
 * MEASURED on the deployed page: core's global styles emit
 *   :root :where(.is-layout-flow) > * { margin-block-start: var(--wp--preset--spacing--30) }
 * which is 24px, and every core/group picks up `is-layout-flow`. In 2.0.0 the
 * hero's inner divs were plain <div>s inside one wp:html block, so no child of
 * them carried any core margin at all. Converting them to blocks so the client
 * can edit them therefore inserts 24px where the design has none.
 *
 * THE MISTAKE, recorded because the correction is the useful part. The first
 * cut of this repair was `:where(.oc-hero__inner, …) > * { margin-block-start: 0 }`,
 * written on the belief that core's rule was also a bare `:where()`, i.e. zero
 * specificity, so the two would tie and the later stylesheet (this one) would
 * win. That was wrong, and it was wrong because of how it was checked: a grep
 * of the served document for the literal substring `:where(.is-layout-flow) > *`
 * MATCHES INSIDE `:root :where(.is-layout-flow) > *`, so the `:root ` prefix was
 * invisible to the pattern and never entered the reasoning. That prefix is one
 * pseudo-class, so core's rule is 0-1-0 and the `:where()` neutraliser at 0-0-0
 * lost every time. It computed to 24px on the live render and the hero's two
 * CTAs sat 17px out of line, which is what a screenshot showed and no
 * specificity argument did.
 *
 * THE REPAIR THAT ACTUALLY HOLDS. Beat 0-1-0, but touch ONLY the two children
 * that leak — the ones with no margin rule of their own. Everything else in the
 * hero already wins against core on its own merits and must not be caught here:
 *   `.oc-lead { margin-block: 12px }`            0-1-0, later in this file
 *   `.oc-hero__cues { margin-block-start: 16px }` 0-1-0, later in this file
 *   `.oc-hero__trust .oc-marks__note { margin: 0 }` 0-2-0
 * A blanket `> *` rule strong enough to beat core would also beat all three and
 * collapse the hero's vertical rhythm. Naming the two leaking children keeps the
 * blast radius at exactly the defect.
 */
/*
 * WHICH children leak, settled against the 2.0.0 DOM rather than against an
 * assumption. A sweep of all 47 hero descendants for a computed
 * `margin-block-start: 24px` returned FOUR. Zeroing all four was wrong, and
 * the check that caught it is worth recording.
 *
 * `.oc-scene__scrim` and `.oc-hero__inner` are direct children of the SECTION,
 * and the section was ALREADY a core/group in 2.0.0 — a pre-cutover capture of
 * the 2.0.0 document shows it carrying
 * `wp-block-group alignfull oc-scene oc-scene--dark oc-hero is-layout-flow
 * wp-block-group-is-layout-flow`, with exactly these same three children
 * (`.oc-scene__media`, `.oc-scene__scrim`, `.oc-hero__inner`). So core's 24px
 * applied to those two in 2.0.0 as well, the design was measured and signed off
 * WITH it, and zeroing them here would have been a 24px deviation introduced
 * under the banner of fixing one. They are deliberately left alone.
 *
 * The two below are genuinely NEW in 2.1.x. In 2.0.0 `.oc-hero__inner` and
 * `.oc-hero__cues` were plain <div>s emitted inside one `wp:html` block, so
 * they had no `is-layout-flow` and their children got no core margin at all.
 * Making them real blocks so the client can edit their contents is what
 * introduced the gap, and it is the only part that is ours to undo.
 *
 * The invariant to re-test after any change here: inside `.oc-hero`, exactly
 * TWO elements may compute `margin-block-start: 24px` — `.oc-scene__scrim` and
 * `.oc-hero__inner` — and no others.
 */
:root .oc-hero__inner.is-layout-flow > .oc-hero__trust,
:root .oc-hero__cues.is-layout-flow > .oc-btn {
	margin-block-start: 0;
}

/*
 * REPAIR 2 — `wp:post-content` inserts `.entry-content` between `.oc-main` and
 * the hero section, so `.oc-main > .oc-scene:first-child` (section 2149 above)
 * no longer matches the hero and its margin reset was lost. The selector is
 * extended rather than replaced, so a template that does NOT route through
 * post_content keeps the original behaviour.
 */
.oc-main > .wp-block-post-content > .oc-scene:first-child,
.oc-main > .entry-content > .oc-scene:first-child {
	margin-block-start: 0;
}

/*
 * `.entry-content` is a layout wrapper only. It must contribute no box of its
 * own, or the scroll engine measures a container that is taller than the scene
 * it holds and every snap offset drifts by that difference.
 */
.oc-main > .wp-block-post-content {
	margin: 0;
	padding: 0;
}

/* ----------------------------------------------------------------------------
 * 2.1.0 — `templates/page-client.html`, the template a CLIENT-CREATED page uses.
 *
 * It deliberately does NOT include `.oc-frame`. The frame is the scroll
 * ENGINE's viewport border: it ships at `opacity: 0` and is only ever revealed
 * by `.is-visible`, which assets/js/scroll.js adds. No engine runs on an inner
 * page, so a frame element there would sit invisible forever — a dead element
 * that looks like a feature in the markup. The existing inner pages
 * (`/customer-map/`, `/legal/`) carry no frame either, so a client page now
 * matches them exactly, which is what "looks native" actually means here.
 *
 * The head block gives the page a brand line above its title, driven by
 * `core/site-title` so it follows Settings > General and never hard-codes a
 * brand name.
 * ------------------------------------------------------------------------- */
.oc-article__head {
	margin-block-end: 32px;
}

.oc-article__head .oc-eyebrow {
	margin-block: 0 8px;
}

.oc-article__head .oc-page__title {
	margin-block-start: 0;
}

/*
 * 2.1.0 — the client's own logo, if they upload one. `custom-logo` was declared
 * in functions.php from the start but nothing ever rendered it; it is wired up
 * now, so it needs a box. Capped to the wordmark's own optical height so a
 * large upload cannot change the header's height and push the nav capsule.
 */
.oc-wordmark__logo {
	display: block;
	inline-size: auto;
	block-size: 26px;
	max-inline-size: 180px;
	object-fit: contain;
}

@media (max-width: 767px) {
	.oc-wordmark__logo { block-size: 22px; }
}
