/* aibotkit-child · the landing-page section system — U6
 *
 * Hand-authored, like assets/css/chrome.css and for the same reason: these are
 * the rules a Tailwind utility cannot express. A ground that changes with a
 * modifier, a radial wash on a pseudo-element, a vertical rhythm driven by
 * adjacent-sibling margins -- none of those are a utility.
 *
 * ⛔ THE CLASS NAMES ARE LOAD-BEARING, AND NOT ONLY FOR STYLING.
 *    ops/measure/check-law.js asserts the law against the RENDERED page using
 *    these exact selectors -- `a.btn`, `.badge`, `figure.figure`, `.draw`.
 *    Its button assertion reads `document.querySelectorAll('a.btn, button.btn')`
 *    and, finding none, reports success while checking nothing. So renaming
 *    `.btn` does not break a check loudly; it switches the check OFF.
 *    That is why this file keeps the prototype's vocabulary. We rebuilt the
 *    CSS; we kept the names the guard measures.
 *
 * ⛔ EVERY --wp--custom-- NAME HERE IS WORDPRESS'S OWN KEBAB FORM.
 *    `3xl` becomes `space--3-xl`, not `space--3xl`. A wrong name makes the
 *    declaration invalid and the property falls back SILENTLY. U5 lost the
 *    footer gap to exactly this. Check every name against theme.json.
 *
 * ⚠ LOADS AFTER tw.css, deliberately -- it overrides the utilities.
 */

/* ---------------------------------------------------------------------------
   0 · The two ground aliases.
   The law names the page's two grounds by role, not by colour: the FLOOR is
   what a section stands on, the PANEL is the inset card it stands on when it
   wants to be read as a separate surface. Q1 settled which hex each is.
   --------------------------------------------------------------------------- */
:root {
	--abk-floor: var(--wp--custom--color--paper);
	--abk-panel: var(--wp--custom--color--tint);
}

/* ⛔ `border-box` INSIDE A SECTION, AND IT IS NOT TIDINESS. The prototype's
   stylesheet opens with a `*` reset that sets it; WordPress does not set it
   universally. Measured: every fixed-height box came out its padding taller --
   the 300px product well painted 348px, and the chip row 35.2px against
   27.2px, so the card trio and the whole section grew. ⚠ A height is a
   PROMISE about the painted box, and under `content-box` it is a promise
   about something the eye never sees.
   ⚠ SCOPED TO `.section`. The prototype's own `*` reset could not be ported
   whole -- it also carries `p { max-width: 68ch }`, which would truncate every
   paragraph on all 22 pages and in the editor. This takes the one declaration
   that is safe and leaves the rest where it belongs. */
.section,
.section *,
.section *::before,
.section *::after { box-sizing: border-box; }

/* ⛔ A TRACKING TIER IS A TOKEN, NOT AN OVERRIDE. Two type roles cross the
   28px display boundary as the viewport narrows, so their tracking has to
   change with it. Doing that with media-query OVERRIDES cost three separate
   defects in one day: the rule was correct and lost on source order each time
   a new section was appended below it.
   ⭐ SWITCHING THE TOKEN INSTEAD ENDS THE ARGUMENT. Every consumer follows
   automatically, nothing has to outrank anything, and a section added later
   cannot break it.
   ⚠ THE BREAKPOINTS ARE DERIVED, NOT CHOSEN -- each is the exact viewport
   where that role's own clamp crosses 28px:
       step--display-small  clamp(1.625rem, 3vw, 2.5rem)   ->  3vw = 28px at  933.33px
       step--subsection     clamp(1.375rem, 2vw, 1.75rem)  ->  2vw = 28px at 1400px */
:root {
	--abk-track-dsm: var(--wp--custom--tracking--display);
	--abk-track-sub: var(--wp--custom--tracking--display);
}
@media (max-width: 1399px) { :root { --abk-track-sub: var(--wp--custom--tracking--lead); } }
@media (max-width: 933px)  { :root { --abk-track-dsm: var(--wp--custom--tracking--lead); } }

/* ---------------------------------------------------------------------------
   1 · THE SECTION. Every landing-page section is an inset rounded panel.
   --------------------------------------------------------------------------- */
.section {
	/* ⛔ `clip`, never `hidden`. Both clip the same box, but `hidden` makes a
	   SCROLL CONTAINER, and a scroll container is the containing block for
	   every `position: sticky` descendant -- so a sticky child scrolls away
	   with its section and never sticks. The prototype's audit reported
	   "0 sticky elements on the page" for this reason and nobody had failed
	   to build one. The `hidden` line is the fallback for a browser with no
	   `clip`, and must stay ABOVE it. */
	overflow: hidden;
	overflow: clip;
	margin: 0 var(--wp--custom--space--panel-inset);
	border-radius: var(--wp--custom--rounded--lg);
	padding: var(--wp--custom--space--section) var(--wp--custom--width--gutter);
	background: var(--abk-panel);
	color: var(--wp--custom--color--body);
}

/* ⛔ TWO CORE RULES OWN THE SECTION'S MARGINS, AND BOTH OUTRANK A SINGLE CLASS.
   This is KD46 again, in the place it always shows up: WordPress prints its
   layout rules with `:root :where(...)`, which is (0,2,0) because `:root`
   counts and `:where()` does not. A one-class rule loses to it however late
   the file loads, because LOAD ORDER CANNOT WIN A SPECIFICITY ARGUMENT.

     1 · the flow rhythm put 19.2px above the section -- the hero started at
         95.2px where the prototype starts at 76px, the bar's own bottom edge.
     2 · the `alignfull` rule pulled the section out to the viewport with a
         -50px inline margin, so the 16px panel inset disappeared and the
         section painted 1440 wide instead of 1408.

   ⭐ So this rule is written at (0,3,0) rather than with `!important`: three
   classes state WHERE the exception applies, which an `!important` does not.
   ⚠ Only MARGIN needs this. Padding, colour, size, flex and display are
   untouched by core's layout CSS. */
:root .entry-content > .section,
:root .wp-site-blocks > .section {
	margin-block: 0;
	margin-inline: var(--wp--custom--space--panel-inset);
}

/* ⛔ AND THE LAST 19.2px SITS ON `main`, NOT ON THE SECTION. templates/
   page-landing.html marks it `.lp-main` for exactly this: core's flow rhythm
   puts a gap between the header and the main element, and a landing page's
   first section has to start at the bar's bottom edge -- 76px, the
   prototype's number. ⚠ SCOPED TO THE LANDING TEMPLATE. The same 19.2px is on
   all 22 other pages and U0's baseline has it; removing it everywhere would
   move every page for no reason anybody asked for. */
:root .wp-site-blocks > .lp-main { margin-block-start: 0; }

.section--floor    { background: var(--abk-floor); }
.section--tight    { padding-block: var(--wp--custom--space--section-tight); }
.section--generous { padding-block: var(--wp--custom--space--section-generous); }

/* R1 at 390: the inset halves rather than eating a fifth of the screen. */
@media (max-width: 480px) {
	:root { --wp--custom--space--panel-inset: 8px; }
}

/* ⛔ THE THREE SECTION PADDINGS TAKE A MOBILE VALUE, AND theme.json CANNOT
   HOLD IT. Each token is a clamp whose MINIMUM is its desktop-ish floor, so
   at 390 the viewport term has long since bottomed out and the section keeps
   its full 88px. Measured on the prototype at 390: 60px, not 88px. A clamp
   cannot express "and below 620px, less than the floor"; only a media query
   can, so the value lives here beside the rule it changes. */
@media (max-width: 620px) {
	.section             { padding-block: 44px; }
	.section--tight      { padding-block: 32px; }
	.section--generous   { padding-block: 60px; }
}

/* The measure. `.wrap` is the section's content column. */
.section .wrap {
	max-width: var(--wp--custom--width--figure);
	margin-inline: auto;
}
.section .wrap--prose   { max-width: var(--wp--custom--width--prose); }
.section .wrap--focused { max-width: var(--wp--custom--width--focused); }

/* ⛔ CORE INJECTS A 24px margin-block-start ON EVERY FLOW SIBLING, and this
   section system drives its rhythm from named adjacent-sibling margins
   instead. Zero core's rhythm first, then state ours. Two classes, so it
   beats core's `:where(.is-layout-flow) > *`, which is one. */
.section .wrap > * { margin-block: 0; }

.section--centred            { text-align: center; }
.section--centred .wrap > *  { margin-inline: auto; }

/* ---------------------------------------------------------------------------
   2 · THE LABEL LAYER — the eyebrow and its dot.
   ⛔ The dot aligns to the FIRST LINE's optical centre, not to the element's,
      so a two-line eyebrow keeps the dot beside the first word rather than in
      the gutter between the lines. `1lh` is the element's own line box, so it
      needs no magic number and survives a change of size or leading.
   --------------------------------------------------------------------------- */
.eyebrow {
	display: inline-flex;
	align-items: flex-start;
	gap: var(--wp--custom--space--sm);
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--label);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--custom--color--secondary);
	text-transform: none;   /* ⛔ no ALL CAPS — PRODUCT.md refuses it */
}
.eyebrow::before {
	content: "";
	flex: none;
	width: 6px;
	height: 6px;
	border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--brand);
	margin-top: calc((1lh - 6px) / 2);
}
.on-ink .eyebrow::before { background: var(--wp--custom--color--brand-on-ink); }
.eyebrow--plain::before  { display: none; }

/* ⭐ THE SECTION OPENING IS A UNIT, and this 16px is most of why the
   prototype's openings read composed. ⚠ It does NOT collapse into the next
   element's margin, because `.eyebrow` is inline-flex and margin collapsing
   is a block-box rule -- so the gap below an eyebrow really is its own 16px
   ON TOP of whatever the next element asks for. Measured: without it the
   hero's stat triple sat 15px high and the section came out 15px short.
   ⚠ Three classes, because the rhythm reset above is two. */
:root .section .eyebrow { margin-bottom: var(--wp--custom--space--md); }

/* ---------------------------------------------------------------------------
   3 · THE BADGE — the hero's pill. One of exactly TWO components the law
   lets take the pill radius; check-law.js asserts that list.
   --------------------------------------------------------------------------- */
.badge {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--custom--space--sm);
	min-height: 34px;
	padding: 0 var(--wp--custom--space--md);
	border-radius: var(--wp--custom--rounded--full);
	background: var(--abk-panel);
	box-shadow: var(--wp--custom--shadow--edge);
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--label);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--custom--color--secondary);
}
.badge::before {
	content: "";
	flex: none;
	width: 6px;
	height: 6px;
	border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--brand);
}

/* ---------------------------------------------------------------------------
   4 · BUTTONS — R17: at least 44×44, and 8px, never a pill.
   ⚠ The class sits on the ANCHOR, not on core's wrapper div, because that is
     the element check-law.js measures and the element the user clicks.
   --------------------------------------------------------------------------- */
.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	min-width: 44px;
	padding: 0 var(--wp--custom--space--lg);
	border: 0;
	border-radius: var(--wp--custom--rounded--sm);
	font-size: var(--wp--custom--step--body);
	font-weight: var(--wp--custom--weight--emphasis);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--wp--custom--tracking--text);
	text-decoration: none;
	cursor: pointer;
	transition: background-color var(--wp--custom--motion--duration) var(--wp--custom--motion--easing);
}
/* ⚠ `color: #fff` AND NOT A LITERAL. check-law.js assertion reads every
   painted colour and refuses a hex that IS one of our tokens: white is
   `--paper`, so writing it by hand states the same value twice and the two
   can drift. The guard caught this one on its first run. */
.btn--brand         { background: var(--wp--custom--color--brand); color: var(--wp--custom--color--paper); }
.btn--brand:hover   { background: var(--wp--custom--color--brand-hover); }
.btn--brand:active  { background: var(--wp--custom--color--brand-active); }
.btn--outline {
	background: transparent;
	color: var(--wp--custom--color--heading);
	box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline);
}
.on-ink .btn--outline {
	color: var(--wp--custom--color--ink-heading);
	box-shadow: inset 0 0 0 1px var(--wp--custom--color--ink-hairline);
}
/* ⛔ THE LARGE BUTTON TAKES THE LEAD TRACKING TOO. `--lg` raises the SIZE to
   the lead rung, and every size takes its own tracking tier; leaving the
   16px tier on a 21px label is the drift R14 exists to stop. */
.btn--lg {
	min-height: 56px;
	padding: 0 var(--wp--custom--space--xl);
	font-size: var(--wp--custom--step--lead);
	letter-spacing: var(--wp--custom--tracking--lead);
}
.btn .ico {
	/* ⛔ AN ICON IS NEVER THE THING THAT GIVES WAY. The button is an inline-flex
	   box, so its svg is a flex item and shrinks like any other once the label
	   runs out of room -- it was measured at ZERO width at 360px, where the
	   arrow simply disappeared from the closing CTA while every number said the
	   button fit. ⚠ THE SCREENSHOT CAUGHT IT AND THE MEASUREMENTS DID NOT:
	   width, height and overflow were all correct with the arrow already gone.
	   ⭐ `flex: none` makes the LABEL wrap instead, which is the legible half of
	   the pair to sacrifice. Nothing shrinks it above 375px, so no painted
	   button changes anywhere the icon was already visible. */
	flex: none;
	width: 20px;
	height: 20px;
	display: block;
	fill: none;
	stroke: currentColor;
	stroke-width: 1;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.btn--lg .ico {
	width: 15px;
	height: 15px;
	margin-left: var(--wp--custom--space--sm);
	stroke-width: 1.3333;   /* ⛔ one RENDERED stroke: 15 / 20 × 1.3333 = 1.000 */
}

/* The button row. */
.row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--custom--space--md);
}
.section--centred .row { justify-content: center; width: max-content; max-width: 100%; }

/* ---------------------------------------------------------------------------
   5 · THE STAT TRIPLE.
   ⛔ NO SIDE-STRIPE. A green rule beside each number is a side-stripe border,
      which the law refuses by name. The separation is PAID FOR WITH AIR: the
      gap goes from 24px to 64px. Three numbers at 40px with 64px between them
      read as three things without a line.
   --------------------------------------------------------------------------- */
.stats {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--wp--custom--space--lg);
	list-style: none;
	margin: 0;
	padding: 0;
}
/* ⚠ `margin-block: 0` is not tidiness. Core gives a list's items the theme's
   block gap -- measured 8px -- which stacked on top of the grid `gap` and
   made the triple 73.2px tall against the prototype's 65.2px. */
.section .stats > li { margin-block: 0; }
.stats > li { display: grid; gap: 2px; align-content: start; padding-left: 0; }
.stats b,
.stats strong {
	font-size: var(--wp--custom--step--subsection);
	font-weight: var(--wp--custom--weight--display);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--abk-track-sub);
	color: var(--wp--custom--color--heading);
}
.stats span {
	font-size: var(--wp--custom--step--label);
	letter-spacing: var(--wp--custom--tracking--text);
	color: var(--wp--custom--color--secondary);
}
.on-ink .stats b,
.on-ink .stats strong { color: var(--wp--custom--color--ink-heading); }
.on-ink .stats span   { color: var(--wp--custom--color--ink-muted); }

.stats--ruled { gap: var(--wp--custom--space--3-xl); }
.stats--ruled b,
.stats--ruled strong { font-size: var(--wp--custom--step--display-small); letter-spacing: var(--abk-track-dsm); }

@media (max-width: 520px) {
	.stats { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------------
   6 · THE HERO.
   ⚠ It does NOT run under the fixed bar. Measured on the prototype at both
     widths: its page wrapper carries a 76px top offset and the hero's own box
     starts exactly at the bar's bottom edge. chrome.css already applies that
     same 76px to `.wp-site-blocks`, so the default is already correct and
     `.abk-hero-under` must NOT be set on this page.
   --------------------------------------------------------------------------- */
/* ⛔ EVERY HEADING IN A SECTION BALANCES ITS LINES, and leaving this out is
   invisible in a token diff. `text-wrap: balance` asks the browser to even
   out the line lengths instead of filling each line greedily. Measured:
   without it the problem section's h2 broke as "Your site never sleeps. Your
   / team does." against the prototype's "Your site never sleeps. / Your team
   does." -- same width, same size, same tracking, a WORSE break. ⭐ Two cards
   in the same section broke differently too.
   ⚠ A computed-style diff of size, tracking and leading passes this cleanly.
   It was found by putting the two screenshots side by side. */
.section :is(h1, h2, h3, h4, h5, h6) { text-wrap: balance; }

/* ⛔ THE SECTION HEADING TAKES ITS OWN RUNG, AND theme.json CANNOT GIVE IT
   ONE. theme.json's `elements.h2` sets a single size for every h2 on the
   site, including the footer's column headings at 14px. A landing-page
   section's h2 is the SECTION rung -- 48px at 1440, 32px at 390. Measured:
   without this it rendered at 28px against the prototype's 48px.
   ⚠ `step--section` is clamp(2rem, 3.6vw, 3rem), so its MINIMUM is 32px and
   it never drops below the 28px display boundary. It needs no tracking
   breakpoint, unlike `step--subsection` and `step--display-small`. */
.section h2 {
	font-size: var(--wp--custom--step--section);
	font-weight: var(--wp--custom--weight--display);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--wp--custom--tracking--display);
	color: var(--wp--custom--color--heading);
}

.hero .wrap { max-width: 1180px; }

.hero h1 {
	max-width: 20ch;
	margin-inline: auto;
	font-size: var(--wp--custom--step--hero);
	font-weight: var(--wp--custom--weight--display);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--wp--custom--tracking--display);
	color: var(--wp--custom--color--heading);
}

/* ⛔ THE SECOND SENTENCE GETS A SIZE RUNG, NOT A COLOUR TRICK. And the rung is
   40, not 48: at 48 the hero would carry 48 beside the 40 stat numbers --
   1.20 apart, the exact collision the placement rule exists to stop, in the
   one place a visitor looks hardest. So this line and the stat numbers SHARE
   40 and separate by weight and colour: 400 and secondary here, 600 and ink
   there. */
.hero .h-second {
	display: block;
	margin-top: var(--wp--custom--space--md);
	font-size: var(--wp--custom--step--display-small);
	font-weight: var(--wp--custom--weight--body);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--abk-track-dsm);
	color: var(--wp--custom--color--secondary);
}

/* The hero's own rhythm, measured off the prototype at 1440 and 390. */
.hero .wrap > .row     { margin-top: var(--wp--custom--space--xl); }
.hero .wrap > .eyebrow { margin-top: var(--wp--custom--space--md); }
.hero .wrap > .stats   { margin-top: var(--wp--custom--space--3-xl); }

.hero .eyebrow { display: inline-flex; }
/* ⛔ THE TRIPLE CENTRES ITS OWN COLUMNS. ALPESH, 2026-09-23, LOOKING AT THE
   PAINTED HERO: "the stats part is not centralized."
   ⚠ IT WAS `text-align: left`, AND THE BOX WAS ALREADY CENTRED -- 1040px
     inside an 1180px wrap. That is what made it hard to see: the BOX was
     centred and the CONTENT was not. Each column is 1fr, so every number
     started at its column's left edge, and the third label is the longest --
     so the painted mass sat left of the hero's axis while the box said
     otherwise. Everything above it, badge to eyebrow, is centred.
   ⭐ THREE CANDIDATES WERE PAINTED ON THE REAL PAGE: left as it shipped, the
      columns centred, and the box pulled in to 820px while staying left. He
      picked the centred columns. */
.hero .stats   { max-width: 1040px; margin-inline: auto; text-align: center; }

/* ⛔ THE HERO'S STAT LABELS TAKE THE HEADING INK, AND NOWHERE ELSE DOES.
   ⚠ MEASURED 2026-09-22, ON THE PAINTED PAGE: the wash runs `to top`, so it is
     STRONGEST AT THE SECTION'S FLOOR -- and the floor is where the smallest
     type on the page lives. The ground sampled beside the labels is #C3E5D6,
     and --secondary #656550 on it is 4.39:1. THE FLOOR IS 4.5. It failed.
   ⛔ NO CHECKER COULD SEE IT. check-law.js grades a colour against its TOKEN
      ground, and the token ground here is --tint, where #656550 is 5.64. The
      wash is a ::before layer, so the real ground is never the token.
   ⭐ THE WASH IS NOT WEAKENED TO PAY FOR THIS. Easing its bottom stop to 26%
      buys 4.60 -- a 0.10 margin, the same margin refused on the terracotta
      gradient -- and visibly drains the band. Four candidates were painted on
      the real page and Alpesh picked this one. Heading ink measures 14.46:1.
   ⚠ THE HIERARCHY IS CARRIED BY SIZE AND WEIGHT, NOT BY COLOUR: 40px semibold
     against 12px regular. That was checked in the paint, not reasoned about.
   ⛔ SCOPED TO `.hero`. The ink-ground stats remap to --ink-muted above and
      must not move; a bare `.stats span` rule would take them too. */
.hero .stats span { color: var(--wp--custom--color--heading); }

/* ---------------------------------------------------------------------------
   7 · THE WASH — grounds member 2. ONE sub-perceptual gradient, no image,
   no second asset, no motif.
   ⛔ IT IS LEGAL BY THE LAW'S OWN WORDS. The refusal names gradient TEXT, a
      two-hue "AI" gradient and a gradient on a button. This is a single-hue
      tonal wash on a GROUND, which the same paragraph permits.
   ⛔ AND ITS MIDDLE STOP NAMES `--brand-lift`, WHICH THE CLOSING BAND'S DOES
      NOT. The close carries `.on-ink`, where system.css remaps
      `--brand-lift` to `--brand` underneath it. Copying this declaration
      stop-for-stop into the close WAS the bug: the hero paints #D8F3E6 and
      the close #D6ECE4. They are deliberately different. Do not unify them.
   --------------------------------------------------------------------------- */
.wash-bloom { position: relative; isolation: isolate; }
.wash-bloom::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background: linear-gradient(to top,
		color-mix(in oklab, var(--wp--custom--color--brand)      34%, transparent)  0%,
		color-mix(in oklab, var(--wp--custom--color--brand-lift) 16%, transparent) 34%,
		transparent 68%);
}

/* ===========================================================================
   ⛔ EVERY TYPE SIZE TAKES ITS OWN TRACKING TIER, AND A CLAMP CHANGES THE SIZE
   WITHOUT CHANGING THE TIER. This block is the correction, and it is the one
   thing in this file that is easiest to get wrong.

   The three tiers, and they step ONE WAY ONLY:
       >= 28px   -0.0275em   display
       20-27px   -0.0100em   lead
       <  20px    0          text

   ⚠ THE BREAKPOINTS ARE DERIVED, NOT CHOSEN. Each role switches at the exact
   viewport where its own clamp crosses 28px:
       step--display-small  clamp(1.625rem, 3vw, 2.5rem)   ->  3vw = 28px at  933.33px
       step--subsection     clamp(1.375rem, 2vw, 1.75rem)  ->  2vw = 28px at 1400px

   ⛔ AND THIS BLOCK SITS AT THE END OF THE FILE ON PURPOSE. A media query adds
   NO specificity. Put it above the rules it corrects and it loses to them on
   source order, silently, at one width only -- which is the hardest kind of
   defect to see. An override must come AFTER the rule it overrides as well as
   match its shape.

   ⭐ ops/measure/check-law.js reads the tier off the RENDERED size, so a value
   that is right at 1440 and wrong at 390 fails there and only there.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   8 · THE CUSTOMER LOGO ROW — U6, section 2.
   Nine real customer marks, scrolling as one continuous wall.
   --------------------------------------------------------------------------- */
.marq {
	position: relative;
	overflow: hidden;
	/* ⛔ A CUT EDGE IS A MASK, NEVER A GRADIENT OVERLAY. An overlay has to
	   know the ground it sits on and stops being invisible the moment the
	   section's ground changes; a mask cuts the element itself. */
	mask-image: linear-gradient(to right, transparent 0%, #000 6%,
	                            #000 94%, transparent 100%);
}

.marq__label {
	text-align: center;
	/* ⛔ 48px, NOT 24. ALPESH, 2026-09-23: "I feel there should a bit more
	   spacing between the text and logos."
	   ⚠ 40px WAS PAINTED AND REFUSED -- it is not on the spacing ladder, and
	     an off-ladder value is exactly what check-tokens.sh exists to catch.
	     The legal rungs either side of 24 are 32 and 48; both were painted at
	     1440 and he took the larger. A 12px caption above a 34px wall of
	     marks needs the room to read as a caption rather than a label stuck
	     to the row. The section's own frame is 64px, so 48 still sits inside
	     it rather than competing with it. */
	margin: 0 0 var(--wp--custom--space--2-xl);
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--label);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--custom--color--secondary);
}

.marq__track {
	display: flex;
	align-items: center;
	gap: var(--wp--custom--space--2-xl);
	width: max-content;
	margin: 0;
	padding: 0;
	list-style: none;
	animation: marq-run 44s linear infinite;
}
/* ⛔ -50% AND NOT -100%, AND THE MARKUP IS WHY. The nine marks are printed
   TWICE, so the track is exactly two identical halves. Moving it by half its
   own width lands the second nine where the first nine started, and the loop
   has no seam. Print the nine once and this jumps. */
@keyframes marq-run { to { transform: translateX(-50%); } }

/* ⛔ MANDATORY, AND IT DOES NOT SLOW THE ROW -- IT STOPS IT. A marquee that
   merely runs slower is still motion, and the request was for none. The row
   wraps into a static centred grid, which keeps all nine visible. The mask
   also goes: with nothing moving, a cut edge hides marks for no reason. */
@media (prefers-reduced-motion: reduce) {
	.marq { mask-image: none; }
	.marq__track {
		animation: none;
		width: 100%;
		flex-wrap: wrap;
		justify-content: center;
		gap: var(--wp--custom--space--xl) var(--wp--custom--space--2-xl);
	}
	/* ⛔ AND THE DUPLICATE NINE GO. They exist ONLY to make the loop seamless.
	   With nothing looping they are just repeats, so a visitor who asked for
	   no motion met every customer TWICE -- eighteen marks in a static grid,
	   which reads as a longer customer list than we have. ⚠ THE PROTOTYPE HAS
	   THIS FAULT TOO: its reduced-motion rule wraps the same eighteen. Found
	   by painting the branch rather than by reading the rule. */
	.marq__mark[aria-hidden="true"] { display: none; }
}

/* ⛔ OPTICAL WEIGHT IS NOT PIXEL HEIGHT, AND MATCHING THE HEIGHT DOES NOT
   DELIVER IT. Measured: the nine source files run from 72px to 484px wide at
   a common 80px height. Set to one height, `Select Interiors` occupied nearly
   SEVEN TIMES the width of `Emerald Yards`, and the row read as scattered
   noise rather than as a wall.
   ⭐ So every mark gets THE SAME BOX and is contained inside it, which gives
   the row one rhythm. `data-w` then trims the DENSE marks, which is the only
   part of optical weight a box cannot do. */
.marq__mark {
	display: block;
	flex: none;
	width: 132px;
	height: 34px;
	object-fit: contain;
	object-position: center;
	/* Q9 candidate A, and it is what the prototype paints. `brightness(0)`
	   AFTER `grayscale(1)`, so a mark's own colour cannot leak through at any
	   lightness. ⚠ 0.55 measured too faint to read as testimony; 0.68 holds
	   the row together without competing with the line above it. */
	filter: grayscale(1) brightness(0) opacity(0.68);
}
/* ⭐ Q9 SETTLED BY ALPESH, 2026-09-19: GREY AT REST, THEIR OWN COLOURS ON
   HOVER. Neither camp on its own -- the flat ink holds the row together as a
   wall, and the colour is there for anyone who stops on a mark, which is the
   moment it reads as a citation rather than as decoration.
   ⛔ AND THE ROW HAS TO STOP FOR THE HOVER TO BE USABLE. On a moving strip the
   mark slides out from under the cursor before the transition finishes, so
   the colour would only ever be glimpsed. Pausing is what makes the feature
   work; it is not an extra.
   ⚠ `@media (hover: hover) and (pointer: fine)` ON PURPOSE. A touch screen
   reports no hover, and without the guard a tap can leave a mark stuck in
   colour with no way to clear it. Touch keeps the flat ink, which is the
   state that was shipped. */
@media (hover: hover) and (pointer: fine) {
	.marq__mark { transition: filter var(--wp--custom--motion--duration) var(--wp--custom--motion--easing); }
	.marq:hover .marq__track { animation-play-state: paused; }
	.marq__mark:hover { filter: none; }
}
/* R26 again: if motion is refused, nothing pauses because nothing runs. The
   colour on hover stays -- it is not motion. */

.marq__mark[data-w="xs"] { transform: scale(0.94); }   /* thin serif wordmark */
.marq__mark[data-w="s"]  { transform: scale(0.86); }
.marq__mark[data-w="sm"] { transform: scale(0.90); }
.marq__mark[data-w="m"]  { transform: scale(0.82); }   /* dense script mark  */
.marq__mark[data-w="l"]  { transform: scale(0.78); }   /* solid glyph        */

@media (max-width: 720px) {
	.marq__mark { width: 104px; height: 28px; }
}

/* ---------------------------------------------------------------------------
   9 · THE PROBLEM SECTION — U6, section 3. Five components, and four of them
   are reused by later sections rather than built again.
   --------------------------------------------------------------------------- */

/* -- 9a · THE GHOST TRANSCRIPT.
   ⭐ The cheapest lift device on the page and the one no reference site can
   copy, because our content IS conversations. It is TEXT, so it costs no
   asset and no request.
   ⛔ EVERY LINE IS ALREADY ON THIS PAGE -- the three visitor rows in the
   drawing below it and the four sentences of the paragraph beside it. The
   section that carried this device the first time was removed precisely
   because it INVENTED words. */
.ghosted { position: relative; isolation: isolate; overflow: hidden; }
.ghost {
	position: absolute;
	inset: 0;
	z-index: -1;
	width: auto;
	max-width: none;
	user-select: none;          /* a drag cannot select it */
	pointer-events: none;
	font-size: 13px;
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--text);
	/* ⛔ 0.055 WAS TUNED FOR AN INK GROUND, where light type on black barely
	   shows. On the tint it measured 1.112 : 1 and every line was READABLE --
	   the one thing this device forbids by name. Read off the paint, halved,
	   and re-read: 1.045 : 1. */
	color: var(--wp--custom--color--ink);
	opacity: 0.026;
	padding: var(--wp--custom--space--lg);
	columns: 3;
	column-gap: var(--wp--custom--space--2-xl);
	overflow: hidden;
	/* ⛔ A TREATMENT MUST COVER WHAT IT TREATS. An earlier centre at 50% 30%
	   left the lower half of an 808px section plain. Centred, and wider. */
	mask-image: radial-gradient(150% 150% at 50% 50%, #000 45%, transparent 95%);
}
.on-ink .ghost { color: var(--wp--custom--color--ink-heading); }
/* ⛔ ONE ROW PER TURN. Without `display: block` the spans run together and the
   field reads as a PARAGRAPH -- a thing to read rather than a texture.
   ⚠ AND NOT `white-space: nowrap`: that made each row 1408px wide, so the
   columns collapsed and only two rows survived. Measured both ways. */
.ghost span { display: block; }
@media (max-width: 760px) { .ghost { columns: 1; } }

/* ⛔ CORE'S 24px FLOW RHYTHM, ZEROED ONCE FOR EVERY CONTAINER THIS SECTION
   INTRODUCES. WordPress prints `:root :where(.is-layout-flow) > *` at (0,2,0),
   so a single-class reset loses to it however late this file loads -- KD46,
   again, and only MARGIN behaves this way. ⭐ Then the rhythm is stated
   positively below, at (0,2,1) or better, which is what makes each gap a
   decision rather than a leftover. */
:root .section > *,
:root .split > *,
:root .fgrid > *,
:root .f > *,
:root .demo__frame > *,
:root .split__copy > * { margin-block: 0; }

/* ⭐ THE SECTION OPENING IS A UNIT, and this rhythm is a large part of why the
   prototype's openings read composed rather than merely correct. */
.section h2 + p,
.section h2 + .lead { margin-top: var(--wp--custom--space--lg); }
/* ⛔ A SECTION'S HEADING PAIR NEVER TOUCHES THE CONTENT BELOW IT.
   ⚠ AND THIS RULE FIRES THROUGH A HIDDEN ELEMENT, WHICH IS WHY THE MOBILE GAP
   IS BIGGER THAN THE DESKTOP ONE. The copy ships a long and a short version of
   the same sentence and hides one by width. At 390 the LONG lead is
   `display: none`, but it is still the previous SIBLING -- so the short lead
   matches `.lead + *` and takes 48px where the long one would have taken 24.
   ⭐ Measured on the prototype at 390 and reproduced deliberately: Alpesh
   decided this section from that paint, so the paint is the specification. */
/* ⛔ `:root` HERE IS LOAD-BEARING. This rule has to outrank BOTH the rhythm
   reset above it AND each component's own default top margin -- the
   definition list asks for 32px on its own and must take 48px when it follows
   a lead. Measured: without it every one of the four term/definition sections
   came out 16px short. */
:root .section .lead + * { margin-top: var(--wp--custom--space--2-xl); }
/* The one paragraph that CLOSES a lead rather than opening a section, so it
   takes the tight gap and not the 48px opening gap.
   ⚠ `:root` here for the same reason it is on the rule above: that rule was
   raised to (0,3,0) to beat each component's own default, and this exception
   had to be raised with it. Measured at 390 the moment it was not: the
   problem section grew 32px. ⭐ Raising one rule's specificity is never local
   -- everything that used to beat it has to be re-checked. */
:root .section p.tight { margin-top: var(--wp--custom--space--md); }
/* ⛔ `>` THROUGHOUT, AND IT IS A RULE RATHER THAN A TIDY-UP: A CARD'S TYPE
   RULES MUST NOT REACH INSIDE A DRAWING IT HOLDS. A drawing states its own
   type. Measured twice in one section: the card's paragraph colour repainted
   the widget's white user bubble grey-on-green, and the card's 8px paragraph
   rhythm repainted the app screen's 2px caption gap, growing every card.
   ⭐ A card's own copy is its DIRECT children; anything deeper belongs to a
   drawing. */
.section .f > h3 + p,
.section .f > p + p { margin-top: var(--wp--custom--space--sm); }

/* -- 9b · THE SPLIT. Two columns on the wide screen, one below 900px. */
.split { display: grid; gap: var(--wp--custom--space--2-xl); align-items: center; }
.split--top { align-items: start; }
@media (min-width: 900px) {
	.split            { grid-template-columns: 1fr 1fr; }
	.split--wide-left { grid-template-columns: 1.15fr 1fr; }
	.split--flip > *:first-child { order: 2; }
}
/* ⛔ SHOW THE MECHANISM BEFORE THE CLAIM, AND BELOW 900px THERE IS NO LEFT AND
   NO RIGHT -- the split stacks in DOM ORDER, and the DOM writes the argument
   first. Measured: all four split sections put the claim first at 390,
   against three of four that pass at 1440. ⭐ The phone was the worse half and
   nobody had looked. ⚠ It costs the desktop rhythm nothing, because a stacked
   column has no side to alternate. */
@media (max-width: 899px) {
	.split > figure,
	.split > .demo__frame { order: -1; }
}

/* -- 9c · RESPONSIVE COPY PAIRS. The live copy ships a LONG and a SHORT
   version of five sentences. The frozen-copy rule keeps both, so both are in
   the DOM and the viewport chooses. ⛔ Not an animation, so the motion rules
   do not apply. */
.only-wide   { display: block; }
.only-narrow { display: none; }
@media (max-width: 620px) {
	.only-wide   { display: none; }
	.only-narrow { display: block; }
}

.lead {
	font-size: var(--wp--custom--step--lead);
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--lead);
	color: var(--wp--custom--color--secondary);
	max-width: 62ch;
}

/* -- 9d · THE FIGURE AND THE DRAWING IT HOLDS.
   ⭐ THE RULE, STATED ONCE: an object takes the light ground its surface is
   NOT, and anything inside it takes the one the OBJECT is not. Written as a
   pair because this exact inversion has been got wrong three times. */
.figure {
	--fig:    var(--abk-floor);
	--fig-in: var(--abk-panel);
	border-radius: var(--wp--custom--rounded--lg);
	background: var(--fig);
	padding: var(--wp--custom--space--xl);
	overflow: hidden;                 /* ⛔ the ground clips its own bleed */
	margin: 0;
}
.section--floor .figure { --fig: var(--abk-panel); --fig-in: var(--abk-floor); }
.figure--ink,
.section--floor .figure--ink {
	--fig: var(--wp--custom--color--ink);
	--fig-in: color-mix(in oklab, var(--wp--custom--color--ink-heading) 8%, var(--wp--custom--color--ink));
}

/* ⭐ THE FIELD -- PICKED BY ALPESH 2026-09-21, from A and B side by side.
   The figure and the drawing's stage stop being two surfaces and become ONE
   tint field, and the only shadow left belongs to the fragment inside it.
   Alpesh: "the product drawing should have a transparent background and it
   should carry its own drop shadow".
   ⛔ THIS IS WHAT ARCADE DOES -- measured 2026-09-21: their grey panel carries
   no shadow, and the drop shadow is inside the fragment's own alpha channel.
   ⚠ WHY A CLASS AND NOT THE DEFAULT: `.illus` is used in ONE file today --
   lp-definitions.php, twice -- so the class buys nothing yet. It is kept as
   the switch the pick was made with, and because the next drawing may want a
   stage that is still a card. ⛔ I first wrote that lp-help.php also uses
   `.illus`. It does not. The reason was invented; a grep corrected it.
   ⭐ AND IT IS WHAT THE EXPORT ROUTE ASSUMES -- the asset owns its shadow, so
   the ground never does. See the design-system skill, asset reference 1a. */
.figure--field .illus { background: transparent; box-shadow: none; }

/* ⛔ NO RADIUS AND NO SHADOW ON A DRAWING. The treatment is the ground it
   sits on, never chrome around it. */
.draw { position: relative; overflow: hidden; border-radius: 0; box-shadow: none; }
/* ⭐ THE SUPPRESSION LADDER, BOLDEST LAST, DONE AS A LAYER and never baked
   into the artwork. Exactly one thing per drawing carries `.lit`. */
.dim { opacity: 0.35; }
.lit { position: relative; z-index: 2; }
/* The bleed: the drawing runs past its ground's edge on the named side, and
   the cut is a MASK, never a gradient overlay. */
.draw--bleed-b {
	margin-bottom: calc(var(--wp--custom--space--xl) * -1);
	mask-image: linear-gradient(to bottom, #000 86%, transparent 100%);
}
@media (max-width: 720px) {
	.figure { padding: var(--wp--custom--space--md); }
	.draw--bleed-b { margin-bottom: calc(var(--wp--custom--space--md) * -1.6); }
}

/* A DRAWN LIST ROW. This component does three of the page's eight drawings. */
.drow {
	display: flex;
	align-items: center;
	gap: var(--wp--custom--space--md);
	padding: var(--wp--custom--space--md);
	border-radius: var(--wp--custom--rounded--sm);
	background: var(--fig-in);
}
.drow + .drow { margin-top: var(--wp--custom--space--sm); }
.drow__body { flex: 1; min-width: 0; }
.drow__t {
	display: block;
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--text);
	line-height: var(--wp--custom--leading--body);
}
.drow__m {
	display: block;
	font-size: var(--wp--custom--step--label);
	letter-spacing: var(--wp--custom--tracking--text);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--custom--color--secondary);
}
.figure--ink .drow__m { color: var(--wp--custom--color--ink-muted); }
.figure--ink .drow__t { color: var(--wp--custom--color--ink-heading); }

/* ⭐ THE AMBER-WAITING / GREEN-DONE PAIR IS THE WHOLE POINT OF THIS COMPONENT,
   so both rungs move together: waiting takes the terracotta light rung, done
   takes the brand's lightest. ⚠ A `dstate` is a PILL, and it is inside a
   drawing root -- the drawn-chrome exemption is what lets it be one. */
.dstate {
	flex: none;
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--text);
	padding: 4px 10px;
	border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--a-terra-light);
	color: var(--wp--custom--color--heading);
}
.dstate--done {
	background: var(--wp--custom--color--tint-brand);
	color: var(--wp--custom--color--brand-text);
}

/* The empty reply slot. ⭐ The suppression ladder's BOTTOM rung: a coloured
   bar with no content at all. */
.dvoid {
	margin: var(--wp--custom--space--xs) 0 var(--wp--custom--space--sm) auto;
	width: 42%;
	height: 34px;
	border-radius: var(--wp--custom--rounded--sm);
	background: color-mix(in oklab, var(--wp--custom--color--ink-heading) 5%, var(--wp--custom--color--ink));
}

/* The drawn caption inside a figure. */
.dnote {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--custom--space--sm);
	margin-top: var(--wp--custom--space--md);
	padding: 10px var(--wp--custom--space--md);
	border-radius: var(--wp--custom--rounded--sm);
	background: var(--fig-in);
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--text);
	line-height: var(--wp--custom--leading--body);
	box-shadow: var(--wp--custom--shadow--contact);
}
.figure--ink .dnote { color: var(--wp--custom--color--ink-heading); box-shadow: none; }

/* -- 9e · THE FEATURE GRID. One component, three densities. This is the reuse
   test: page 4558 runs SIX consecutive feature sections, and if each needed
   its own CSS the vocabulary has failed. */
/* ⚠ `:root .section .fgrid` AND NOT `.fgrid`. The rhythm reset on
   `.section .wrap > *` is (0,2,0) and it zeroed this margin -- the four cards
   sat flush against the paragraph above them, 48px too high. A positive
   rhythm rule has to outrank the reset that cleared core's. */
.fgrid { display: grid; gap: var(--wp--custom--space--md); }
:root .section .fgrid { margin-top: var(--wp--custom--space--2-xl); }
.fgrid--2 { grid-template-columns: repeat(2, 1fr); }
.fgrid--3 { grid-template-columns: repeat(3, 1fr); }
.fgrid--4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .fgrid--3, .fgrid--4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .fgrid--2, .fgrid--3, .fgrid--4 { grid-template-columns: 1fr; } }

/* ⛔ A CARD IS ALWAYS THE FLOOR, ON EVERY GROUND. An earlier rule read "a card
   takes the light ground its surface is NOT", which tinted every card on a
   white section. Alpesh overruled it: the tint is a SECTION-scale token and a
   card stays white. ⭐ A white card on a white section reads by its EDGE --
   `shadow--edge` is ink at 10%, which is exactly the barely-there 1px border
   he cited two references for, and it costs no shadow budget. */
.f {
	background: var(--abk-floor);
	border-radius: var(--wp--custom--rounded--md);
	padding: var(--wp--custom--space--lg);
	box-shadow: var(--wp--custom--shadow--edge);
	margin: 0;
}
/* ⛔ THIS HEADING RENDERED AT BODY SIZE CARRYING DISPLAY TRACKING AND DISPLAY
   LEADING, on eleven cards. Two faults in one line: it was the SAME SIZE as
   the paragraph under it, and it inherited the display tracking from the
   shared heading rule -- a 72px correction applied to 16px text, which
   squeezed the letters shut. ⭐ Alpesh picked the LEAD size from four painted
   candidates. It is not a new rung; the card reuses one of the nine.
   ⚠ It takes the LEAD tracking because that is 21px's tier, and the DISPLAY
   line-height because it is a heading, not a paragraph. */
.f > h3 {
	margin: 0;
	font-size: var(--wp--custom--step--lead);
	font-weight: var(--wp--custom--weight--display);
	letter-spacing: var(--wp--custom--tracking--lead);
	line-height: var(--wp--custom--leading--display);
	color: var(--wp--custom--color--heading);
}
.f > p {
	margin-top: var(--wp--custom--space--sm);
	color: var(--wp--custom--color--secondary);
	max-width: none;
}

/* ---------------------------------------------------------------------------
   10 · THE PUNCH — U6, section 4. One sentence at display size, alone on the
   floor. No card, no ground, no icon: THE TYPE IS THE SECTION.
   ⭐ This is the gear change the middle of the page was missing. Eleven
   sections read heading → lead → row of cards, and both accent grounds were
   already spent, so the variation had to come from GEOMETRY.
   --------------------------------------------------------------------------- */
.punch { text-align: center; }

/* ⛔ THE TWO RUNGS SWAPPED ON 2026-09-23, AND THE RULE DID NOT CHANGE --
   ONLY WHICH SENTENCE IT POINTS AT. Alpesh, looking at the painted section:
   "the second line is the main thing but we have it smaller and greyed."
   ⚠ HE IS RIGHT AND THE OLD NOTE HERE ARGUED FOR THE OPPOSITE OF WHAT IT
     SHIPPED. It said the second sentence "gets a size rung, not a colour",
     as a HIERARCHY signal -- then gave that sentence the SMALLER size AND
     the quieter colour. Both signals pointed away from the line carrying the
     argument. The rung was real; it was aimed at the wrong sentence.
   ⭐ FOUR CANDIDATES WERE PAINTED AT 1440 AND HE PICKED THIS ONE: the setup
      quiet at the subsection rung, the punch at the display rung in ink.
      ⚠ The one that was refused on sight is both sentences at 60px with the
        setup in --secondary: a display-size line in grey reads as something
        that failed to load, not as something deliberately quiet.
   ⛔ EACH SIZE KEEPS ITS OWN TRACKING TIER, AND THE TIERS SWAPPED WITH THE
      SIZES. `--abk-track-sub` drops to the lead tier below 1400px because the
      subsection rung renders under 28px there; the display rung never does.
      Swapping the sizes without swapping these two would put a display
      tracking on a 22px line, which ops/measure/check-law.js fails by name. */
.punch__line {
	margin: 0 auto;
	max-width: 46ch;
	font-size: var(--wp--custom--step--subsection);
	font-weight: var(--wp--custom--weight--body);
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--abk-track-sub);
	color: var(--wp--custom--color--secondary);
}
/* ⚠ 24ch, NOT THE 22ch THE SETUP USED. This sentence is 50 characters
   against the setup's 41, and 22ch broke it into three lines. 24ch gives two,
   measured at 1440. */
.punch__sub {
	max-width: 24ch;
	font-size: var(--wp--custom--step--display);
	font-weight: var(--wp--custom--weight--display);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--wp--custom--tracking--display);
	color: var(--wp--custom--color--heading);
	text-wrap: balance;
}
:root .section .punch__sub { margin: var(--wp--custom--space--lg) auto 0; }

/* ---------------------------------------------------------------------------
   11 · HOW WE HELP — U6, section 5. A trio of welled cards, each holding a
   drawing of the real product, plus a pull quote.
   ⛔ THE TWO BACKGROUND CURVE OBJECTS ARE NOT BUILT, AND THAT IS MEASURED,
      NOT AN OMISSION. The prototype carries two `<svg class="obj obj--help
      obj--cv">` here, and both compute `display: none`: the curve family is
      an unpicked experiment gated behind `data-obj="curve"`, and the
      prototype's own `<html>` sets no `data-obj` at all. Engrave is the
      default and it has no window in this section. Building inert markup
      would have looked like faithfulness and painted nothing.
   --------------------------------------------------------------------------- */

/* The icon. ONE rendered stroke width across the page: an icon drawn at a
   different size takes a compensating `stroke-width` so the painted line is
   always 1.000. Two stroke widths read as assembled. */
.ico {
	width: 20px;
	height: 20px;
	display: block;
	fill: none;
	stroke: currentColor;
	stroke-width: 1;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* The icon tile — the one repeated small object. */
.tile {
	width: 44px;
	height: 44px;
	display: grid;
	place-items: center;
	border-radius: var(--wp--custom--rounded--sm);
	background: var(--abk-panel);
	border: 0;
	box-shadow: var(--wp--custom--shadow--contact);
	color: var(--wp--custom--color--brand-text);
}
.on-ink .tile { background: var(--wp--custom--color--paper); color: var(--wp--custom--color--brand); }

/* ⛔ A GREEN GROUND UNDER GREY TEXT IS DECORATION, NOT MEANING. The chip's own
   text is `--secondary`, so its ground is the neutral tint. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; padding: 0; }
/* ⚠ `margin: 0` because core gives a LIST'S ITEMS the theme's block gap, and
   the chip row is a flex row with its own `gap`. Measured: each chip came out
   8px taller and the row 8px taller with it. */
:root .chips li,
.chips li {
	margin: 0;
	font-size: 12px;
	color: var(--wp--custom--color--secondary);
	padding: 4px 8px;
	border-radius: var(--wp--custom--rounded--sm);
	background: var(--wp--custom--color--tint);
}

/* ⛔ THE STEP NUMERAL GAVE UP ITS PILL. It is neither a badge nor a pill, and
   those are the only two components the law lets take the full radius -- and
   it sits outside every drawing root, so the drawn-chrome exemption does not
   cover it either. The Three-Radii Rule holds on this page with NO exception. */
.step__n {
	display: grid;
	place-items: center;
	width: 32px;
	height: 32px;
	border-radius: var(--wp--custom--rounded--sm);
	background: var(--wp--custom--color--ink);
	color: var(--wp--custom--color--ink-heading);
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	line-height: var(--wp--custom--leading--display);
	margin-bottom: var(--wp--custom--space--md);
}

/* -- 11a · THE WELLED CARD, AND ITS MEDIA-FIRST VARIATION.
   ⛔ `.f--media-first` IS A VARIATION OF `.f--welled`, NOT A SECOND CARD.
      Only the ORDER changes, so a comparison isolates one attribute. */
.f--welled { padding-bottom: 0; overflow: hidden; display: flex; flex-direction: column; }
:root .f--welled > :last-child { margin-top: auto; }
:root .f--welled > .step__n {
	/* ⭐ Inside a welled card the numeral is a LABEL, not a step in a sequence:
	   small, in the accent, on no ground at all. */
	width: auto; height: auto; padding: 0; background: none;
	color: var(--wp--custom--color--brand-text);
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--label);
	display: block;
	margin-bottom: var(--wp--custom--space--sm);
}
:root .f--welled > h3   { margin: 0; }
/* ⛔ `:not(.step__n)` BECAUSE THE NUMERAL IS A PARAGRAPH HERE AND A `<span>`
   in the prototype -- a block editor has no span-level block, so the label had
   to become a `<p>`. Without the exclusion it took this 8px and the whole
   card grew by it. ⭐ A structural difference forced by the editor, absorbed
   in the stylesheet rather than paid for in the paint. */
:root .f--welled > p:not(.step__n) { margin-top: var(--wp--custom--space--sm); }
/* ⚠ 16px UNDER THE CHIP ROW TOO. In the prototype that gap is the browser's
   own default bottom margin on a `<ul>`, which WordPress zeroes -- so it has
   to be stated here or the card closes up 16px tighter than the one Alpesh
   approved. */
:root .f--welled > .chips { margin-top: var(--wp--custom--space--md); margin-bottom: var(--wp--custom--space--md); }

/* ⭐ THE WELL IS THE OLIVE TINT AND THE DRAWING LIFTS OFF IT WITH A SHADOW.
   ⛔ THE ROUTE MATTERS MORE THAN THE ANSWER. The well began as the brand at
      8%, where the white product card separated at only 1.16 : 1. Five accent
      candidates were measured off the paint and terracotta won at 2.60 -- and
      it was STILL wrong, because a card-sized panel of terracotta is an
      ACCENT GROUND and the page rations those to two. ⭐ A SHADOW SEPARATES
      WITHOUT SPENDING ONE. Alpesh, 2026-09-16: "the whole background can be
      the same olive tint… for the product drawings we can add a bit of a drop
      shadow so that it lifts on the page."
   ⛔ AND THE HEIGHT IS FIXED AT 300px BY DESIGN. The wells started at
      different heights because their content differs, so the trio read as
      three unequal cards. A well is a CROP: the content fits the crop, never
      the reverse. */
.well {
	margin: var(--wp--custom--space--lg) calc(var(--wp--custom--space--lg) * -1) 0;
	padding: var(--wp--custom--space--lg);
	border-radius: 0;
	/* ⛔ --paper-raised, NOT --tint, AND ALPESH FOUND THE FAULT HIMSELF ON
	   2026-09-22: "the section background and the product drawings panel here
	   are the same tint." He was exactly right. The section is --tint #F9F9F6,
	   the card is --paper, and this well was --tint AGAIN -- so the OUTERMOST
	   and the INNERMOST surfaces were one colour and the one between them was
	   not. The well read as a hole punched back through to the page.
	   ⭐ He picked the fix off the canvas: "let's go with option 1:
	      paper-raised." #FDFDFA. Heading on it measures 19.22:1.
	   ⚠ THE THREE DRAWINGS BAKE THIS COLOUR into their mp4s, so changing it
	     here without re-exporting them would show as a rectangle. */
	background: var(--wp--custom--color--paper-raised);
	--fig:    var(--wp--custom--color--paper-raised);
	--fig-in: var(--abk-floor);
	display: grid;
	align-content: start;
	/* ⛔ THE FIXED 300px HEIGHT IS GONE, and the videos are why it can be.
	   It existed because the three wells held DIFFERENT DOM drawings and so
	   started at different heights, and the trio read as three unequal cards.
	   Three videos of one frame are equal by construction. ⚠ AND THE FIXED
	   HEIGHT WAS ITSELF A DEFECT: with overflow hidden, the drawings overflowed
	   it by 64px at 768 and by 137px of 321 at 390, so a third of card 2 was
	   sliced away on a phone. */
	min-height: 0;
	overflow: hidden;
	/* The inset hairline is what FUSES the well to the card's own edge. */
	box-shadow: inset 0 1px 0 var(--wp--custom--color--hairline), var(--wp--custom--shadow--edge);
}

/* ⛔ A SHORTER CROP IS STILL THE RULE; A TALLER ONE JUST BUYS BLANK TINT AT
   390. Measured on the prototype: 208px below 620px. */
@media (max-width: 620px) { .well { height: 208px; } }

.f--media-first { padding-top: 0; padding-bottom: var(--wp--custom--space--lg); }
:root .f--media-first .well {
	order: -1;
	/* ⭐ It bleeds TOP, left and right -- the mirror of the welled card's
	   bottom bleed. The card's own `overflow: hidden` clips it to the radius. */
	margin: 0 calc(var(--wp--custom--space--lg) * -1) var(--wp--custom--space--lg);
	box-shadow: inset 0 -1px 0 var(--wp--custom--color--hairline);
}
/* ⛔ A MEDIA-FIRST WELL CUTS THE DRAWING IN THE MIDDLE OF THE CARD, and a hard
   edge there reads as an error rather than as a crop. So the cut is faded.
   ⭐ The mask goes on the well's CONTENT, never on the well itself, or the
   ground fades out along with the drawing. */
.f--media-first .well > * {
	-webkit-mask-image: linear-gradient(to bottom, #000 80%, transparent 100%);
	        mask-image: linear-gradient(to bottom, #000 80%, transparent 100%);
}
/* ⛔ THE LIFT CANNOT GO ON `.well > *`. That element carries the fade mask,
   and a mask clips an element's own shadow -- the lift would vanish exactly
   where the drawing needs it. It goes on the PANEL one layer in. */
.f--media-first .well .appui__card,
.f--media-first .well .wgt { box-shadow: var(--wp--custom--shadow--depth); }

/* A well already bleeds, so a drawing inside it must not bleed again. */
.well .draw--bleed-b { margin: 0; mask-image: none; }
/* The crop must land in a GAP, never through a row. */
.well > .draw { display: grid; gap: var(--wp--custom--space--sm); align-content: start; }
.well .dnote  { display: none; }   /* the card's own copy already says it */
/* ⛔ The rows COMPACT so the lit thing fits inside a fixed crop. */
.well .drow   { padding: 8px 12px; }
.well .draw > .drow { padding: 6px 12px; }
.well .drow__m { font-size: 11px; }
/* ⛔ The real constraint was the 44px TILE, not the padding: four rows of 44
   plus padding and gap came to 304px against a 300px crop. Inside a drawing
   the tile is a drawn mark rather than a control, so the 44px floor for a
   real control does not reach it. */
.well .tile      { width: 28px; height: 28px; }
.well .tile .ico { width: 15px; height: 15px; stroke-width: 1.3333; }
.well .dfield--lit,
.well .drow.lit  { background: var(--wp--custom--color--tint-brand); }

/* -- 11b · THE APP SCREEN. A drawing of the product's own admin.
   ⛔ AN APP SCREEN IN A CARD-SIZED WELL MUST BE CROPPED, NOT SQUEEZED. At
      290px the sidebar plus the card compressed until "Web Sources" wrapped
      one word per line, which reads as broken. So the drawing is authored at
      its NATURAL width and the well clips it -- and the card is the argued
      thing, so the sidebar is what drops at this size. */
.appui { display: grid; grid-template-columns: 168px 1fr; min-width: 0; background: var(--abk-floor); }
.appui__side { padding: var(--wp--custom--space--md) 0; background: var(--wp--custom--color--tint); min-width: 0; }
.appui__brand { display: flex; align-items: center; gap: var(--wp--custom--space--sm); padding: 0 var(--wp--custom--space--md) var(--wp--custom--space--md); }
.appui__logo { flex: none; width: 24px; height: 24px; border-radius: var(--wp--custom--rounded--sm); background: var(--wp--custom--color--brand); display: grid; place-items: center; }
.appui__logo .ico { width: 14px; height: 14px; stroke: var(--wp--custom--color--paper); stroke-width: 1.4286; }
.appui__brand b { font-size: var(--wp--custom--step--label); font-weight: var(--wp--custom--weight--emphasis); letter-spacing: var(--wp--custom--tracking--text); line-height: var(--wp--custom--leading--body); }
.appui__group { padding: var(--wp--custom--space--md) var(--wp--custom--space--md) 4px; font-size: 10px; font-weight: var(--wp--custom--weight--emphasis); letter-spacing: var(--wp--custom--tracking--label); color: var(--wp--custom--color--secondary); margin: 0; }
.appui__nav { list-style: none; margin: 0; padding: 0; }
.appui__nav li { display: flex; align-items: center; gap: var(--wp--custom--space--sm); padding: 7px var(--wp--custom--space--md); font-size: var(--wp--custom--step--label); letter-spacing: var(--wp--custom--tracking--text); line-height: var(--wp--custom--leading--body); color: var(--wp--custom--color--secondary); }
.appui__nav .ico { width: 14px; height: 14px; flex: none; stroke-width: 1.4286; }
/* ⭐ The real active state: a brand-tinted wash, a brand icon, ink text. */
.appui__nav li[aria-current] { background: var(--wp--custom--color--tint-brand); color: var(--wp--custom--color--heading); font-weight: var(--wp--custom--weight--emphasis); }
.appui__nav li[aria-current] .ico { stroke: var(--wp--custom--color--brand); }
.appui__main { padding: var(--wp--custom--space--md); min-width: 0; }
.appui__card { background: var(--abk-floor); border-radius: var(--wp--custom--rounded--md); box-shadow: var(--wp--custom--shadow--edge); overflow: hidden; }
.appui__head { display: flex; align-items: flex-start; gap: var(--wp--custom--space--md); padding: var(--wp--custom--space--md); box-shadow: inset 0 -1px 0 var(--wp--custom--color--hairline); }
:root .appui__head h4 { margin: 0; font-size: var(--wp--custom--step--body); font-weight: var(--wp--custom--weight--emphasis); letter-spacing: var(--wp--custom--tracking--text); line-height: var(--wp--custom--leading--body); }
:root .appui__head p  { margin: 2px 0 0; font-size: var(--wp--custom--step--label); letter-spacing: var(--wp--custom--tracking--text); line-height: var(--wp--custom--leading--body); color: var(--wp--custom--color--secondary); }
/* ⛔ THE APP'S HIGH-EMPHASIS FILL IS INK, AND GREEN IS THE SCARCE ACCENT.
   A drawing that paints every button green is drawing a different product. */
.appui__btn { margin-left: auto; flex: none; min-height: 30px; padding: 0 12px; border-radius: var(--wp--custom--rounded--sm); background: var(--wp--custom--color--ink); color: var(--wp--custom--color--ink-heading); font-size: var(--wp--custom--step--label); font-weight: var(--wp--custom--weight--emphasis); letter-spacing: var(--wp--custom--tracking--text); display: inline-flex; align-items: center; gap: 6px; }
.appui__btn--ghost { background: var(--abk-floor); color: var(--wp--custom--color--heading); box-shadow: var(--wp--custom--shadow--edge); }
.appui__body { padding: var(--wp--custom--space--md); display: grid; gap: var(--wp--custom--space--sm); }

/* ⛔ THE PANEL WAS AUTHORED AT 420px INSIDE A 368px COLUMN and pushed 28px
   past the well's right edge -- and every button in this drawing is flush to
   that edge, so the crop landed INSIDE each one. Six controls were cut, each
   losing 12px of itself. ⭐ AND THE CUT WAS HIDING THE ARGUMENT: the row
   labels are "Synced" and "Updated", and the crop ate the last letters of
   each, turning a finished state into a pending action in a drawing whose
   whole point is that the answer has ALREADY updated. A frame crops at its
   edge, never through a control. */
.well .appui { grid-template-columns: 1fr; mask-image: linear-gradient(to right, #000 84%, transparent 100%); }
.well .appui__side { display: none; }
.well .appui__main { padding: 0; }
.well .appui__card { width: 100%; max-width: none; }
.well .appui__head { padding: 12px var(--wp--custom--space--md); }
.well .appui__body { padding: 12px var(--wp--custom--space--md) var(--wp--custom--space--md); }
@media (max-width: 720px) {
	.appui { grid-template-columns: 1fr; }
	.appui__side { display: none; }
}

/* -- 11c · THE CHAT WIDGET, DRAWN. ⛔ The page runs ONE widget vocabulary:
   two treatments of one object on one page reads as assembled. */
.wgt {
	width: 100%;
	max-width: 380px;
	background: var(--abk-floor);
	border-radius: var(--wp--custom--rounded--md);
	overflow: hidden;
	box-shadow: var(--wp--custom--shadow--edge);
	font-size: 14px;
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--text);
}
/* ⛔ The reference's header is a green GRADIENT; our law refuses gradients,
   and Alpesh chose white over a flat green, so the widget reads as OURS. */
.wgt__bar { display: flex; align-items: center; gap: 8px; padding: 12px var(--wp--custom--space--md); background: var(--abk-floor); color: var(--wp--custom--color--heading); box-shadow: inset 0 -1px 0 var(--wp--custom--color--hairline); font-size: var(--wp--custom--step--body); font-weight: var(--wp--custom--weight--emphasis); letter-spacing: var(--wp--custom--tracking--text); }
.wgt__live { flex: none; width: 7px; height: 7px; border-radius: var(--wp--custom--rounded--full); background: var(--wp--custom--color--brand); }
.wgt__log { padding: var(--wp--custom--space--md); display: grid; gap: 10px; }
.wgt__msg { padding: 9px 12px; border-radius: var(--wp--custom--rounded--md); max-width: 84%; margin: 0; }
.wgt__msg--bot  { background: var(--wp--custom--color--tint); color: var(--wp--custom--color--heading); justify-self: start; border-bottom-left-radius: var(--wp--custom--rounded--sm); }
.wgt__msg--user { background: var(--wp--custom--color--brand); color: var(--wp--custom--color--paper); justify-self: end; border-bottom-right-radius: var(--wp--custom--rounded--sm); }
.wgt--fade .wgt__log {
	max-height: 250px;
	overflow: hidden;
	-webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
	        mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
}
/* ⭐ THE PROGRESS TRACK, and it replaced a green "live" dot. The dot claimed an
   ONLINE STATE on a scripted replay -- it said "someone is here", which is not
   true of a walk-through. This says what is actually true: it is a recording,
   and it is this far in. ⚠ It sits on the bar's bottom edge so it reads as the
   header's own underline until it moves. */
.wgt__bar { position: relative; }
.wgt__prog {
	position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
	overflow: hidden;
}
.wgt__prog i {
	display: block; height: 100%; width: 100%;
	background: var(--wp--custom--color--brand);
	transform: scaleX( 0 ); transform-origin: 0 50%;
}
/* ⛔ THE FILL IS AN ANIMATION AND THE PLAYER SETS ONLY ITS DURATION. Driving a
   width from a timer would be one setTimeout per frame for 13 seconds; one
   linear animation is the compositor's job, and its duration is the scene's
   own length so the bar cannot disagree with what is playing. */
.wgt__prog.is-running i { animation: abk-prog var( --abk-scene, 10s ) linear both; }
@keyframes abk-prog { from { transform: scaleX( 0 ); } to { transform: scaleX( 1 ); } }

/* ⭐ EVERY LINE RISES INTO PLACE, AND THE CURVE IS THE LIVE PAGE'S OWN. Alpesh:
   "they just appear directly there, and it is sort of hard to read... can we
   give it an animation where it pops up from the bottom to the top, so someone
   who is watching can follow the user question coming instead of it popping up
   directly there out of nowhere." The live page's `abkin` is
   `.32s cubic-bezier(.34,1.3,.64,1)` from `opacity:0, translateY(6px)`; the
   control point past 1 is the small overshoot that makes it read as arriving
   rather than fading. Copied rather than re-invented. */
@keyframes abk-msg-in {
	from { opacity: 0; transform: translateY( 6px ); }
	to   { opacity: 1; transform: none; }
}
.wgt--play .wgt__log > *:not( [ hidden ] ) {
	animation: abk-msg-in .32s cubic-bezier( .34, 1.3, .64, 1 ) both;
}

/* ⛔ AND `hidden` HAS TO BE RE-ASSERTED, WHICH COST A ROUND. The player hides
   a row with `el.hidden = true`, and the browser's own `[hidden]{display:none}`
   lives in the USER-AGENT sheet -- so ANY author rule that sets `display` beats
   it. Every row type below sets one: the dots are `inline-flex`, the agent
   bubble is `flex`, the lead card is `grid`. The result painted five sets of
   typing dots stacked in one window while the JavaScript believed it had hidden
   them all. ⭐ (0,2,0) here outranks the (0,1,0) row rules and costs one line. */
.wgt__log > [ hidden ] { display: none; }

/* -- 11c-i · THE PLAYED CONVERSATION. Five row types the static widget never
   needed: the scene avatar in the bar, the typing dots, a human colleague's
   message, the "Sarah joined" system row and the captured-lead card. They are
   scoped to `.wgt--play` so the page's other widgets keep the plain vocabulary
   -- the page runs ONE widget, and this is one widget with more to say. */
.wgt__av {
	flex: none; width: 26px; height: 26px; border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--brand); color: var(--wp--custom--color--paper);
	display: grid; place-items: center;
	font-size: 12px; font-weight: var(--wp--custom--weight--emphasis);
}
.wgt__who { flex: 1 1 auto; }
/* ⚠ the live dot moves to the END of the bar once an avatar leads it, so the
   bar reads name-then-state rather than two markers competing on the left. */
.wgt--play .wgt__live { margin-left: auto; }

/* ⛔ THE DOTS ARE THE ONLY THING REDUCED MOTION REMOVES OUTRIGHT, because they
   are pure motion and carry no words. Everything else is revealed. */
.wgt__typ {
	justify-self: start; display: inline-flex; gap: 4px; align-items: center;
	padding: 11px 13px; border-radius: var(--wp--custom--rounded--md);
	border-bottom-left-radius: var(--wp--custom--rounded--sm);
	background: var(--wp--custom--color--tint);
}
.wgt__typ i {
	width: 4px; height: 4px; border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--secondary);
	animation: abk-dot 1.3s ease-in-out infinite;
}
.wgt__typ i:nth-child( 2 ) { animation-delay: .15s; }
.wgt__typ i:nth-child( 3 ) { animation-delay: .3s; }
@keyframes abk-dot {
	0%, 60%, 100% { opacity: .35; transform: translateY( 0 ); }
	30%           { opacity: 1;   transform: translateY( -2px ); }
}

/* the colleague. ⛔ Their bubble is NOT the bot's: a human taking the thread
   over is the section's whole argument, so it has to be visibly a third voice
   rather than the assistant in a different mood. */
.wgt__msg--agent {
	justify-self: start; background: var(--abk-floor);
	color: var(--wp--custom--color--heading);
	box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline);
	border-bottom-left-radius: var(--wp--custom--rounded--sm);
	/* ⚠ flex-START, not flex-end. Bottom-aligned, the avatar sank to the last
	   line of a two-line message and the face appeared to belong to the wrong
	   sentence. A speaker's mark belongs beside the line they START. */
	display: flex; align-items: flex-start; gap: 8px;
}
.wgt__agentav {
	flex: none; width: 20px; height: 20px; border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--a-terra-dark-2); color: var(--wp--custom--color--paper);
	display: grid; place-items: center; font-size: 10px;
	font-weight: var(--wp--custom--weight--emphasis);
	/* ⚠ centred on the FIRST LINE BOX, not on its top edge. The line box is
	   14px x 1.55 = 21.7px tall and the disc is 20px, so it drops the 0.85px
	   difference to sit on the line's optical centre rather than its ascender. */
	margin-top: .85px;
}
/* the system row -- centred, quiet, and not a message */
.wgt__join {
	justify-self: center; margin: 2px 0; padding: 4px 11px;
	border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--tint);
	color: var(--wp--custom--color--secondary);
	font-size: 11.5px;
}
/* the captured lead. ⛔ It carries its own "demo state" label, because a bare
   card claiming an outcome nothing was sent for is a claim about an outcome. */
.wgt__lead {
	justify-self: start; width: 90%; display: grid; gap: 4px;
	padding: 10px 12px; border-radius: var(--wp--custom--rounded--md);
	background: var(--wp--custom--color--tint-brand);
	color: var(--wp--custom--color--brand-text);
}
.wgt__lead p { margin: 0; font-size: 11.5px; }
.wgt__lead-t {
	font-weight: var(--wp--custom--weight--emphasis);
	display: flex; align-items: center; justify-content: space-between;
	gap: var(--wp--custom--space--sm);
}
.wgt__lead-r { color: var(--wp--custom--color--heading); }
.wgt__lead-r b {
	display: inline-block; min-width: 58px;
	color: var(--wp--custom--color--secondary);
	font-weight: var(--wp--custom--weight--body);
}

.wgt__state { margin: 0 var(--wp--custom--space--md) var(--wp--custom--space--md); padding: 8px 12px; border-radius: var(--wp--custom--rounded--sm); background: var(--wp--custom--color--tint-brand); color: var(--wp--custom--color--brand-text); font-size: 12px; font-weight: var(--wp--custom--weight--emphasis); display: flex; align-items: center; justify-content: space-between; gap: var(--wp--custom--space--sm); }
.wgt__demotag { font-size: 11px; font-weight: var(--wp--custom--weight--body); color: var(--wp--custom--color--secondary); }
.wgt__foot { display: flex; align-items: center; gap: 8px; margin: 0 var(--wp--custom--space--md) var(--wp--custom--space--md); padding: 7px 7px 7px 14px; border-radius: var(--wp--custom--rounded--full); box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline); color: var(--wp--custom--color--secondary); }
.wgt__foot > span:first-child { flex: 1 1 auto; }
.wgt__send { flex: none; width: 32px; height: 32px; border-radius: var(--wp--custom--rounded--full); background: var(--wp--custom--color--brand); display: grid; place-items: center; }
.wgt__send .ico { width: 15px; height: 15px; color: var(--wp--custom--color--paper); stroke-width: 1.3333; }
/* ⛔ SCOPE A STROKE RECIPROCAL BY THE viewBox, NEVER BY THE PARENT. `.wgt__send`
   holds two different marks: demo-stage uses `<use href="#i-send">`, whose
   symbol is 20 units, and industry-sim inlines a 12-unit path. One reciprocal
   cannot serve both -- 1.3333 x (15/20) paints 1.000, and 1.3333 x (15/12)
   paints 1.667. Measured on the live page: one icon at 1.667 against the law's
   single rendered width. 0.8 x (15/12) = 1.000. SVC-15. */
.wgt__send .ico[viewBox="0 0 12 12"] { stroke-width: 0.8; }

/* -- 11d · THE PULL QUOTE. ⛔ NOT A CARD. It was a 680px white panel with a
   1px ring, so it read as a stray FOURTH card under a row of three.
   ⚠ AND IT IS NOT A DUPLICATE OF THE TESTIMONIAL BELOW. The live page carries
   this person TWICE on purpose: here with no box at all, and again in the
   testimonial grid with a rating. */
/* The prototype sets these two gaps with an inline style. A class is the same
   value in a place a checker can read. */
:root .section .fgrid--roomy { margin-top: var(--wp--custom--space--3-xl); }
:root .section .pull         { margin-top: var(--wp--custom--space--3-xl); }

.pull { text-align: center; max-width: 900px; margin-inline: auto; }
:root .section .pull__body {
	margin: 0;
	font-size: var(--wp--custom--step--display-small);
	font-weight: var(--wp--custom--weight--display);
	letter-spacing: var(--abk-track-dsm);
	line-height: var(--wp--custom--leading--display);
	color: var(--wp--custom--color--heading);
	text-wrap: balance;
}
:root .section .pull__by {
	display: flex; align-items: center; justify-content: center;
	gap: var(--wp--custom--space--md);
	margin-top: var(--wp--custom--space--xl);
	text-align: initial;
}
/* ⛔ A ROUNDED SQUARE, NOT A CIRCLE. A round avatar paints the pill radius,
   which only a badge and a pill may take -- and the step numeral gave up its
   pill for that exact reason, so an exception here would undo that. */
.quote__face { width: 40px; height: 40px; flex: none; border-radius: var(--wp--custom--rounded--sm); object-fit: cover; box-shadow: var(--wp--custom--shadow--edge); }
.quote__who { display: grid; }
.quote__who b { font-weight: var(--wp--custom--weight--emphasis); color: var(--wp--custom--color--heading); }
.quote__who > span { font-size: var(--wp--custom--step--body-small); font-weight: var(--wp--custom--weight--body); letter-spacing: var(--wp--custom--tracking--text); color: var(--wp--custom--color--secondary); }

/* ---------------------------------------------------------------------------
   12 · THE TESTIMONIALS — U6, section 6. Four real customers, each quote
   fused to the person who gave it.
   ⛔ A NAMED QUOTE AND ITS OUTCOME ARE ONE PHYSICAL UNIT. The same two
      elements 200px apart read as a quote and an unsupported number.
   --------------------------------------------------------------------------- */
.quote { display: grid; }
:root .section .quote__body { padding: var(--wp--custom--space--lg); margin: 0; }
.quote__outcome {
	/* FUSED to the bottom edge -- no gap, and NO GROUND OF ITS OWN. Two
	   earlier answers were both wrong and the second more interestingly: a 5%
	   ink tint of the card's own ground is correct in theory, because it
	   darkens what is behind it rather than naming a colour -- but on a card
	   that is already a gradient it reads as a SECOND gradient, a band with
	   its own edge across the bottom third. ⭐ The photograph and the name are
	   what separate the strip; the card's own wash runs through it. */
	margin: 0;
	padding: var(--wp--custom--space--md) var(--wp--custom--space--lg);
	border-radius: 0 0 var(--wp--custom--rounded--md) var(--wp--custom--rounded--md);
	font-weight: var(--wp--custom--weight--emphasis);
	display: flex;
	align-items: center;
	gap: var(--wp--custom--space--md);
}

/* ⭐ THE RATING SHIPS, AND IT NARROWS A REFUSAL RATHER THAN BREAKING ONE.
   The Don't list read "fabricated proof -- an outcome, a figure, a logo, a
   RATING or a testimonial. Ever." It now reads "a rating THE LIVE PAGE DOES
   NOT ALREADY MAKE". The live page makes this one, on all four cards.
   ⛔ THE STAR IS A FILL, NOT A STROKE, so the one-rendered-stroke-width rule
      does not reach it -- and the warm accent is legal exactly this way: it is
      refused as TEXT or as a mark on a light ground, and permitted as a FILL. */
.quote__rating { display: flex; gap: 3px; padding: var(--wp--custom--space--lg) var(--wp--custom--space--lg) 0; }
/* ⛔ THE STAR IS OUTLINED, AND THE OUTLINE IS WHAT MAKES IT LEGAL. 2026-09-23.
   ⚠ `fill: --warm` WITH `stroke: none` BROKE THE LIT-WINDOW RULE, which refuses
     a warm stop as a MARK on a light ground by name. Measured off the paint:
     #FDC830 reads 1.53 on the lime card and 1.38 on the terracotta one --
     under even the 3:1 floor a graphical object needs.
   ⛔ AND check-law.js COULD NOT SEE IT. The colour is an SVG `fill`, not text,
      so a text-contrast assertion walks straight past 20 star marks. Same
      blind spot as gotcha 68, one layer over.
   ⭐ ALPESH KEPT THE YELLOW: "the stars color needs to be yellow". A darkened
      fill was painted at oklch(0.600 0.164 87.6) -- it clears 3.87/3.50 and
      reads as ochre, and he chose the outline instead. The bright fill stays;
      the dark boundary does the contrast work.
   ⭐ THE COLOUR IS MIXED FROM `--warm`, NOT DECLARED. A token for one use would
      grow the palette for nothing, and a literal hex here would drift from the
      stop it is derived from. 65% lands at #9E7F29: lime 3.73, terracotta
      3.38, paper 3.80 -- all measured. ⚠ 70% was tried first and fails the
      terracotta card at 2.94, which is the binding ground, not paper.
   ⭐ IT ALSO MOVES THE STAR TOWARD THE ICON SYSTEM. Every other mark on this
      page is stroked and unfilled; this was the only solid one. */
.quote__rating .star {
	width: 16px; height: 16px; flex: none;
	fill: var(--wp--custom--color--warm);
	stroke: color-mix( in oklab, var(--wp--custom--color--warm) 65%, var(--wp--custom--color--heading) );
	stroke-width: 1.2;
	stroke-linejoin: round;
	paint-order: stroke fill;
}
:root .section .quote__rating + .quote__body { padding-top: var(--wp--custom--space--sm); }

/* -- 12a · THE DRIFT. ⭐ DEPTH FROM POSITION, NOT FROM SHADOW: four cards at
   different vertical offsets. Set every box-shadow to none and the depth
   survives, which is the test that it is real.
   ⛔ THE HORIZONTAL OVERLAP WAS REMOVED. Each card carried `margin-left:
      -120px`, which covered 104px of the card before it and ate the opening
      words of three of the four quotes -- and it made card 1 narrower than
      the others, because a negative margin makes a grid item WIDER than its
      track rather than moving it. ⭐ Occlusion reads as depth only when the
      covered thing is a SURFACE. Never over running text, a quote, a heading
      or a name. The VERTICAL drift stays: it occludes nothing. */
.drift { display: grid; gap: var(--wp--custom--space--md); }
:root .section .drift { margin-top: var(--wp--custom--space--2-xl); }
:root .drift > * { margin: 0; position: relative; }

@media (min-width: 900px) {
	.drift { grid-template-columns: repeat(4, 1fr); align-items: start; }
	:root .drift > *:nth-child(1) { margin: 0;                              z-index: 4; }
	:root .drift > *:nth-child(2) { margin: var(--wp--custom--space--2-xl) 0 0 0; z-index: 3; }
	:root .drift > *:nth-child(3) { margin: var(--wp--custom--space--lg)  0 0 0;  z-index: 2; }
	:root .drift > *:nth-child(4) { margin: var(--wp--custom--space--3-xl) 0 0 0; z-index: 1; }
}

/* ⛔ AT 390 THE FOUR CARDS WERE A COLUMN ~1,600px DEEP, for a device whose
   whole point at 1440 is the stagger. A scrolled row keeps all four, keeps
   the cut edge, and costs one screen instead of four.
   ⚠ THE FADE IS TRAILING-ONLY. A leading fade ate the first character of
   every row. */
@media (max-width: 620px) {
	.drift {
		display: flex;
		gap: var(--wp--custom--space--md);
		margin-right: calc(var(--wp--custom--width--gutter) * -1 - var(--wp--custom--space--panel-inset));
		overflow-x: auto;
		scrollbar-width: none;
		mask-image: linear-gradient(to right, #000 0%, #000 90%, transparent 100%);
	}
	.drift::-webkit-scrollbar { display: none; }
	:root .drift > * { flex: 0 0 82%; margin: 0; }
}

/* ⭐ THE WASH RUNS VERTICALLY, PALEST AT THE TOP, BEHIND THE STARS.
   Alpesh: "the lighter version at top behind the stars."
   ⛔ SO THE CARD COMPOSES ITS OWN DIRECTION FROM THE TWO RUNGS RATHER THAN
      USING THE `g-lime-light` GRADIENT TOKEN. That token runs at 100deg --
      nearly horizontal -- which puts the pale end at one SIDE, so the stars
      sit on the deeper end and the card has no top. A token is law and is
      spent elsewhere; one card's composition is not a reason to move it.
   ⚠ A GRADIENT SETS `background-image` AND A FLAT COLOUR SETS
     `background-color`, so the colour must be CLEARED or it paints on top.
   ⚠ Ink and grey both survive on the deeper stops -- 17.79 and 15.33 -- and
     both washes travel toward white, so the pale end is safer still. */
:root .section--proof .quote {
	background-color: transparent;
	background-image: linear-gradient(to bottom, var(--wp--custom--color--a-lime-light-2), var(--wp--custom--color--a-lime-light));
	padding: 0;
}
:root .section--proof .quote:nth-child(even) {
	background-image: linear-gradient(to bottom, var(--wp--custom--color--a-terra-light-2), var(--wp--custom--color--a-terra-light));
}

/* ---------------------------------------------------------------------------
   13 · THE FOUR TERM/DEFINITION SECTIONS — U6, section 7.
   ⭐ ONE COMPONENT, FOUR USES. Lead capture, customisation, WordPress and
   multi-channel are the same shape: a heading, a lead, a definition list, and
   a drawing that alternates sides down the page. ⛔ THAT IS THE REUSE TEST --
   if each needed its own CSS the vocabulary has failed.
   --------------------------------------------------------------------------- */

/* ⛔ THE 2px HAIRLINE ON EVERY ROW CAME OFF — thirteen of them, and a
   side-stripe border is refused however faint it is. A rule that turns on
   width and colour is a rule the next page argues past.
   ⭐ PAID FOR WITH ALIGNMENT, AND IT IS MEASURABLE: with the stripe the row
      text sat at x=98 while its own h2 sat at x=80. The list now starts on
      the same column as the heading it belongs to. ⚠ The bold lead-in was
      already marking each row; the stripe was marking it a second time. */
.dl {
	list-style: none;
	padding: 0;
	display: grid;
	gap: var(--wp--custom--space--md);
	max-width: var(--wp--custom--width--prose);
}
.section .dl { margin: var(--wp--custom--space--xl) 0 0; }
:root .dl > li { padding-left: 0; margin: 0; }
.dl b    { font-weight: var(--wp--custom--weight--emphasis); letter-spacing: var(--wp--custom--tracking--text); }
.dl span { color: var(--wp--custom--color--secondary); letter-spacing: var(--wp--custom--tracking--text); }

/* -- 13a · THE COMPOSED ILLUSTRATION. A pixel stage holding two real product
   fragments, one at full size and one cut by the frame's own edge. */
.illus {
	position: relative;
	isolation: isolate;
	height: var(--stage, 400px);
	background: var(--abk-floor);
	border-radius: var(--wp--custom--rounded--lg);
	box-shadow: var(--wp--custom--shadow--depth);
	overflow: hidden;
}
.illus__back, .illus__front { position: absolute; }
.illus__back  { z-index: 1; }
.illus__front { z-index: 2; }
/* ⛔ A PIXEL STAGE CANNOT SURVIVE A PHONE. At 390 the absolute fragments
   spilled 116px past the frame and the section's own clip cut them at an
   arbitrary line. ⭐ So below 700 the stage STOPS being a stage: the
   fragments go back into the flow, stacked, with one small overlap kept. */
@media (max-width: 700px) {
	.illus { height: auto; display: grid; }
	.illus__back,
	.illus__front {
		position: relative;
		left: auto !important;
		top: auto !important;
		width: auto !important;
		max-width: 100%;
	}
	.illus__back  { margin: 20px 0 0 16px; }
	.illus__front { margin: -22px 16px 20px 0; max-width: 320px; }
}

/* -- 13b · FRAGMENT · THE LEADS PANEL. */
.lp {
	background: var(--abk-floor);
	border-radius: var(--wp--custom--rounded--md);
	box-shadow: var(--wp--custom--shadow--edge);
	overflow: hidden;
	font-size: 12px;
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--text);
}
.lp__tabs { display: flex; align-items: center; gap: 6px; padding: 10px 12px; box-shadow: inset 0 -1px 0 var(--wp--custom--color--hairline); }
.lp__tab  { padding: 4px 10px; border-radius: var(--wp--custom--rounded--sm); color: var(--wp--custom--color--secondary); }
.lp__tab.is-on { background: var(--wp--custom--color--tint-brand); color: var(--wp--custom--color--brand-text); font-weight: var(--wp--custom--weight--emphasis); }
.lp__export { margin-left: auto; display: flex; align-items: center; gap: 5px; padding: 4px 10px; border-radius: var(--wp--custom--rounded--sm); box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline); color: var(--wp--custom--color--body); }
.lp__export .ico { width: 13px; height: 13px; stroke-width: 1.5385; }
.lp__row { display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; padding: 10px 12px; box-shadow: inset 0 -1px 0 var(--wp--custom--color--hairline); }
.lp__row b    { font-weight: var(--wp--custom--weight--emphasis); color: var(--wp--custom--color--heading); }
.lp__row span { color: var(--wp--custom--color--secondary); }
.lp__row time { grid-row: 1; grid-column: 2; color: var(--wp--custom--color--secondary); font-size: 11px; }
/* ⚠ A SIDE STRIPE, AND IT IS THE ONE THAT SURVIVES. The law refuses a stripe
   by name and thirteen came off this page -- but this one sits inside `.draw`,
   so the drawn-chrome exemption covers it, and it is this drawing's "exactly
   one thing lit" device rather than decoration. */
.lp__row--lit { background: var(--wp--custom--color--tint-brand); box-shadow: inset 0 -1px 0 var(--wp--custom--color--hairline), inset 3px 0 0 var(--wp--custom--color--brand); }

/* The colour-picker fragment is a padded stack rather than a row list, so the
   panel takes its own padding. ⚠ The prototype sets this inline; a class is
   the same value somewhere a checker can read it. */
.lp--pad { padding: 14px 16px 16px; display: grid; gap: 9px; }
/* The drawn field label, shared with the form fragments. */
.dfield__k { display: block; font-size: var(--wp--custom--step--label); letter-spacing: var(--wp--custom--tracking--text); color: var(--wp--custom--color--secondary); line-height: var(--wp--custom--leading--body); }

/* -- 13c · FRAGMENT · THE IN-CHAT CONTACT FORM the lead-capture toggle
   switches on. ⛔ NOT a real form -- it must not be focusable, so every part
   is a `<div>`, `<span>` or `<i>` and the 44px control floor never applies. */
.wgt__form { justify-self: start; width: 84%; display: grid; gap: 7px; padding: 11px 12px; background: var(--wp--custom--color--tint); border-radius: var(--wp--custom--rounded--md); border-bottom-left-radius: var(--wp--custom--rounded--sm); }
.wgt__form label { display: grid; gap: 3px; font-size: 11px; color: var(--wp--custom--color--secondary); }
.wgt__form i { font-style: normal; display: block; padding: 6px 9px; background: var(--abk-floor); border-radius: var(--wp--custom--rounded--sm); box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline); color: var(--wp--custom--color--heading); font-size: 12px; }
.wgt__formbtn { margin-top: 1px; justify-self: start; padding: 6px 14px; border-radius: var(--wp--custom--rounded--full); background: var(--wp--custom--color--brand); color: var(--wp--custom--color--paper); font-size: 12px; font-weight: var(--wp--custom--weight--emphasis); }

/* -- 13d · FRAGMENT · THE PRIMARY-COLOUR ROW, exactly as the product draws it.
   ⛔ THE FIRST SWATCH TAKES THE TOKEN AND THE OTHER FOUR STAY LITERAL, and
      that split is the whole point. The first is OUR green, so hard-coding it
      would make the one swatch meant to show "our green" the only green on
      the page that did not match. The other four are the CUSTOMER'S colours
      in a drawing of a colour picker, and a drawing of that MUST show colours
      that are not ours. */
.sw      { display: flex; align-items: center; gap: 9px; }
.sw__dot { width: 26px; height: 26px; border-radius: var(--wp--custom--rounded--full); }
.sw__dot--on { box-shadow: 0 0 0 3px var(--abk-floor), 0 0 0 5px currentColor; }
.sw__add { width: 26px; height: 26px; border-radius: var(--wp--custom--rounded--full); display: grid; place-items: center; color: var(--wp--custom--color--secondary); box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline); font-size: 14px; }

/* ⭐ ONE VARIABLE DRIVES THE WIDGET'S BRAND, exactly as the product does.
   ⛔ An earlier version set the drawing's HEADER to the customer's colour and
      left the user bubble OUR green -- so the drawing argued the opposite of
      the section it sits in. ⚠ A customer's colour is not ours, and that is
      the point of the section. */
.wgt[data-brand] .wgt__live,
.wgt[data-brand] .wgt__msg--user,
.wgt[data-brand] .wgt__formbtn,
.wgt[data-brand] .wgt__send { background: var(--cb); }
.wgt[data-brand="violet"] { --cb: #6D28D9; }
.wgt[data-brand="blue"]   { --cb: #1D4ED8; }

/* ⛔ A HIGHLIGHTER STRIPE HAS NO RADIUS, so 0 is in the set AND is the better
   drawing -- 8px on a 13px inline span reads as a pill fragment. ⚠ And it is
   a FLAT wash, not the gradient: a highlighter with a direction would read as
   a fade, which is the one thing a highlighter is not. */
.wgt__mark { background: var(--wp--custom--color--a-terra-light); border-radius: 0; padding: 0 2px; box-decoration-break: clone; }

/* -- 13c2 · AN EXPORTED DRAWING.
   ⭐ THE ASSET CARRIES ITS OWN SHADOW inside its alpha channel, so this rule
   adds NO radius, NO shadow and NO ground -- the same refusal `.draw` makes.
   ⛔ AND IT NEVER REFLOWS. Alpesh, 2026-09-21: "we should keep the product
   drawing fixed -- not changing for mobile and desktop version". It scales as
   ONE piece; there is no second layout to get wrong. */
/* ⛔ AND THE FIGURE'S OWN PADDING IS THE OUTER WHITE BAND Alpesh saw. 32px on
   every side of a 546x382 asset that already carries its own shadow is more
   breathing room than a bordered card would need. 20px returns 24px of width
   and 24px of height to the drawing. */
.figure--field { padding: 20px; }

.dimg {
	display: block;
	width: 100%;
	max-width: 546px;       /* its painted size; a raster is never upscaled */
	height: auto;
	margin-inline: auto;

	/* ⭐ THE LIFT, and it is a FILTER rather than a box-shadow ON PURPOSE.
	   `drop-shadow` follows the image's ALPHA CHANNEL, so it casts a shadow in
	   the SHAPE of the drawing instead of around its rectangle -- and it paints
	   OUTSIDE the image box, so it needs no margin inside the frame and no
	   re-export when the value is tuned.

	   ⚠ THE VALUE IS MEASURED, AND ALPESH TIGHTENED IT TWICE.
	   Round 1, he rejected four candidates: "too much drop shadow and spread.
	   That doesn't look like what Arcade has." ⛔ The fault was the DECAY, not
	   the density -- Arcade falls to a nineteenth of its strength over 32px
	   while those fell to between a half and a quarter. A soft wide cloud where
	   Arcade has a tight edge.
	   Round 2, 2026-09-21: "I would like to go with the lighter one, T2, and
	   even maybe we can make it tighter... Spread is a bad thing." ⭐ SO THIS IS
	   CRISPER THAN T2 AND DEAD BY 8px:
	     below the card edge   1px     2px     4px     8px    12px
	     T2                   16.1%   13.7%    9.0%    3.5%    1.2%
	     THIS                 20.8%   15.7%    7.1%    0.8%    0.0%
	   Candidates and curves: docs/research/prototypes/lift/ */
	filter: drop-shadow( 0 2px 3px oklch( 0.153 0.006 107.1 / 0.19 ) )
		drop-shadow( 0 1px 1px oklch( 0.153 0.006 107.1 / 0.14 ) );
}

/* ⛔ NO SHADOW ON A CUT EDGE. Alpesh, 2026-09-21: "take the leads product. We
   have the chat widget cut at the top, right? It is like it expands there, so
   at the top edge of the chat widget, we don't need the drop shadow. It should
   be at the sides and at the bottom only."
   ⭐ HE IS RIGHT AND IT IS A REAL FLAW IN WHAT I SHIPPED. `drop-shadow` follows
   the WHOLE silhouette, so an edge that runs off the frame -- an edge that is
   meant to continue -- was getting a shadow that says "this is where it ends".
   ⚠ WHICH EDGE IS CUT WAS MEASURED, not assumed: the share of each outer row or
   column of the raster that is fully opaque.
     leads-capture         TOP 83.5%   -> cut at the top
     wordpress-autosync    BOTTOM 92.7% -> cut at the bottom
     brand-customization   every edge 0.0% -> nothing cut, so no class
   `clip-path: inset()` takes NEGATIVE values, which let the shadow out on the
   sides it belongs on and cut it flat on the one it does not. */
/* ── 13c-ii · AN ANIMATED DRAWING IS A VIDEO, NOT AN IMAGE.
   ⛔ WHY. Animated WebP only compresses well when consecutive frames are
   IDENTICAL. The moment the WordPress drawing gained the film's camera, every
   frame differed: 23 encoded frames became 72 and the file went 188 KB to
   1639 KB. Lossy bought 28%, 12fps lost smoothness -- the container was wrong.
   ⭐ OUR OWN LIVE FILMS SHIP THIS CONTENT AT 253 KB and they are H.264 with the
   ground baked in (`pix_fmt=yuv420p`, measured). Ours is now 301.6 KB at
   900x634 -- 1.65x the display size, the same ratio the live film uses.
   ⚠ SO AN ANIMATED DRAWING'S PANEL AND SHADOW ARE BAKED INTO THE ASSET, agreed
   with Alpesh for videos. It gets NO `filter` here: the lift is already inside
   it, and a filter on an opaque rectangle would draw a second shadow around the
   whole panel. A STILL drawing is unchanged -- transparent, on the CSS panel,
   lifted by the filter on `.dimg`. */
.dvid {
	display: block;
	width: 100%;
	max-width: 546px;       /* its painted size; never upscaled */
	height: auto;
	margin-inline: auto;
}

/* ⛔ prefers-reduced-motion IS MANDATORY, and CSS cannot stop a video
   autoplaying -- so the still is a real sibling element and the two swap.
   The video carries `preload="none"` so the swap does not cost a download. */
.dvid__still { display: none; }
@media ( prefers-reduced-motion: reduce ) {
	.dvid { display: none; }
	.dvid__still { display: block; }
}

/* ⭐ THE PORTRAIT VARIANT, added 2026-09-22 for overnight-enquiries. Alpesh
   chose portrait for that one drawing -- "Portrait, portrait." -- and every
   other drawing we ship is landscape 546 wide. ⚠ A DECISION FOR ONE DRAWING,
   which is why this is a modifier and not a change to `.dvid`. */
.dvid--tall { max-width: 480px; }

/* ⭐ A DRAWING THAT FILLS ITS WELL. The three lp-help videos are 420x380 and
   the well is the card's full width, so they stretch to it rather than sitting
   centred in a 546px box that is wider than the card.
   ⚠ THE WELL'S SIDE PADDING IS CANCELLED FOR THEM: a drawing whose own ground
     is baked must reach the well's edges, or the well's --paper-raised shows as
     a frame around an identical --paper-raised rectangle and the seam appears
     exactly where the video's corner rounding does not match. */
/* ⛔ A VIDEO WELL IS SIZED BY ITS VIDEO, AND TWO RULES HAD TO BE BEATEN.
   Measured after the swap, not reasoned about:
   1. `:root .f--welled > :last-child { margin-top: auto }` on a COLUMN FLEX
      card made the well a stretching flex item -- 777px tall around a 377px
      video, so 400px of bare paper-raised sat under each drawing.
   2. `@media (max-width: 620px) { .well { height: 208px } }` survived the
      fixed-height deletion and CLIPPED 119px off the video at 390.
   ⭐ Both are answered here rather than edited above, because the DOM wells
      elsewhere on the site still want them. */
:root .f--welled > .well:has( > .dvid--well ) {
	padding: var(--wp--custom--space--lg) 0 0;
	height: auto;
	flex: 0 0 auto;
	margin-top: auto;
}
/* ⛔ NO `display` HERE, AND THAT IS [GOTCHA 66] FOR THE THIRD TIME.
   The first version said `display: block`. `.dvid__still { display: none }` is
   also (0,1,0) and sits EARLIER in this file, so the still won its width and
   lost its hiding: BOTH the video and the still painted, 376 + 376 + 24 = the
   777px well I then went looking for a flex bug to explain.
   ⭐ A rule that only needs to set a SIZE must only set a size. `.dvid` and
      `.dvid__still` own `display`, and the reduced-motion swap depends on it. */
.dvid--well { width: 100%; max-width: none; height: auto; }
@media ( max-width: 620px ) {
	:root .well:has( > .dvid--well ) { height: auto; }
}

/* ⛔ A MEDIA-FIRST WELL TAKES NO `margin-top: auto`, AND THE SLACK MOVES TO THE
   CHIPS. Alpesh, 2026-09-23: "we should keep the divisions of these three
   cards at the same length... the panels are not the same length."
   ⚠ HE IS RIGHT AND THE WELLS WERE ALREADY IDENTICAL -- all three measured
     400.8px tall. What differed was the well's MARGIN-TOP: 25.59px on cards 01
     and 03, 0px on card 02. So the drawings were the same size and the seam
     under them was not, which is the part a reader sees.
   ⭐ THE CAUSE IS AN auto MARGIN DOING ITS JOB IN THE WRONG DIRECTION. Two
      rules put `margin-top: auto` on the well -- `.f--welled > :last-child`
      and the video-well rule above -- and both were written for a well that
      sits at the card's FOOT, where auto correctly pushes it down. A
      media-first well is `order: -1`, so it PAINTS first while staying the
      DOM's last child, and the same auto margin banks the card's leftover
      space ABOVE the drawing instead of below the text.
   ⚠ THE LEFTOVER VARIES BY LINE COUNT: card 02's paragraph runs FOUR lines
     against three, so it had no slack to bank and its drawing started 25.59px
     higher than its neighbours'. Every future copy edit would move the seam
     again. This rule ends that class of drift, not just today's instance.
   ⭐ THE SLACK GOES TO THE CHIPS, not to nothing. With no auto margin at all
      the wells align and the chip rows still sit one line apart. Pinning the
      chips to the card's foot aligns the top seam AND the bottom row. */
/* ⛔ THE `:has()` FORM IS NOT BELT-AND-BRACES. IT IS THE ONLY ONE THAT WINS.
   ⚠ MEASURED, NOT REASONED: with the plain selector alone the misalignment
     HALVED -- 25.59px to 12.80px -- instead of going to zero, and a half-fix
     is the tell. `:has()` takes the specificity of its MOST SPECIFIC ARGUMENT,
     so `:root .f--welled > .well:has( > .dvid--well )` is (0,4,0) while
     `:root .f--media-first > .well` is only (0,3,0). The well kept its auto
     margin, the chips had just been given one too, and flexbox did exactly
     what it should: it split the free space between the two of them, 12.8px
     each. ⭐ The number named the cause -- an exact half is two claimants. */
:root .f--media-first > .well,
:root .f--media-first > .well:has( > .dvid--well ) { margin-top: 0; }
:root .f--media-first > .chips { margin-top: auto; }

/* ⭐ VARIATION A — THE TERRACOTTA-LIGHT CARDS, picked by Alpesh off the canvas
   https://claude.ai/artifact/J3Wd55muPgbSQweLTxHHzr on 2026-09-22, against a
   plain white set, a terracotta-DARK set and a night sky.
   ⛔ IT IS A MODIFIER, NOT A CHANGE TO `.f`. Eleven other cards on this page
      stay as they are. A decision made looking at ONE section is a decision for
      that section. */
.fgrid--terra .f {
	background: var(--wp--preset--gradient--g-terra-light);
	box-shadow: 0 0 0 1px color-mix( in oklab, var(--wp--custom--color--a-terra) 45%, transparent );
}
/* ⛔ THE BODY TEXT COMES OFF GREY, and that is measured rather than taste.
   `--secondary` is 4.66:1 on the gradient's DARK end -- it clears the 4.5 floor
   by 0.16 and nothing should ship that close. `--a-terra-dark` is 7.38:1 there
   and 9.04:1 at the light end. The heading stays ink: 15.33:1. */
.fgrid--terra .f p { color: var(--wp--custom--color--a-terra-dark); }

/* ⭐ THE FIRST PARAGRAPH, AT BODY SIZE. Alpesh dropped both paragraphs of this
   section to 16px on 2026-09-22, so size no longer separates them -- colour
   does. `--secondary` on the floor is 5.64:1; the paragraph under it is
   `--heading` at 18.57:1.
   ⚠ SCOPED TO THIS SECTION ON PURPOSE. The site-wide `.lead` rule is
     untouched; he is taking the other sections himself, one at a time. */
/* ⛔ AND IT NEEDS THE MARGIN TOO, BECAUSE `.section h2 + p` LOSES.
   `:root .split__copy > * { margin-block: 0 }` is (0,2,0) and the opening rule
   `.section h2 + p` is (0,1,2), so dropping `.lead` silently dropped the space
   under the heading to ZERO and the pair collided.
   ⚠ IT COST TWO FAILED PROBES: `margin-block` is a LOGICAL property, so
     `rule.style.marginTop` reads EMPTY for it and a scan of the stylesheets
     found nothing. The browser named it at once through
     CSS.getMatchedStylesForNode. [Gotcha 67]
   ⚠ Both copies carry it -- the long and the short sentence -- because exactly
     one of the two is visible at any width. */
/* ⛔ IT IS `opener`, NOT `muted`, SINCE 2026-09-23, AND IT NO LONGER SETS A
   COLOUR. Alpesh, looking at the painted section: "we should keep our regular
   ink font color for this highlighted para."
   ⚠ HE IS RIGHT -- the paragraph below it is the SAME 16px/400 in --heading,
     so the section opened with two paragraphs of identical size where the
     FIRST one was the quieter. Nothing in the argument asks for that.
   ⛔ THE CLASS COULD NOT SIMPLY BE DELETED, AND THAT IS THE WHOLE REASON THIS
      RULE EXISTS. It also restores the 24px under the heading that
      `:root .split__copy > * { margin-block: 0 }` takes away at (0,2,0)
      against `.section h2 + p` at (0,1,2). Dropping the class would silently
      collide the paragraph with the headline again. [Gotcha 67]
   ⭐ SO IT KEEPS THE SPACING JOB AND LOSES THE COLOUR JOB -- and with the
      colour gone, `muted` was a lying name for a rule that mutes nothing.
   ⚠ Both copies carry it -- the long and the short sentence -- because
     exactly one of the two is visible at any width. */
:root .section p.opener {
	margin-top: var(--wp--custom--space--lg);
	/* ⛔ A MEASURE, BECAUSE DROPPING `.lead` DROPPED ITS 62ch CAP TOO.
	   ⚠ MEASURED ON THE REBUILT PAGE, 2026-09-23: lp-help's opener painted
	     1280px wide at 1440 -- 162 characters on ONE 16px line, about a 160ch
	     measure. `.lead` had been holding it at 62ch and nothing else did.
	   ⭐ 72ch AT 16px IS ~660px, which is the same painted measure 62ch gave
	      at the 21px lead rung. The cap changed units, not width.
	   ⚠ It is a no-op in lp-problem, where the copy column is already 659px.
	     That is the point: one rule, correct in a narrow column and in a wide
	     one, rather than a second class for the wide case. */
	max-width: 72ch;
}

/* ⭐ TEXT FOR A MACHINE, NOT FOR THE EYE. An exported video is pixels, so the
   three enquiries a crawler and a screen reader could read while the drawing
   was DOM would simply vanish. This is the standard clip-rect pattern: it stays
   in the accessibility tree and in the document, and paints nothing.
   ⛔ NOT `display: none` and NOT `visibility: hidden` -- both remove it from
      the accessibility tree, which is the one thing it exists for. */
.u-sr-only {
	position: absolute;
	width: 1px; height: 1px;
	margin: -1px; padding: 0; border: 0;
	overflow: hidden;
	clip: rect( 0 0 0 0 );
	clip-path: inset( 50% );
	white-space: nowrap;
}

.dimg--cut-top    { clip-path: inset( 0 -24px -24px -24px ); }
.dimg--cut-bottom { clip-path: inset( -24px -24px 0 -24px ); }
.dimg--cut-left   { clip-path: inset( -24px -24px -24px 0 ); }
.dimg--cut-right  { clip-path: inset( -24px 0 -24px -24px );
}

/* -- 13d · FRAGMENT · THE THEME THUMBNAILS.
   ⛔ MEASURED OFF THE RUNNING APP, bot 90, `?section=styles`, 2026-09-21.
      The app's card is 250×157 with a 1px #E5E7EB border and `rounded-t-lg`;
      the chosen one carries a #008858 border and a tick badge on its corner.
   ⛔ THE WIREFRAME INSIDE EACH CARD IS AN <img> IN THE APP, and R7 refuses a
      pasted product screenshot, so it is rebuilt here in the DOM.
   ⭐ WHY THIS CONTROL AND NOT THE COLOUR PICKER. Styles carries eight
      controls; we drew the weakest. Six colour dots say "you can change a
      colour". Four themes say "this is yours". Alpesh chose plain, plain,
      dark -- the doodle-pattern themes are real and are left out, because
      they read as a toy next to the argument the section is making. */
.thm { display: flex; gap: 10px; }
.thm__card {
	position: relative;
	display: flex;
	flex-direction: column;
	flex: 0 0 88px;
	/* ⛔ THE CARD IS AN ASPECT RATIO, NOT A HEIGHT. The app's thumbnail is
	   250×157; below 700 the cards flex to share the phone's width, and a
	   fixed body height would have stretched them out of proportion. */
	aspect-ratio: 250 / 157;
	border-radius: var(--wp--custom--rounded--sm);
	overflow: hidden;
	background: var(--thm-bg);
	box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline);
}
/* ⛔ THE APP'S OWN GREEN, NOT OURS. The thumbnails paint a muted forest that
   is not in our palette, and a drawing is a RECORD -- repainting it in brand
   green would make the drawing argue for a product that does not exist.
   Sampled from the running app: rgb(93,123,112) header and user bubble,
   rgb(207,204,204) bot bubble, rgb(249,254,252) card ground. */
.thm { --thm-app: #5D7B70; --thm-bg: #F9FEFC; --thm-quiet: #CFCCCC; }
.thm__bar {
	flex: 0 0 13px;
	background: var(--abk-floor);
	box-shadow: inset 0 -1px 0 var(--wp--custom--color--hairline);
}
.thm__card--dark .thm__bar { background: var(--thm-app); box-shadow: none; }
.thm__body { flex: 1; min-height: 0; padding: 7px; display: grid; align-content: end; gap: 5px; }
.thm__pill { height: 7px; border-radius: var(--wp--custom--rounded--full); }
.thm__pill--user  { width: 46px; justify-self: end;   background: var(--thm-app); }
.thm__pill--bot   { width: 34px; justify-self: start; background: var(--thm-quiet); }
/* ⭐ THE ONE LIT THING IN THIS DRAWING. The badge is the only saturated mark,
   so the eye lands on the CHOICE rather than on the thumbnails. */
.thm__card--on { box-shadow: inset 0 0 0 1px var(--wp--custom--color--brand); }
.thm__tick {
	position: absolute; top: 0; right: 0;
	width: 15px; height: 15px;
	display: grid; place-items: center;
	background: var(--wp--custom--color--brand);
	color: var(--abk-floor);
	border-bottom-left-radius: var(--wp--custom--rounded--sm);
}
.thm__tick .ico { width: 9px; height: 9px; stroke-width: 2.6; }
/* ⛔ AT 390 THE STAGE STOPS BEING A STAGE and the fragments go back into the
   flow, so a fixed 88px card overran the panel and the CUT ATE THE BADGE --
   the one lit mark in the drawing. The cards share the width instead. */
@media (max-width: 700px) {
	.thm { gap: 8px; }
	.thm__card { flex: 1 1 0; }
}

/* -- 13e · TWO SURFACES, ONE ABOVE THE OTHER. */
.dstack { position: relative; padding-bottom: 108px; }
.dstack .appui { width: 560px; max-width: none; mask-image: linear-gradient(to right, #000 88%, transparent 100%); }
.dstack .appui__main { padding: 0; }
.dstack .appui__card { border-radius: var(--wp--custom--rounded--md); }
@media (max-width: 900px) {
	/* ⛔ At 390 an overlap is a collision, not depth. */
	.dstack { padding-bottom: 0; }
	.dstack .appui { width: 100%; mask-image: none; }
}

/* ⛔ THE BLEED RUNS RIGHT ON SOME DRAWINGS AND NOT ON OTHERS, AND THE REASON
   IS WHAT MAY BE CROPPED. Rows, card grids, logotypes and decorative objects
   crop; anything a visitor must READ stays whole. An earlier version bled at
   -2 units with a 78% fade, which clipped "sent" and half a customer's name. */
.draw--bleed-r {
	margin-right: calc(var(--wp--custom--space--xl) * -1);
	mask-image: linear-gradient(to right, #000 90%, transparent 100%);
}
@media (max-width: 720px) {
	.draw--bleed-r { margin-right: calc(var(--wp--custom--space--md) * -1.6); }
}

/* ---------------------------------------------------------------------------
   14 · SECURITY — U6, section 11. A centred opening, four cells, and one ink
   bar that carries the memorable half of the argument.
   --------------------------------------------------------------------------- */

/* ⭐ A CENTRED SECTION OPENING. Every other section on this page opens
   left-aligned over its content, so this one had no opening of its own. */
.shead { max-width: var(--wp--custom--width--focused); text-align: center; }
:root .section .shead { margin: 0 auto var(--wp--custom--space--2-xl); }
:root .shead > * { margin-inline: auto; }
.shead .eyebrow { display: inline-flex; }
/* ⚠ The wrapper owns the air below it, so the generic rules must not add a
   second gap underneath. */
:root .section .shead + .fgrid,
:root .section .shead + .split { margin-top: 0; }

/* ⭐ THE ICON IS BARE — no tile, no ground, no ring. A repeated affordance is
   not the page's focal action, and the page allows exactly one focal green
   action per fold.
   ⚠ `place-items: start` is not redundant. The tile this replaced declared
   `place-items: center` for its grid, and Chrome aligns the in-flow children
   of a BLOCK container too -- so the declaration kept working after `display`
   changed from grid to block, and the mark stayed centred. */
.mark {
	display: block;
	place-items: start;
	justify-items: start;
	color: var(--wp--custom--color--brand-text);
}
/* ⚠ Separate, and at (0,3,0), because the mark is a DIRECT child of the card
   and the rhythm reset zeroes those. Measured: the icon sat flush on the
   heading and every cell came out 16px short. */
:root .section .f .mark { margin-bottom: var(--wp--custom--space--md); }
.mark > .ico { width: 28px; height: 28px; stroke-width: 0.7143; }  /* 28 / vb 20 = 1.000 rendered */
.on-ink .mark { color: var(--wp--custom--color--brand-on-ink); }

/* ⭐ THE MEMORABLE HALF: WHAT IT NEVER DOES. ONE tracked-out label and three
   fragments on an ink bar, each with one drawn mark at the page's single
   stroke width.
   ⛔ IT IS ONE "NEVER", NOT THREE. Rebuilding it as a bulleted list with
      "Never" repeated three times was a COPY deviation as well as a design
      one -- the live page records one label and three fragments.
   ⚠ THE MARK INHERITS ITS COLOUR AND MUST. An explicit `--ink-muted` made the
     mark DIMMER (7.91) than the words beside it (12.03), for no reason. */
/* ⛔ THE BAND WENT WHITE ON 2026-09-23, ON ALPESH'S CALL, AND EVERY ROLE IN
   IT HAD TO MOVE WITH THE GROUND. It was the neutral dark gradient carrying
   dark-ground text; it is now paper carrying brand-text green.
   ⚠ A WHITE BAND ON THE TINT SECTION IS 1.05 : 1 -- measured -- so it needs
     the explicit hairline ring below or it does not read as a band at all.
     That separation is what the dark gradient was buying. */
.nevers {
	padding: var(--wp--custom--space--xl);
	border-radius: var(--wp--custom--rounded--md);
	background: var(--wp--custom--color--paper);
	box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--wp--custom--space--md) var(--wp--custom--space--xl);
	list-style: none;
}
:root .section .nevers { margin: var(--wp--custom--space--2-xl) 0 0; }
/* ⛔ THE WORDS ARE INK AND ONLY THE MARKS CARRY COLOUR. Alpesh painted the
   all-green version, saw it, and corrected it: "build it as shown in the
   screenshot" -- black words, coloured marks.
   ⚠ A GREEN BAND WAS TRIED AND IT WAS THE WRONG SIGNAL ANYWAY. The sentence
     is a list of things the product NEVER does; green is this page's colour
     for the thing it DOES. ⭐ The state colour belongs on the refusal marks
     and nowhere else in the band.
   ⚠ AND `--brand` WAS REFUSED ON THE NUMBER BEFORE THAT: #028858 on paper
     measures EXACTLY 4.50, the floor itself with no clearance. */
:root .nevers > li {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--custom--space--sm);
	margin: 0;
	padding-left: 0;
	font-size: var(--wp--custom--step--lead);
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--lead);
	color: var(--wp--custom--color--heading);          /* 19.59 : 1 on paper */
}
/* ⛔ THE LABEL LOST ITS OWN SIZE AND ITS OWN TRACKING, 2026-09-23. Alpesh:
   "the 'Never' font text looks odd... needs to be consistent with everything...
   overall, in that band, we should use a lead font size."
   ⚠ HE IS RIGHT AND THE MEASUREMENT SAYS WHY: it was 12px beside items at
     16px -- TWO rungs down, in a row meant to read as one sentence.
   ⛔ AND THE TRACKING HAD TO GO WITH THE SIZE. The label layer is the one
      place the scale tracks OUT, +0.72px. At the lead rung the tier is
      -0.21px, and ops/measure/check-law.js fails a size carrying another
      size's tier by name. ⭐ SO THE LABEL NOW SEPARATES ON WEIGHT ALONE --
      600 against 400 -- which costs no contrast and breaks no tier. */
:root .nevers > li.nevers__l {
	/* ⛔ WEIGHT IS ALL IT HAS LEFT, AND THAT IS THE POINT. It used to separate
	   three ways -- a smaller size, the label layer's OUTWARD tracking, and a
	   different ink. All three are gone: the size because Alpesh saw 12px
	   beside 16px and called it odd, the tracking because it MUST follow the
	   size, and the ink because the band's ground changed.
	   ⚠ THE TRACKING SURVIVED MY FIRST EDIT AND WAS MEASURED WRONG: 1.26px on
	     a 21px word while its neighbours ran -0.21px. Deleting a `font-size`
	     does not delete the tier that went with it, and ops/measure/check-law.js
	     fails a size carrying another size's tier by name. ⭐ The declaration
	     that must change is rarely only the one you meant to change. */
	font-weight: var(--wp--custom--weight--display);
}
/* ⛔ THE ONE PLACE `--danger` IS USED ON THIS PAGE, AND IT IS WHY THE TOKEN
   EXISTS. Alpesh, 2026-09-23: "add one red color according to our color
   patterns that is hue, warmth etc in our design system and use it here,
   label the colr for danger/warning."
   ⭐ #B33832 -- paper 5.95, tint 5.64, the SAME measured pair as `--secondary`.
   ⛔ THE RED WE ALREADY HAD COULD NOT DO THIS. `--warm-deep` #F37335 is
      2.87 on paper -- under even the 3:1 floor a graphical mark needs -- and
      the law refuses a warm stop as a mark on a light ground BY NAME. That
      refusal is untouched; this is a new token, not a way around it.
   ⚠ IT IS A FOREGROUND ONLY. It is not an accent and it never becomes a
     ground, so it spends nothing from the two-accent-ground budget. */
.nevers .ico { flex: none; color: var(--wp--custom--color--danger); }
@media (max-width: 760px) {
	/* ⛔ Centred fragments in a column read as a poem. The label leads. */
	.nevers {
		flex-direction: column;
		align-items: flex-start;
		justify-content: flex-start;
		gap: var(--wp--custom--space--md);
		padding: var(--wp--custom--space--lg);
	}
}

/* ---------------------------------------------------------------------------
   14b · THE CLOSE — the page's last word, BUILT 2026-09-23 and MISSING UNTIL
   THEN. Alpesh found it by looking; no checker counts sections against the
   prototype, and flatten-patterns.php takes an explicit list, so a section
   nobody names simply does not exist.
   --------------------------------------------------------------------------- */
/* ⛔ THE GROUND IS THE FLOOR, NOT INK, AND THE PROTOTYPE'S MARKUP MISLEADS.
   Its section carries `section--ink`, and a rule 1,300 lines further down in
   foundation.css overrides that class's ground back to the floor:
   "C IS THE PAGE NOW, 2026-09-18 -- Alpesh: 'the content block, like option C,
   I like.' The lime is still the last word... but it stopped being a 1408x669
   full-bleed slab: 24% less area."
   ⚠ I READ THE MARKUP AND REPORTED THIS AS THE PAGE'S ONLY DARK SECTION. It
     is not dark at all. A class name is not a ground. */
.section--close { background: var(--abk-floor); padding-inline: var(--wp--custom--space--2-xl); }
.section--close > .wrap {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	border-radius: var(--wp--custom--rounded--lg);
	padding: var(--wp--custom--space--4-xl) var(--wp--custom--space--2-xl);
	/* ⚠ THE FOOT IS DEEPER THAN THE HEAD, AND IT IS NOT A STYLE CHOICE. The
	   band below is opaque to 34% of the height and clear above 68%, so the
	   last line of copy has to finish above 68% or it sits in the green. */
	padding-bottom: calc( var(--wp--custom--space--4-xl) * 2.2 );
}
.section--close h2 { max-width: 26ch; }

/* ⭐ THE CLOSING BAND IS THE HERO'S OWN WASH, and the page ends where it
   began. ⛔ IT IS A `::before`, EXACTLY AS THE HERO IS -- and that is not a
   way of dodging ops/measure/check-law.js. The guard reads a ground off an
   ANCESTOR; a ::before is a sibling, so it cannot see either surface. ⚠ SO
   THIS ONE IS VERIFIED BY SAMPLING THE RENDERED PIXELS UNDER EVERY LINE, the
   same method that found the hero's stat labels failing at 4.39 this morning.
   ⛔ THE MIDDLE STOP NAMES `--brand` WHILE THE HERO'S NAMES `--brand-lift`,
      AND THE DIFFERENCE IS DELIBERATE. This section carries `.on-ink`, whose
      remap re-points `--brand-lift` to `--brand`. Copying the hero's
      declaration stop-for-stop WAS a real defect: it silently resolved to a
      different colour here. The stop is written as what it actually paints. */
.section--close > .wrap::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background: linear-gradient(to top,
		color-mix(in oklab, var(--wp--custom--color--brand)      34%, transparent)  0%,
		color-mix(in oklab, var(--wp--custom--color--brand)      16%, transparent) 34%,
		transparent 68%);
}

/* ⛔ `.btn.` IS LOAD-BEARING IN BOTH SELECTORS. A bare `.btn--banner` is
   (0,1,0) and loses to anything that colours a link inside a remapped section,
   which in the prototype painted the label BRAND GREEN on an ink fill at
   2.44 : 1. The doubled class costs nothing and closes that. */
.btn.btn--banner        { background: var(--wp--custom--color--ink); color: var(--wp--custom--color--paper); }
.btn.btn--banner:hover  { background: var(--wp--custom--color--heading); }
.btn.btn--banner-2      { background: transparent; color: var(--wp--custom--color--ink);
	box-shadow: inset 0 0 0 1px color-mix( in oklab, var(--wp--custom--color--ink) 30%, transparent ); }
.btn.btn--banner-2:hover { background: color-mix( in oklab, var(--wp--custom--color--ink) 6%, transparent ); }

/* ⛔ AT 390 THE PANEL SPENT 192px ON PADDING -- 48px on the section and 48px
   again on the wrap -- which left a 182px TEXT COLUMN. ⚠ 182 AND NOT 198: the
   section itself is 374px wide, not 390, because the page insets it 8px a side
   before any of this. The primary
   button wanted 315.8px inside it, and `.wrap` is `overflow: hidden`, so the
   button was CLIPPED FLAT at both ends.
   ⭐ THE BUTTON WAS NEVER FULL-WIDTH. It was too wide and the panel trimmed it,
      which is why it read as an edge-to-edge bar. Alpesh, 2026-09-24: "the
      banners should not have that much margin on the left and right... the
      main primary button is touching end to end there."
   ⚠ THE BUTTON'S OWN INLINE PADDING HAS TO STEP DOWN TOO. 294px is still not
     enough for a 251.8px label inside 2 x 32px. ⛔ ONLY THE PADDING MOVES --
     the 21px lead size and the 56px tap floor are untouched, so neither the
     type ladder nor check-law.js's control floor is disturbed.
   ⚠ `padding-inline` ALONE, NOT THE SHORTHAND. The wrap's bottom padding is
     deliberately 2.2x its top so the last line finishes above the green band;
     a shorthand here would silently reset it. */
@media (max-width: 620px) {
	.section--close { padding-inline: var(--wp--custom--space--md); }
	.section--close > .wrap { padding-inline: var(--wp--custom--space--lg); }
	/* ⛔ AND THE CAP IS THE GUARD, NOT THE PADDING. The padding is a fixed
	   number, so the button's appetite does not shrink with the viewport. At
	   360px -- the commonest Android width, and BELOW the 390 floor stated in
	   theme.json -- the column is 264px and the button still wants 283.8px, so
	   the clip comes back. ⭐ Capped, the label WRAPS to a second line and the
	   button grows TALLER instead of being trimmed. A wrapped label is legible;
	   a trimmed one is the defect Alpesh reported.
	   ⛔ `min-width: 0` ON THE WRAPPER IS LOAD-BEARING, AND `max-width` ALONE
	      DOES NOTHING WITHOUT IT. Core wraps every button in a
	      `.wp-block-button` div, and that div is the FLEX ITEM. A flex item's
	      `min-width` defaults to `auto`, which means "never smaller than my
	      content" -- so the wrapper stayed 283.8px wide and the anchor's
	      `max-width: 100%` resolved against a box that had already overflowed.
	   ⚠ MEASURED, NOT REASONED: with the cap on the anchor alone the button was
	     still 283.8px and still clipped at 320, 360 and 375. */
	/* ⛔ 227px OF EMPTY GREEN SAT UNDER THE LAST LINE, and the band is why.
	   The wrap's bottom padding is 2.2x its top so the copy always finishes
	   above the gradient's clear point at 68%. ⚠ SO CUTTING THE PADDING ALONE
	     PUTS THE COPY IN THE GREEN: measured, the last line moves from 69.5%
	     to 83.5% of the wrap. ⭐ THE BAND HAS TO MOVE WITH IT. On a phone the
	     band is a thin echo of the hero rather than a slab, so its stops
	     compress and the padding can fall to one 4xl. Alpesh drew the box,
	     2026-09-24: "there is too much white space".
	   ⚠ THE STOPS ARE RE-STATED IN FULL, not patched. A gradient is one
	     property; overriding two of three stops is not possible. */
	.section--close > .wrap {
		padding-top: var(--wp--custom--space--3-xl);
		padding-bottom: var(--wp--custom--space--4-xl);
	}
	/* ⛔ THE BAND REACHES THE SECOND BUTTON, AND EVERY STOP IS A MEASURED
	   POSITION RATHER THAN A ROUND NUMBER. Alpesh, 2026-09-24: "the gradient at
	   the bottom is not right. It's just a small band. At least it should
	   stretch till the Book a Demo button."
	   ⚠ THE GRADIENT RUNS `to top`, SO 0% IS THE BOTTOM EDGE. Measured at 390,
	     as fractions UP from the wrap's bottom: "Book a 30-min demo" starts at
	     0.340 and the eyebrow's middle line sits at 0.204. So the wash clears
	     at 34% -- the button's own top -- and not before.
	   ⛔ AND THE MIDDLE STOP IS A CONTRAST BUDGET, NOT A TASTE. The eyebrow is
	      INSIDE the band now, and its ink needs the ground to stay pale enough
	      for 4.5 : 1. ⚠ MEASURED ON THE PAINTED PIXELS: at 28% brand the ground
	      is rgb(184,222,208) and the ratio is 4.08 -- a fail. The ceiling is
	      about 21% brand; 16% at the 20% stop leaves the eyebrow at roughly
	      15.5% and holds the margin.
	   ⭐ THIS IS WHY THE STOPS ARE RE-STATED IN FULL. A gradient is one
	      property, so two of three stops cannot be patched. */
	.section--close > .wrap::before {
		background: linear-gradient(to top,
			color-mix(in oklab, var(--wp--custom--color--brand) 34%, transparent)  0%,
			color-mix(in oklab, var(--wp--custom--color--brand) 16%, transparent) 20%,
			transparent 34%);
	}
	.section--close .row--cta .wp-block-button { min-width: 0; max-width: 100%; }
	/* ⛔ `white-space: normal` IS THE REST OF THE FIX, AND WITHOUT IT THE CAP
	      ONLY MOVES THE DEFECT. The button inherits `nowrap`, so a capped box
	      does not wrap the label -- it lets the label hang OUT of the box, and
	      an inline-flex centres the overhang, so it spills equally at both ends
	      and reads exactly like the clipped bar we started from.
	   ⚠ EVERY BOX MEASUREMENT SAID THIS WAS FIXED. Width, height, `scrollWidth`
	     and the overflow check were all clean while the label hung 4.8px past
	     each edge; a Range around the text node measured 228.8px inside a 224px
	     button. ⛔ THE SCREENSHOT FOUND IT, NOT THE NUMBERS -- the tenth time in
	     this work that a green measurement sat beside a wrong page. */
	.section--close .btn--lg { padding-inline: var(--wp--custom--space--md); max-width: 100%; white-space: normal; }
}

/* ---------------------------------------------------------------------------
   15 · THE FAQ — U6, section 12. Twenty-two native disclosures, no JavaScript.
   --------------------------------------------------------------------------- */
.faq { max-width: var(--wp--custom--width--prose); }
:root .section .faq { margin-top: var(--wp--custom--space--2-xl); }
/* ⛔ THE RULE IS ON THE BOTTOM EDGE, NOT THE TOP. With a top border on every
   item plus a bottom border on the last, the section painted 23 rules over
   1,290px -- and a hairline repeated 23 times is not a hairline, it is a
   TABLE. ⭐ 22 of 23 is not the point: a BOTTOM rule reads as "this item ENDS
   here", while a top rule reads as a table's row divider. */
.faq details { border-bottom: 1px solid var(--wp--custom--color--hairline); }
.faq summary {
	cursor: pointer;
	list-style: none;
	padding: var(--wp--custom--space--md) var(--wp--custom--space--2-xl) var(--wp--custom--space--md) 0;
	min-height: 44px;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--custom--space--md);
	font-weight: var(--wp--custom--weight--emphasis);
	position: relative;
}
.faq summary::-webkit-details-marker { display: none; }
/* ⭐ A REAL DISCLOSURE ROTATES ONE MARK; the affordance was a bare text "+".
   ⛔ AND IT IS DRAWN IN CSS, NOT AS AN ICON. It must be pixel-crisp at 11px,
      and a stroked SVG at that size lands off-pixel. The FAMILY is the stroke
      width and the ink, not the medium. */
.faq summary::after {
	content: "";
	position: absolute;
	right: 6px;
	top: 50%;
	width: 11px;
	height: 11px;
	margin-top: -5px;
	background:
		linear-gradient(var(--wp--custom--color--ink) 0 0) center/100% 1px no-repeat,
		linear-gradient(var(--wp--custom--color--ink) 0 0) center/1px 100% no-repeat;
	transition: transform var(--wp--custom--motion--duration) var(--wp--custom--motion--easing);
}
.faq details[open] summary::after { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { .faq summary::after { transition: none; } }
:root .section .faq details > p {
	margin: 0;
	padding-bottom: var(--wp--custom--space--lg);
	color: var(--wp--custom--color--secondary);
}

/* ⛔ A QUESTION LIST IS PROSE: IT TAKES A MEASURE AND IT CENTRES. It was a
   half-width LEFT slab in a 1408px section -- 22 questions with an empty right
   half beside them.
   ⭐ TWO COLUMNS, and `break-inside: avoid` keeps an item whole, because a
      disclosure must be able to grow without re-flowing its siblings. */
.faq--centred {
	max-width: var(--wp--custom--width--frame);
	margin-inline: auto;
	column-count: 2;
	column-gap: var(--wp--custom--space--3-xl);
}
.faq--centred details { break-inside: avoid; }
/* ⚠ ONE COLUMN BELOW THE FRAME. Two columns of 22 questions at 390 would be
   two columns of unreadable ones. */
@media (max-width: 899px) { .faq--centred { column-count: 1; } }
.section--faq h2 { max-width: 24ch; }
/* The list itself reads left; only the OPENING is centred. */
.section--faq .faq { text-align: initial; }
@media (max-width: 620px) {
	/* 22 questions at 390 wrap to two lines each, so the VERTICAL padding is
	   paid 22 times. The 44px control floor is untouched.
	   ⛔ AND ONLY THE VERTICAL PADDING GOES -- THE PROTOTYPE LOSES BOTH AND IT
	   IS A MEASURED DEFECT. Its rule is `padding: 10px 0`, which also removes
	   the 48px gutter the disclosure mark sits in. Measured at 390 on the
	   prototype: FIVE of the 22 questions run under their own +/- mark, the
	   worst by 12px. ⭐ The rule's own comment says the padding is about the
	   VERTICAL cost; losing the gutter was collateral. ⚠ THE COST IS HONEST
	   AND STATED: our FAQ is 128px taller at 390 because five questions wrap
	   one line further. Text under a control is a defect; a longer column is
	   not. */
	.faq summary { padding-top: 10px; padding-bottom: 10px; }
	:root .section .faq details > p { padding-bottom: var(--wp--custom--space--md); }
}

/* ---------------------------------------------------------------------------
   16 · THE LIVE BOT — U7. The one object on this page served from another
   host, and the section's whole argument is that THIS ONE IS REAL.
   ⛔ IT IS NOT A BLOCK, AND THE PLAN SAID IT WOULD BE. A block exists to hold
      STATE and this section holds none: an iframe, a fallback behind it, and
      some copy. Measured on the prototype -- three scripts, and not one of
      them touches this section. So it is a pattern.
   --------------------------------------------------------------------------- */
.demo__frame { display: grid; place-items: center; }
:root .section .demo__frame { margin-top: var(--wp--custom--space--2-xl); }
:root .section .demo__frame > .eyebrow { margin-bottom: var(--wp--custom--space--md); }

/* ⭐ THE LIVE WIDGET STANDS ON A PAINTED RIBBON — the FIRST painted field to
   reach the theme. Twenty were generated on 2026-09-20; Alpesh kept two, and
   docs/research/prototypes/painted/README.md states that the copy into this
   directory IS the review gate. This is that copy.
   ⛔ IT IS f1-ribbon, ALL GREEN. ⚠ A DERIVED RECOLOUR -- f1's shape carrying
      r1-duo's colour -- SHIPPED FIRST AND HE REJECTED IT THE SAME DAY, after
      seeing it here: the duo's orange landed beside the headline, the one warm
      thing in a green section, pulling the eye off the live demo.
   ⭐ f1's CURVE IS WHY IT WINS, not its colour. It is the fatter of the two
      kept ribbons, so more of it survives past a 480px widget; r1-duo's
      thinner curve showed as two unrelated corners. And the record's
      "greens alone read as eco" does not hold behind a green-headed chat
      widget -- there they read as of a piece with the product.
   ⚠ A FLAT PANEL CAME FIRST AND HE REFUSED IT: tint measured 1.05:1 against
     the section's paper and did almost nothing; dark neutral worked but "feels
     dull". A field BEHIND the widget is atmosphere. A panel AROUND it is a
     box, and the widget is already a box.
   ⛔ NO TEXT LANDS ON THE RIBBON, so nothing here needs a contrast ruling. The
      caption sits above it and the hint below it, both on the section's paper.
      ⚠ If either ever moves onto the field, it stops being decoration and
        needs a measured floor like any other text. */
#livebot .demo__frame {
	padding: var(--wp--custom--space--3-xl) var(--wp--custom--space--2-xl);
	border-radius: var(--wp--custom--rounded--lg);
	background-image: url("../img/lb-ribbon.webp");
	background-size: cover;
	background-position: center;
}
/* ⭐ THE WIDGET'S LIFT GOES UP ON THE FIELD. --shadow--depth was tuned against
   paper; over a painted ground the same cast disappears and the widget reads
   as pasted on rather than standing on. */
#livebot .lb-frame {
	box-shadow:
		0 28px 48px -12px color-mix( in oklab, var(--wp--custom--color--heading) 34%, transparent ),
		0 6px 10px -2px color-mix( in oklab, var(--wp--custom--color--heading) 16%, transparent );
}
@media (max-width: 620px) {
	/* ⛔ THE PANEL BREAKS OUT OF THE SECTION'S GUTTER, and its own padding is
	   then the only margin the widget needs. Alpesh, 2026-09-24: "the panel is
	   already providing margin for the widget, the panel can have full width
	   with no margins."
	   ⚠ MEASURED AT 390: the widget lost 40px to the section's 20px gutter
	     BEFORE the panel's own 16px ever applied -- 374 -> 334 -> 302. Pulling
	     the panel back over the gutter returns all 40px to the chat window.
	   ⛔ THE GUTTER ITSELF STAYS. It still holds the headline and the lead above
	      this panel off the screen edge; only the PANEL escapes it, which is
	      why this is a negative margin and not a smaller gutter. */
	#livebot .demo__frame {
		padding: var(--wp--custom--space--xl) var(--wp--custom--space--md);
		margin-inline: calc( var(--wp--custom--width--gutter) * -1 );
	}
}
/* ⛔ AND THE COLUMN'S OWN TOP MARGIN GOES, OR "CENTRED" IS 24px OUT.
   Alpesh asked for the copy to be centred against the panel, and dropping
   `split--top` was only half of it: `:root .section .demo__frame` carries
   `margin-top: 48px`, which a centred grid item keeps -- so the panel's centre
   sat 24px below the copy's, MEASURED at 617.8 against 593.8. ⭐ Half of a
   margin is exactly the offset it puts into a centred pair, which is the
   number to look for.
   ⚠ ONLY ABOVE 900px. Below that the split stacks and the frame moves ABOVE
     the copy, where that margin is the gap doing its real job. */
@media (min-width: 900px) {
	:root .section .split > .demo__frame { margin-top: 0; }
}

.lb-frame {
	position: relative;
	isolation: isolate;
	width: 100%;
	max-width: 480px;
	aspect-ratio: 480 / 580;
	background: var(--abk-floor);
	border-radius: var(--wp--custom--rounded--lg);
	box-shadow: var(--wp--custom--shadow--depth);
	overflow: hidden;
	display: grid;
	place-items: center;
}
/* ⛔ BEHIND THE IFRAME, NOT INSTEAD OF IT — z-index 0 against the iframe's 1.
   If app.aibotkit.io fails, the panel still paints with this line showing
   through, so a failure reads as an empty panel rather than a hole in the
   page. A drawing of a loading state that can never finish is the thing this
   replaced. */
.lb-fallback {
	position: absolute;
	z-index: 0;
	margin: 0;
	padding: var(--wp--custom--space--lg);
	max-width: 34ch;
	text-align: center;
	font-size: var(--wp--custom--step--body-small);
	letter-spacing: var(--wp--custom--tracking--text);
	color: var(--wp--custom--color--secondary);
}
.lb-iframe { position: relative; z-index: 1; width: 100%; height: 100%; border: 0; display: block; }

/* ⛔ AN ASPECT RATIO TIES HEIGHT TO WIDTH, AND ON A PHONE THAT IS BACKWARDS.
   `.lb-frame` is 480 x 580 on a desktop, but the ratio makes the box SHORTER as
   the screen narrows: measured 302 x 365 at 390, which is 43% of an 844px
   viewport. ⭐ WIDTH IS THE SCARCE THING ON A PHONE AND HEIGHT IS THE ABUNDANT
   ONE -- and height is what carries a conversation. Alpesh, 2026-09-24: "it
   should be such that it can take a mobile browser's viewport by having some
   breathing space at the top and bottom."
   ⛔ `dvh`, NOT `vh`. `vh` measures the viewport with the browser's URL bar
      HIDDEN, so a `vh` box is taller than what a reader can actually see when
      the page opens. `dvh` is the viewport as it is right now.
   ⚠ THE `vh` LINE ABOVE IT IS A FALLBACK, NOT A DUPLICATE. A browser that does
     not know `dvh` drops the second declaration and keeps the first.
   ⚠ `aspect-ratio: auto` MUST BE STATED. Leaving the ratio in place with a
     height set makes the box compute a WIDTH from the new height. */
@media (max-width: 620px) {
	.lb-frame {
		aspect-ratio: auto;
		height: clamp(420px, 72vh, 620px);
		height: clamp(420px, 72dvh, 620px);
	}
}
/* ⛔ THE CAPTION AND THE HINT ARE ONE UNIT, AND THE WIDGET IS NOT.
   They were 32px apart while the hint sat 0px off the widget, so the line read
   as a label ON the widget rather than as the second half of the caption above
   it. ⭐ SPACING IS WHAT GROUPS TWO LINES -- 8px inside the pair, 32px to the
   object below. Alpesh drew this on 2026-09-24. */
:root .section .demo__frame .eyebrow { margin-bottom: var(--wp--custom--space--sm); }
:root .section .lb-hint {
	margin-top: 0;
	margin-bottom: var(--wp--custom--space--xl);
	font-size: var(--wp--custom--step--body-small);
	letter-spacing: var(--wp--custom--tracking--text);
	color: var(--wp--custom--color--secondary);
}

/* ⛔ `.pills`, `.pill`, `.ask` AND `.ask-lead` WERE DELETED HERE, 2026-09-23,
   in the same commit that removed their markup. Alpesh removed the four claim
   pills and the three suggested questions from the live-bot section, and those
   four selectors appeared in NO other pattern and NO other row of page 4925 --
   grepped, not assumed. ⭐ A rule whose only element is gone is dead weight
   that the next person has to read and rule out. The audit he scheduled
   REMOVES rather than adds, and this is what that looks like.
   ⚠ `.on-ink .pill` went with them; it had no live element either. */
:root .section .row--cta        { margin-top: var(--wp--custom--space--xl); }
:root .section .row--cta + .eyebrow { margin-top: var(--wp--custom--space--md); }

/* ---------------------------------------------------------------------------
   17 · THE DEMO STAGE — U7, and it is a BLOCK because it holds state.
   ⛔ THE STAGE IS WHAT MAKES THE FOUR STEPS ONE PRODUCT. As four separate
      feature sections there is no sense that step 04 is the same system as
      step 01. One window that stays put while the argument moves past it says
      so without a sentence.
   --------------------------------------------------------------------------- */
.stage { display: grid; gap: var(--wp--custom--space--2-xl); }
/* ⚠ (0,2,0), NOT (0,3,0). The stage asks for 64px on its own and must take
   48px when it follows a lead -- the same rule every other component obeys.
   At (0,3,0) it outranked that rule and sat 16px low. */
.section .stage { margin-top: var(--wp--custom--space--3-xl); }

/* ⭐ THE RAIL IS A PROGRESS BAR DISGUISED AS A MARGIN RULE: a 3px left border
   on the list, and the ACTIVE step paints its own segment of it. */
:root .stage .stage__steps {
	list-style: none;
	margin: 0;
	padding: 0;
	border-left: 3px solid var(--wp--custom--color--hairline);
}
:root .stage .stage__step {
	margin: 0;
	padding: var(--wp--custom--space--lg) 0 var(--wp--custom--space--lg) var(--wp--custom--space--xl);
	margin-left: -3px;
	border-left: 3px solid transparent;
	transition: opacity var(--wp--custom--motion--duration) ease,
	            border-color var(--wp--custom--motion--duration) ease;
}
/* ⭐ THE DIMMING, AND THE RULE IS "IT DOES NOT VANISH; IT GOES QUIET."
   ⛔ `data-on` IS NOT A BOUND ATTRIBUTE, AND THAT COST TWO FINDINGS TO REACH.
      Bound per-step, WordPress's server-side directive processing REMOVED it
      -- PHP cannot evaluate a derived per-element value -- so the page
      arrived with no lit step at all. Bound as a boolean, it renders as the
      STRING "false" rather than being removed, so every step carried it. ⭐ It
      is written server-side on step 1 and maintained by the view module,
      because ops/measure/check-law.js reads this exact attribute to tell a
      STATEFUL rail from a decorative side-stripe -- and it refused the rail
      the moment the attribute stopped distinguishing the lit step. */
.stage__step { opacity: 0.55; }
/* ⚠ (0,4,0), BECAUSE THE RESET ABOVE IS (0,3,0) AND USES THE `border-left`
   SHORTHAND. The dimming worked and the RAIL DID NOT: `opacity` was set by a
   one-class rule and won, while the colour was set inside a three-class
   shorthand and lost. ⭐ check-law.js found it by reading the PAINT -- it
   refuses a side-stripe that carries no state, and a rail whose lit colour
   never paints carries none. */
:root .stage .stage__step[data-on] { opacity: 1; border-left-color: var(--wp--custom--color--brand); }
/* ⛔ `>` ON BOTH, AND IT IS THE SAME LEAK AS THE CARD'S. Below 900px each
   transcript MOVES INSIDE its own step -- so `.stage__step p` matched every
   chat message in it and gave each one the step's own 8px rhythm. Measured at
   390: the widget came out 16px tall of nothing and the whole column with it.
   ⭐ A step's own copy is its DIRECT children; anything deeper is a drawing,
   and a drawing states its own type. Third time this rule has earned itself. */
:root .stage .stage__step > h3 {
	margin: var(--wp--custom--space--sm) 0 0;
	font-size: var(--wp--custom--step--subsection);
	font-weight: var(--wp--custom--weight--display);
	letter-spacing: var(--abk-track-sub);
	line-height: var(--wp--custom--leading--display);
	color: var(--wp--custom--color--heading);
}
:root .stage .stage__step > p { margin: var(--wp--custom--space--sm) 0 0; color: var(--wp--custom--color--secondary); }
.stage__card .wgt { box-shadow: var(--wp--custom--shadow--depth); }

/* ⛔ THE STACKED CARD NEEDS ITS OWN HEIGHT, AND IT HAD NONE. The height that
   makes this widget a WINDOW is declared inside `@media (min-width: 900px)`
   below, so on a phone the widget was sized by whatever was in it.
   ⚠ THAT WAS HARMLESS ONLY WHILE THE PHONE SHOWED EVERY LINE AT ONCE. The
     moment the player runs here, an empty log collapses the card to 146px and
     it grows line by line -- so the section gets TALLER under the reader's
     thumb, the page scrolls out from under them, and the card crosses the
     observer's band repeatedly. MEASURED: card 1 restarted four times in one
     500px scroll.
   ⭐ A FIXED BOX IS ALSO WHAT `trim()` NEEDS. The log drops its oldest line
      when a new one will not fit, and "will not fit" has no meaning in a box
      that simply grows. Alpesh, 2026-09-23: "we can fix the height of the chat
      widget relative to the viewport available so that at least 2 turns are
      always visible."
   ⚠ `dvh` FOR THE SAME REASON AS `.lb-frame`, with a `vh` fallback first. */
@media (max-width: 899px) {
	.stage__card .wgt {
		height: clamp(320px, 52vh, 460px);
		height: clamp(320px, 52dvh, 460px);
		display: flex;
		flex-direction: column;
	}
	.stage__card .wgt__log { flex: 1 1 auto; overflow: hidden; align-content: start; }
}

/* ⛔ ONLY THE ACTIVE CARD SHOWS, AND CSS DECIDES IT FROM ONE ATTRIBUTE ON THE
   ROOT. The cards carry no directives of their own precisely so the narrow
   layout may re-parent them; a bound element the runtime has hydrated should
   not be moved. */
.stage__card { display: none; }
.stage[data-active="1"] .stage__prop > .stage__card[data-step="1"],
.stage[data-active="2"] .stage__prop > .stage__card[data-step="2"],
.stage[data-active="3"] .stage__prop > .stage__card[data-step="3"],
.stage[data-active="4"] .stage__prop > .stage__card[data-step="4"],
.stage[data-flat="true"] .stage__card,
.stage__step > .stage__card { display: block; }

@media (min-width: 900px) {
	.stage { grid-template-columns: 1fr 1fr; gap: var(--wp--custom--space--4-xl); align-items: start; }
	/* ⛔ THE STEPS NEED SCROLL DISTANCE OR THE STAGE HAS NOTHING TO STAGE. */
	/* ⛔ 70vh, NOT 46vh -- Alpesh chose to lengthen the steps rather than speed
	   the conversations up. The four live scenes run to 9.8s, 9.8s, 12.0s and
	   13.0s, and at 46vh a reader scrolling normally left before Sarah arrived,
	   which is the best beat in the whole section. ⚠ It costs the section about
	   1000px of height and that was the trade he accepted. */
	:root .stage .stage__step { min-height: 70vh; display: flex; flex-direction: column; justify-content: center; }
	/* ⛔ ~~THE LAST STEP NEEDS NO DWELL~~ -- WITHDRAWN 2026-09-22, AND IT WAS
	   MEASURED WRONG. The reasoning was that a step's 46vh exists so the window
	   can be read before the NEXT step claims it, and the fourth has no next
	   step. ⚠ THE READER STILL HAS TO READ IT. Measured at 1440x900, the pinned
	   dwell per step was 560 / 480 / 400 / 80px -- the fourth got FIVE TO SEVEN
	   TIMES LESS than the others, because it was 194px tall against 414px and
	   the pin released the moment the stage ended. Alpesh: "even though the
	   fourth section is not landed, things get scrolled away completely." */

	/* ⭐ AND THE TAIL IS WHAT KEEPS THE PIN ALIVE THROUGH STEP 4. `position:
	   sticky` releases when its CONTAINING BLOCK's bottom arrives, and the
	   containing block is `.stage` -- which used to end at step 4's last line.
	   So the fourth step could never be centred with the window still pinned.
	   The padding is on the STEP LIST, not the stage, so the window's own
	   column is unaffected. ⚠ The value is tuned against the measured dwell
	   table, not chosen: see the plan record for the before and after. */
	:root .stage .stage__steps { padding-bottom: 43vh; }
	/* ⭐ THE PIN. This is the whole of the device, and it is one property. */
	.stage__prop { position: sticky; }

	/* ⭐ THE WINDOW IS NOW A PANEL, AND IT FIXED TWO THINGS AT ONCE. Measured
	   before: the widget painted #ffffff on a #ffffff section -- 1.00:1, no
	   separation at all, the same "it is jelling with the background" Alpesh
	   caught on the WordPress drawing. And the pinned box was 279 / 401 / 502 /
	   516px tall across the four steps, a 237px swing, so the one thing that
	   exists to HOLD STILL was the thing that moved most.
	   ⛔ min-height IS THE HALF THAT MATTERS. A panel that resizes per step is
	      still a moving frame; fixed, the frame holds and only the contents
	      swap, which is the whole device.
	   ⭐ THE GRADIENT IS THE TESTIMONIAL CARDS', RUN THE OTHER WAY. Theirs go
	      pale at the top to deeper at the bottom, because the stars sit at the
	      top. Alpesh asked for the reverse here, so the chat sits on the pale
	      end and the panel has a defined top edge. */
	.stage__prop {
		background-image: linear-gradient(
			to bottom,
			var(--wp--custom--color--a-terra-light),
			var(--wp--custom--color--a-terra-light-2)
		);
		border-radius: var(--wp--custom--rounded--lg);
		padding: var(--wp--custom--space--xl);
		display: flex;
		flex-direction: column;
		justify-content: center;
	}
	/* ⛔ AND THE PANEL NO LONGER CARRIES A min-height. It used to stand 576px
	   tall while step 1's card was 257px, so the window floated in 160px of
	   empty ground above and below. The WINDOW is fixed now, so the panel can
	   simply hug it and the dead bands cannot come back. */

	/* ⭐ THE WINDOW IS SIZED TO THE VIEWPORT, so at least two turns are always
	   in view -- Alpesh: "we can fix the height of the chat widget relative to
	   the view port avaible so that atleat 2 turns are always avisible".
	   ⚠ THE SUBTRACTION IS THE PINNED FURNITURE ABOVE AND BELOW IT: the fixed
	     site header, the pinned section heading, the gap under it, and the
	     panel's own padding top and bottom. `--abk-demo-head` is measured by
	     the view module, so a heading that wraps to two lines shrinks the
	     window rather than pushing it off screen.
	   ⛔ THE CLAMP IS NOT DECORATION. Without a floor, a short laptop window
	      collapses the log to nothing; without a ceiling, a tall monitor makes
	      a chat window taller than any chat window has ever been. */
	.stage__card .wgt {
		max-width: none;
		height: clamp(
			340px,
			calc( 100vh - 48px - var( --abk-demo-head, 140px ) - 24px - 64px - 24px ),
			560px
		);
		display: flex;
		flex-direction: column;
	}
	/* ⛔ overflow:hidden AND NOT auto. The log NEVER scrolls: when a new line
	   will not fit, the player drops the oldest one. A scrollbar here would be
	   a second, competing way to move through the same conversation. */
	/* ⛔ `align-content: start` IS A DESIGN RULING, NOT ONLY A MECHANICAL ONE --
	      ALPESH, 2026-09-23: "the conversation should start from top to bottom
	      always."
	   ⚠ THE AUDIT PROPOSED THE OPPOSITE AND HE REFUSED IT. Measured on beat 4:
	     the log is 446px and the beat ends 121px full, so the copy beside it
	     ("It knows when to fetch a human") sits 162px BELOW the last message.
	     `align-content: end` was painted -- it closes the gap to -71px and the
	     two halves line up -- and he looked at it and chose the white space:
	     "the white space is not much of an issue in this case."
	   ⭐ SO THE PAIRING FAULT IS KNOWN AND ACCEPTED. Do not re-open it by
	      reading the gap as a bug; a bottom-anchored log was tried and lost on
	      a reason that outranks it. A shrink-to-fit panel was painted too and
	      lost harder -- it moves the blank rather than removing it, and it
	      changes the sticky panel's height on every beat. */
	.stage__card .wgt__log {
		flex: 1 1 auto;
		overflow: hidden;
		align-content: start;
	}
}
/* Declared outside the query so the offset is one value in one place; below
   the breakpoint the prop is static and `top` is simply ignored.
   ⚠ AND IT NOW CLEARS THE PINNED HEADER. `--abk-demo-head` is written by the
     view module from the header's MEASURED height, because the heading wraps
     at some widths and a guessed constant would overlap at exactly the widths
     nobody tests. It falls back to 0, which is the pre-pin behaviour. */
.stage__prop { top: calc(48px + var(--abk-demo-head, 0px) + var(--wp--custom--space--lg)); }

/* ⭐ THE PINNED SECTION HEADER. Alpesh, 2026-09-22: "the header should also
   be there. As we scroll, only the right-side image and the left-side content,
   the timeline, should keep changing." Measured before: the heading left the
   viewport at scrollY 5186 in a section running to 7006 -- absent for 80% of
   its own scroll, so steps 2, 3 and 4 played with no title above them.
   ⛔ THE OPAQUE GROUND IS NOT DECORATION. Without it the steps scroll THROUGH
      the letters. It takes the section's own panel colour, so the header does
      not read as a second surface.
   ⚠ AND `.section` IS `overflow: clip`, NEVER `hidden`, WHICH IS WHY THIS CAN
     WORK AT ALL -- `hidden` makes a scroll container, and a scroll container
     is the containing block for every sticky descendant. Section 1 records
     that the prototype audit once reported "0 sticky elements on the page"
     for exactly this reason. */
@media (min-width: 900px) {
	#demo .demo-head {
		position: sticky;
		top: 48px;
		z-index: 3;
		/* ⛔ THE SECTION'S OWN GROUND, MIRRORING THE SECTION'S OWN RULE. It was
		   `--abk-panel`, which IS the tint token -- so the band painted #f9f9f6
		   over a #ffffff section and read as a second surface. `.section--floor`
		   paints `--abk-floor`, so the header has to follow the same fork or
		   the two can disagree again the next time a section changes ground. */
		background: var(--abk-panel);
		/* ⚠ THE TOP PADDING IS THE HEADING'S AIR WHEN IT IS STUCK, and it is
		   NOT pulled back out of the rhythm. It used to be, with a negative
		   margin -- which put the header's opaque box 8px over the eyebrow's
		   last line and made the eyebrow look deleted. Measured: eyebrow
		   238-257, header box starting 249. Letting the padding stand moves
		   the heading down 24px and the eyebrow is clear. ⭐ The cost is that
		   the gap under the eyebrow is 40px here against 16px elsewhere; the
		   gain is an eyebrow you can see. */
		padding-block: var(--wp--custom--space--lg);
	}
	/* ⛔ `#demo` AND NOT `.section--floor`, AND THAT COST A ROUND. The rule
	   above is (1,1,0) because it is scoped by an ID; a `.section--floor`
	   override is (0,2,0) and LOSES to it, so the band kept painting tint and
	   the fix appeared to do nothing. ⭐ An ID in the base rule forces an ID in
	   every override -- the argument against the ID, but the section IS
	   addressed by its anchor everywhere else on this page. */
	#demo.section--floor .demo-head { background: var(--abk-floor); }

	/* ⭐ THE 48px ABOVE THE PINNED HEADING IS COVERED BY THE STEP COLUMN, NOT BY
	   THE HEADING, AND THAT COST A ROUND TOO. The site header is `position:
	   fixed`, a floating pill 64px tall inset 12px, so the strip between the
	   viewport edge and the pinned heading is live page -- the next step's
	   paragraph scrolled through it and two texts stacked.
	   ⛔ MY FIRST FIX WAS AN UPWARD BOX-SHADOW ON THE HEADING, AND IT ATE THE
	      EYEBROW. Any band tall enough to reach the viewport top when stuck
	      also reaches 48px above the heading AT REST, and the eyebrow sits 16px
	      above the heading. Measured: eyebrow 238-257, band reaching 201. It
	      looked deleted, and Alpesh read it as deleted.
	   ⭐ SO THE BAND BELONGS WHERE THE BLEED IS. The text that shows through is
	      the STEP column's, so a sticky strip at the top of the step list
	      covers it -- and it lives far below the eyebrow, where it cannot
	      reach. The negative margin keeps it out of the layout, so the stage
	      is not 48px taller for it. */
	:root .stage .stage__steps::before {
		content: '';
		display: block;
		position: sticky;
		top: 0;
		z-index: 2;
		height: 48px;
		margin: -48px 0 0 -3px;   /* -3px covers the rail's own border */
		background: var(--abk-panel);
	}
	#demo.section--floor .stage__steps::before { background: var(--abk-floor); }
	/* ⛔ THE HEADER OUTRANKS THE RAIL AND THE WINDOW, and both need saying.
	   The rail is a 3px border on a list that scrolls under the header, and
	   the pinned window sits in the same stacking context. */
	.stage__prop { z-index: 1; }
}

@media (max-width: 899px) {
	/* ⛔ Below the breakpoint every step is lit, because without the pin
	   there is nothing for a dim step to be quieter THAN. */
	.stage .stage__step { opacity: 1; }
	/* ⛔ AT 390 IT UNPINS AND BECOMES THE STACK IT EXISTS TO AVOID. We accept
	   the loss rather than fake it: a sticky window in a single column has
	   nothing to stay still against. */
	.stage__prop { position: static; }
	.stage__step { opacity: 1; }
	/* ⛔ AND THE RAIL GOES WITH THE STATE. Without the pin the rail marks
	   nothing, and a decorative vertical line down a phone is a side-stripe,
	   which the law refuses by name. */
	:root .stage .stage__steps,
	:root .stage .stage__step { border-left: 0; padding-left: 0; margin-left: 0; }
	:root .stage .stage__step > .stage__card { margin-top: var(--wp--custom--space--lg); }
	.stage__prop:empty { display: none; }
}

/* ⛔ WITH THE PREFERENCE SET, NOTHING MOVES -- and "nothing moves" is only
   half the rule. "Nothing is MISSING" is the other half, which the view
   module enforces by lighting every step and showing every card. */
@media (prefers-reduced-motion: reduce) {
	:root .stage .stage__step { transition: none; }
	/* ⛔ NOTHING PLAYS, SO NOTHING RISES AND NOTHING FILLS. The whole transcript
	   is already shown by the view module; an arrival animation on a line that
	   was never absent is motion for its own sake, and a progress bar for a
	   recording that is not running is a lie. */
	/* ⚠ `:not([hidden])` HAS TO BE REPEATED, or this loses. The rule it
	   overrides is (0,3,0) -- two classes plus the attribute inside `:not` --
	   and a bare `> *` is (0,2,0). Measured: reduced motion still reported
	   `abk-msg-in 0.32s`. Equal specificity, later in the file, so it wins. */
	.wgt--play .wgt__log > *:not( [ hidden ] ) { animation: none; }
	.wgt__prog { display: none; }
}

/* ---------------------------------------------------------------------------
   18 · THE INDUSTRY SIMULATOR — U7, and the largest object on the page.
   --------------------------------------------------------------------------- */
/* ⚠ 26px, WHICH IS HALF THE CONTROL. Alpesh, 2026-09-22: "this tab swticher we
   should move up a bit so that is half sits inside and half outsite as
   earlier." The control is 52px tall, so half of it is 26 -- the block reserves
   exactly that above the panel and the other half sits on the site.
   ⭐ It still costs the frame far less than the old full row did: 26px against
      52, because only half of it is outside now. */
.chromed { position: relative; padding-top: 26px; }
:root .section .chromed { margin-top: var(--wp--custom--space--xl); }

/* ⛔ NO BROWSER BAR. Every product-depiction reference refuses one by name, so
   the chrome is a segmented pill row sitting half-outside the panel's top
   edge rather than three window dots and five fake tabs.
   ⭐ AND IT LIFTS. The edge shadow alone left it at 1.055 against the panel;
   it is FLOATING CHROME, and floating chrome is one of the shapes the shadow
   budget already counts. */
/* ⭐ AND IT MOVED TO THE LEFT AND ONTO THE EDGE. Alpesh, 2026-09-22: "at the
   top, we have this switcher tab, and that is also restricting the height of
   the chat widget from becoming bigger" -- so it stopped being a full row above
   the panel; then "we should move up a bit so that is half sits inside and half
   outsite as earlier" -- so it straddles rather than floating clear.
   ⚠ `top: 0` WITH THE BLOCK'S 26px OF PADDING IS WHAT MAKES IT HALF AND HALF:
     the control runs 0-52 and the panel starts at 26. */
.chromed__ctl {
	position: absolute;
	top: 0;
	left: var(--wp--custom--space--xl);
	z-index: 7;
	display: flex;
	gap: var(--wp--custom--space--xs);
	padding: var(--wp--custom--space--xs);
	border-radius: var(--wp--custom--rounded--full);
	background: var(--abk-panel);          /* ⛔ opaque -- translucency refused */
	box-shadow: var(--wp--custom--shadow--edge), var(--wp--custom--shadow--depth);
	max-width: calc(100% - var(--wp--custom--space--2-xl));
	overflow-x: auto;
	scrollbar-width: none;
}
/* ⛔ ON A TINTED SECTION THE CONTROL TAKES THE FLOOR, or it is the same colour
   as the panel behind it and stops reading as a separate object. */
.section:not(.section--floor) .chromed__ctl { background: var(--abk-floor); }
.chromed__ctl::-webkit-scrollbar { display: none; }
@media (max-width: 720px) {
	/* At 390 the control scrolls, with a fade at both ends. */
	.chromed__ctl { mask-image: linear-gradient(to right, transparent 0, #000 5%, #000 95%, transparent 100%); }
}
/* ⛔ A TAB IS A REAL CONTROL, so the 44px floor applies to it. */
.chromed__tab {
	flex: none;
	border: 0;
	cursor: pointer;
	font: inherit;
	min-height: 44px;
	padding: 0 16px;
	border-radius: var(--wp--custom--rounded--full);
	background: transparent;
	color: var(--wp--custom--color--secondary);
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--text);
	white-space: nowrap;
	transition: background .16s ease, color .16s ease;
}
.chromed__tab:hover { color: var(--wp--custom--color--heading); }
/* ⭐ THE SELECTED TAB TAKES THE SAME RAMP AS THE CARD, so the switcher and the
   card read as one system rather than two dark things that happen to differ.
   ⚠ It was a flat `ink`; the ramp is a step lighter, which is why the label
     stays `ink-heading` rather than dropping to `ink-body`. */
.chromed__tab[aria-selected="true"] { background: var(--wp--preset--gradient--g-neutral); color: var(--wp--custom--color--ink-heading); }
@media (prefers-reduced-motion: reduce) { .chromed__tab { transition: none; } }

.chromed__panel {
	position: relative;
	border-radius: var(--wp--custom--rounded--lg);
	overflow: hidden;                      /* clips the simulated site */
	background: var(--abk-floor);
	box-shadow: var(--wp--custom--shadow--depth);
}

/* ⭐ THE FRAME TAKES WHAT THE VIEWPORT LEAVES. Alpesh, 2026-09-22: "can we keep
   it height relative to viewports, as the header above it takes all the
   available viewport height?" ⚠ THE SUBTRACTION IS THE FURNITURE ABOVE AND
   BELOW IT, measured on the rendered page: the fixed site header, this
   section's heading (53px) and lead (67px), the section's own padding and the
   note beneath. ⛔ THE CLAMP IS NOT DECORATION -- without a floor a short laptop
   window leaves no site to look at, and without a ceiling a tall monitor makes
   a frame nobody can take in at once. */
.sim { position: relative; overflow: hidden; height: clamp(460px, calc(100vh - 316px), 780px); background: var(--abk-floor); }
/* ⚠ 26px AGAIN. The control straddles the panel's top edge, so its lower half
   lands on the site -- and every simulated site puts its own logo at top left,
   exactly where the control now sits. The site starts below it. `SITE_TOP` in
   the view module matches this. */
/* ⚠ `max-width: none` because the frame is SCALED, not fitted. It is authored
   at 1280px and transformed down; a max-width of 100% would clamp it to the
   panel first and the scale would then shrink an already-shrunk site.
   Measured at 390: 334px wide against the prototype's 1040. */
:root .sim .sim__frame { position: absolute; left: 0; top: 26px; border: 0; display: block; max-width: none; transform-origin: top left; background: var(--abk-floor); }

/* ⛔ THE CAPABILITY STRIP IS OPAQUE, NOT GLASS. Translucency is refused, and
   the reason is measured: glass only shows over something colourful, and our
   floor is plain by choice. */
/* ⭐ A CARD, NOT A BAND, AND IT LIVES ON THE SITE. It used to span the panel's
   full width along the bottom; it now sits bottom-LEFT facing the widget, so
   the site carries two cards -- the assistant, and what it does here. They are
   a pair, both change with the tab, and both survive a full-height frame,
   which a band stacked above the frame would not. */
/* ⭐ THE WRAPPER IS `display: contents` ABOVE 720px, SO NOTHING MOVES THERE.
   The strips left `.sim` in the markup on 2026-09-23; `contents` makes the
   wrapper vanish from the box tree, so each card still positions against
   `.chromed__panel` -- which is `position: relative` and the same box `.sim`
   occupied. ⛔ MEASURED AT 1440 BEFORE AND AFTER THE MOVE: identical. */
.sim__strips { display: contents; }
.sim__strip {
	position: absolute;
	left: var(--wp--custom--space--md);
	bottom: var(--wp--custom--space--md);
	width: min(330px, 46%);
	z-index: 4;
	pointer-events: none;
	display: grid;
	gap: 2px;
	padding: var(--wp--custom--space--md) 0 var(--wp--custom--space--sm);
	border-radius: var(--wp--custom--rounded--md);
	/* ⚠ THE CARD STAYS ON THE PANEL COLOUR, AND THAT WAS A ROUND TRIP. It was
	   built on `g-neutral` after he asked to see it -- "let's implement the dark
	   neutral gradient. Let me see. If I don't like it, we will switch back" --
	   and he switched back: "switch back from the capability card to the
	   previous white BG, and just keep the green bullet points."
	   ⭐ THE RAMP STAYED ON THE SELECTED TAB, which is the half he kept. The
	      two variations remain drawn on the canvas either way. */
	background: var(--abk-panel);
	box-shadow: var(--wp--custom--shadow--depth);
}

/* ⛔ NO TRACKING, AND check-law.js CAUGHT IT. I gave this kicker an uppercase
   0.12em, which is the DISPLAY tier, on text that renders at 11px -- and the
   guard reads tracking off the RENDERED SIZE, never the tag: "p.sim__strip-k
   at 11.0px tracks 0.1200, its tier is 0". ⭐ It took the size token and the
   text tracking that goes with it, so the label is on the ladder like
   everything else. */
.sim__strip-k {
	margin: 0 var(--wp--custom--space--md) 6px;
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	letter-spacing: var(--wp--custom--tracking--text);
	color: var(--wp--custom--color--secondary);
}
/* ⛔ MUST FOLLOW THE `display: grid` ABOVE. A class rule outranks the browser's
   own `[hidden] { display: none }`, so without this every strip paints at
   once and all five stack on top of each other. */
.sim__strip[hidden] { display: none; }
/* ⚠ 6px, NOT 11px, and NO divider. The snips sat side by side in a row and
   carried a vertical hairline between them; stacked, that rule drew a line
   down the left of every line after the first. */
/* ⛔ `display: contents` SO THE DESKTOP PANEL IS GENUINELY UNCHANGED. The
   wrapper exists only to be the scroll container on a phone. `.sim__strip` is
   a GRID with a 2px gap, and a grid's gap applies between ITS OWN children --
   so boxing the three snips turned three grid items into one and the two gaps
   disappeared. ⚠ MEASURED AT 1440: the strip went 212.31px -> 208.31px and the
   three lines sat flush. ⭐ FOUND BY A SEPARATE VERIFIER, against a comment of
   mine that claimed the wrapper was inert at desktop. It was not. */
.sim__snips { display: contents; }
.sim__snip { padding: 6px var(--wp--custom--space--md); }
.sim__snip b {
	display: block; position: relative; padding-left: 14px;
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--text);
	color: var(--wp--custom--color--heading);
}
.sim__snip b::before {
	content: ""; position: absolute; left: 0; top: 0.55em;
	width: 5px; height: 5px; border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--brand);
}
.sim__snip span {
	display: block; padding-left: 14px; margin-top: 2px;
	font-size: var(--wp--custom--step--label);
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--text);
	color: var(--wp--custom--color--secondary);
}
@media (max-width: 720px) {
	/* ⛔ 340px MADE THE ASSISTANT UNUSABLE, AND THE BULLETS WERE NOT THE CAUSE.
	   Alpesh, 2026-09-24: "the chat widget height has become too small. It is
	   not usable. The reason for that is these three bullet points that are
	   coming below." ⚠ MEASURED AT 390 AND THE CAUSE IS ELSEWHERE: the widget
	     is `position: absolute` INSIDE `.sim`, so it never shared space with
	     anything below it. `.sim` was 340px, the widget was capped at 220px,
	     and `.wgt__log` came out at 46px -- ONE LINE. Freeing the bullets would
	     have shortened the section and left the conversation exactly as it was.
	   ⭐ SO THE TWO NUMBERS MOVE, AND HE RE-MADE THE TRADE THEY ENCODE. The cap
	      existed so the fake shop stayed visible behind the widget -- the shop
	      IS the argument. He chose the conversation: about 80px of shop instead
	      of 120, and a log near 246px instead of 46. */
	.sim { height: 520px; }
	/* ⭐ NOW IT REALLY IS A LIST UNDER THE FRAME. The wrapper stops being
	   `contents` and becomes the block the cards sit in, AFTER `.sim` -- which
	   is what the old rule intended and could not reach from inside `.sim`. */
	.sim__strips { display: block; padding: var(--wp--custom--space--md); }
	.sim__strip { position: static; width: auto; padding: 0; background: none; box-shadow: none; pointer-events: auto; }
	/* ⭐ THE HEADING COMES BACK. It was hidden because the list had no panel of
	   its own to head; below the frame it does, and without it three bullets
	   arrive with nothing saying what they are. */
	.sim__strip-k { display: block; margin: 0 0 var(--wp--custom--space--xs); }
	.sim__snip { padding-inline: 0; }
	/* ⛔ THE THREE LINES SCROLL SIDEWAYS, AS THE TESTIMONIALS DO. Alpesh,
	   2026-09-24: "instead of showing these three bullet points vertically, we
	   can show them as scrollable cards horizontally, as we have done for the
	   testimonial section."
	   ⚠ IT IS NOT WHAT FIXES THE WIDGET, AND HE KNOWS THAT NOW -- see `.sim`
	     above. It is worth doing on its own: three stacked bullets cost a third
	     of a screen and a scrolled row costs one line of it.
	   ⭐ THE DEVICE IS `.drift`'s, NOT A NEW ONE. Same flex row, same hidden
	      scrollbar, same TRAILING-ONLY mask -- a leading fade ate the first
	      character of every row when `.drift` was built, and it would here too.
	   ⚠ THE HEADING STAYS PUT. Only the snips move, so `.sim__strip` becomes a
	     grid whose second row is the scroller; the heading is not a card. */
	.sim__strip { display: grid; gap: var(--wp--custom--space--xs); }
	.sim__snips {
		display: flex;
		/* ⚠ overrides the `display: contents` the wrapper carries by default */
		gap: var(--wp--custom--space--md);
		overflow-x: auto;
		scrollbar-width: none;
		-webkit-mask-image: linear-gradient(to right, #000 0%, #000 90%, transparent 100%);
		mask-image: linear-gradient(to right, #000 0%, #000 90%, transparent 100%);
	}
	.sim__snips::-webkit-scrollbar { display: none; }
	/* ⚠ 78% AND NOT 100%: the next card must be visibly cut, or nothing says
	   the row scrolls. `.drift` uses 82% against wider cards. */
	.sim__snips > .sim__snip { flex: 0 0 78%; }
	/* ⭐ THE OVERFLOW IS VISIBLE NOW. The strip scrolls -- 572px of tabs in a
	   286px box, measured at 390 -- and said nothing, so two of the five
	   industries were unreachable unless a reader guessed. ⛔ A mask, because
	   the law already names one: a cut edge is a mask-image, never a gradient
	   overlay painted in the ground colour. */
	.chromed__ctl {
		-webkit-mask-image: linear-gradient( to right, #000 calc(100% - 32px), transparent );
		mask-image: linear-gradient( to right, #000 calc(100% - 32px), transparent );
	}
}

/* ⭐ OUR PRODUCT, FLOATING ON THEIR SITE. It is `.wgt`, unchanged. */
/* ⚠ NO `width` OF ITS OWN. The prototype declares 336px here and it is DEAD:
   `.wgt` is defined later in that stylesheet and its `width: 100%` wins, so
   the widget paints at its 380px cap. Measured, and the paint is what Alpesh
   approved. */
/* ⭐ IT FILLS THE FRAME IT ALREADY SAT IN. Measured before this change: the
   widget was 380x322 inside a 568px frame -- 246px of slack, unused, while the
   tab row above and the band below were being blamed for the squeeze. Top and
   bottom now, so it takes the frame's height and grows with the viewport.
   ⛔ ~~AND IT OVERLAPS THE SITE'S RIGHT EDGE~~ -- WITHDRAWN, AND IT NEVER
      WORKED. I hung it 16px past the edge so it would read as being IN FRONT
      without a scrim, reported the overhang as built, and had measured only
      the BOX: getBoundingClientRect() returns the geometry whether or not the
      pixels paint. ⛔ `.chromed__panel` is `overflow: hidden`, so the overhang
      was CLIPPED -- the widget simply had its right edge cut off. Alpesh saw
      it: "the chat widget right edge is outside the frame it is getting cuttof,
      it should move in a it liitle bit of maign on right".
   ⚠ A BOX IS NOT A PAINT, and that is the third time this class of mistake has
     cost a round today.
   ⭐ SO THE LIFT IS THE SHADOW AND THE RING, and nothing else: the tight
      three-layer pair deepened one step, which is the drawing's own lift
      language rather than a new one. */
/* ⛔ IT HUGS ITS CONTENT AND IS CAPPED -- IT IS NOT STRETCHED. 2026-09-23.
   ⚠ `top` AND `bottom` TOGETHER STRETCHED IT TO THE FRAME: measured at 1440,
     400x732 holding a greeting bubble, three chips and a composer -- about
     600px of blank white in the middle of the page's most-looked-at object.
     The rule that did it was written to stop the OPPOSITE fault, a widget
     sitting in 246px of unused slack, and it overshot.
   ⭐ ALPESH ASKED FOR A MAX HEIGHT AND THE MAX HEIGHT ALONE DOES NOTHING --
      520px and 440px were both painted and NEITHER BOUND, because a stretched
      box is already shorter than any cap you would want. Releasing `top` is
      what collapses it: measured 732 -> 319, with no constant invented.
   ⭐ THEN THE CAP DOES THE JOB HE MEANT IT FOR. A conversation grows the log,
      and this stops the widget outgrowing the frame it floats on. */
.sim__widget {
	position: absolute;
	right: var(--wp--custom--space--lg);
	top: auto;
	bottom: var(--wp--custom--space--lg);
	max-height: calc( 100% - var(--wp--custom--space--xl) );
	width: min(400px, 46%);
	z-index: 6;
	display: flex;
	flex-direction: column;
	box-shadow: 0 28px 60px -18px oklch(0.153 0.006 107.1 / 0.34),
		0 6px 16px -6px oklch(0.153 0.006 107.1 / 0.16),
		0 0 0 1px oklch(0.153 0.006 107.1 / 0.10);
}
/* ⛔ `max-width: none` OR IT PAINTS AT 380. `.wgt` caps itself at 380px and is
   declared later in this file, so the width above would be ignored. */
:root .sim .sim__widget { max-width: none; }
/* the conversation takes whatever the bar, the chips and the footer leave.
   ⛔ `align-content: start` OR EVERY BUBBLE STRETCHES. `.wgt__log` is a GRID,
   and a grid's default `align-content` is `stretch` -- so with the log given a
   height of its own, one greeting filled the entire window and the user's first
   question came out as a green slab. Alpesh: "the first greeting bubble is too
   big. There is a lot more white space there." ⭐ Rows now take their content's
   height and stack from the top, which is also where a chat writes. */
.sim__widget .wgt__log { flex: 1 1 auto; overflow-y: auto; align-content: start; }
/* ⭐ THE BAR TAKES THE BRAND GREEN, AND THAT REVERSES AN EARLIER CALL OF HIS.
   The `.wgt__bar` rule records that "the reference's header is a green
   GRADIENT; our law refuses gradients, and Alpesh chose white over a flat
   green, so the widget reads as OURS." He has now asked for the flat green
   here: "this header bg we should keep brand green and the text as white."
   ⭐ A FLAT GREEN WAS NEVER WHAT THE LAW REFUSED -- the GRADIENT was. The white
      was a preference, and this is the same person changing it.
   ⛔ SCOPED TO THE SIMULATOR'S WIDGET. The page runs one widget vocabulary and
      the drawn widgets elsewhere keep their white bar; this one sits on a
      CUSTOMER'S SITE and has to be unmistakably ours. */
:root .sim__widget .wgt__bar {
	background: var(--wp--custom--color--brand);
	color: var(--wp--custom--color--paper);
	box-shadow: none;
}
/* ⭐ THE ROLE IS THE NAME NOW, so it stops being the quiet half of a pair. */
:root .sim__widget .wgt__role { font-weight: var(--wp--custom--weight--emphasis); color: var(--wp--custom--color--paper); }
/* ⚠ AND THE DISC INVERTS, or a brand-green disc on a brand-green bar vanishes. */
:root .sim__widget .wgt__av { background: oklch(1 0 0 / 0.22); color: var(--wp--custom--color--paper); }
/* the initial disc, in place of the green "live" dot */
.wgt__av {
	flex: none;
	width: 28px;
	height: 28px;
	border-radius: var(--wp--custom--rounded--full);
	background: var(--wp--custom--color--brand);
	color: var(--wp--custom--color--paper);
	display: grid;
	place-items: center;
	font-size: 12px;
	font-weight: var(--wp--custom--weight--emphasis);
}
@media (max-width: 720px) {
	/* ⛔ BELOW 720 IT IS AN OVERLAY AGAIN, not a full-height column: the site
	   must stay visible behind it, because the site IS the argument. */
	/* ⛔ `height`, NOT `max-height`. A cap only stops the box GROWING, and this
	   widget is sized by its content, so raising the cap from 220 to 420 moved
	   it to 291px -- the height of the conversation so far, not the height of a
	   window. ⚠ MEASURED: the log went 46 -> 117px and stopped, because nothing
	     was asking for the rest. ⭐ A stated height makes the log's `flex: 1`
	     meaningful, and the window stops resizing as lines arrive. */
	.sim__widget { left: var(--wp--custom--space--sm); right: var(--wp--custom--space--sm); top: auto; bottom: var(--wp--custom--space--sm); width: auto; height: 420px; max-height: calc(100% - var(--wp--custom--space--md)); }
	/* ⛔ The site must stay visible behind the widget -- it IS the argument. */
	/* ⚠ NO `max-height` ANY MORE. The widget has a stated height, so `flex: 1`
	   on the log gives it exactly what the bar, the chips and the input row do
	   not use -- about 246px at 390. A second cap here would fight it. */
	/* One scrolling row, not three stacked.
	   ⛔ `flex: none` SO THE CONTROL ROW IS NEVER SQUASHED. The widget is a
	   fixed 220px column, so the browser shrinks whatever it can -- and it
	   was shrinking the CHIPS, to 15.4px tall against their natural 30.5px.
	   ⚠ The prototype has the same fault; measured there too. A conversation
	   can scroll, a control cannot be compressed to half its height. */
	.sim__widget .wgt__chips { flex: none; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; mask-image: linear-gradient(to right, #000 0, #000 88%, transparent 100%); }
	.sim__widget .wgt__chips::-webkit-scrollbar { display: none; }
	.sim__widget .wgt__chip { flex: none; }
}

/* ⛔ THE MESSAGE'S SIDE COMES FROM A CONTEXT VALUE, NOT A BOUND CLASS. A
   per-element class bind needs a derived value and PHP cannot evaluate one,
   so `data-who` carries it -- a plain context value the server resolves. The
   `--bot` / `--user` classes stay for the four drawn widgets elsewhere. */
.wgt__msg[data-who="bot"],
.wgt__msg[data-who="typing"] { background: var(--wp--custom--color--tint); color: var(--wp--custom--color--heading); justify-self: start; border-bottom-left-radius: var(--wp--custom--rounded--sm); }
.wgt__msg[data-who="user"] { background: var(--wp--custom--color--brand); color: var(--wp--custom--color--paper); justify-self: end; border-bottom-right-radius: var(--wp--custom--rounded--sm); }
/* The capture line is the widget's own state row rather than a message. */
.wgt__msg[data-who="state"] {
	background: var(--wp--custom--color--tint-brand);
	color: var(--wp--custom--color--brand-text);
	justify-self: stretch;
	max-width: none;
	border-radius: var(--wp--custom--rounded--sm);
	font-size: 12px;
	font-weight: var(--wp--custom--weight--emphasis);
	display: flex; align-items: center; justify-content: space-between; gap: var(--wp--custom--space--sm);
}
.wgt__msg span[data-w="b"] { font-weight: var(--wp--custom--weight--display); }
.wgt__msg span[data-w="i"] { font-style: italic; }
/* ⛔ THE DEMO IS LABELLED AS A DEMO, INSIDE THE DRAWING. A bare "Lead
   captured" line with nothing sent anywhere is a claim about an OUTCOME. */
.wgt__msg span[data-w="tag"] { font-size: 11px; font-weight: var(--wp--custom--weight--body); color: var(--wp--custom--color--secondary); flex: none; }

/* ⛔ THE CHIPS DRIVE STATE, so they ARE controls -- unlike the drawn footer
   beneath them, which is part of the picture. */
.wgt__chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 var(--wp--custom--space--md) var(--wp--custom--space--md); }
/* ⛔ 44px, AND THE GUARD IS THE REASON. These chips drive state, so they ARE
   controls, and `ops/measure/check-law.js` reads every control against a 44px
   floor in both directions. At their old 5px padding they measured about 30px
   and only escaped because the guard exempts what is inside a drawing -- which
   is the wrong exemption for something you click. */
.sim__widget .wgt__chip { min-height: 44px; padding-block: 0; }
.wgt__chip {
	/* ⛔ `font-family: inherit` OR THE CHIP PAINTS IN ARIAL. A `<button>` does
	   not inherit the family, and the prototype set it through the `font:`
	   shorthand -- which I split into longhands and lost. ⚠ Measured on the
	   rendered chip: `font-family: Arial`, and the text came out 4px narrower,
	   which changed how the row wrapped. ⭐ The same class of defect as the
	   impostor font faces in U2: the size and weight were right and the
	   LETTERFORMS were not. */
	font-family: inherit;
	font-size: var(--wp--custom--step--label);
	font-weight: var(--wp--custom--weight--emphasis);
	line-height: var(--wp--custom--leading--body);
	letter-spacing: var(--wp--custom--tracking--text);
	padding: 5px 11px;
	border-radius: var(--wp--custom--rounded--full);
	border: 0;
	cursor: pointer;
	background: var(--abk-floor);
	color: var(--wp--custom--color--brand-text);
	box-shadow: inset 0 0 0 1px var(--wp--custom--color--hairline);
}
.wgt__chip:hover { background: var(--wp--custom--color--tint-brand); }
.wgt__chip.is-off { display: none; }

/* The three dots while it thinks. */
.wgt__msg[data-who="typing"] { display: flex; align-items: center; gap: 5px; min-height: 22px; }
.wgt__msg[data-who="typing"]::before,
.wgt__msg[data-who="typing"]::after,
.wgt__msg[data-who="typing"] > i {
	content: ""; width: 5px; height: 5px; border-radius: var(--wp--custom--rounded--full);
	background: currentColor; animation: abk-wgt-dot 1.2s infinite ease-in-out;
}
.wgt__msg[data-who="typing"]::after { animation-delay: .15s; }
@keyframes abk-wgt-dot {
	0%, 60%, 100% { opacity: .25; transform: translateY(0); }
	30%           { opacity: 1;   transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
	.wgt__msg[data-who="typing"]::before,
	.wgt__msg[data-who="typing"]::after { animation: none; opacity: .55; }
}

:root .section .sim__note { margin-top: var(--wp--custom--space--xl); }
