/*
 * Fluent Commerce 2026 design system — the composed pages' compositions.
 *
 * layout.css is the ENGINE: the container, the fluid ramps, the grids, the type
 * roles, the buttons, and the primitives another page would also reach for. This
 * file is the pages' COMPOSITIONS — the capability list, the bento, the diagram
 * panels, the rolling vertical list, the routes, the report gate. The test for
 * which file a rule belongs in is whether a further page could plausibly use it,
 * not which page it first appeared on.
 *
 * Four pages so far: home, Fluent Order Management, the analyst report landing
 * page, and the Gartner report offer. Each has its own banner below; anything
 * above the first banner is shared by more than one of them.
 *
 * ── WHY THERE IS NO pages.js ────────────────────────────────────────────────
 *
 * The Claude Design export drives five things from JavaScript. All five are CSS
 * here, and the reasons are worth stating because each one is a decision:
 *
 *   1. The seven fixed-canvas drawings. The export measures each panel with a
 *      ResizeObserver and sets a --scale custom property. Here the panel is an
 *      inline-size container and the scale is
 *          scale: tan(atan2(100cqw, 640px))
 *      CSS trig lets you divide two lengths — atan2 takes same-type arguments and
 *      tan undoes it — so that expression IS "panel width ÷ authored width" with
 *      no script. The drawings therefore stay in the px the design authored them
 *      in; nothing was rewritten into em or a --u unit.
 *   2. The FAQ accordion → <details>/<summary>, with ::details-content for the
 *      open/close transition where the browser has it.
 *   3. The capability bento's tile selector → seven radios and seven labels, and
 *      :checked ~ to reveal the matching panel.
 *   4. The specialty roll → a scroll container, so a touch drag comes free and the
 *      list is reachable with nothing running. The slow drift and the grab-and-pull
 *      a mouse needs are the parts that need a script; see assets/ds/roll.js.
 *   5. The capability rows' sleeve reveal → :hover on the row.
 *
 * Beyond the nav's, the JavaScript these two pages ship is decoration that has to
 * be optional — see cardart.js, video.js, rail.js and roll.js. Every one of them
 * is written so that failing to load leaves the design finished rather than half
 * built, and none of them is load-bearing for layout or for reading the page.
 *
 * ── BREAKPOINTS ─────────────────────────────────────────────────────────────
 *
 * @container page, not @media, for the same reason as layout.css: the page is a
 * container, not a viewport, so the editor and a 390px preview reflow correctly.
 * The export's responsive.css mixes in plain @media; those are converted here.
 * Three stay as @media on purpose — (max-height: 640px) for the folds, because a
 * container query cannot read height without container-type: size,
 * (prefers-reduced-motion: reduce), and the specialty roll's (pointer: coarse).
 * None of those three is asking about width, which is the only thing the page
 * container knows.
 *
 * @package Fluent_Commerce
 */

/* ═════════════════════════════════════════════════════════════════════════════
 * SHARED — used by both pages
 * ══════════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────────────────────
 * THE CENTRED SECTION HEAD
 * Both pages open most sections the same way: a serif headline over one paragraph,
 * centred, with a CTA sometimes under it. The only thing that varies between the
 * nine of them is how wide each is allowed to be — so that is the only thing
 * stated per section, as --shead-title / --shead-lead on the wrapper.
 *
 * 1.05 rather than the display default of 1: these headlines break over two or
 * three lines and a flush leading closes them up too tightly at 62px.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-shead__title {
	max-width: var(--shead-title, none);
	margin-inline: auto;
	line-height: 1.05;
	text-wrap: pretty;
}

body.fc-ds .fc-shead__lead {
	max-width: var(--shead-lead, 620px);
	margin-inline: auto;
	line-height: var(--lh-loose);
	color: var(--text-on-light-muted);
	text-wrap: pretty;
}

/*
 * The three heads that are not 620 wide. These are classes rather than inline
 * `style="--shead-lead:420px"` because the heads are core/group blocks inside a
 * locked template, and Gutenberg treats an unrecognised attribute on a block's
 * wrapper as invalid content — the editor would offer to "recover" the block by
 * throwing the declaration away. A class it already knows about survives.
 */
body.fc-ds .fc-shead--why {
	--shead-lead: 420px;
}

body.fc-ds .fc-shead--flexible {
	--shead-title: 820px;
	--shead-lead: 700px;
}

body.fc-ds .fc-shead--teams {
	--shead-title: 720px;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * MARQUEE ITEMS
 * The band and the track are primitives (layout.css). Only the ink surface and
 * the item's type live here.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-marquee-band--ink {
	background: var(--surface-dark);
}

body.fc-ds .fc-marquee__item {
	color: var(--text-on-dark);
	white-space: nowrap;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE IMAGE BANDS
 * Two of them: the waves band on the home page and the noise band on the OMS
 * page. Both are .fc-band (layout.css) with the source image's ratio declared as
 * --band-w / --band-h.
 *
 * The export's vertical padding is a percentage — 6% of the band's own width — so
 * the copy sits at the same optical height as the artwork's horizon at every
 * size. That holds down to about 1100 and then goes thin, so it is floored at
 * --sp-54.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-band--waves {
	--band-w: 3840;
	--band-h: 1252;
	background: var(--surface-dark);
}

body.fc-ds .fc-band--waves .fc-band__inner {
	padding: max(6%, var(--sp-54)) var(--gutter-page);
	color: var(--text-on-dark);
}

body.fc-ds .fc-band--waves h2 {
	color: var(--text-on-dark);
}

body.fc-ds .fc-band--waves p {
	max-width: 620px;
	color: var(--text-on-dark-muted);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE WORDMARK BAND
 * The logotype cropped by the fold of the page — 98% wide and pulled 6% below
 * its own box, so the descender is cut by the footer rather than sitting on it.
 * Percentages, so the crop is identical at every width.
 *
 * The filter is the export's: it flattens whatever fill the SVG ships with down
 * to near-ink, so the file can be replaced without touching this rule.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-wordmark-band {
	display: flex;
	justify-content: center;
	padding-top: var(--pad-section);
	overflow: hidden;
	background: var(--surface-light);
}

body.fc-ds .fc-wordmark-band__img {
	display: block;
	width: 98%;
	margin-bottom: -6%;
	filter: brightness(0) saturate(100%) invert(7%);
}

/*
 * The band grounds itself on bone, because that is the surface the two long pages
 * end on. The report page ends on white, and a bone band under a white section
 * would draw a line across the page exactly where there is meant to be none.
 */
body.fc-ds .fc-wordmark-band--white {
	background: var(--surface-lightest);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE CUSTOMER PROOF WALL
 * A 6 × 3 tile wall that scrolls sideways forever. Three things make the loop
 * seamless and they are a set — change one and it seams:
 *
 *   · the row is TWO identical six-column loops (12 tiles emitted twice),
 *   · the animation travels exactly -50%,
 *   · the column unit is a fixed 224px and the 16px gutter is the TILE's
 *     margin-right, not the grid's column-gap — a column-gap would not be part
 *     of the repeating unit, so one loop would measure 6 × 224 + 5 × 16 and -50%
 *     would land 16px off the seam.
 *
 * The section is its own inline-size container so the edge fades can be sized in
 * cqw against the wall rather than the page.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-proof {
	container-type: inline-size;
	overflow: hidden;
	background: var(--surface-light);
}

body.fc-ds .fc-proof__standfirst {
	max-width: 720px;
	margin: 0 auto;
	color: var(--text-on-light-muted);
	text-align: center;
	text-wrap: pretty;
}

body.fc-ds .fc-proof__viewport {
	position: relative;
	margin: var(--sp-54) calc(var(--gutter-page) * -1) 0;
}

body.fc-ds .fc-proof__mask {
	overflow: hidden;
}

body.fc-ds .fc-proof-row {
	display: grid;
	grid-template-rows: repeat(3, 96px);
	grid-auto-columns: 224px;
	grid-auto-flow: column;
	row-gap: var(--sp-16);
	column-gap: 0;
	flex: none;
	width: max-content;
	animation: fcProofRow 96s linear infinite;
}

@keyframes fcProofRow {
	from {
		transform: translateX(0);
	}

	to {
		transform: translateX(-50%);
	}
}

body.fc-ds .fc-proof-tile {
	display: flex;
	align-items: center;
	gap: var(--sp-16);
	grid-column: var(--col, auto);
	grid-row: var(--row, auto);
	margin-right: var(--sp-16);
	padding: 0 20px;
	overflow: hidden;
	background: var(--surface-lightest);
	border: var(--stroke-1) solid var(--border-on-light);
	transition: border-color var(--dur-fast) var(--ease-out);
}

body.fc-ds .fc-proof-tile:hover {
	border-color: rgba(18, 18, 18, 0.5);
}

/*
 * ── THE THREE RULES BELOW ARE DORMANT ────────────────────────────────────────
 *
 * The tiles are plain <div>s, so `a.fc-proof-tile` matches nothing and the pause has
 * nothing to make clickable. They are kept, and kept together, because they are the CSS
 * half of one switch: fluent_commerce_ds_proof_links() in inc/ds-blocks.php is false, and
 * these three rules plus the six slugs behind it are what that `false` is holding off.
 * Six of the twelve tiles were anchors for one day, 2026-08-26 to 2026-08-27.
 *
 * Deleting them would cost nothing today and would cost the whole reasoning below the day
 * anyone turned the links back on — which is the reasoning that stopped the first attempt
 * being a wall of links nobody could hit.
 */

/*
 * Two declarations, both undoing the UA:
 *
 *   · text-decoration INHERITS into descendants, so a link's underline would draw
 *     under the quote and the metric label even though both set their own colour.
 *     Every piece of type in a tile is muted ink on purpose; nothing here is meant
 *     to read as a link, because the whole tile is the link.
 *   · colour, for the same reason in the other direction — the mark is a background
 *     image and the words all state a colour, so this only catches a future child
 *     that does not, rather than fixing anything visible today.
 *
 * cursor: pointer is not stated; an <a href> already has it, and the border darkening
 * above is the hover state both forms of the tile share.
 */
body.fc-ds a.fc-proof-tile {
	color: inherit;
	text-decoration: none;
}

/*
 * The keyboard gets what the pointer gets. The violet ring itself comes from the
 * global :focus-visible in tokens.css; this is only the border half of the hover
 * state, so a tile reached by tab looks like a tile under the cursor.
 */
body.fc-ds a.fc-proof-tile:focus-visible {
	border-color: rgba(18, 18, 18, 0.5);
}

/*
 * ── THE WALL STOPS WHILE YOU ARE AIMING AT IT ────────────────────────────────
 *
 * A 224px tile crossing the viewport on a 96-second loop is not a target anyone can
 * click on purpose, so the animation pauses on hover and on focus-within. That rule is
 * what made the links usable, and it is the reason the wall could not simply be given
 * hrefs and left alone.
 *
 * IT IS GATED ON .fc-proof-row:has(a) so that it goes off with the links. A marquee that
 * freezes under the pointer and offers nothing to click reads as broken rather than as
 * considerate — and the pause was never a treatment anyone asked for, only the price of
 * the links. With every tile a <div> the row has no anchor, the selector does not match,
 * and the wall runs continuously as it did before 2026-08-26.
 *
 * It pauses rather than stopping: animation-play-state keeps the row exactly where it
 * is, so nothing jumps under the pointer at the moment it arrives. It resumes from the
 * same place on the way out.
 *
 * On the viewport rather than the row, because the row is what animates — a :hover on
 * an element cannot pause that element's own animation without the hover target moving
 * with it. The viewport is stationary and is the box the pointer is actually in. The
 * :has() therefore sits on the ROW in the subject position, not on the viewport: it asks
 * "this row, if it contains a link", which is the row the rule already applies to.
 *
 * :focus-within covers the keyboard: tabbing into a moving row would otherwise scroll
 * the focused link out from under itself.
 */
body.fc-ds .fc-proof__viewport:hover .fc-proof-row:has(a),
body.fc-ds .fc-proof__viewport:focus-within .fc-proof-row:has(a) {
	animation-play-state: paused;
}

/*
 * The mark is a background image on an empty box with role="img", not an <img>:
 * twelve logos at twelve intrinsic sizes have to be normalised to one optical
 * weight, and background-size: contain against a per-logo height is the only way
 * to do that without editing the files.
 */
body.fc-ds .fc-proof-tile__mark {
	flex: var(--mark-flex, 1 1 auto);
	height: var(--mark-h, 24px);
	background-image: var(--mark);
	background-position: var(--mark-x, center) center;
	background-repeat: no-repeat;
	background-size: contain;
}

/* A tile that also carries words gives the mark a third of the width, left-set. */
body.fc-ds .fc-proof-tile__mark--rich {
	--mark-flex: 0 0 34%;
	--mark-x: left;
}

body.fc-ds .fc-proof-tile__quote {
	flex: 1 1 55%;
	font-family: var(--font-quote);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-15);
	line-height: var(--lh-body);
	letter-spacing: var(--ls-quote);
	color: var(--text-on-light-muted);
	text-wrap: pretty;
}

body.fc-ds .fc-proof-tile__metric {
	flex: 1 1 50%;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 4px;
}

body.fc-ds .fc-proof-tile__value {
	font-family: var(--font-quote);
	font-weight: var(--fw-semibold);
	font-size: var(--type-numeral-24);
	line-height: var(--lh-flush);
	letter-spacing: var(--ls-quote);
	color: var(--fc-ink-a60);
	white-space: nowrap;
}

body.fc-ds .fc-proof-tile__label {
	font-size: var(--type-body-15);
	font-weight: var(--fw-medium);
	line-height: var(--lh-snug);
	color: var(--fc-ink-a60);
	text-wrap: pretty;
}

/*
 * Edge fades in the surface colour, never a scrim — the wall has to end without
 * a visible edge, and a black gradient over bone would grey the tiles.
 */
body.fc-ds .fc-proof__fade {
	position: absolute;
	top: 0;
	bottom: 0;
	width: clamp(80px, 20cqw, 407px);
	pointer-events: none;
}

body.fc-ds .fc-proof__fade--left {
	left: 0;
	background: linear-gradient(90deg, var(--fc-bone) 0%, rgba(242, 241, 237, 0) 100%);
}

body.fc-ds .fc-proof__fade--right {
	right: 0;
	background: linear-gradient(270deg, var(--fc-bone) 0%, rgba(242, 241, 237, 0) 100%);
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE HOME PAGE
 * ══════════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────────────────────
 * THE HERO
 * A full fold of magnetic-field artwork with the copy centred on it. The kicker
 * is the one place in the whole site where the mono face carries a spoken line
 * rather than a machine string, and it gets a white glow instead of a scrim so
 * the artwork stays legible behind it.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-home-hero {
	background: var(--fc-ink-10);
	/*
	 * THE ARTWORK IS HELD BACK SO THE HEADLINE READS OVER IT.
	 *
	 * Ported from .fc-home-hero in assets/home.css, where this was settled against
	 * the Figma renders: the field layer renders at full strength on its own —
	 * near-white at the crest — but visibly muted inside the Hero frame, and the
	 * frame has no scrim rectangle of its own, so the difference is the layer's own
	 * opacity. 0.6 was the estimate matched against the two renders (the node's
	 * opacity is not exposed over MCP); it was taken to 0.45 deliberately, so the
	 * white headline holds its contrast over the brightest part of the crest.
	 *
	 * Over a near-black ground an opacity reduction and a black overlay of the
	 * inverse alpha are the same arithmetic, so this does it with opacity: one
	 * property, no extra layer to paint, and it reads as what it is. The ground here
	 * is --fc-ink-10 (#0a0a0a) rather than the legacy --fc-void (#000), which is
	 * four RGB units of difference at 55% — not a visible one.
	 *
	 * A custom property rather than two literals because BOTH layers have to be held
	 * back by the same amount. They are two renders of one picture, and the film
	 * cross-fades in over the still: if the values ever drift apart, the hero
	 * brightens or dims when playback starts, which is the one thing the still-under-
	 * film construction exists to avoid.
	 */
	--fc-hero-visual-opacity: 0.45;
}

/*
 * BOTH LAYERS OF THE HERO ARTWORK ARE ANCHORED TO THE BOTTOM.
 *
 * The field is a composition on a horizon: the peaks stand on a floor near the
 * foot of the frame and everything above them is black. The still and the film are
 * two renders of it at different heights — 1920×1018 and 1920×1270 — and the fold
 * is wider than either, so `cover` crops height off both. Centred, it would take
 * 3% off the still and 17% off the film: the same picture with its horizon in two
 * different places, and the film's floor cut away.
 *
 * Anchoring both to the bottom pins the horizon to the foot of the fold and spends
 * the whole crop on the black headroom, where there is nothing to lose. It is also
 * what the legacy hero did — assets/home.css sets object-position: center
 * calc(100% + 50px) on its video. The 50px is not ported; it was pushing against
 * that hero's fixed height, and this one is a fold.
 */
body.fc-ds .fc-home-hero .fc-cover,
body.fc-ds .fc-home-hero .fc-motion {
	object-position: center bottom;
}

body.fc-ds .fc-home-hero .fc-cover {
	opacity: var(--fc-hero-visual-opacity);
}

/*
 * The film fades in TO the held-back value, not to 1.
 *
 * .fc-motion is already using opacity for its own job — video.js adds .is-playing
 * and layout.css transitions 0 -> 1 — so this cannot be set on .fc-motion itself
 * without either fighting that rule or making the film appear at 0.45 before it
 * has a frame to show. Overriding the END of the fade instead leaves the cross-fade
 * intact and lands it exactly on the still's opacity, so the handover stays
 * invisible: 0 -> 0.45 over a still already sitting at 0.45.
 *
 * Scoped to the hero on purpose. The globe in the two-up is the same construction
 * but a different composition — it is artwork on a light surface with no copy over
 * it, so it has nothing to hold back for and keeps fading to full strength.
 */
body.fc-ds .fc-home-hero .fc-motion.is-playing {
	opacity: var(--fc-hero-visual-opacity);
}

body.fc-ds .fc-home-hero .fc-hero-copy {
	padding: 7% var(--gutter-page) 0;
}

body.fc-ds .fc-home-hero__kicker {
	font-family: var(--font-mono);
	font-size: var(--type-body-18);
	letter-spacing: var(--ls-label);
	text-transform: uppercase;
	color: var(--fc-white);
	text-shadow: 1px 1px 4px var(--fc-white-a29);
	font-variant-ligatures: none;
}

body.fc-ds .fc-home-hero__title {
	max-width: 820px;
	line-height: var(--lh-tight);
	color: var(--fc-white);
}

body.fc-ds .fc-home-hero__lead {
	max-width: 640px;
	color: var(--fc-white-a80);
	text-wrap: pretty;
}

/* 24 between the copy lines, 32 before the button — so the button gains 8. */
body.fc-ds .fc-home-hero .fc-btn {
	margin-top: var(--sp-8);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE TWO PLAIN COPY COLUMNS
 * "You’ll wonder how you managed without it." and "Move fast. See value faster."
 * are the same construct — a serif headline in the left half of a .fc-split and a
 * narrow column of muted body in the right. Only the measure differs, and the two
 * sections sit far enough apart on the page to be worth naming separately.
 *
 * The measures are classes rather than inline custom properties for the reason
 * given at .fc-shead--why: these columns are core/group blocks in a locked
 * template, and a style attribute Gutenberg did not put there is invalid content.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-home-conduct__copy {
	max-width: 460px;
	color: var(--text-on-light-muted);
}

body.fc-ds .fc-home-value__copy {
	max-width: 560px;
	color: var(--text-on-light-muted);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE INK SECTION
 * Two split rows on the dark surface: the globe and the eclipse. The tone is set
 * on the section rather than per element because everything inside it is either a
 * headline or body copy, and .fc-btn--light already carries its own.
 *
 * Only the FIRST headline takes a measure — "Of course we have AI." is short
 * enough that a max-width would be a rule with nothing to do.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-home-signal {
	background: var(--surface-dark);
}

body.fc-ds .fc-home-signal h2 {
	color: var(--text-on-dark);
}

body.fc-ds .fc-home-signal p {
	color: var(--text-on-dark-muted);
}

/* The export narrows both rows from the split's default gap to 20. */
body.fc-ds .fc-home-signal__row {
	gap: 20px;
}

body.fc-ds .fc-home-signal__row + .fc-home-signal__row {
	margin-top: var(--gap-blocks);
}

/*
 * THE ECLIPSE VIGNETTE
 * The sphere's film fills its box edge to edge, so the particle field ran out at four
 * straight lines and the picture read as a rectangle of video dropped onto the section
 * rather than as something happening in the dark. The design closes it with a round
 * ink falloff over the media, and this is that layer: transparent across the middle,
 * the surface's own #121212 by the time it reaches the frame.
 *
 * The stops are the design's, converted from its fill:
 *
 *   radial-gradient(39.47% 50% at 50% 50%, rgba(18,18,18,0) 60.39%,
 *                   rgba(18,18,18,0.38) 79.73%, #121212 99.1%)
 *
 * `circle closest-side` in place of the 39.47%/50% pair. In the design's 743×561 frame
 * those radii are 293px and 280.5px — a circle to within four per cent, and the
 * vertical one is exactly half the height, which is what closest-side computes. Written
 * that way it stays a circle in our box, whose aspect is not the design's: percentage
 * radii would have stretched the falloff into an ellipse that tracks the column width.
 *
 * --fc-ink-a40 for the middle stop, 0.4 against the design's 0.3849. Under two units of
 * alpha inside a gradient nobody can see the difference, and the token says what the
 * colour is; the two literal ink values there would not.
 *
 * ::after on the frame rather than a mask on the media: the frame wraps BOTH layers —
 * the still and the film that dissolves onto it — so one element vignettes both and the
 * cross-fade underneath is untouched. It is also why this is not scoped to the video:
 * the still is the same rectangle and had the same edge.
 *
 * Second row only, and by position rather than by a class of its own, because the
 * markup lives in the page's post_content: a class would mean re-seeding the home page
 * on three environments to land a gradient. The globe above is deliberately left alone —
 * its still is authored with clean black margins and does not reach its frame.
 */
body.fc-ds .fc-home-signal__row + .fc-home-signal__row .fc-motion-frame::after {
	content: "";
	position: absolute;
	inset: 0;
	background: radial-gradient(circle closest-side at 50% 50%, rgba(18, 18, 18, 0) 60.39%, var(--fc-ink-a40) 79.73%, var(--surface-dark) 99.1%);
	/* Above both media layers, and never in the way of anything: the frame holds no
	   controls, and the film is already pointer-events: none. */
	pointer-events: none;
}

body.fc-ds .fc-home-signal__title {
	max-width: 560px;
	text-wrap: pretty;
}

body.fc-ds .fc-home-signal__copy {
	max-width: 560px;
}

body.fc-ds .fc-home-signal__copy--narrow {
	max-width: 520px;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE CAPABILITY LIST
 * Six ruled rows; hovering one lifts it to white, steps it in from both edges,
 * turns its numeral violet and fades in the record sleeve for that capability.
 *
 * The export puts all six sleeves in one absolutely-positioned overlay and
 * computes each top as i × 79 + 39.5. Here each sleeve lives INSIDE its own row,
 * which is position: relative — so the row's own centre positions it and the
 * arithmetic disappears. The row height is the only number.
 *
 * The hovered row lifts to z-index 2 so its sleeve paints over the rows below it
 * (later siblings would otherwise cover it), and the sleeve to 3 so it also
 * clears the white plate it belongs to.
 *
 * :focus-visible IS PAIRED WITH :hover THROUGHOUT AND CURRENTLY MATCHES NOTHING. The rows
 * were links until 2026-08-27 — all six pointing at #product, the section above, so a click
 * scrolled the reader backwards — and they are now <div>s that go nowhere, which is not
 * focusable. The selectors are kept rather than stripped: they cost nothing, they are the
 * record that the reveal is meant to be reachable by keyboard, and they are what makes it
 * reachable again the moment these rows have somewhere real to lead. Nothing else in this
 * section cared which element it was on.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-caps__lead {
	max-width: 400px;
	margin-top: var(--sp-32);
	color: var(--text-on-light-muted);
}

body.fc-ds .fc-caplist {
	position: relative;
	display: flex;
	flex-direction: column;
	margin-top: var(--sp-72);
}

/* The list closes on a rule, so it reads as a table and not as six loose rows. */
body.fc-ds .fc-caplist::after {
	content: "";
	border-top: var(--stroke-1) solid var(--border-on-light);
}

body.fc-ds .fc-caprow {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-24);
	height: 79px;
	padding: 0 24px;
	border-top: var(--stroke-1) solid var(--border-on-light);
	color: var(--text-on-light);
	transition: background 200ms var(--ease-out), padding 200ms var(--ease-out);
}

body.fc-ds .fc-caprow:hover,
body.fc-ds .fc-caprow:focus-visible {
	z-index: 2;
	padding: 0 48px;
	background: var(--surface-lightest);
	color: var(--text-on-light);
}

body.fc-ds .fc-caprow__index {
	font-family: var(--font-quote);
	font-weight: var(--fw-semibold);
	font-size: var(--type-numeral-24);
	letter-spacing: var(--ls-quote);
	color: var(--fc-ink-12);
	transition: color var(--dur-fast) var(--ease-out);
}

body.fc-ds .fc-caprow:hover .fc-caprow__index,
body.fc-ds .fc-caprow:focus-visible .fc-caprow__index {
	color: var(--fc-violet);
}

body.fc-ds .fc-caprow__sleeve {
	position: absolute;
	left: 52%;
	top: 50%;
	z-index: 3;
	width: min(294px, 28%);
	translate: 0 -50%;
	opacity: 0;
	pointer-events: none;
	transition: opacity 400ms var(--ease-out);
}

body.fc-ds .fc-caprow:hover .fc-caprow__sleeve,
body.fc-ds .fc-caprow:focus-visible .fc-caprow__sleeve {
	opacity: 1;
}

/*
 * Below 768 there is no hover, so the sleeve can never be reached and the inset
 * has nothing to reveal. The rows become a plain list and are allowed to wrap.
 */
@container page (max-width: 820px) {
	body.fc-ds .fc-caprow {
		height: auto;
		min-height: 79px;
		padding: var(--sp-16) 0;
	}

	body.fc-ds .fc-caprow:hover,
	body.fc-ds .fc-caprow:focus-visible {
		padding: var(--sp-16) 0;
	}

	body.fc-ds .fc-caprow__sleeve {
		display: none;
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE THREE ILLUSTRATION CARDS
 * The export writes `max-width: 944px; padding: 0 var(--gutter-page)`, which with
 * border-box sizing means the three cards share 944 MINUS two gutters — 530px at
 * 1920, so a 293px drawing in a 155px panel. The intent is plainly 944 of cards
 * with the gutter only as a floor at narrow widths, and that is exactly what
 * width: min() says.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-cards-head {
	padding: 0 var(--gutter-page);
}

body.fc-ds .fc-cards-head__lead {
	font-weight: var(--fw-semibold);
	color: var(--text-on-light-muted);
}

body.fc-ds .fc-cards-3 {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--sp-32);
	width: min(944px, 100% - var(--gutter-page) * 2);
	margin: var(--sp-72) auto 0;
}

@container page (max-width: 820px) {
	body.fc-ds .fc-cards-3 {
		grid-template-columns: 1fr;
	}
}

/*
 * THE SCALE TRICK
 * The panel is a square of dot-grid paper; the drawing inside it is authored at a
 * fixed 300px and scaled as ONE PIECE, because a diagram that reflows stops being
 * the same diagram. container-type makes the panel the cqw reference, and
 * tan(atan2(84cqw, 300px)) is the unitless ratio 84% of panel ÷ 300 — 84 rather
 * than 100 so the drawing sits inside the paper rather than bleeding off it.
 *
 * min(1, …) stops it enlarging past the authored size on a very wide canvas.
 *
 * Centring uses the `translate` property and the fit uses `scale`, deliberately
 * not one `transform`: if a browser lacks trig functions it drops the scale line
 * and keeps the centring, so the drawing is cropped rather than thrown into the
 * corner.
 */
body.fc-ds .fc-card-panel {
	container-type: inline-size;
	position: relative;
	aspect-ratio: 1;
	overflow: hidden;
	/* The export tints this dot two points lighter than the diagram grid. */
	--dot-color: rgba(18, 18, 18, 0.12);
	background: var(--dot-grid) 0 0 / var(--dot-cell) var(--dot-cell), var(--surface-lightest);
}

body.fc-ds .fc-card-scale {
	position: absolute;
	left: 50%;
	top: 50%;
	width: var(--art-w, 300px);
	height: var(--art-h, 300px);
	translate: -50% -50%;
	scale: min(1, tan(atan2(84cqw, 300px)));
}

/* Card 2 — the engine and the three systems it feeds. */
body.fc-ds .fc-cardart--fan {
	display: flex;
	align-items: center;
	justify-content: space-between;
}

body.fc-ds .fc-cardart--fan > svg {
	position: absolute;
	left: 104px;
	top: 0;
}

/*
 * The three glyph cards the fan arrives at. 20px apart and 76px each is 268 of the
 * card's 300 — the remaining 32 is the flex gap either side of the wires, which is
 * why this column is not centred vertically by anything: at that height it fills
 * the card and there is nothing left to centre.
 */
body.fc-ds .fc-cardart__col {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 20px;
}

/* Card 3 — the dashed orbit with one thing at its centre. */
body.fc-ds .fc-cardart--orbit {
	display: flex;
	align-items: center;
	justify-content: center;
}

body.fc-ds .fc-cardart--orbit > svg {
	position: absolute;
	inset: 0;
}

body.fc-ds .fc-cardart__disc {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 112px;
	height: 112px;
	border-radius: var(--radius-pill);
	background: var(--fc-lilac-pale);
	color: var(--fc-violet);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE HOME PAGE'S MOTION
 *
 * Ported from the Figma prototype (FC Brand Refresh / Honey Deliverables, the HOME
 * frame) by reading its keyframe data rather than by eye. Every percentage and
 * every easing curve below is the file's own; what changed is what they are
 * attached to, because our drawings are built from inline SVG and flex boxes and
 * the file's are built from Figma layers.
 *
 * Five things move: the three cards in "But wait. There's more.", the violet route
 * in "Why us?" — which sits directly beneath "Hard to believe? Sure." and reads as
 * though it belongs to it — and the row of customer photographs in that proof
 * section.
 *
 * The first four came out of the file's keyframe data. The fifth could not: the
 * proof row is a component instance whose motion is a six-variant Smart Animate
 * sequence, and a sequence like that reports no keyframes at all — which is why an
 * earlier pass through this file concluded, wrongly, that nothing in the proof
 * section moved. Its geometry was measured off the variants instead, and its clock
 * is ours rather than the file's. THE SPOTLIGHT, further down, sets out what was
 * taken from it and what was deliberately left behind.
 *
 * ── ONE CLOCK ───────────────────────────────────────────────────────────────
 *
 * The prototype puts all four on a SINGLE 4.0036s timeline, so they are
 * choreographed rather than independent — the fan finishes wiring up before the
 * release tick sets off, and the orbit turns underneath both. That is preserved by
 * giving every element the same duration and differing only in keyframe
 * percentages, which is also why the percentages look arbitrary: 12.489% is
 * 500ms of 4.0036s.
 *
 *     0        → 1.500s   the fan draws its three wires and lights each glyph
 *     1.500s   → 2.038s   the release tick travels its rule
 *     2.400s   → 2.550s   the rule deepens behind it
 *     2.555s   → 2.789s   the marker at the end of the rule arrives
 *     0        → 3.711s   the orbit turns once, then holds for the remainder
 *     0        → 4.004s   the route draws end to end, eased at both ends
 *
 * The sections are far enough apart that they are never on screen together, so each
 * starts its own copy of the clock when it is scrolled to. Sharing the duration is
 * what keeps the beats inside each one in the designed relation; it does not mean
 * they are in step with each other, and they are not meant to be. The proof row is
 * not on this clock at all — it has its own, and a much slower one.
 *
 * ── WHY THIS IS ALL IN CSS, AND WHAT THE CLASS IS FOR ───────────────────────
 *
 * The card markup is a locked block template already inserted in the database, so
 * none of it can be given new hooks without regenerating the page content. Nothing
 * here needs any: the wires are addressed as path:nth-of-type(), the glyphs as
 * children of .fc-cardart__col, and the release card by the classes it already has.
 * The stroke lengths are the only numbers that had to be derived — 216, 120 and 216,
 * counted off the path data in inc/ds-diagram.php, which is exact because every
 * segment in those wires is axis-aligned.
 *
 * Everything hangs off .is-live, which assets/ds/cardart.js adds when the row is
 * scrolled to. That is deliberate and not just a performance gate: at 0% the wires
 * are undrawn and the marker is invisible, so if the class never arrives — no
 * JavaScript, an old browser, a reduced-motion visitor — the cards render exactly
 * as the static design draws them instead of being stuck half-built.
 *
 * .is-paused only stops the clock, and is what keeps three looping animations from
 * burning a phone's battery while the visitor reads something else entirely.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-cards-3.is-live,
body.fc-ds .fc-route-wide.is-live {
	--motion-loop: 4.0036s;
}

body.fc-ds .fc-cards-3.is-paused *,
body.fc-ds .fc-route-wide.is-paused *,
body.fc-ds .fc-proofgrid.is-paused * {
	animation-play-state: paused;
}

/* ── Card 1: RELEASE ──────────────────────────────────────────────────────────
 * A tick travels the rule, the rule deepens behind it, and a marker lands at the
 * end. Scoped under .fc-cards-3 because .fc-dg-frame is shared with the OMS page's
 * Extend drawing, which is on a different clock and has its own rules — see Panel 3
 * in THE ORDER MANAGEMENT PAGE'S MOTION further down. The two must not reach each
 * other, so neither is allowed to address the component unscoped.
 */

/*
 * 157px is the design's travel scaled to our rule. The prototype moves its tick
 * 188px along a 215px rule; ours is 180px — 300px card, less 44px of padding, less
 * 3px of border, less the 48px tick and the 34px marker, plus the 9px the marker
 * is pulled back by — and 188/215 of 180 is 157. Stated as a token so the two
 * numbers that have to agree are next to each other.
 */
body.fc-ds .fc-cards-3.is-live .fc-dg-frame__check {
	--travel: 157px;

	animation: fcCardRelease var(--motion-loop) linear infinite;
}

@keyframes fcCardRelease {
	0%,
	37.466% {
		translate: 0;
	}

	50.904%,
	100% {
		translate: var(--travel);
	}
}

/*
 * The rule rests at --fc-iris, which is the colour the prototype ends on (#6b4fa0
 * is the token's own value), so this animates from pale and arrives where the
 * static design already sits. step-end holds the pale flat until the tick is most
 * of the way across rather than easing the colour for two seconds.
 */
body.fc-ds .fc-cards-3.is-live .fc-dg-frame__rule {
	animation: fcCardRule var(--motion-loop) linear infinite;
}

@keyframes fcCardRule {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-lilac-pale);
	}

	59.96% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-lilac-pale);
	}

	63.706%,
	100% {
		background-color: var(--fc-iris);
	}
}

/*
 * DEVIATION, and the only one in the three cards. The prototype's last beat is a
 * 25px vertical stroke that rises 77px out of the end of the rule — a riser our
 * card does not have, because our card ends in an arrow_forward instead. Adding the
 * stroke would mean changing the drawing, which is not what was asked for, so the
 * beat is kept and mapped onto the arrow we do have: same 63.7% entry, same 234ms
 * ease-out, moving 10px in the direction the arrow already points rather than 77px
 * in a direction nothing on our card points.
 */
body.fc-ds .fc-cards-3.is-live .fc-dg-frame__arrow {
	animation: fcCardArrow var(--motion-loop) linear infinite;
}

@keyframes fcCardArrow {
	0% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		opacity: 0;
		translate: -10px;
	}

	63.706% {
		opacity: 0;
		translate: -10px;
	}

	64.652% {
		opacity: 1;
	}

	69.676%,
	100% {
		opacity: 1;
		translate: 0;
	}
}

/* ── Card 2: ENABLE ──────────────────────────────────────────────────────────
 * Three wires draw out of the engine one at a time, and each system lights up as
 * its wire arrives. Order is the prototype's: the straight middle wire first, then
 * the lower elbow, then the upper — not top to bottom.
 *
 * nth-of-type() counts per element type, so the three paths and the three dots are
 * numbered separately. Both are in document order, which for the wires svg in
 * inc/ds-diagram.php is top, middle, bottom.
 */
body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg path {
	animation: var(--motion-loop) linear infinite;
}

/* Top elbow — third to draw. 216 is its length: 40 across, 96 up, 80 across. */
body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg path:nth-of-type(1) {
	stroke-dasharray: 216;
	animation-name: fcWireTop;
}

/* Middle, straight — first to draw, and the shortest at 120. */
body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg path:nth-of-type(2) {
	stroke-dasharray: 120;
	animation-name: fcWireMid;
}

/* Bottom elbow — second to draw, and the mirror of the top, so also 216. */
body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg path:nth-of-type(3) {
	stroke-dasharray: 216;
	animation-name: fcWireBottom;
}

@keyframes fcWireMid {
	0% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 120;
	}

	12.489%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcWireBottom {
	0%,
	12.489% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 216;
	}

	24.978%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcWireTop {
	0%,
	24.978% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 216;
	}

	37.466%,
	100% {
		stroke-dashoffset: 0;
	}
}

/*
 * OURS, not the prototype's: the three terminal dots. The file does not animate
 * them because in its layer tree they belong to the icon frames, but in our svg
 * they are siblings of the wires — so left alone they would sit in mid-air waiting
 * for a wire that has not been drawn yet. Each one appears the instant its own wire
 * lands, which is a step and not a fade: a 4px dot has nothing to fade.
 */
body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg circle {
	animation: var(--motion-loop) step-end infinite;
}

body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg circle:nth-of-type(1) {
	animation-name: fcDotTop;
}

body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg circle:nth-of-type(2) {
	animation-name: fcDotMid;
}

body.fc-ds .fc-cards-3.is-live .fc-cardart--fan > svg circle:nth-of-type(3) {
	animation-name: fcDotBottom;
}

@keyframes fcDotMid {
	0% {
		opacity: 0;
	}

	12.489%,
	100% {
		opacity: 1;
	}
}

@keyframes fcDotBottom {
	0% {
		opacity: 0;
	}

	24.978%,
	100% {
		opacity: 1;
	}
}

@keyframes fcDotTop {
	0% {
		opacity: 0;
	}

	37.466%,
	100% {
		opacity: 1;
	}
}

/*
 * The three systems lighting up. The prototype takes each from #121212 on white to
 * #9055EE on #E0DAEB; ink and lilac are those two values exactly (--fc-ink-12,
 * --fc-lilac-pale), and the lit glyph uses --fc-violet — the DS brand violet, which
 * is the colour .fc-cardart__disc already uses for the same "lit" treatment on the
 * card beside it. The 1.5px border is left alone, as it is in the file.
 *
 * Column order is database, package, storefront, so these are children 1, 2 and 3
 * — lighting in the order 2, 3, 1 to follow their wires.
 */
body.fc-ds .fc-cards-3.is-live .fc-cardart__col .fc-dg-glyph {
	animation: var(--motion-loop) linear infinite;
}

body.fc-ds .fc-cards-3.is-live .fc-cardart__col .fc-dg-glyph:nth-child(1) {
	animation-name: fcGlyphDatabase;
}

body.fc-ds .fc-cards-3.is-live .fc-cardart__col .fc-dg-glyph:nth-child(2) {
	animation-name: fcGlyphPackage;
}

body.fc-ds .fc-cards-3.is-live .fc-cardart__col .fc-dg-glyph:nth-child(3) {
	animation-name: fcGlyphStorefront;
}

@keyframes fcGlyphPackage {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white);
		color: var(--fc-ink-12);
	}

	12.489% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white);
		color: var(--fc-ink-12);
	}

	15.411%,
	100% {
		background-color: var(--fc-lilac-pale);
		color: var(--fc-violet);
	}
}

@keyframes fcGlyphStorefront {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white);
		color: var(--fc-ink-12);
	}

	21.398% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white);
		color: var(--fc-ink-12);
	}

	24.878%,
	100% {
		background-color: var(--fc-lilac-pale);
		color: var(--fc-violet);
	}
}

@keyframes fcGlyphDatabase {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white);
		color: var(--fc-ink-12);
	}

	37.466% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white);
		color: var(--fc-ink-12);
	}

	41.288%,
	100% {
		background-color: var(--fc-lilac-pale);
		color: var(--fc-violet);
	}
}

/* ── Card 3: EXTEND ──────────────────────────────────────────────────────────
 * One full turn, eased at both ends, finishing at 92.699% — 3.711s — and holding
 * for the last 289ms.
 *
 * The prototype states this as four separate rotations, one on the dashed ring and
 * one on each of the three points, all with identical keyframes. Rotating a circle
 * about its own centre is invisible, so what that describes is the assembly
 * turning: here it is one rotation of the svg that holds all four, about its centre,
 * which is also the ring's. .fc-cardart__disc is outside that svg and is not in the
 * file's animated set either, so the platform at the middle stays still while the
 * orbit goes round it — which is the whole point of the drawing.
 */
body.fc-ds .fc-cards-3.is-live .fc-cardart--orbit > svg {
	transform-origin: 50% 50%;
	animation: fcCardOrbit var(--motion-loop) linear infinite;
}

@keyframes fcCardOrbit {
	0% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		rotate: 0deg;
	}

	92.699%,
	100% {
		rotate: 360deg;
	}
}

/* ── "Why us?": the route draws itself ───────────────────────────────────────
 * The violet line threading the five plates, drawn end to end over the whole
 * 4.0036s with a dot appearing at each plate as the line reaches it.
 *
 * ── ONE LINE IN FIGMA, SIX PATHS HERE ───────────────────────────────────────
 *
 * The prototype states this as a single vector with one path-trim from nothing to
 * whole, eased cubic-bezier(0.5, 0, 0.5, 1) across the full duration. Ours is six
 * separate paths, because the line passes behind five 160px plates and our SVG
 * simply stops and restarts either side of each one rather than drawing a length
 * that is then covered up.
 *
 * Six paths cannot share one easing curve, so the curve is baked into where the
 * segments hand over instead. Each segment animates LINEARLY inside its own window,
 * and the windows are the eased curve sampled at each segment's end — which is why
 * the first 101px take a fifth of the runtime and the 407px in the middle take
 * about the same. Read down the left column and the ease-in-out is visible as
 * numbers: slow, quick, quick, quick, slow.
 *
 *     path 1   101px    0        → 19.874%
 *     path 2   323px    19.874%  → 37.517%
 *     path 3   407px    37.517%  → 51.166%
 *     path 4   374px    51.166%  → 64.114%
 *     path 5   280px    64.114%  → 79.948%
 *     path 6   103px    79.948%  → 100%
 *
 * The curve is y = 3u² − 2u³ against x = u³ − 1.5u² + 1.5u; the windows above are
 * that inverted at each cumulative length over the 1588px total. Six linear pieces
 * is an approximation of one smooth curve, but the handover points are exact and
 * the segments are short, so what is lost is not visible on a 2px line.
 *
 * ── THE LENGTHS ─────────────────────────────────────────────────────────────
 *
 * stroke-dasharray needs a real length per path, since pathLength="1" cannot be
 * added to markup that is already in the database. Each is the straight runs plus
 * 32.4645px per rounded corner — the arc length of the export's 20px quadratic,
 * which is not the 31.42px a 20px circular quarter would be — then taken up to the
 * next whole pixel, and past it where the run came out exact. Up matters: a dash
 * longer than its path still hides it completely at full offset, whereas one a
 * pixel short would leave a mark on screen before the draw begins.
 */
body.fc-ds .fc-route-wide.is-live svg path {
	animation: var(--motion-loop) linear infinite;
}

body.fc-ds .fc-route-wide.is-live svg path:nth-of-type(1) {
	stroke-dasharray: 101;
	animation-name: fcRoute1;
}

body.fc-ds .fc-route-wide.is-live svg path:nth-of-type(2) {
	stroke-dasharray: 323;
	animation-name: fcRoute2;
}

body.fc-ds .fc-route-wide.is-live svg path:nth-of-type(3) {
	stroke-dasharray: 407;
	animation-name: fcRoute3;
}

body.fc-ds .fc-route-wide.is-live svg path:nth-of-type(4) {
	stroke-dasharray: 374;
	animation-name: fcRoute4;
}

body.fc-ds .fc-route-wide.is-live svg path:nth-of-type(5) {
	stroke-dasharray: 280;
	animation-name: fcRoute5;
}

body.fc-ds .fc-route-wide.is-live svg path:nth-of-type(6) {
	stroke-dasharray: 103;
	animation-name: fcRoute6;
}

@keyframes fcRoute1 {
	0% {
		stroke-dashoffset: 101;
	}

	19.874%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcRoute2 {
	0%,
	19.874% {
		stroke-dashoffset: 323;
	}

	37.517%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcRoute3 {
	0%,
	37.517% {
		stroke-dashoffset: 407;
	}

	51.166%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcRoute4 {
	0%,
	51.166% {
		stroke-dashoffset: 374;
	}

	64.114%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcRoute5 {
	0%,
	64.114% {
		stroke-dashoffset: 280;
	}

	79.948%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcRoute6 {
	0%,
	79.948% {
		stroke-dashoffset: 103;
	}

	100% {
		stroke-dashoffset: 0;
	}
}

/*
 * The junction dots, each appearing at the moment the line arrives at its plate — so
 * the same times as the six handovers above, minus the last one, which is the line
 * running off the right edge with no plate to land on.
 *
 * Ours again rather than the prototype's, for the same reason as the dots on the
 * fan card: in Figma they belong to the plate they touch, in our SVG they are
 * siblings of the line, and a dot sitting on a plate the line has not reached yet
 * gives away that the drawing was assembled rather than drawn.
 *
 * ── TEN DOTS, FIVE KEYFRAMES (BUILD-15) ─────────────────────────────────────
 *
 * Build-15 marks the departures as well as the arrivals: circles 1–5 are where the
 * line reaches each plate, 6–10 where it leaves. That looks like it needs five more
 * timings and does not, because of how the paths are cut. Path N+1's first command is
 * the exact point path N ends on — M260 129 follows H100…, M520 245 follows …V199 —
 * so the six segments are a relay with no gaps, and the instant a plate is reached is
 * the same instant the next segment starts leaving it.
 *
 * A departure dot therefore lights at its own plate's arrival time, and each pair
 * shares one keyframe: (1)&(6) → Dot1, (2)&(7) → Dot2, and so on. Grouped in one
 * selector per pair rather than listed twice, so the pairing is visible in the code
 * and cannot be half-edited.
 *
 * If a path is ever re-cut so a segment no longer starts where the last one finished,
 * this pairing is the first thing that breaks — the departure would fire early, before
 * the line had anything to depart along.
 */
body.fc-ds .fc-route-wide.is-live svg circle {
	animation: var(--motion-loop) step-end infinite;
}

body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(1),
body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(6) {
	animation-name: fcRouteDot1;
}

body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(2),
body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(7) {
	animation-name: fcRouteDot2;
}

body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(3),
body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(8) {
	animation-name: fcRouteDot3;
}

body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(4),
body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(9) {
	animation-name: fcRouteDot4;
}

body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(5),
body.fc-ds .fc-route-wide.is-live svg circle:nth-of-type(10) {
	animation-name: fcRouteDot5;
}

@keyframes fcRouteDot1 {
	0% {
		opacity: 0;
	}

	19.874%,
	100% {
		opacity: 1;
	}
}

@keyframes fcRouteDot2 {
	0% {
		opacity: 0;
	}

	37.517%,
	100% {
		opacity: 1;
	}
}

@keyframes fcRouteDot3 {
	0% {
		opacity: 0;
	}

	51.166%,
	100% {
		opacity: 1;
	}
}

@keyframes fcRouteDot4 {
	0% {
		opacity: 0;
	}

	64.114%,
	100% {
		opacity: 1;
	}
}

@keyframes fcRouteDot5 {
	0% {
		opacity: 0;
	}

	79.948%,
	100% {
		opacity: 1;
	}
}

/* ── The proof row: THE SPOTLIGHT ─────────────────────────────────────────────
 *
 * "Hard to believe? Sure. Easy to prove? Certainly." — five customer photographs,
 * and one at a time is picked out of the row while the other four sit back.
 *
 * ── WHERE THE NUMBERS CAME FROM ─────────────────────────────────────────────
 *
 * Not from keyframes: this section is a component instance, and its motion is a set
 * of six sibling variants stepped through with Smart Animate. There is no timeline
 * to read, so the geometry was taken by measuring the same photograph across two
 * variants — resting, then picked out — and dividing.
 *
 *     size      356.648 → 376.861      = 1.0567
 *     centre    y 492.901 → 481.668    = 11.233px higher
 *
 * Both are in the file's 1920-wide frame. Our sleeves are 280px, so the lift scales
 * by 280/356.648 to a little under 9px. The tilt is not derivable that way — a
 * rotated square's bounding box grows the same amount whichever way it leans — so it
 * was measured off the corners of a render of the picked-out frame: the top edge
 * rises 13px across 337px, which is 2.2°, anticlockwise.
 *
 * ── WHAT WAS LEFT BEHIND, AND WHY ───────────────────────────────────────────
 *
 * In the file the five photographs OVERLAP. Their pitch runs 247, 307, 301, 278
 * against a width of 357, so it is a crate of records rather than a row of cards,
 * and each variant reshuffles the crate by hand to open a gap around the one being
 * looked at — 98px left for the neighbour on one side, 84px right for the other.
 *
 * Ours is a five-column grid with 20px between the cells and nothing overlapping
 * anything. Sliding the neighbours sideways there would not read as flipping through
 * records; it would read as four cards wandering about. So the shuffle is not here.
 * What is here is the part that survives the change of composition: the picked-out
 * photograph grows, tilts, lifts and casts a shadow, and the other four go dark.
 *
 * The file also greys one caption per variant, but never the same one relative to the
 * active brand — it greys whichever name belongs to the photograph the shuffle has
 * just covered up. With no shuffle there is nothing covered up, so the captions are
 * left alone and stay readable throughout.
 *
 * ── THE CLOCK ───────────────────────────────────────────────────────────────
 *
 * 7s, five slots of 1.4s. Each slot holds its photograph out for 1.12s and hands
 * over across 0.28s, and the hand-over is a genuine crossfade: one card's 16%→20%
 * fall is the same 280ms as the next card's 96%→100% rise, which is what the five
 * negative delays below arrange. There is no moment when all five are dark.
 *
 * The duration is a decision, not a measurement — a variant sequence carries no
 * timings. 1.4s a brand is about as fast as five results can be read at.
 *
 * Everything is one keyframe set plus a delay, so the cadence is in one place. And as
 * with the rest of this file it is all under .is-live: with no script, no observer or
 * a reduced-motion preference the row is simply the five photographs at full
 * brightness, level and undimmed, which is the file's own Default variant.
 */
body.fc-ds .fc-proofgrid.is-live {
	--proof-loop: 7s;
}

body.fc-ds .fc-proofgrid.is-live > * > .fc-sleeve {
	animation: fcProofSpot var(--proof-loop) cubic-bezier(0.5, 0, 0.5, 1) infinite;
}

/*
 * Slot n begins at 1.4s × (n-1), so the animation has to be that much FURTHER
 * through its cycle, not less: a negative delay of loop − 1.4s × (n-1). The first
 * needs none.
 */
body.fc-ds .fc-proofgrid.is-live > :nth-child(2) > .fc-sleeve {
	animation-delay: calc(var(--proof-loop) / -5 * 4);
}

body.fc-ds .fc-proofgrid.is-live > :nth-child(3) > .fc-sleeve {
	animation-delay: calc(var(--proof-loop) / -5 * 3);
}

body.fc-ds .fc-proofgrid.is-live > :nth-child(4) > .fc-sleeve {
	animation-delay: calc(var(--proof-loop) / -5 * 2);
}

body.fc-ds .fc-proofgrid.is-live > :nth-child(5) > .fc-sleeve {
	animation-delay: calc(var(--proof-loop) / -5);
}

/*
 * translate, rotate and scale are the separate properties rather than one transform
 * so that each reads as what it is. They compose in a fixed order — translate, then
 * rotate, then scale — which is the order wanted here anyway.
 *
 * brightness() rather than an overlay because the file's dimming is a black rectangle
 * over the photograph and there is nowhere to put one: .fc-sleeve clips its children,
 * so a pseudo-element could not carry the shadow either, which is why that stays on
 * the sleeve itself. 0.45 is the one value here judged by eye against the render.
 *
 * The picked-out card grows 8px and lifts 9px, and the gap between cells is 20px, so
 * nothing ever overlaps a neighbour and no stacking order is needed. The shadow does
 * spill into that gap, which is the point of it.
 */
@keyframes fcProofSpot {
	0%,
	16% {
		filter: brightness(1);
		translate: 0 -9px;
		rotate: -2.2deg;
		scale: 1.0567;
		box-shadow: 0 16px 36px rgb(18 18 18 / 0.3);
	}

	20%,
	96% {
		filter: brightness(0.45);
		translate: 0 0;
		rotate: 0deg;
		scale: 1;
		box-shadow: 0 0 0 rgb(18 18 18 / 0);
	}

	100% {
		filter: brightness(1);
		translate: 0 -9px;
		rotate: -2.2deg;
		scale: 1.0567;
		box-shadow: 0 16px 36px rgb(18 18 18 / 0.3);
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE ORDER MANAGEMENT PAGE'S MOTION
 *
 * The five pattern drawings in "A platform so flexible" — Connect, Configure,
 * Extend, Release and Leverage. Read out of the same Figma prototype as the home
 * page's motion above (FC Brand Refresh / Honey Deliverables, the ORDER MANAGEMENT
 * frame, node 6005:3731) by taking its keyframe data rather than by eye.
 *
 * One warning about reading that file: from the fourth card on, the frame NAMES lag
 * the copy by one. The frame called "Leverage" holds the Release copy and the
 * Release artwork, and the frame called "Build" is the Leverage card. The copy is
 * right and the names are stale; everything below is keyed to the copy.
 *
 * ── ONE CLOCK, ONE OBSERVER ─────────────────────────────────────────────────
 *
 * The file puts all five drawings on a SINGLE 2.620075s loop — one cohort, thirty-eight
 * animated layers — so .fc-pats gets one .is-live and one --motion-loop, and every
 * percentage below is a percentage of that one loop. That is the difference from the
 * home page: there the shared duration only preserved the beats within each section,
 * here it also has to hold five cards in step with each other.
 *
 * The row scrolls sideways (see .fc-pats), so the last card or two is usually off the
 * right edge — which is the argument FOR one observer on the grid rather than five on
 * the panels, not against it. One clock means a card scrolled into view is found
 * mid-sequence, in step with the ones beside it. Five clocks would start each card
 * from 0% as it was scrolled to, and a row choreographed as one movement would come
 * apart the moment anybody dragged it.
 *
 * Two numbers explain the arbitrary-looking percentages. 7.633% is 200ms, the grid
 * the whole frame is cut on; 19.083% is 500ms, which is how long every one of the six
 * Connect spokes takes to draw.
 *
 *     CONNECT    0       → 0.500s   the accented Store spoke draws
 *                0.392s  → 0.892s   Carrier
 *                0.590s  → 1.090s   Warehouse, the first dashed one
 *                0.853s  → 1.353s   Service
 *                1.090s  → 1.590s   ERP, the second dashed one
 *                1.353s  → 1.853s   Commerce
 *     CONFIGURE  0       → 0.998s   the wire from the order header reaches the plate
 *                0.998s  → 1.196s   the four facts read, top to bottom
 *                1.159s  → 1.377s   the wire leaves the VIP row for the meter
 *                1.377s  → 1.770s   the meter fills, six segments in a wave
 *     EXTEND     0       → 1.706s   the tick crosses the rule
 *                1.506s  → 1.706s   the rule deepens behind it
 *                1.800s  → 2.000s   the arrow lands
 *     RELEASE    0.200s  → 1.506s   the sparkline draws, vertices landing under it
 *     LEVERAGE   0.200s  → 1.331s   the three sources reach the engine
 *                1.404s  → 1.770s   the three answers leave it
 *
 * ── WHAT COULD NOT COME ACROSS ──────────────────────────────────────────────
 *
 * Four of the file's beats have nothing in our markup to attach to, and each is
 * handled at the card it belongs to rather than dropped. In short:
 *
 *   CONFIGURE  the file's two path-trims are on two coincident 343.5 × 221 vectors —
 *              box outlines drawing themselves. Our plate and meter are HTML boxes
 *              with borders and cannot be trimmed. Our two violet wires are ours, not
 *              the file's, so they take the file's timestamps as anchors instead.
 *   EXTEND     the file animates a 2 × 2 grid of tiles growing to 3 × 3 with a cursor
 *              flying in. Our Extend drawing is one window — deliberately the emptiest
 *              of the four — with no grid and no cursor.
 *   LEVERAGE   the file trims one path per beat; our drawing has three per beat. Each
 *              beat draws its three together, because a beat is what the file
 *              choreographs and three lines on a shared spine are one arrival.
 *   TERMINALS  the dots at the end of the Connect spokes and on the sparkline's
 *              vertices are not animated in the file, because there they belong to the
 *              layers they touch. In our SVG they are siblings of the line, so left
 *              alone they would sit in mid-air waiting for a line to arrive.
 *
 * ── ADDRESSING, AND WHY IT IS BY COLUMN ─────────────────────────────────────
 *
 * The five panels are a locked block template in the database, so none of the drawings
 * can be given new hooks without regenerating the page content — the same constraint
 * as the home page, and the reason this is all CSS against markup that already exists.
 * Every rule is scoped to its column, .fc-pats > :nth-child(n), because .fc-dg-wires
 * is the only handle Connect, Configure and Leverage have and all three have one. The
 * columns are templateLock:"all", so nothing can be reordered under it.
 *
 * Stroke lengths had to be derived, since pathLength="1" cannot be added to markup
 * that is already stored. Corners count 1.623225px per px of leg — the arc length of
 * the export's quadratic, not the 1.5708 a circular quarter would be — and each total
 * is taken up to the next whole pixel: a dash longer than its path still hides it
 * completely, whereas one a pixel short leaves a mark before the draw begins.
 *
 * Everything hangs off .is-live, which assets/ds/cardart.js adds when the row is
 * scrolled to, and .is-paused stops the clock again when it leaves. That matters more
 * here than on the home page: this is forty-four looping animations in one row, and
 * they keep running for whichever cards are scrolled off to the right as well as the
 * ones being looked at. Without the class none of it is declared at all and all five
 * drawings render finished, which is the only acceptable resting state — an undrawn
 * Connect is six labelled systems with nothing connecting them.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-pats.is-live {
	--motion-loop: 2.620075s;
}

body.fc-ds .fc-pats.is-paused * {
	animation-play-state: paused;
}

/* ── Panel 1: CONNECT ─────────────────────────────────────────────────────────
 * Six spokes draw out of the hub one at a time, each over 500ms and each starting
 * before the one before it has landed. The order is the file's and it is a clockwise
 * sweep from the accented spoke: Store, Carrier, Warehouse, Service, ERP, Commerce.
 * That is DOM order 2, 3, 4, 5, 6, 1, because ds-diagram.php emits them from
 * Commerce at -90°.
 *
 * Lengths are exact rather than counted off path data, because these are straight
 * lines between two ellipse edges: 72px for the two vertical spokes, 34.35px for the
 * four diagonals, rounded up to 35.
 */
body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires path {
	animation: var(--motion-loop) linear infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires path:nth-of-type(1) {
	stroke-dasharray: 72;
	animation-name: fcOmsSpokeCommerce;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires path:nth-of-type(2) {
	stroke-dasharray: 35;
	animation-name: fcOmsSpokeStore;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires path:nth-of-type(3) {
	stroke-dasharray: 35;
	animation-name: fcOmsSpokeCarrier;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires path:nth-of-type(4) {
	animation-name: fcOmsSpokeWarehouse;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires path:nth-of-type(5) {
	stroke-dasharray: 35;
	animation-name: fcOmsSpokeService;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires path:nth-of-type(6) {
	animation-name: fcOmsSpokeErp;
}

@keyframes fcOmsSpokeStore {
	0% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 35;
	}

	19.083%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcOmsSpokeCarrier {
	0%,
	14.975% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 35;
	}

	34.059%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcOmsSpokeService {
	0%,
	32.561% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 35;
	}

	51.645%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcOmsSpokeCommerce {
	0%,
	51.645% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 72;
	}

	70.728%,
	100% {
		stroke-dashoffset: 0;
	}
}

/*
 * The two dashed spokes, which cannot be drawn the way the other four are: a dash
 * PATTERN and a dash TRIM are the same property, so stroke-dashoffset would march the
 * 2/7 pattern along the line instead of revealing it. A mask would be the usual answer
 * and needs markup we cannot reach, so these step their pattern out one dash at a
 * time — 2 1000, then 2 7 2 1000, and so on — which is the same reveal at the
 * resolution the dashes already impose. The last step is the plain 2 7 the markup
 * carries, so the resting state is exactly what ds-diagram.php drew.
 *
 * step-end holds each pattern flat until the next, and the step times are the ease-out
 * curve inverted at each dash's own position along the line, so these keep pace with
 * the four solid spokes drawing either side of them rather than sweeping linearly
 * past them.
 *
 * Both end early relative to their window — 36.008% against 41.617%, 53.096% against
 * 60.7% — because the last dash starts a gap short of the node and there is nothing
 * left to reveal after it. The terminal dot still waits for the window to close.
 */
@keyframes fcOmsSpokeWarehouse {
	0% {
		animation-timing-function: step-end;
		opacity: 0;
		stroke-dasharray: 2 1000;
	}

	22.533% {
		animation-timing-function: step-end;
		opacity: 1;
		stroke-dasharray: 2 1000;
	}

	24.003% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 1000;
	}

	25.579% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 7 2 1000;
	}

	27.259% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 7 2 7 2 1000;
	}

	29.069% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 7 2 7 2 7 2 1000;
	}

	31.05% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 7 2 7 2 7 2 7 2 1000;
	}

	33.283% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 7 2 7 2 7 2 7 2 7 2 1000;
	}

	36.008%,
	100% {
		opacity: 1;
		stroke-dasharray: 2 7;
	}
}

@keyframes fcOmsSpokeErp {
	0% {
		animation-timing-function: step-end;
		opacity: 0;
		stroke-dasharray: 2 1000;
	}

	41.617% {
		animation-timing-function: step-end;
		opacity: 1;
		stroke-dasharray: 2 1000;
	}

	44.819% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 1000;
	}

	48.502% {
		animation-timing-function: step-end;
		stroke-dasharray: 2 7 2 7 2 1000;
	}

	53.096%,
	100% {
		opacity: 1;
		stroke-dasharray: 2 7;
	}
}

/*
 * The six terminal dots, each appearing the instant its own spoke lands. A step and
 * not a fade: a 4.5px dot has nothing to fade. Paths and markers are emitted in pairs
 * by fluent_commerce_dg_wires(), so circle n belongs to path n.
 */
body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires circle {
	animation: var(--motion-loop) step-end infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires circle:nth-of-type(1) {
	animation-name: fcOmsDotCommerce;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires circle:nth-of-type(2) {
	animation-name: fcOmsDotStore;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires circle:nth-of-type(3) {
	animation-name: fcOmsDotCarrier;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires circle:nth-of-type(4) {
	animation-name: fcOmsDotWarehouse;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires circle:nth-of-type(5) {
	animation-name: fcOmsDotService;
}

body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-wires circle:nth-of-type(6) {
	animation-name: fcOmsDotErp;
}

@keyframes fcOmsDotStore {
	0% {
		opacity: 0;
	}

	19.083%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsDotCarrier {
	0% {
		opacity: 0;
	}

	34.059%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsDotWarehouse {
	0% {
		opacity: 0;
	}

	41.617%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsDotService {
	0% {
		opacity: 0;
	}

	51.645%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsDotErp {
	0% {
		opacity: 0;
	}

	60.7%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsDotCommerce {
	0% {
		opacity: 0;
	}

	70.728%,
	100% {
		opacity: 1;
	}
}

/*
 * The one node the route reached. Its violet border is the whole argument of the
 * drawing — not that Fluent connects to everything at once, but that any one of these
 * is a live route — so it arrives with its spoke rather than before it. The other five
 * are left alone: they are systems the retailer already runs, and they exist whether
 * or not anything has been wired to them yet. There is exactly one accented node per
 * system diagram, which is what makes this selector safe.
 */
body.fc-ds .fc-pats.is-live > :nth-child(1) .fc-dg-node--accent {
	animation: fcOmsNodeAccent var(--motion-loop) step-end infinite;
}

@keyframes fcOmsNodeAccent {
	0% {
		border-color: var(--diagram-edge);
	}

	19.083%,
	100% {
		border-color: var(--fc-violet-light);
	}
}

/* ── Panel 2: CONFIGURE ───────────────────────────────────────────────────────
 * A wire arrives at the plate, the four facts read top to bottom, a second wire
 * leaves the row that decided it, and the meter fills.
 *
 * DEVIATION, the first of two on this card. The file's two path-trims are on two
 * coincident 343.5 × 221 vectors — box outlines drawing themselves, over 0 → 61.178%
 * and 15.267% → 76.921%. Our plate and meter are HTML boxes with borders and cannot
 * be trimmed, and the two things here that CAN be are the violet wires, which
 * ds-diagram.php added and the file does not have. Taking the file's windows literally
 * would have both wires still drawing after the elements they feed had already reacted
 * — the plate read at 45.658% off a wire that arrives at 61.178% — which reads as the
 * drawing having been assembled rather than followed. So the wires keep the file's
 * timestamps and not its windows: each one arrives exactly on the beat of the thing it
 * feeds. Wire 1 lands at 38.091%, the moment the first fact starts to read; wire 2
 * leaves at 44.221% as the VIP row resolves and lands at 52.556% as the meter's first
 * segment starts to fill. Easing is the file's ease-in-out.
 *
 * Lengths are the straight runs plus 19.479px per 12px corner, up to the next pixel:
 * 249px for the header wire, 181px for the meter wire.
 */
body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-wires path {
	animation: var(--motion-loop) linear infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-wires path:nth-of-type(1) {
	stroke-dasharray: 249;
	animation-name: fcOmsReadWireIn;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-wires path:nth-of-type(2) {
	stroke-dasharray: 181;
	animation-name: fcOmsReadWireOut;
}

@keyframes fcOmsReadWireIn {
	0% {
		animation-timing-function: ease-in-out;
		stroke-dashoffset: 249;
	}

	38.091%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcOmsReadWireOut {
	0%,
	44.221% {
		animation-timing-function: ease-in-out;
		stroke-dashoffset: 181;
	}

	52.556%,
	100% {
		stroke-dashoffset: 0;
	}
}

/*
 * The four facts being read, at the file's own four start times — 38.091%, 39.779%,
 * 42.49%, 44.221% — all converging within 40ms of each other, so it reads as a glance
 * down the plate rather than four separate events.
 *
 * DEVIATION, the second. The file takes all four values from #121212 to #9747FF. Our
 * plate accents exactly ONE row on purpose: four facts read neutral and the one that
 * changed the decision is violet, which is the whole point of the component and not
 * something to break for a beat. So the direction is reversed instead — each value
 * starts at --diagram-noise, the colour this system already uses for "this did not
 * matter", and lands on the colour the static design gives it: ink for the first
 * three, violet for VIP. Same four beats, same easing, and the resting state is
 * untouched.
 */
body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-plate__value {
	animation: var(--motion-loop) linear infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-plate__row:nth-of-type(1) .fc-dg-plate__value {
	animation-name: fcOmsReadMargin;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-plate__row:nth-of-type(2) .fc-dg-plate__value {
	animation-name: fcOmsReadValue;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-plate__row:nth-of-type(3) .fc-dg-plate__value {
	animation-name: fcOmsReadSell;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-plate__row:nth-of-type(4) .fc-dg-plate__value {
	animation-name: fcOmsReadVip;
}

@keyframes fcOmsReadMargin {
	0% {
		animation-timing-function: step-end;
		color: var(--diagram-noise);
	}

	38.091% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		color: var(--diagram-noise);
	}

	45.658%,
	100% {
		color: var(--fc-ink-12);
	}
}

@keyframes fcOmsReadValue {
	0% {
		animation-timing-function: step-end;
		color: var(--diagram-noise);
	}

	39.779% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		color: var(--diagram-noise);
	}

	45.658%,
	100% {
		color: var(--fc-ink-12);
	}
}

@keyframes fcOmsReadSell {
	0% {
		animation-timing-function: step-end;
		color: var(--diagram-noise);
	}

	42.49% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		color: var(--diagram-noise);
	}

	45.609%,
	100% {
		color: var(--fc-ink-12);
	}
}

@keyframes fcOmsReadVip {
	0% {
		animation-timing-function: step-end;
		color: var(--diagram-noise);
	}

	44.221% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		color: var(--diagram-noise);
	}

	45.609%,
	100% {
		color: var(--fc-violet);
	}
}

/*
 * The meter filling. The file staggers seven segments — 52.556%, 53.572%, 54.855%,
 * 56.157%, 57.787%, 59.543%, 61.238% — all converging on the same 67.555%, so the
 * wave lands as one. Ours reads 72/100 across nine segments, which rounds to six lit,
 * so the first six of those seven times are used and the seventh is dropped rather
 * than lighting a segment the static design leaves off.
 *
 * The direction is reversed for the same reason as the values above: the file goes
 * violet → white, and our resting state is six iris segments on the violet bar. So
 * each segment starts at the unlit --fc-white-a29 and arrives at --fc-iris.
 */
body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-meter__seg:nth-of-type(-n + 6) {
	animation: var(--motion-loop) linear infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-meter__seg:nth-of-type(1) {
	animation-name: fcOmsMeter1;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-meter__seg:nth-of-type(2) {
	animation-name: fcOmsMeter2;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-meter__seg:nth-of-type(3) {
	animation-name: fcOmsMeter3;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-meter__seg:nth-of-type(4) {
	animation-name: fcOmsMeter4;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-meter__seg:nth-of-type(5) {
	animation-name: fcOmsMeter5;
}

body.fc-ds .fc-pats.is-live > :nth-child(2) .fc-dg-meter__seg:nth-of-type(6) {
	animation-name: fcOmsMeter6;
}

@keyframes fcOmsMeter1 {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white-a29);
	}

	52.556% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white-a29);
	}

	67.555%,
	100% {
		background-color: var(--fc-iris);
	}
}

@keyframes fcOmsMeter2 {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white-a29);
	}

	53.572% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white-a29);
	}

	67.555%,
	100% {
		background-color: var(--fc-iris);
	}
}

@keyframes fcOmsMeter3 {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white-a29);
	}

	54.855% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white-a29);
	}

	67.555%,
	100% {
		background-color: var(--fc-iris);
	}
}

@keyframes fcOmsMeter4 {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white-a29);
	}

	56.157% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white-a29);
	}

	67.555%,
	100% {
		background-color: var(--fc-iris);
	}
}

@keyframes fcOmsMeter5 {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white-a29);
	}

	57.787% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white-a29);
	}

	67.555%,
	100% {
		background-color: var(--fc-iris);
	}
}

@keyframes fcOmsMeter6 {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-white-a29);
	}

	59.543% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-white-a29);
	}

	67.555%,
	100% {
		background-color: var(--fc-iris);
	}
}

/* ── Panel 3: EXTEND ──────────────────────────────────────────────────────────
 * DEVIATION, and the largest of the three. The file's Extend card is a 2 × 2 grid of
 * tiles growing to 3 × 3 — thirteen of them fading in one per 200ms from 0 to
 * 76.334% — with a cursor flying in from below left at 26.771% → 34.46%. Our Extend
 * drawing is one application window and nothing else, deliberately the emptiest of
 * the four, because extending the platform is a normal piece of work in a normal tool
 * and a busy drawing would argue the opposite. There is no grid to grow and no cursor
 * to fly, and adding either would mean changing the drawing, which is not what was
 * asked for.
 *
 * So the beat is kept and mapped onto what our window does have: the progress row,
 * whose tick, rule and arrow are exactly "a change, built and shipped" — which is
 * this card's caption. The timings are still the file's own timestamps rather than
 * invented ones. The tick crosses the rule over the span the tiles fill (0 →
 * 65.128%, the last tile's entry), the rule deepens behind it over the final tile's
 * window before that (57.494% → 65.128%), and the arrow lands in the file's own last
 * beat, 68.7% → 76.334%.
 *
 * The same .fc-dg-frame carries the home page's release card, which has its own
 * motion under .fc-cards-3 — see Card 1 up in THE HOME PAGE'S MOTION. The two are
 * scoped apart and share nothing but the component.
 *
 * 227px is the design's travel scaled to our rule, by the same arithmetic the home
 * card uses: the prototype moves its tick 188px along a 215px rule, and ours is 260px
 * — a 380px window, less 44px of body padding, less 3px of border, less the 48px tick
 * and the 34px arrow, plus the 9px the arrow is pulled back by.
 */
body.fc-ds .fc-pats.is-live > :nth-child(3) .fc-dg-frame__check {
	--travel: 227px;

	animation: fcOmsBuildTick var(--motion-loop) linear infinite;
}

@keyframes fcOmsBuildTick {
	0% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		translate: 0;
	}

	65.128%,
	100% {
		translate: var(--travel);
	}
}

/*
 * The rule rests at --fc-iris, so this starts pale and arrives where the static
 * design already sits. step-end holds the pale flat rather than easing a colour for a
 * second and a half.
 */
body.fc-ds .fc-pats.is-live > :nth-child(3) .fc-dg-frame__rule {
	animation: fcOmsBuildRule var(--motion-loop) linear infinite;
}

@keyframes fcOmsBuildRule {
	0% {
		animation-timing-function: step-end;
		background-color: var(--fc-lilac-pale);
	}

	57.494% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		background-color: var(--fc-lilac-pale);
	}

	65.128%,
	100% {
		background-color: var(--fc-iris);
	}
}

body.fc-ds .fc-pats.is-live > :nth-child(3) .fc-dg-frame__arrow {
	animation: fcOmsBuildArrow var(--motion-loop) linear infinite;
}

@keyframes fcOmsBuildArrow {
	0% {
		animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
		opacity: 0;
		translate: -10px;
	}

	68.7% {
		opacity: 0;
		translate: -10px;
	}

	69.7% {
		opacity: 1;
	}

	76.334%,
	100% {
		opacity: 1;
		translate: 0;
	}
}

/* ── Panel 4: RELEASE ─────────────────────────────────────────────────────────
 * One beat in the file and one line in the drawing: the sparkline draws itself over
 * 7.633% → 57.494%, eased out. 651px is the sum of the five segment lengths, which
 * are exact — every vertex is at a known unit coordinate on a 560 × 200 chart.
 *
 * The chart is deliberately violet-free. Nothing here resolved; it is the record.
 */
body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend path:nth-of-type(2) {
	stroke-dasharray: 651;
	animation: fcOmsTrendLine var(--motion-loop) linear infinite;
}

@keyframes fcOmsTrendLine {
	0%,
	7.633% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 651;
	}

	57.494%,
	100% {
		stroke-dashoffset: 0;
	}
}

/*
 * The ink fill under the line. Ours, not the file's: it is one path covering the whole
 * area, so on screen from the start it would give the shape away before the line got
 * there. Fading it in across exactly the line's own window means it is only ever as
 * dark as the line is far along.
 */
body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend path:nth-of-type(1) {
	animation: fcOmsTrendFill var(--motion-loop) linear infinite;
}

@keyframes fcOmsTrendFill {
	0%,
	7.633% {
		opacity: 0;
	}

	57.494%,
	100% {
		opacity: 1;
	}
}

/*
 * The vertices, each appearing as the line reaches it. Ours again, and the one place
 * on this page where the times had to be computed rather than read: the draw is eased
 * out, so a vertex 37.39% of the way along the line is not 37.39% of the way through
 * the window. Each time below is ease-out inverted at that vertex's cumulative length
 * over the 651px total — 0, 118.93, 243.27, 378.62, 500.47, 650.62 — which puts the
 * six of them at 7.633%, 13.345%, 19.945%, 28.043%, 36.717% and 57.494%.
 *
 * There are eight circles, not six: fluent_commerce_dg_trend() adds a smaller reading
 * tick on alternate vertices, emitted straight after the vertex it belongs to. So
 * circles 2 and 3 are one vertex, and so are 5 and 6.
 */
body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle {
	animation: var(--motion-loop) step-end infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(1) {
	animation-name: fcOmsTrendDot1;
}

body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(2),
body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(3) {
	animation-name: fcOmsTrendDot2;
}

body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(4) {
	animation-name: fcOmsTrendDot3;
}

body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(5),
body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(6) {
	animation-name: fcOmsTrendDot4;
}

body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(7) {
	animation-name: fcOmsTrendDot5;
}

body.fc-ds .fc-pats.is-live > :nth-child(4) .fc-dg-trend circle:nth-of-type(8) {
	animation-name: fcOmsTrendDot6;
}

@keyframes fcOmsTrendDot1 {
	0% {
		opacity: 0;
	}

	7.633%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsTrendDot2 {
	0% {
		opacity: 0;
	}

	13.345%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsTrendDot3 {
	0% {
		opacity: 0;
	}

	19.945%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsTrendDot4 {
	0% {
		opacity: 0;
	}

	28.043%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsTrendDot5 {
	0% {
		opacity: 0;
	}

	36.717%,
	100% {
		opacity: 1;
	}
}

@keyframes fcOmsTrendDot6 {
	0% {
		opacity: 0;
	}

	57.494%,
	100% {
		opacity: 1;
	}
}

/* ── Panel 5: LEVERAGE ────────────────────────────────────────────────────────
 * Two beats, and they are the two the file has: one run in, one run out.
 *
 *     6005:2526  Rectangle 313   7.633%  → 50.81%   ease-out
 *     6005:2550  Line 261       53.572% → 67.555%   ease-out
 *
 * The file trims one path per beat. Our drawing has three per beat — three sources
 * arriving at the engine, three answers leaving it — so each beat draws its three
 * together rather than one of them. That is the substitution, and it is a smaller
 * one than it looks: the beats are what the file choreographs, and three lines on
 * a shared spine are one arrival. Staggering them would invent a sequence the file
 * does not have and would argue that the sources are read in an order, which is
 * the opposite of the card's claim.
 *
 * The gap between the beats is real and worth keeping: the ink is fully in at
 * 50.81% and nothing leaves until 53.572%, so the engine visibly holds for 73ms
 * before it answers. That pause IS the drawing — data in, then a decision, then
 * out. Overlapping them would make it a pipe.
 *
 * Lengths, on the house rule from the header — legs exact, corners at 1.623225px
 * per px of leg, each total up to the next whole pixel:
 *
 *     in,  outer   12 + 19.479 + 124 + 19.479 + 20  = 194.96  → 195
 *     in,  middle  56 straight, no elbow to draw             →  56
 *     out, outer   44 + 19.479 + 124 + 19.479 + 44  = 250.96  → 251
 *     out, middle  112 straight                              → 112
 *
 * The middles get their own dashoffset rather than sharing the outer ones' so that
 * all three of a beat's lines finish at the same instant. A shared 195 on a 56px
 * path would have the middle one done a third of the way through the window and
 * then sitting still, which reads as three arrivals, not one.
 */
body.fc-ds .fc-pats.is-live > :nth-child(5) .fc-dg-wires path:nth-of-type(1),
body.fc-ds .fc-pats.is-live > :nth-child(5) .fc-dg-wires path:nth-of-type(3) {
	stroke-dasharray: 195;
	animation: fcOmsLeverageIn var(--motion-loop) linear infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(5) .fc-dg-wires path:nth-of-type(2) {
	stroke-dasharray: 56;
	animation: fcOmsLeverageInMid var(--motion-loop) linear infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(5) .fc-dg-wires path:nth-of-type(4),
body.fc-ds .fc-pats.is-live > :nth-child(5) .fc-dg-wires path:nth-of-type(6) {
	stroke-dasharray: 251;
	animation: fcOmsLeverageOut var(--motion-loop) linear infinite;
}

body.fc-ds .fc-pats.is-live > :nth-child(5) .fc-dg-wires path:nth-of-type(5) {
	stroke-dasharray: 112;
	animation: fcOmsLeverageOutMid var(--motion-loop) linear infinite;
}

@keyframes fcOmsLeverageIn {
	0%,
	7.633% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 195;
	}

	50.81%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcOmsLeverageInMid {
	0%,
	7.633% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 56;
	}

	50.81%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcOmsLeverageOut {
	0%,
	53.572% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 251;
	}

	67.555%,
	100% {
		stroke-dashoffset: 0;
	}
}

@keyframes fcOmsLeverageOutMid {
	0%,
	53.572% {
		animation-timing-function: ease-out;
		stroke-dashoffset: 112;
	}

	67.555%,
	100% {
		stroke-dashoffset: 0;
	}
}

/*
 * Belt and braces. cardart.js already returns before adding the class, so this
 * should never be the thing that stops it — but an animation this file can start
 * is an animation this file should be able to stop, without depending on a script
 * having made the right decision.
 */
@media (prefers-reduced-motion: reduce) {
	body.fc-ds .fc-cards-3.is-live,
	body.fc-ds .fc-cards-3.is-live *,
	body.fc-ds .fc-route-wide.is-live,
	body.fc-ds .fc-route-wide.is-live *,
	body.fc-ds .fc-proofgrid.is-live,
	body.fc-ds .fc-proofgrid.is-live *,
	body.fc-ds .fc-pats.is-live,
	body.fc-ds .fc-pats.is-live * {
		animation: none;
	}
}

body.fc-ds .fc-card__title {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-body-18);
	line-height: var(--lh-title);
	letter-spacing: 0;
	color: var(--text-on-light);
}

body.fc-ds .fc-card__body {
	color: var(--text-on-light-muted);
	text-wrap: pretty;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE STAT BAND
 * .fc-statband is the ruled three-column table (layout.css); this bleeds it out
 * to the section's edges so the hairlines run the full width of the page.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-home-stats {
	margin: var(--sp-72) calc(var(--gutter-page) * -1) 0;
}

body.fc-ds .fc-statcol__prefix {
	font-size: var(--type-body-18);
	font-weight: var(--fw-medium);
	line-height: var(--lh-body);
	color: var(--fc-ink-a60);
}

/*
 * The figures are brand violet rather than ink. #793dff on #f2f1ed is 4.75:1, so it
 * clears AA as body text and clears it several times over at this size — worth
 * stating because the same violet on the same bone would NOT be safe for the 18px
 * prefix and suffix above and below it, which stay ink for that reason.
 *
 * tabular-nums was already here for the design's own sake, three figures lining up
 * across three columns. It is load-bearing for the count in assets/ds/stats.js too:
 * proportional digits are different widths, so "111" and "150" would not occupy the
 * same space and the number would jitter as it ran.
 */
body.fc-ds .fc-statcol__value {
	font-family: var(--font-quote);
	font-weight: var(--fw-semibold);
	font-size: var(--type-numeral-96);
	line-height: 0.95;
	letter-spacing: var(--ls-quote);
	color: var(--fc-violet);
	font-variant-numeric: tabular-nums;
}

/*
 * The span stats.js counts inside. It exists only when that script has run, and the
 * one thing it needs is to be a box: min-width is what stops a centred figure
 * drifting while it counts, and min-width does nothing to an inline element.
 */
body.fc-ds .fc-statcol__num {
	display: inline-block;
}

body.fc-ds .fc-statcol__suffix {
	font-size: var(--type-body-18);
	font-weight: var(--fw-semibold);
	line-height: var(--lh-body);
	color: var(--text-on-light);
	text-wrap: pretty;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE PROOF GRID
 * Five customers, each a record sleeve over a name and one result.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-proof-title {
	text-align: center;
	text-wrap: pretty;
}

body.fc-ds .fc-proofgrid {
	gap: 20px;
	margin-top: var(--sp-72);
}

body.fc-ds .fc-proofgrid .fc-sleeve {
	--sleeve-max: 280px;
}

/*
 * THE CARD, which is one <a> to its case study — the rule stated at the head of
 * blog.css. All five are links today, but a customer without a published case study
 * gets the same class list on a <div> — see inc/ds-home.php. Everything except :hover is
 * shared, which is why the two read as one component and not as a linked card next to an
 * odd one, and why the hover rules below stay qualified with a.
 *
 * text-decoration is dropped here rather than per line: the name and the result are
 * the link text, and two underlined paragraphs under a photograph read as a list.
 *
 * Nothing animates the sleeve on hover. The spotlight in this same sheet already owns
 * .fc-proofgrid.is-live > * > .fc-sleeve, and a transform here would fight it.
 */
body.fc-ds .fc-proofcard {
	color: var(--text-on-light-muted);
	text-decoration: none;
}

body.fc-ds .fc-proofcard__name {
	font-size: var(--type-body-18);
	font-weight: var(--fw-semibold);
	line-height: var(--lh-title);
	color: var(--text-on-light);
	transition: color var(--dur-base) var(--ease-out);
}

/*
 * inherit, so the result's colour is the anchor's and the hover is declared once on
 * the card. This is the same division blog.css uses.
 */
body.fc-ds .fc-proofcard__result {
	color: inherit;
	text-wrap: pretty;
	transition: color var(--dur-base) var(--ease-out);
}

/* The name carries the affordance; the result just comes up to meet it. */
body.fc-ds a.fc-proofcard:hover {
	color: var(--text-on-light);
}

body.fc-ds a.fc-proofcard:hover .fc-proofcard__name {
	color: var(--fc-violet);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE VIOLET ROUTE
 * One argument drawn twice, because the drawing has to change axis rather than
 * scale: horizontally above 1024, vertically below it, where five 160px labels
 * would fall under the 12px type floor. Only one is ever in the document flow.
 *
 * The dot field here is NOT --dot-grid: the route's dot is centred in its cell
 * (`radial-gradient(… 1px, transparent 1px)`), where the diagram grid's sits at
 * 1px 1px. They read the same and are drawn differently; keeping the export's
 * value means the route lines up with the artwork it came from.
 *
 * ── THE WHITE BAND (BUILD-15) ───────────────────────────────────────────────
 *
 * The band used to sit on the section's bone, so the dot field was ink at 18% over
 * #F2F1ED and the plates were bone-on-bone with a hairline. Build-15 lays a white
 * ground under the whole thing and keeps the same 18% dots, which lifts the field's
 * contrast (18% over white, not over bone) and lets the plates go properly white.
 *
 * --surface-lightest, not --surface-light: the band is a white plate the diagram is
 * drawn on, and --surface-light is bone. The <rect>s inside the SVG take the same
 * token, so the plates and the band they sit on can never drift apart.
 *
 * The extra padding is the export's too — 56 wide, 54 narrow, up from 32 and 24. It is
 * doing real work now that there is an edge to be inset from: at the old 32 the
 * outermost dots of the field sat two rows from the top of a visible rectangle.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-route-wide,
body.fc-ds .fc-route-narrow {
	position: relative;
	background-color: var(--surface-lightest);
	background-image: radial-gradient(rgba(18, 18, 18, 0.18) 1px, transparent 1px);
	background-size: 16px 16px;
	background-position: 0 0;
}

body.fc-ds .fc-route-wide {
	display: block;
	margin: var(--sp-72) calc(var(--gutter-page) * -1) 0;
	padding: 56px 0;
}

body.fc-ds .fc-route-wide > svg {
	display: block;
	width: 100%;
	height: auto;
}

body.fc-ds .fc-route-narrow {
	display: none;
	--route-pad: var(--sp-54);
	margin: var(--sp-72) 0 0;
	padding: var(--route-pad) 0 var(--route-pad) 48px;
}

@container page (max-width: 1100px) {
	body.fc-ds .fc-route-wide {
		display: none;
	}

	body.fc-ds .fc-route-narrow {
		display: block;
	}
}

/*
 * The spine starts and finishes ON a dot rather than in space, so its inset is the
 * band's padding plus half a chip (46 / 2). That used to be a flat 47px, which was
 * 24 + 23 and read as a single magic number until Build-15 moved the padding to 54 and
 * left the 47 behind — in the export the spine now overshoots the first dot by 30px at
 * both ends. Derived from --route-pad here so the two can't come apart again.
 */
body.fc-ds .fc-route-narrow__spine {
	position: absolute;
	left: 23px;
	top: calc(var(--route-pad) + 23px);
	bottom: calc(var(--route-pad) + 23px);
	width: var(--stroke-2);
	background: var(--fc-violet);
}

body.fc-ds .fc-route-narrow__row {
	position: relative;
	display: flex;
	align-items: center;
}

body.fc-ds .fc-route-narrow__dot {
	position: absolute;
	left: -31px;
	width: 7px;
	height: 7px;
	border-radius: var(--radius-pill);
	background: var(--fc-ink-12);
}

/*
 * The route's label, and the same object inside the wide SVG's <rect> + <text>.
 *
 * White on a full-strength ink hairline as of Build-15, where it was the light surface
 * (bone) on --border-on-light (ink at 20%). Both halves of that changed together and
 * had to: the chip now sits on a white band, so a bone fill would read as a smudge and
 * a 20% rule would all but vanish against the dot field showing through behind it.
 */
body.fc-ds .fc-chip {
	display: inline-flex;
	align-items: center;
	height: 46px;
	padding: 0 18px;
	background: var(--surface-lightest);
	border: var(--stroke-1) solid var(--fc-ink-12);
	border-radius: var(--radius-10);
	font-family: var(--font-core);
	font-size: var(--type-body-15);
	font-weight: var(--fw-semibold);
	line-height: var(--lh-snug);
	color: var(--text-on-light);
	white-space: nowrap;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE FIVE REASONS — STICKY RAIL AND PANES
 *
 * A rail of the five titles beside a single column of five panes, on .fc-rail-split /
 * .fc-sticky-rail from layout.css. The rail names where you are; the pane you are
 * reading has the whole measure to itself.
 *
 * This replaced a ruled five-column table (.fc-whytable / .fc-whycol) in Build-15. Not
 * a restyle of it — the table read across in 18px grotesk with a shared 640px floor for
 * its mono indices and a hover that dimmed the other four columns to 20%; this reads
 * down in display serif at 40. The hover has no successor and needs none: the rail's
 * own highlight is what tells you which of the five you are in, and it does it while
 * you scroll rather than only while you point.
 *
 * ── THE RAIL'S THREE TINTS ──────────────────────────────────────────────────
 *
 * Build-15 asks for ink at 45% on the label, 35% on the index and 15% on the border.
 * The theme's alpha ramp goes …50, 40, 20, 06, and gaining three near-duplicate rungs
 * for one component is a worse trade than moving each by five points: 50 / 40 / 20. The
 * shift is uniform, so the ordering the design depends on — label darker than index,
 * border lightest — holds exactly, and five points of alpha on a bone ground is under
 * thirteen levels of one channel.
 * ────────────────────────────────────────────────────────────────────────── */

/*
 * THE RAIL'S SMOOTH SCROLL, and the two rules in this file that are not under
 * body.fc-ds — scroll-behavior only has an effect on the scrolling element, and html is
 * not inside the body.
 *
 * In CSS rather than a click handler, so the rail's five rows stay ordinary anchors:
 * they work with whyrail.js absent, they put focus on the pane the way a jump link
 * should, and middle-clicking one still opens it in a tab. None of that survives a
 * handler calling preventDefault. innovation.css states the same case at more length.
 *
 * :has(.fc-whyrail) and not a bare html, because this file is enqueued for all sixteen
 * DS templates: unscoped it would turn every anchor jump on the site smooth, which is a
 * change nine other pages did not ask for. The rail is the only thing on the home page
 * that jumps, so the condition is "there is a rail here".
 *
 * It needs its own reduced-motion guard for the same reason it needs the html selector:
 * the global one in tokens.css is under `body.fc-ds *`.
 */
html:has(.fc-whyrail) {
	scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
	html:has(.fc-whyrail) {
		scroll-behavior: auto;
	}
}

body.fc-ds .fc-whysplit {
	margin-top: var(--sp-72);
}

body.fc-ds .fc-whyrail {
	display: flex;
	flex-direction: column;
}

/*
 * align-items: baseline, so the mono index sits on the same line as the first line of
 * a title that wraps to two. Aligned to the top of the box instead it would ride above
 * the cap height, because 12px mono and 18px grotesk have different ascents.
 *
 * The left rule is always drawn and only its colour changes, so nothing shifts sideways
 * when the highlight moves — the same reasoning as the Innovation rail.
 */
body.fc-ds .fc-whyrail__link {
	display: flex;
	align-items: baseline;
	gap: var(--sp-16);
	padding: 14px 0 14px var(--sp-16);
	border-left: var(--stroke-2) solid var(--fc-ink-a20);
	color: var(--fc-ink-a50);
	text-decoration: none;
	transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}

body.fc-ds .fc-whyrail__index {
	flex: none;
	font-family: var(--font-mono);
	font-weight: var(--fw-regular);
	font-size: var(--type-mono-12);
	letter-spacing: var(--ls-label);
	color: var(--fc-ink-a40);
	font-variant-ligatures: none;
	transition: color var(--dur-fast) var(--ease-out);
}

body.fc-ds .fc-whyrail__label {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-body-18);
	line-height: var(--lh-snug);
	color: inherit;
	text-wrap: pretty;
}

body.fc-ds .fc-whyrail__link:hover,
body.fc-ds .fc-whyrail__link:focus-visible {
	color: var(--fc-violet);
}

/*
 * The reading highlight, set by whyrail.js. .is-on is the only thing the script does:
 * without it every link rests at the tints above, which is a legible rail rather than
 * a broken one — the same bargain as partners.js and the pulse.
 */
body.fc-ds .fc-whyrail__link.is-on {
	border-left-color: var(--fc-violet);
	color: var(--text-on-light);
}

body.fc-ds .fc-whyrail__link.is-on .fc-whyrail__index {
	color: var(--fc-violet);
}

body.fc-ds .fc-whypanes {
	--stack-gap: var(--gap-blocks);
}

/*
 * The landing offset, and it is the rail's own sticky top rather than the export's flat
 * 160: click a row and its pane should arrive level with the rail, not somewhere under
 * it. whyrail.js computes no offset for the same reason — scroll-margin states it once.
 */
body.fc-ds .fc-whypane {
	scroll-margin-top: var(--pad-section);
}

/* 1.06 is the export's, and display leading is the one place this file still spells a
   number: the ramp's tokens start at 1.1, which is loose for a 40px serif. */
body.fc-ds .fc-whypane__title {
	font-family: var(--font-display);
	font-weight: var(--fw-regular);
	font-size: var(--type-display-40);
	line-height: 1.06;
	letter-spacing: var(--ls-display);
	color: var(--text-on-light);
	text-wrap: pretty;
}

/* 620 is the export's measure, and the paragraphs inside are .fc-copy — the colour and
   the size come from that role rather than being restated here. */
body.fc-ds .fc-whypane__paras {
	max-width: 620px;
}

/*
 * Collapsed to one column, the rail would be a list of the five titles sitting directly
 * above the first of them — the same words twice — and it is no longer sticky, so it
 * cannot track where you are either. It goes. The panes keep their own titles, and the
 * indices go with the rail because they were never anything but rail furniture.
 *
 * This is a deviation: the export keeps the rail in one column. It is worth it, and it
 * is one declaration to put back. Note that the Innovation rail survives its own
 * collapse because its labels are short index words rather than the capability titles.
 */
@container page (max-width: 1100px) {
	body.fc-ds .fc-whyrail {
		display: none;
	}

	body.fc-ds .fc-whypanes {
		--stack-gap: var(--sp-72);
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE CLOSING SECTION
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-demo__lead {
	max-width: 620px;
	color: var(--text-on-light-muted);
}

body.fc-ds .fc-demo__eyebrow {
	color: var(--fc-violet);
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE OMS PAGE
 * ══════════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────────────────────
 * THE HERO
 * Dot-grid paper with six violet routes drawing themselves in from both edges and
 * terminating on an ink block — the platform — that sits at the optical centre.
 * The copy is bottom-left, so the drawing has the top and the right.
 *
 * The dot here is a 24px cell at the diagram hairline's weight, not the 16px page
 * grid: at hero scale the tighter lattice reads as texture rather than as paper.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-oms-hero {
	--dot-cell: 24px;
	--dot-color: var(--diagram-edge);
	background: var(--dot-grid) 0 0 / var(--dot-cell) var(--dot-cell), var(--surface-light);
}

body.fc-ds .fc-oms-hero__copy {
	justify-content: flex-end;
	align-items: flex-start;
	padding: 0 var(--gutter-page) 96px;
	text-align: left;
}

body.fc-ds .fc-oms-hero__title {
	max-width: min(720px, 56%);
	line-height: 1.05;
}

body.fc-ds .fc-oms-hero__lead {
	max-width: min(560px, 56%);
	line-height: var(--lh-loose);
	color: var(--text-on-light-muted);
}

body.fc-ds .fc-oms-hero__ctas {
	display: flex;
	align-items: center;
	gap: var(--sp-24);
	flex-wrap: wrap;
	margin-top: var(--sp-8);
}

/*
 * The mark. Percentage position and width, so it stays on the point where the
 * wires converge at every size — the wire SVG is preserveAspectRatio="none" and
 * stretches with the fold, so anything absolute in px would drift off it.
 */
body.fc-ds .fc-oms-hero__mark {
	position: absolute;
	left: 63.02%;
	top: 42.22%;
	width: 12.5%;
	aspect-ratio: 1;
	translate: 0 -50%;
	background: var(--surface-dark);
}

body.fc-ds .fc-oms-hero__mark > svg {
	display: block;
	width: 100%;
	height: 100%;
}

/* The two terminals the wires land on, centred on the block's left and right. */
body.fc-ds .fc-oms-hero__node {
	position: absolute;
	top: 50%;
	width: 20px;
	height: 20px;
	border-radius: var(--radius-pill);
	background: var(--surface-dark);
}

body.fc-ds .fc-oms-hero__node--left {
	left: 0;
	translate: -50% -50%;
}

body.fc-ds .fc-oms-hero__node--right {
	right: 0;
	translate: 50% -50%;
}

/*
 * Below 820 the fold releases its height and the drawing becomes a band above the
 * copy rather than the ground behind it. The band is cropped to the routes' own
 * bounding box — see HERO WIRES in layout.css, which reads these four numbers — and
 * everything below is stated in the same units so the mark cannot come off the
 * point the routes converge on.
 *
 * --crop-h is the only free number here: the routes span 540 units vertically, and
 * the rest is air below the mark. Raising it only puts more dot grid between the
 * drawing and the headline; it does not move the mark, for the reason given below.
 */
@container page (max-width: 820px) {
	body.fc-ds .fc-oms-hero {
		--crop-x: 700;
		--crop-y: -40;
		--crop-w: 1260;
		--crop-h: 660;
	}

	body.fc-ds .fc-oms-hero__copy {
		padding-bottom: var(--sp-54);
	}

	body.fc-ds .fc-oms-hero__title,
	body.fc-ds .fc-oms-hero__lead {
		max-width: none;
	}

	/*
	 * Back on. Hiding it was the other half of the bug: the six routes exist to
	 * arrive somewhere, and without the block they ended in mid-air.
	 *
	 * The block spans x 1210–1450 and centres on y 380 in the wire SVG, and each
	 * figure below is (that unit − the crop's origin) ÷ --crop-w. The vertical one
	 * divides by the WIDTH too, and is a cqw rather than a percentage, because the
	 * band's height is itself a ratio of the band's width — the two ratios cancel,
	 * which is why --crop-h does not appear.
	 */
	body.fc-ds .fc-oms-hero__mark {
		left: calc((1210 - var(--crop-x)) / var(--crop-w) * 100%);
		top: calc((380 - var(--crop-y)) / var(--crop-w) * 100cqw);
		width: calc(240 / var(--crop-w) * 100%);
	}

	/* 20px on a 240-unit block is 8.33% of it; fixed px would swamp it at this size. */
	body.fc-ds .fc-oms-hero__node {
		width: 8.33%;
		height: 8.33%;
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE CAPABILITY BENTO
 * Seven capabilities: three tiles down the left, four along the bottom, and the
 * open one's detail filling the middle. No script — seven radios, seven labels
 * and seven panels, with :checked ~ pairing a radio to its panel by data-cap.
 *
 * The panels ARE the mobile stack. The export ships a second copy of all seven
 * as .fc-bento-stack cards, which is the same content twice in the DOM and two
 * places to fix a typo; below 768 the bento becomes a column and every panel
 * shows, which is the same design with none of the duplication.
 *
 * The radios are .fc-sr rather than display: none because a hidden input is not
 * focusable — the tab order and :focus-visible ring both depend on it being in
 * the accessibility tree.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-bento {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	grid-template-rows: repeat(3, 120px) auto;
	gap: 20px;
	max-width: 1240px;
	margin: var(--sp-72) auto 0;
	/* Containing block for the radios — see the note below them. */
	position: relative;
}

body.fc-ds .fc-bento__tile {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: var(--sp-16);
	grid-column: var(--col, auto);
	grid-row: var(--row, auto);
	min-height: 120px;
	padding: 20px;
	background: transparent;
	border: var(--stroke-1) solid var(--border-on-light);
	color: var(--text-on-light);
	text-align: left;
	cursor: pointer;
	transition: background 160ms var(--ease-out), border-color 160ms var(--ease-out), color 160ms var(--ease-out);
}

body.fc-ds .fc-bento__tile:hover {
	border-color: var(--fc-violet);
}

body.fc-ds .fc-bento__icon {
	color: var(--fc-violet);
	transition: color 160ms var(--ease-out);
}

body.fc-ds .fc-bento__label {
	font-size: var(--type-body-18);
	font-weight: var(--fw-semibold);
	line-height: var(--lh-title);
	text-wrap: pretty;
}

/*
 * Each radio sits in its own tile's cell, which is what stops the page jumping.
 *
 * .fc-sr is position: absolute with no offsets (assets/ds/layout.css), so each
 * 1px input is placed at its static position inside the nearest positioned
 * ancestor. The bento had none and neither does anything above it, so all seven
 * resolved against the initial containing block and physically sat at the top of
 * the document. Clicking a label moves focus to its radio, and the browser
 * scrolls a focused off-screen input into view — that was the jump to the top.
 *
 * .fc-bento is now positioned, and an absolutely positioned grid child with a
 * definite grid placement is laid out inside that grid area, so each radio ends
 * up under the tile that was just clicked and there is nothing to scroll to.
 *
 * The line pairs mirror the col/row values in $caps (inc/ds-oms.php). The
 * alternative — emitting --col/--row on the input the way the label already
 * does — is a markup change, and this markup lives in post_content, so it would
 * mean re-seeding local, staging and live to fix a scroll bug.
 */
body.fc-ds #fc-cap-1 {
	grid-area: 1 / 1;
}

body.fc-ds #fc-cap-2 {
	grid-area: 2 / 1;
}

body.fc-ds #fc-cap-3 {
	grid-area: 3 / 1;
}

body.fc-ds #fc-cap-4 {
	grid-area: 4 / 1;
}

body.fc-ds #fc-cap-5 {
	grid-area: 4 / 2;
}

body.fc-ds #fc-cap-6 {
	grid-area: 4 / 3;
}

body.fc-ds #fc-cap-7 {
	grid-area: 4 / 4;
}

body.fc-ds .fc-bento__radio:checked + .fc-bento__tile {
	background: var(--fc-violet);
	border-color: var(--fc-violet);
	color: var(--fc-white);
}

body.fc-ds .fc-bento__radio:checked + .fc-bento__tile .fc-bento__icon {
	color: var(--fc-white);
}

/* The ring belongs on the thing you can see, not on the 1px input. */
body.fc-ds .fc-bento__radio:focus-visible + .fc-bento__tile {
	outline: var(--stroke-2) solid var(--fc-violet);
	outline-offset: 2px;
}

body.fc-ds .fc-bento-panel {
	display: none;
	flex-direction: column;
	justify-content: space-between;
	gap: var(--sp-32);
	grid-column: 2 / 5;
	grid-row: 1 / 4;
	padding: var(--sp-32);
	background: var(--surface-dark);
	color: var(--text-on-dark);
}

body.fc-ds #fc-cap-1:checked ~ .fc-bento-panel[data-cap="1"],
body.fc-ds #fc-cap-2:checked ~ .fc-bento-panel[data-cap="2"],
body.fc-ds #fc-cap-3:checked ~ .fc-bento-panel[data-cap="3"],
body.fc-ds #fc-cap-4:checked ~ .fc-bento-panel[data-cap="4"],
body.fc-ds #fc-cap-5:checked ~ .fc-bento-panel[data-cap="5"],
body.fc-ds #fc-cap-6:checked ~ .fc-bento-panel[data-cap="6"],
body.fc-ds #fc-cap-7:checked ~ .fc-bento-panel[data-cap="7"] {
	display: flex;
}

body.fc-ds .fc-bento-panel__icon {
	color: var(--fc-white);
}

body.fc-ds .fc-bento-panel__copy {
	gap: 12px;
}

body.fc-ds .fc-bento-panel__title {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-h5-24);
	line-height: var(--lh-title);
	letter-spacing: 0;
	color: var(--fc-white);
}

body.fc-ds .fc-bento-panel__body {
	max-width: 560px;
	line-height: var(--lh-loose);
	color: var(--fc-white-a80);
	text-wrap: pretty;
}

/* 1024 — the panel can no longer share a row with the tiles, so it takes its own. */
@container page (max-width: 1100px) {
	body.fc-ds .fc-bento {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		/*
		 * Four rows for the seven auto-placed tiles. They were implicit before and
		 * sized the same either way, but a row line has to exist in the explicit
		 * grid for the radios below to be placed against it.
		 */
		grid-template-rows: repeat(4, auto);
	}

	body.fc-ds .fc-bento__tile {
		grid-column: auto;
		grid-row: auto;
	}

	/*
	 * Two columns, so the tiles auto-place in pairs and the radios follow them:
	 * odd caps left, even caps right, a new row every two.
	 */
	body.fc-ds #fc-cap-1 {
		grid-area: 1 / 1;
	}

	body.fc-ds #fc-cap-2 {
		grid-area: 1 / 2;
	}

	body.fc-ds #fc-cap-3 {
		grid-area: 2 / 1;
	}

	body.fc-ds #fc-cap-4 {
		grid-area: 2 / 2;
	}

	body.fc-ds #fc-cap-5 {
		grid-area: 3 / 1;
	}

	body.fc-ds #fc-cap-6 {
		grid-area: 3 / 2;
	}

	body.fc-ds #fc-cap-7 {
		grid-area: 4 / 1;
	}

	body.fc-ds .fc-bento-panel {
		grid-column: 1 / -1;
		grid-row: auto;
		min-height: 320px;
	}
}

/*
 * 768 — tapping a tile to change something elsewhere on the page does not read on
 * a phone, so the bento becomes what it was describing: all seven capabilities as
 * ink cards, one after another on scroll.
 */
@container page (max-width: 820px) {
	body.fc-ds .fc-bento {
		display: flex;
		flex-direction: column;
		gap: var(--sp-16);
	}

	body.fc-ds .fc-bento__radio,
	body.fc-ds .fc-bento__tile {
		display: none;
	}

	body.fc-ds .fc-bento-panel {
		display: flex;
		gap: var(--sp-24);
		min-height: 0;
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE FIVE DIAGRAM PANELS
 * Same scale trick as the home cards, with the pattern set's own canvas: every
 * diagram in the system is 640 × 560, so the panel's ratio is the canvas's and
 * the fit is the full panel width rather than 84% of it — the canvases already
 * carry their own padding.
 *
 * ── WHY THIS ROW RUNS OFF THE PAGE ──────────────────────────────────────────
 *
 * The design draws five cards 430px wide with a 24px gap — 2246px of cards in a
 * 1506px column — and it does not shrink them to fit. It runs them off the right
 * edge of the page. That is a deliberate reading: a card at 430 is the width at
 * which a 640px drawing is still legible, and five of those do not fit any screen
 * anyone has, so the choice is between five readable cards the visitor scrolls and
 * five unreadable ones they don't. The design picks scrolling, and dividing the
 * column by five would have made the drawings 271px wide — smaller than the four
 * they replaced.
 *
 * So the row is a scroll container that bleeds through the section's right gutter
 * to the edge of the page. Three consequences worth knowing:
 *
 *   · The bleed is margin-right negative + padding-right positive, the same idiom
 *     as the stat band and the wide route above. The padding is what stops the
 *     fifth card from ending flush against the window when it is scrolled to.
 *   · Nothing is clipped at the page edge, because .fc-page is overflow-x: clip
 *     (layout.css) — the row's own scroll is the only sideways scroll.
 *   · The old 1340px → 2 columns and 820px → 1 column queries are gone with the
 *     grid. A scroller has no breakpoints to reflow at; it is the same row at
 *     every width.
 *
 * ── WHY THE WIDTH IS A PROPORTION AND NOT 430px ─────────────────────────────
 *
 * 430px is Figma's number, and setting it literally makes the cards look far too
 * big — which is not a contradiction. The prototype is a single 1920px frame shown
 * with scale-down-width, so what anybody actually looks at is the frame multiplied
 * by viewport / 1920. The card is never 430 on screen; it is 430 × that factor,
 * and what stays constant is the share of the page it occupies: 429.4 / 1920 =
 * 22.396%. Pin the px and you pin the one thing the design does not fix, while the
 * gutters around it step down at every breakpoint — at a 1440 window the column is
 * 1200 and a 430 card is 36% of it instead of 28.5%.
 *
 * So the width is 22.396cqw against .fc-shell, in the same fluid-constant idiom as
 * the type scale in layout.css, capped at the design's 430 (the shell stops at
 * 1920, so the cap is really just a guard) and floored at 300. The floor is a
 * legibility limit, not a design value: below roughly a 1340 window the proportion
 * would take the 640px drawing under 300px, where the node labels stop being
 * readable, and holding 300 there also leaves the next card's edge showing past the
 * screen on a phone. That peek is the whole mobile affordance — a row that ends
 * exactly at the screen edge reads as a row that ended.
 *
 * ── WHY THERE IS NO SCROLLBAR ───────────────────────────────────────────────
 *
 * The prototype has none. You put the cursor on a card and drag the row across, so
 * the row is styled as something to grab rather than something with a control on
 * it. Both halves of that are gated on .is-grab, which rail.js adds and which
 * therefore only exists where the drag does: with the script gone the bar comes
 * back and the row is an ordinary scroller. See the head of rail.js.
 *
 * ── WHY THE RIGHT-HAND CARD FADES OUT ───────────────────────────────────────
 *
 * With no bar there is nothing left to say the row goes on, so the design says it
 * with the cards themselves: whichever one the page edge cuts through is faded to
 * nothing as it reaches the edge. It is the only cue there is, which is why it is a
 * mask on the row and not decoration on card five — it has to sit at the edge of
 * the window and stay there while the cards pass under it.
 *
 * The ramp is measured off the 1920 frame rather than guessed. Sampling the ink
 * across the row: fully opaque out to x = 1370, half opaque at x ≈ 1625, gone by
 * x = 1920. So it is 550px long, it ends exactly at the page edge — which is the
 * row's border box, since the bleed puts it there — and its midpoint sits at 46%
 * rather than 50%, hence the three stops. The design's last third falls away a
 * little faster still; a fourth stop to chase that was 0.05 of alpha on text
 * already down at 0.2, and not worth the reading.
 *
 * 550px is 28.646% of the 1920 canvas and is expressed that way for the same
 * reason the card width is (above): pinned in px it would swallow a whole card on a
 * laptop. Floored at 120px so it stays a fade and not a hairline on a phone.
 *
 * The length is a registered custom property so it can be transitioned, and it goes
 * to zero on .is-end — which rail.js sets when the row is scrolled as far as it
 * goes. Without that the cue becomes a defect: at the far end card five sits inside
 * the ramp and only 87 of its 430px would be legible. Everything about the fade is
 * therefore unconditional and only its retraction needs the script — the failure
 * state is the design's own frame.
 * ────────────────────────────────────────────────────────────────────────── */

/* Registered so it interpolates; a plain custom property cannot be transitioned. */
@property --fc-pats-fade {
	syntax: "<length>";
	inherits: false;
	initial-value: 0px;
}

body.fc-ds .fc-pats {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: clamp(300px, 22.396cqw, 430px);
	gap: var(--sp-24);

	--fc-pats-fade: clamp(120px, 28.646cqw, 550px);
	-webkit-mask-image: linear-gradient(
		to right,
		#000000 calc(100% - var(--fc-pats-fade)),
		rgba(0, 0, 0, 0.5) calc(100% - var(--fc-pats-fade) * 0.536),
		rgba(0, 0, 0, 0) 100%
	);
	mask-image: linear-gradient(
		to right,
		#000000 calc(100% - var(--fc-pats-fade)),
		rgba(0, 0, 0, 0.5) calc(100% - var(--fc-pats-fade) * 0.536),
		rgba(0, 0, 0, 0) 100%
	);
	transition: --fc-pats-fade 240ms ease-out;

	/* Bleed right to the page edge; keep a gutter's worth of run-off past card 5. */
	margin: var(--sp-72) calc(var(--gutter-page) * -1) 0 0;
	padding-right: var(--gutter-page);

	/*
	 * Room for the scrollbar, so it never sits on the last card's body copy. Only
	 * the no-rail.js case ever shows one, and .is-grab takes this back off again.
	 */
	padding-bottom: var(--sp-16);

	/*
	 * x auto, y hidden. The cards are auto-height grid items so nothing overflows
	 * downward, and leaving y to compute to auto would put a vertical scrollbar on
	 * the row the first time a font rounded a card a pixel taller than its sibling.
	 */
	overflow: auto hidden;
	overscroll-behavior-x: contain;

	/*
	 * proximity, not mandatory. Five cards is a row someone drags through, and
	 * mandatory snapping fights a flick that was meant to travel — it also makes a
	 * card that is wider than the viewport impossible to read the far edge of.
	 */
	scroll-snap-type: x proximity;
	scrollbar-width: thin;
	scrollbar-color: var(--fc-ink-a20) transparent;
}

body.fc-ds .fc-pats > * {
	/* Snap to the content edge, which is where the heading above starts. */
	scroll-snap-align: start;
}

/*
 * Scrolled as far as it goes: the fade has nothing left to promise, so it retracts
 * rather than sitting on the last card's copy. rail.js owns the class.
 */
body.fc-ds .fc-pats.is-end {
	--fc-pats-fade: 0px;
}

/*
 * Grab-and-drag: the design's interaction, and only ever on with rail.js present.
 * The scrollbar goes, and with it the padding that was reserved to clear it.
 */
body.fc-ds .fc-pats.is-grab {
	padding-bottom: 0;
	cursor: grab;
	scrollbar-width: none;
}

body.fc-ds .fc-pats.is-grab::-webkit-scrollbar {
	display: none;
}

/*
 * Held. user-select stops the drag from painting a selection across three cards'
 * body copy, and snapping is suspended for the duration: proximity snap reasserted
 * itself on every scrollLeft the handler wrote, which reads as the row fighting
 * back. It returns on release, which is where it was wanted anyway.
 */
body.fc-ds .fc-pats.is-dragging {
	cursor: grabbing;
	scroll-snap-type: none;
	user-select: none;
}

/*
 * rail.js also makes the row focusable, because taking the scrollbar away takes
 * away the pointer-only route as well as the mouse one. Arrow keys scroll a focused
 * scroll container for free; all this has to do is say which thing has the focus.
 */
body.fc-ds .fc-pats.is-grab:focus-visible {
	outline: 2px solid var(--fc-violet);
	outline-offset: 4px;
}

body.fc-ds .fc-pat-panel {
	container-type: inline-size;
	position: relative;
	aspect-ratio: 640 / 560;
	overflow: hidden;
}

body.fc-ds .fc-pat-scale {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 640px;
	height: 560px;
	translate: -50% -50%;
	scale: min(1, tan(atan2(100cqw, 640px)));
}

body.fc-ds .fc-pat__title {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-body-18);
	line-height: var(--lh-title);
	letter-spacing: 0;
	color: var(--text-on-light);
}

body.fc-ds .fc-pat__body {
	color: var(--text-on-light-muted);
	text-wrap: pretty;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE TWO AUDIENCE CARDS
 * A photograph on black with the copy over it, marked A and B in the mono face —
 * the design's one use of a plate marker, and the reason it is aria-hidden: the
 * heading already says which is which.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-teams {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--sp-16);
	max-width: 952px;
	margin: var(--sp-72) auto 0;
}

body.fc-ds .fc-teamcard {
	position: relative;
	aspect-ratio: 1192 / 880;
	overflow: hidden;
	background: var(--fc-black);
	border: var(--stroke-1) solid var(--fc-ink-12);
}

/*
 * ── ONE FILM, HALF OF IT PER CARD ────────────────────────────────────────────
 *
 * The terrain moves, and it is one landscape running through both cards. The stills
 * do that by being two files, a half each; the film cannot, because two <video>
 * elements decoding two files keep two clocks and the drift would show at the seam
 * as the halves undulating out of phase. So it is one file in both cards, and each
 * card takes its half by cropping.
 *
 * .fc-motion is already object-fit: cover, which does the arithmetic for free. The
 * film is 1600 / 592 = 2.7027 : 1 and a card is 1192 / 880 = 1.3545 : 1, so cover
 * fills the height and each card shows 1.3545 / 2.7027 = 50.1% of the film's width.
 * Pinning one to each end therefore gives 0–50.1% and 49.9–100%: halves, overlapping
 * by a quarter of a percent, which is nearer than the 16px gutter between them.
 *
 * Nothing here needs undoing when the grid collapses to one column at 820px. The
 * cards keep their ratio, so they keep their halves, and they stack in the order the
 * landscape reads — the same as the stills. Below 440px the ratio gives way to the
 * copy and each card shows less than half; that is the still's behaviour too.
 */
body.fc-ds .fc-teamcard:first-child .fc-motion {
	object-position: left center;
}

body.fc-ds .fc-teamcard:last-child .fc-motion {
	object-position: right center;
}

body.fc-ds .fc-teamcard__marker {
	position: absolute;
	top: var(--sp-32);
	right: var(--sp-32);
	z-index: 2;
	font-size: var(--type-label-12);
	letter-spacing: var(--ls-label);
	color: var(--fc-white);
	font-variant-ligatures: none;
}

body.fc-ds .fc-teamcard__copy {
	position: relative;
	z-index: 2;
	padding: var(--sp-32);
}

body.fc-ds .fc-teamcard__title {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-h5-24);
	line-height: var(--lh-title);
	letter-spacing: 0;
	color: var(--fc-white);
}

body.fc-ds .fc-teamcard__body {
	max-width: 420px;
	line-height: var(--lh-loose);
	color: var(--fc-white-a80);
	text-wrap: pretty;
}

@container page (max-width: 820px) {
	body.fc-ds .fc-teams {
		grid-template-columns: 1fr;
	}
}

/* On a phone the copy is taller than the ratio, so the ratio has to give. */
@container page (max-width: 440px) {
	body.fc-ds .fc-teamcard {
		aspect-ratio: auto;
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE SCALE ROWS
 * A narrow argument beside a wider block of evidence — 1 : 1.35, not the page's
 * even split, because the evidence includes a three-column ruled table.
 *
 * The hairline is the export's rgba(238,238,238,0.2) expressed as --fc-bone-a20.
 * The two differ by four RGB units at a fifth opacity on an ink ground; a second
 * near-identical alpha token would be noise.
 *
 * The section's ground is --fc-ink-10 and not the --surface-dark the rest of the
 * dark sections use: this is the deepest black on either page, and it sits between
 * two bone sections where the extra two units of contrast are the point.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-oms-scale {
	background: var(--fc-ink-10);
}

body.fc-ds .fc-scale-row {
	display: grid;
	grid-template-columns: 1fr 1.35fr;
	gap: var(--sp-72);
	align-items: start;
}

body.fc-ds .fc-scale-row + .fc-scale-row {
	margin-top: var(--sp-120);
	align-items: center;
}

body.fc-ds .fc-scale-row__art {
	display: block;
	width: 100%;
	height: auto;
}

body.fc-ds .fc-scale-row__title {
	max-width: 520px;
	line-height: 1.02;
	color: var(--text-on-dark);
}

body.fc-ds .fc-scale-row__lead {
	max-width: 480px;
	line-height: var(--lh-loose);
	color: var(--text-on-dark-muted);
}

body.fc-ds .fc-scale__subtitle {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-h5-24);
	line-height: var(--lh-title);
	letter-spacing: 0;
	color: var(--text-on-dark);
}

body.fc-ds .fc-scale__body {
	max-width: 520px;
	line-height: var(--lh-loose);
	color: var(--text-on-dark-muted);
}

/*
 * A customer's words, set in Fraunces at body size — the brand's quote voice.
 * Not .fc-quote, which is the 32px display quote; this one sits inside a column
 * of evidence and has to stay at reading size.
 */
body.fc-ds .fc-scale__quote {
	max-width: 620px;
	font-family: var(--font-quote);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-16);
	line-height: 1.45;
	letter-spacing: var(--ls-quote);
	color: var(--fc-bone-a93);
	text-wrap: pretty;
}

body.fc-ds .fc-scale-cards {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	border: var(--stroke-1) solid var(--fc-bone-a20);
}

body.fc-ds .fc-scale-card {
	display: flex;
	flex-direction: column;
	gap: var(--sp-16);
	padding: var(--sp-32);
	border-left: var(--stroke-1) solid var(--fc-bone-a20);
}

body.fc-ds .fc-scale-card:first-child {
	border-left: 0;
}

body.fc-ds .fc-scale-card__title {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-body-18);
	line-height: var(--lh-title);
	letter-spacing: 0;
	color: var(--text-on-dark);
	text-wrap: pretty;
}

body.fc-ds .fc-scale-card__body {
	color: var(--text-on-dark-muted);
	text-wrap: pretty;
}

@container page (max-width: 1100px) {
	body.fc-ds .fc-scale-row {
		grid-template-columns: 1fr;
		gap: var(--sp-54);
	}

	body.fc-ds .fc-scale-row + .fc-scale-row {
		margin-top: var(--sp-72);
	}
}

@container page (max-width: 820px) {
	body.fc-ds .fc-scale-cards {
		grid-template-columns: 1fr;
	}

	body.fc-ds .fc-scale-card {
		border-left: 0;
		border-top: var(--stroke-1) solid var(--fc-bone-a20);
	}

	body.fc-ds .fc-scale-card:first-child {
		border-top: 0;
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE SPECIALTY ROLL
 * A vertical list of the industries the platform has shipped into, drifting
 * slowly upwards — and draggable by hand. The export runs a 2600ms setInterval
 * over eight verticals and fades four visible rows on an opacity ladder.
 *
 * ── WHY THE WINDOW IS SOMETHING YOU PULL ────────────────────────────────────
 *
 * It was a keyframe: the track stepped by exactly one row height, eight times,
 * over 8 × 2600ms, so the last step landed on a second copy's start and the loop
 * was invisible. steps(8) made it a rotation rather than a scroll — the type held
 * and then it cut, which is what the export does. It could not be touched, either:
 * eight things went past and the visitor could reach none of them.
 *
 * So the window is a scroll container drifting at the same 22px a second, and the
 * visitor moves it the way they move the row of diagram cards further up this page
 * — cursor on the list, pull it. assets/ds/roll.js does both.
 *
 * The wheel is deliberately NOT one of the ways in. A 190px box in the middle of a
 * long page that answers the wheel is a box the page gets stuck against on the way
 * down, and overscroll-behavior cannot help with that: the choice is between
 * absorbing up to eight rows of the gesture before the page moves and not taking
 * the gesture at all. .is-grab takes it away with the same overflow: hidden that
 * takes away the scrollbar the design does not have — scrollTop still moves under
 * script, which is all the drift and the drag ever needed.
 *
 * ── WHY .fc-roll AND .fc-roll__window ARE TWO ELEMENTS ──────────────────────
 *
 * The marker. It is position: absolute, and inside a scroll container that means
 * it is positioned against the scrolled content and travels away with it. So the
 * scrolling is one element down: .fc-roll is the positioning parent and holds the
 * marker still, .fc-roll__window is the 190px box that moves.
 *
 * The mask goes on the window rather than the shell for the same reason in
 * reverse — a mask paints against the border box, so on the window it stays put
 * over a moving list, and it must not be allowed to fade the marker.
 *
 * The opacity ladder is dropped in favour of that mask. Two mechanisms fading the
 * same four rows is one too many, and the mask is the one that survives without
 * script.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-specialty {
	display: grid;
	grid-template-columns: 1fr 1.2fr;
	gap: var(--sp-72);
	align-items: center;
}

body.fc-ds .fc-specialty__title {
	max-width: 420px;
	line-height: 1.02;
}

body.fc-ds .fc-specialty__lead {
	max-width: 420px;
	line-height: var(--lh-loose);
	color: var(--text-on-light-muted);
}

body.fc-ds .fc-roll {
	position: relative;
}

body.fc-ds .fc-roll__window {
	height: 190px;
	overflow-x: hidden;
	overflow-y: auto;
	-webkit-mask-image: linear-gradient(180deg, #000000 0%, #000000 52%, rgba(0, 0, 0, 0) 94%);
	mask-image: linear-gradient(180deg, #000000 0%, #000000 52%, rgba(0, 0, 0, 0) 94%);
}

/*
 * The marker sits OUTSIDE the track, pinned to the first row's centre, because it
 * marks the position and not the item — the same reason the export only ever
 * gives it to the row at the top. Outside the WINDOW too, now that the window
 * scrolls; see the note above.
 */
body.fc-ds .fc-roll__marker {
	position: absolute;
	left: 0;
	top: 29px;
	z-index: 2;
	width: 20px;
	translate: 0 -50%;
	color: var(--fc-ink-12);
}

body.fc-ds .fc-roll__track {
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * ── WHAT .is-live TURNS ON ───────────────────────────────────────────────────
 *
 * The list is in the markup three times over, and the second and third copies are
 * only somewhere for the drift to travel into — assets/ds/roll.js keeps the scroll
 * position inside the middle one and wraps by a copy's height, which is invisible
 * because the copies are identical. That is no use to a visitor scrolling by hand
 * with nothing driving the wrap, so without the script the duplicates do not
 * exist and the window is a plain scrollable list of eight. They are the
 * aria-hidden rows, which is the same fact stated for screen readers.
 */
body.fc-ds .fc-roll:not(.is-live) .fc-roll__row[aria-hidden] {
	display: none;
}

/*
 * ── WHAT .is-grab TURNS ON ───────────────────────────────────────────────────
 *
 * The list stops being scrollable and becomes something to pull — see the head of
 * this section for why the wheel is not wanted here, and rail.js for the same
 * bargain on the row of diagram cards. overflow-y: hidden is doing both halves of
 * it at once: no bar, and no wheel.
 *
 * .is-grab arrives from roll.js, and only where Pointer Events exist, so the
 * failure state is the usable one: no class, no drag, and the window is an
 * ordinary scrollable list with its scrollbar — the only affordance left saying
 * the box can be moved at all.
 */
body.fc-ds .fc-roll.is-grab .fc-roll__window {
	overflow-y: hidden;
	cursor: grab;
}

/*
 * Held. user-select stops the pull from painting a selection across four rows of
 * 48px type on the way past.
 */
body.fc-ds .fc-roll.is-grab.is-dragging .fc-roll__window {
	cursor: grabbing;
	user-select: none;
}

/*
 * Touch keeps the scroll it already had. A finger pan carries momentum and
 * rubber-banding that a pointermove handler writing scrollTop cannot imitate, and
 * a wheel taken away from a device with no wheel is nothing gained — so here the
 * window stays a scroller and only loses its bar. This is the same line rail.js
 * draws by ignoring touch pointers outright.
 */
@media (hover: none) and (pointer: coarse) {
	body.fc-ds .fc-roll.is-grab .fc-roll__window {
		overflow-y: auto;
		scrollbar-width: none;
	}

	body.fc-ds .fc-roll.is-grab .fc-roll__window::-webkit-scrollbar {
		display: none;
	}
}

/*
 * The window is focusable from the markup, being a scroll container with or without
 * the script. What .is-grab changes is who answers the arrow keys: overflow: hidden
 * takes the keyboard's way in along with the wheel's, so roll.js answers them.
 */
body.fc-ds .fc-roll__window:focus-visible {
	outline: 2px solid var(--fc-violet);
	outline-offset: 4px;
}

body.fc-ds .fc-roll__row {
	display: flex;
	align-items: center;
	height: 58px;
	padding-left: 44px;
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-h3-48);
	line-height: var(--lh-flush);
	letter-spacing: var(--ls-display);
	color: var(--fc-ink-12);
	white-space: nowrap;
}

@container page (max-width: 1100px) {
	body.fc-ds .fc-specialty {
		grid-template-columns: 1fr;
		gap: var(--sp-54);
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE NOISE BAND
 * The one section on either page whose body copy is indented rather than
 * left-aligned with its headline. The indent is half the page's --indent-body,
 * so it halves at 1024 and disappears at 768 with everything else.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-band--noise {
	--band-w: 3840;
	--band-h: 1616;
	/*
	 * THIS COLOUR IS THE ARTWORK NOW, so it is the design's section fill rather than
	 * the #0a0a0a it used to be. The still that used to cover the band is gone (see
	 * fluent_commerce_ds_oms_noise_markup()), and the film only covers the middle
	 * 78.125%, so this is what fills the 10.9375% either side of it — flat field,
	 * exactly as the design draws it, and the whole band until the film fades in.
	 *
	 * #121212 against the film's #111111 field is one value in 255, which is why the
	 * two vertical seams where they meet cannot be seen. #0a0a0a would have been seven,
	 * and seven values apart in the near-blacks is a visible edge.
	 */
	background: var(--fc-ink-12);
}

body.fc-ds .fc-band--noise .fc-band__inner {
	max-width: 720px;
	margin: 0 auto;
	padding: var(--pad-section) var(--gutter-page);
	align-items: flex-start;
}

body.fc-ds .fc-band--noise__title {
	line-height: 1.05;
	color: var(--fc-white);
}

body.fc-ds .fc-band--noise__copy {
	margin-top: var(--sp-32);
	padding-left: calc(var(--indent-body) / 2);
	max-width: 520px;
	color: var(--fc-white-a80);
}

body.fc-ds .fc-band--noise__copy p {
	line-height: var(--lh-loose);
}

/*
 * The film is 1500x808 in a 1920x808 band, not full bleed, so it overrides
 * .fc-motion's inset: 0. The design has it at 1501 wide at x=209 — full height,
 * horizontally centred — with flat #111111 field either side, which the band's own
 * background provides. That is a fifth of the frame the encode never has to carry.
 * 1500/1920 = 78.125%, and the remainder splits evenly: 210 a side = 10.9375%, which
 * is also why the encode rounds 1501 down to an even 1500 rather than up. See
 * fluent_commerce_ds_oms_noise_markup() in inc/ds-oms.php.
 *
 * left rather than margin, and right: auto to release .fc-motion's inset.
 *
 * The side field used to come from a full-bleed still instead, and could not: the copy
 * makes this band taller than its 2.376:1 ratio at every width, so object-fit: cover
 * was height-driven and cropped 174px off each side of a 1920-wide still at 1440 —
 * taking the flat columns off first and leaving its grain standing still in exactly
 * the strips this film vacates. A background cannot be cropped.
 */
body.fc-ds .fc-band--noise .fc-motion {
	left: 10.9375%;
	right: auto;
	width: 78.125%;
}

/* 24 from the stack plus 8 — the design's one extra step before an action. */
body.fc-ds .fc-band--noise__copy .fc-btn {
	margin-top: var(--sp-8);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE Q&A
 * <details>/<summary>, so it works with CSS off, with JS off, and with the
 * browser's own find-in-page — which opens a closed <details> to show a match and
 * cannot do that for a div.
 *
 * The glyph is CSS content on an empty span rather than two characters in the
 * markup: [ + ] and [ – ] are state, not copy, and a screen reader already gets
 * the state from the disclosure role.
 *
 * ::details-content is the transition, and it is progressive: where it is not
 * supported the whole rule is dropped and the panel simply appears.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-faq {
	display: grid;
	grid-template-columns: 1fr 3fr;
	gap: var(--sp-72);
	align-items: start;
	/* interpolate-size is what lets block-size animate to and from auto. */
	interpolate-size: allow-keywords;
}

body.fc-ds .fc-faq__title {
	line-height: 1.02;
}

/*
 * The left cell, when it is more than the heading. The OMS page puts the h2 straight
 * into the grid; the Integrations page puts the h2 and "(aka our FAQ)" in here, because
 * two grid children would have filled both columns and pushed the list to a third.
 */
body.fc-ds .fc-faq__head {
	display: flex;
	flex-direction: column;
	gap: var(--sp-16);
}

body.fc-ds .fc-faq__sub {
	color: var(--text-on-light-muted);
}

body.fc-ds .fc-faq__item {
	border-top: var(--stroke-1) solid var(--border-on-light);
}

/* The list closes on a rule, as the capability list does. */
body.fc-ds .fc-faq__list::after {
	content: "";
	display: block;
	border-top: var(--stroke-1) solid var(--border-on-light);
}

body.fc-ds .fc-faq__q {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--sp-32);
	min-height: 79px;
	padding: var(--sp-24) 0;
	cursor: pointer;
	list-style: none;
	transition: color 160ms var(--ease-out);
}

/* Two vendors, two ways of drawing the disclosure triangle. Both have to go. */
body.fc-ds .fc-faq__q::-webkit-details-marker {
	display: none;
}

body.fc-ds .fc-faq__q::marker {
	content: "";
}

body.fc-ds .fc-faq__q:hover {
	color: var(--fc-violet);
}

body.fc-ds .fc-faq__qtext {
	max-width: 640px;
	font-size: var(--type-h5-24);
	font-weight: var(--fw-semibold);
	line-height: var(--lh-snug);
}

body.fc-ds .fc-faq__glyph {
	flex: none;
	font-family: var(--font-mono);
	font-size: var(--type-eyebrow-16);
	letter-spacing: var(--ls-label);
	color: var(--fc-ink-a60);
	font-variant-ligatures: none;
}

body.fc-ds .fc-faq__glyph::after {
	content: "[ + ]";
}

body.fc-ds .fc-faq__item[open] .fc-faq__glyph {
	color: var(--fc-violet);
}

body.fc-ds .fc-faq__item[open] .fc-faq__glyph::after {
	content: "[ – ]";
}

body.fc-ds .fc-faq__a {
	max-width: 640px;
	margin: 0 0 var(--sp-32);
	line-height: var(--lh-loose);
	color: var(--text-on-light-muted);
}

/*
 * An answer that runs to two paragraphs closes tighter between them than it does at the
 * end of the panel: 32 is the gap to the next question's rule, and holding it inside one
 * answer would read as two answers.
 */
body.fc-ds .fc-faq__a:not(:last-child) {
	margin-bottom: var(--sp-24);
}

body.fc-ds .fc-faq__item::details-content {
	block-size: 0;
	overflow: hidden;
	transition: block-size var(--dur-base) var(--ease-out), content-visibility var(--dur-base) allow-discrete;
}

body.fc-ds .fc-faq__item[open]::details-content {
	block-size: auto;
}

@container page (max-width: 1100px) {
	body.fc-ds .fc-faq {
		grid-template-columns: 1fr;
		gap: var(--sp-54);
	}
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE ANALYST REPORT PAGE
 *
 * Four sections: the gated hero on ink, the logo row on bone, the quote on white,
 * and the wordmark. It is the shortest page in the system and the only one whose
 * first section is the conversion — everything below the fold is corroboration, so
 * nothing below the fold is allowed to compete with the field.
 * ══════════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────────────────────
 * THE HERO
 * 2fr 1fr rather than .fc-split, because the artwork is a 430px chart and not a
 * picture: given half the canvas it would be upscaled past the point where its
 * axis labels hold together.
 *
 * padding-top is 54 rather than --pad-section. The nav is dark here and sits on
 * the same ink, so there is no edge between them — a full 120 would leave the
 * kicker floating in the middle of a black field with nothing above it.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-report-hero {
	padding-top: var(--sp-54);
	background: var(--surface-dark);
}

body.fc-ds .fc-report-hero__grid {
	display: grid;
	grid-template-columns: 2fr 1fr;
	gap: var(--sp-54);
	align-items: start;
}

body.fc-ds .fc-report-hero__grid > * {
	min-width: 0;
}

/*
 * 820 and not 1100: the copy column is 2fr, so it is still a comfortable measure
 * at 1024 with the chart beside it, and stacking that early would push the field
 * a screen further down for no gain.
 */
@container page (max-width: 820px) {
	body.fc-ds .fc-report-hero__grid {
		grid-template-columns: 1fr;
	}
}

/*
 * NOT .fc-eyebrow. The site's eyebrow role is bold, uppercase and tracked out;
 * this one is sentence case, semibold and untracked — the same 16px slot wearing
 * a different piece of type. On ink it is --fc-violet-light, because the brand
 * violet fails contrast on #121212. See the .fc-em / .fc-em-dark note in
 * layout.css: violet type is surface-dependent, always.
 */
body.fc-ds .fc-report-hero__kicker {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-eyebrow-16);
	line-height: 1.45;
	color: var(--fc-violet-light);
}

body.fc-ds .fc-report-hero__title {
	margin: var(--sp-24) 0 0;
	max-width: 900px;
	/* 1.08, not the display role's flush 1 — this headline always wraps. */
	line-height: 1.08;
	color: var(--text-on-dark);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE FIVE CRITERIA
 * A real core list, so the copy stays editable. The export draws an inline 14px
 * SVG tick per row, which cannot live inside a core <li> without making the block
 * invalid — so the tick is the export's own path, drawn as a mask on ::before and
 * filled with currentColor. Same geometry, same 1.6 stroke, same round joins.
 *
 * The mask is deliberate rather than a background-image: the tick has to inherit
 * the row's colour, because this list appears on ink here and would appear on bone
 * on any light version of the page, and a two-colour asset would be two assets.
 * ────────────────────────────────────────────────────────────────────────── */
/*
 * 40 is not one of the seven scale steps, and it is the export's number in both
 * places it appears on this page — the list's top margin and the gate's. Written
 * as a literal rather than smuggled in as an eighth token: an --sp-40 would be
 * available to every future composition and the scale would quietly become eight.
 */
body.fc-ds .fc-report-hero__criteria {
	margin: 40px 0 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--sp-16);
}

body.fc-ds .fc-report-hero__criteria li {
	display: flex;
	align-items: baseline;
	gap: var(--sp-16);
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-18);
	line-height: var(--lh-body);
	color: var(--fc-bone-a93);
}

body.fc-ds .fc-report-hero__criteria li::before {
	content: "";
	flex: none;
	width: 14px;
	height: 14px;
	/*
	 * An empty inline box baselines on its bottom edge, which is exactly how the
	 * export's inline <svg> behaves — so the tick spans the cap height of the line
	 * it belongs to, and the 2px is the export's own optical nudge.
	 */
	position: relative;
	top: 2px;
	background-color: currentColor;
	mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cpath d='M1 7.5 L5 11.5 L13 2.5' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE GATE
 * One field and one button in a single 1px box, 448 wide, 56 tall — deliberately
 * not .fc-btn geometry. .fc-btn is 48 tall with a 10px radius and an arrow; this
 * is a control PAIR, so the button is square, the same height as the field, and
 * shares its border. A rounded 48px button inside a square 56px box would read as
 * two components that had been pushed together.
 *
 * The markup is injected at render time — see inc/ds-report.php. There is no JS on
 * this page at all: the form posts, the handler redirects back to #cta, and the
 * states below are drawn by the server.
 *
 * SINCE 2026-08-26 THIS IS NOT WHAT THE PAGE DRAWS. The slot holds an embedded Pardot
 * form instead — fluent_commerce_ds_report_uses_pardot(), default true — and these
 * rules style the branch behind that filter. They are also the SPECIFICATION the
 * embedded form is matched against: assets/ds/pardot-form-report.css is a hand-paste
 * into Pardot's layout template that reproduces the numbers below on their side of a
 * cross-origin frame. Change a value here and that file has to be changed and re-pasted,
 * because nothing can do it automatically.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-report-gate {
	margin-top: 40px;
}

body.fc-ds .fc-report-gate__row {
	display: flex;
	align-items: stretch;
	max-width: 448px;
	border: var(--stroke-1) solid var(--border-on-dark);
}

body.fc-ds .fc-report-gate__row[data-invalid="true"] {
	border-color: var(--fc-red);
}

body.fc-ds .fc-report-gate__input {
	flex: 1 1 auto;
	min-width: 0;
	height: 56px;
	padding: 0 20px;
	background: transparent;
	border: 0;
	font-family: var(--font-core);
	/* The form size — see layout.css. */
	font-size: var(--fc-field-size);
	font-weight: var(--fw-medium);
	color: var(--text-on-dark);
}

body.fc-ds .fc-report-gate__input::placeholder {
	color: var(--fc-bone-a50);
}

/* Inside the box, so it is drawn in rather than around — hence the negative offset. */
body.fc-ds .fc-report-gate__input:focus-visible {
	outline: var(--stroke-2) solid var(--fc-violet-light);
	outline-offset: calc(var(--stroke-2) * -1);
}

body.fc-ds .fc-report-gate__submit {
	flex: none;
	height: 56px;
	padding: 0 24px;
	border: 0;
	background: var(--fc-white);
	color: var(--fc-ink-12);
	font-family: var(--font-core);
	font-size: 14px;
	font-weight: var(--fw-semibold);
	white-space: nowrap;
	cursor: pointer;
	transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

body.fc-ds .fc-report-gate__submit:hover,
body.fc-ds .fc-report-gate__submit:focus-visible {
	background: var(--fc-violet);
	color: var(--fc-white);
}

body.fc-ds .fc-report-gate__error {
	display: flex;
	align-items: center;
	gap: var(--sp-8);
	margin: var(--sp-16) 0 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-label-12);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--fc-red);
}

/*
 * The alert region is in the DOM at all times and carries `hidden` when there is
 * nothing to say — so this is not optional. The browser's own [hidden] rule is a
 * single attribute selector and loses to the class above it on specificity, which
 * would leave a red "[ ! ]" sitting under the field on every idle page load.
 */
body.fc-ds .fc-report-gate__error[hidden] {
	display: none;
}

/* The mono face exists for exactly this: a machine's own punctuation. */
body.fc-ds .fc-report-gate__mark {
	font-family: var(--font-mono);
	font-variant-ligatures: none;
}

/*
 * The opt-in, added 2026-08-25 when every form on the site got one. A margin rather
 * than a gap on the form: this form is not a flex column, and making it one would fold
 * the error message's own 16px top margin into a gap and move a message that is
 * currently positioned correctly. 24px because the consent statement is a new thought
 * after the field, not the field's own caption like the message is.
 *
 * Also constrained to the field's width. The sentence is three lines at 448px and would
 * otherwise run the full width of the hero column, which is twice that.
 */
body.fc-ds .fc-report-gate__form .fc-consent {
	max-width: 448px;
	margin-top: var(--sp-24);
}

body.fc-ds .fc-report-gate__done {
	margin: 0;
	max-width: 520px;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-16);
	line-height: var(--lh-loose);
	color: var(--text-on-dark-muted);
}

/*
 * THE EMBEDDED PARDOT FORM — the branch the page actually renders, in the same slot the
 * row above used to fill.
 *
 * Everything the frame has in common with the other two embedded forms — block display,
 * full width, no chrome, color-scheme:light — is EMBEDDED PARDOT FORMS in layout.css, on
 * .fc-pardot / .fc-pardot__frame, which this frame also carries. Only the floor is this
 * page's, and it matches what fluent_commerce_ds_report_frame_height() declares: 560,
 * itemised there.
 *
 * The floor exists because BOTH halves of the resize can be missing — assets/ds/
 * pardot-frame.js is deferred, and its counterpart is pasted into a Pardot template by
 * hand. Unmeasured, the frame stays at the tall case, so the failure is whitespace under
 * the button rather than a scrollbar inside a form. pardot-frame.js zeroes it as soon as
 * it knows better.
 */
body.fc-ds .fc-report-gate__frame {
	min-height: 560px;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE WAVE GRID
 * 430 × 500 is the only size this artwork exists at, so it is capped at its own
 * intrinsic width and pushed to the right gutter rather than stretched to fill the
 * 1fr track. At the 1920 canvas that track is ~484px, so filling it would upscale
 * the chart by 13% and soften the one thing on the page anyone is here to read.
 * Below 1440 the track is narrower than the file and the cap stops applying.
 *
 * If a larger export ever arrives, delete the max-width and nothing else changes.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-report-hero__art {
	display: block;
	width: 100%;
	max-width: 430px;
	height: auto;
	justify-self: end;
}

@container page (max-width: 820px) {
	body.fc-ds .fc-report-hero__art {
		justify-self: start;
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE LOGO ROW
 * The row itself is a primitive (layout.css). Only the surface and the line of
 * copy above it are the page's.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-report-logos {
	display: flex;
	flex-direction: column;
	gap: var(--sp-54);
	align-items: center;
	background: var(--surface-light);
}

body.fc-ds .fc-report-logos__eyebrow {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-eyebrow-16);
	line-height: 1.45;
	color: var(--text-on-light);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE QUOTE
 * A core/quote, so the words stay editable, with the customer's mark after it as
 * a sibling — no core block puts a logo in a figcaption.
 *
 * Fraunces at MEDIUM here, not the regular of the .fc-quote role: this is the
 * page's only long-form serif and it is centred on white at 32px, where the
 * regular goes thin. Deliberate, and the export's own weight.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-report-quote {
	background: var(--surface-lightest);
	text-align: center;
}

body.fc-ds .fc-report-quote__figure {
	margin: 0 auto;
	padding: 0;
	border: 0;
	max-width: 900px;
}

body.fc-ds .fc-report-quote__text {
	margin: 0;
	font-family: var(--font-quote);
	font-weight: var(--fw-medium);
	font-size: var(--type-quote-32);
	line-height: 1.25;
	letter-spacing: var(--ls-quote);
	color: var(--text-on-light);
	text-wrap: pretty;
}

body.fc-ds .fc-report-quote__figure cite {
	display: block;
	margin-top: var(--sp-32);
	font-style: normal;
}

body.fc-ds .fc-report-quote__who,
body.fc-ds .fc-report-quote__role {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-body-18);
}

body.fc-ds .fc-report-quote__who {
	color: var(--text-on-light);
}

/* The attribution's own detail, half a tone back — the name is the claim. */
body.fc-ds .fc-report-quote__role {
	color: var(--fc-ink-a60);
}

body.fc-ds .fc-report-quote__mark {
	margin: var(--sp-24) auto 0;
	width: 156px;
	height: 34px;
	background-image: var(--mark);
	background-repeat: no-repeat;
	background-position: center;
	background-size: contain;
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE GARTNER REPORT PAGE
 *
 * One section, and that is the whole page: the offer on the left, the form sticky
 * on the right, a hairline down the middle. Nothing follows it — no proof wall, no
 * quote, no wordmark — because Gartner's attribution has to be the last thing on
 * the page and anything after it would be reading around it.
 *
 * The form is injected at request time (inc/ds-gartner.php) and every one of its
 * states is drawn by the server. Like the analyst report, this page ships no JS.
 * ══════════════════════════════════════════════════════════════════════════ */

/*
 * padding-top 54 rather than --pad-section, which is the export's own number and
 * the same reduction the analyst report makes. The nav here is bone on a bone
 * section — one hairline apart, in the same colour — so the full 120 would read as
 * empty page rather than as air above a headline, on the one page in the system
 * whose entire job is above the fold.
 */
body.fc-ds .fc-gartner-page {
	padding-top: var(--sp-54);
	background: var(--surface-light);
}

/*
 * 1.35fr 1px 1fr: the middle track IS the hairline, so the 54 gap falls on both
 * sides of it and the rule sits at the optical centre of the gutter rather than
 * hanging off one column's edge.
 */
body.fc-ds .fc-gartner {
	display: grid;
	grid-template-columns: 1.35fr 1px 1fr;
	gap: var(--sp-54);
	align-items: start;
}

body.fc-ds .fc-gartner > * {
	min-width: 0;
}

/*
 * The hairline, as a ::before rather than the export's empty <div> — a decorative
 * div in stored content is one more locked empty box for an editor to wonder
 * about. The negative margins take it to the section's own edges: up through the
 * 54 of padding to the nav's hairline, and down through --pad-section to the
 * footer, so it reads as a full-height division of the page and not as a rule
 * around the copy. align-self overrides the grid's align-items: start.
 */
body.fc-ds .fc-gartner::before {
	content: "";
	grid-column: 2;
	grid-row: 1;
	align-self: stretch;
	background: var(--border-on-light);
	margin-top: calc(var(--sp-54) * -1);
	margin-bottom: calc(var(--pad-section) * -1);
}

/*
 * Both columns are placed explicitly, so the ::before cannot take column 1 from
 * the copy by being first in source order.
 */
body.fc-ds .fc-gartner__copy {
	grid-column: 1;
	grid-row: 1;
	display: flex;
	flex-direction: column;
	gap: var(--sp-32);
	max-width: 720px;
}

/*
 * 120 of clearance, which is the nav's height plus its air — the form must never
 * stick to a position the bar is already occupying.
 */
body.fc-ds .fc-gartner__form {
	grid-column: 3;
	grid-row: 1;
	position: sticky;
	top: var(--sp-120);
}

/*
 * 1100 and not 820: the form is a two-up field grid, so it stops being usable
 * beside 720px of copy well before the copy stops being readable. Below this the
 * page is simply the offer, then the form — which is also the order someone reads
 * it in, so nothing is lost by stacking.
 */
@container page (max-width: 1100px) {
	body.fc-ds .fc-gartner {
		grid-template-columns: 1fr;
	}

	body.fc-ds .fc-gartner::before {
		display: none;
	}

	body.fc-ds .fc-gartner__copy,
	body.fc-ds .fc-gartner__form {
		grid-column: 1;
		grid-row: auto;
	}

	body.fc-ds .fc-gartner__form {
		position: static;
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE OFFER
 * ────────────────────────────────────────────────────────────────────────── */

/*
 * The cover. This IS the <img> — a theme asset in a wp:html block, the same
 * construction as .fc-report-hero__art, so there is no figure to reset.
 *
 * No aspect-ratio and no object-fit, unlike the export's 4:5 contained slot: the
 * file is 520 × 617 and exists at exactly one size, so width plus height:auto gives
 * the browser the real ratio to reserve. A ratio box would letterbox the page inside
 * itself for no reason. max-width is the export's own 260.
 */
body.fc-ds .fc-gartner__cover {
	display: block;
	width: 100%;
	max-width: 260px;
	height: auto;
}

body.fc-ds .fc-gartner__lede {
	display: flex;
	flex-direction: column;
	gap: var(--sp-24);
}

/*
 * Not .fc-eyebrow — same 16px slot, sentence case, semibold, untracked. See the
 * note on .fc-report-hero__kicker: that one is on ink and takes violet-light, this
 * one is on bone and takes the brand violet itself. Violet type is always
 * surface-dependent.
 */
body.fc-ds .fc-gartner__kicker {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-eyebrow-16);
	line-height: 1.45;
	color: var(--fc-violet);
}

/* .fc-display-section already carries the face, the ramp, the flush 1 and the
   -0.01em tracking, which is the export's headline exactly. */
body.fc-ds .fc-gartner__title {
	margin: 0;
	color: var(--text-on-light);
}

body.fc-ds .fc-gartner__body {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-16);
	line-height: var(--lh-loose);
	color: var(--text-on-light-muted);
	text-wrap: pretty;
}

/* The report's own title, inside the sentence that quotes it. */
body.fc-ds .fc-gartner__body strong {
	font-weight: var(--fw-semibold);
	color: var(--text-on-light);
}

/*
 * The closing line — the ask. Full ink, so it carries the weight of a CTA without
 * being one; the button is six inches to the right.
 *
 * __ask and not __body--strong, which is what BEM wants to call it. A double hyphen
 * inside a stored block attribute is re-escaped by serialize_block_attributes into
 * a pair of unicode escapes, so a generator that writes it plainly stops being
 * byte-identical to what WordPress itself would write, and the editor rewrites
 * post_content on the first save. Harmless — the home page has done it since it
 * shipped — but an element name costs nothing and keeps the round-trip check honest.
 */
body.fc-ds .fc-gartner__ask {
	font-weight: var(--fw-semibold);
	color: var(--text-on-light);
}

/*
 * The four insights. Same construction as the analyst report's criteria — a real
 * core list with the export's tick drawn as a mask on ::before, because an <svg>
 * inside a core <li> is invalid block content. This is where the mask earns its
 * keep: currentColor makes the identical rule ink on bone here and bone on ink
 * there, from one asset.
 */
body.fc-ds .fc-gartner__insights {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--sp-16);
}

body.fc-ds .fc-gartner__insights li {
	display: flex;
	align-items: baseline;
	gap: var(--sp-16);
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-18);
	line-height: var(--lh-body);
	color: var(--text-on-light);
}

body.fc-ds .fc-gartner__insights li::before {
	content: "";
	flex: none;
	width: 14px;
	height: 14px;
	/* An empty inline box baselines on its bottom edge, exactly as the export's
	   inline <svg> does; the 2px is the export's own optical nudge. */
	position: relative;
	top: 2px;
	background-color: currentColor;
	mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'%3E%3Cpath d='M1 7.5 L5 11.5 L13 2.5' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
}

/*
 * The Gartner attribution. margin-top sits ON TOP of the column's 32 gap, which is
 * the export's arrangement and the right one: 56 of air and a rule is how you say
 * "this is not our copy" without a heading saying so.
 */
body.fc-ds .fc-gartner__legal {
	margin-top: var(--sp-24);
	padding-top: var(--sp-32);
	border-top: var(--stroke-1) solid var(--border-on-light);
	display: flex;
	flex-direction: column;
	gap: var(--sp-16);
}

/* Italic and 15 — the smallest size the brand allows, which is what this is for. */
body.fc-ds .fc-gartner__legal-line {
	margin: 0;
	font-family: var(--font-core);
	font-style: italic;
	font-weight: var(--fw-medium);
	font-size: var(--type-body-15);
	line-height: var(--lh-loose);
	color: var(--fc-ink-a60);
	text-wrap: pretty;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE FORM
 * One field on a light surface, at .fc-btn's own 48/10px control geometry — unlike
 * the analyst report's gate, which is a 56px control PAIR and shares one border
 * between field and button. Here the button is a real .fc-btn, so the field has to
 * be the same height as one.
 *
 * The export's form is six fields and the rules below still dress six: the grid, the
 * two-up breakpoint and --wide all survive the reduction to email alone, because
 * putting a field back is a one-line edit in PHP and nothing here should have to
 * change with it. See fluent_commerce_ds_gartner_fields().
 *
 * ── SINCE 2026-08-26 THIS IS NOT WHAT THE PAGE DRAWS ────────────────────────
 *
 * The form column holds an embedded Pardot form — see fluent_commerce_ds_gartner_uses_pardot(),
 * which defaults to true. These rules style the branch behind that filter, AND they are the
 * specification assets/ds/pardot-form-gartner.css is matched against: that file is a hand-made
 * copy of this section for a document we cannot reach with a stylesheet. So a change here needs
 * the same change there, followed by a re-paste into Pardot's layout template. Where the two
 * disagree, this is right and that is out of date.
 * ────────────────────────────────────────────────────────────────────────── */

/*
 * THE EMBEDDED PARDOT FORM — the branch the page actually renders, in the same column the
 * rules below used to fill.
 *
 * layout.css owns .fc-pardot and .fc-pardot__frame, which do the real work: full width, no
 * border, and a height the PHP declares. Only the floor is this page's, and it matches what
 * fluent_commerce_ds_gartner_frame_height() declares — 620, itemised line by line there.
 *
 * A floor and not a height: the frame reports its own once it can, and pardot-frame.js zeroes
 * this as soon as it knows better. It exists so the sticky column has a shape during the two
 * frames before the cross-origin document has painted, rather than collapsing and then jumping.
 */
body.fc-ds .fc-gartner__frame {
	min-height: 620px;
}

/* .fc-eyebrow carries the face; this only places it. It renders in BOTH states —
   the confirmation appears under the same words the visitor was answering. */
body.fc-ds .fc-gartner-form__heading {
	margin: 0 0 var(--sp-32);
	color: var(--text-on-light);
}

body.fc-ds .fc-gartner-form__form {
	display: flex;
	flex-direction: column;
	gap: var(--sp-24);
}

body.fc-ds .fc-gartner-form__grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--sp-24);
}

/* Two-up until the column itself is phone-width. The page has already stacked at
   1100, so this is the second stage, not the first. */
@container page (max-width: 820px) {
	body.fc-ds .fc-gartner-form__grid {
		grid-template-columns: 1fr;
	}
}

/* A <p> per field, so the hidden <label> and its input are one grid cell. */
body.fc-ds .fc-gartner-form__field {
	margin: 0;
	display: flex;
	min-width: 0;
}

body.fc-ds .fc-gartner-form__field--wide {
	grid-column: 1 / -1;
}

body.fc-ds .fc-gartner-form__input {
	width: 100%;
	min-width: 0;
	height: 48px;
	padding: 0 16px;
	background: transparent;
	border: var(--stroke-1) solid var(--border-on-light);
	border-radius: var(--radius-10);
	font-family: var(--font-core);
	/* The form size — see layout.css. */
	font-size: var(--fc-field-size);
	font-weight: var(--fw-medium);
	color: var(--text-on-light);
}

body.fc-ds .fc-gartner-form__input::placeholder {
	color: var(--fc-ink-a40);
}

/*
 * :focus and not :focus-visible, which is the export's own choice and the right one
 * for a text field: a click into an input is a request to type, so it should show
 * where the caret went. Outline outside the border, both violet — the edge reads as
 * one 2px line rather than two rings.
 */
body.fc-ds .fc-gartner-form__input:focus {
	outline: var(--stroke-1) solid var(--fc-violet);
	border-color: var(--fc-violet);
}

/*
 * data-invalid, not :invalid — the server decides what is wrong and says so once,
 * on submit. A required field is :invalid from the moment the page loads, so
 * styling the error off it would accuse someone of a mistake before they had made
 * one. See fluent_commerce_ds_gartner_form().
 */
body.fc-ds .fc-gartner-form__input[data-invalid="true"] {
	border-color: var(--fc-red);
}

/*
 * The consent statement was styled here at --type-body-15, which layout.css overrides to
 * 17px. It is now .fc-consent in layout.css at a literal 14 — the contact page's size,
 * which is the reference every form matches. See inc/ds-consent.php.
 */
body.fc-ds .fc-gartner-form__error {
	display: flex;
	align-items: center;
	gap: var(--sp-8);
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-label-12);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--fc-red);
}

/*
 * The alert region is in the DOM at all times and carries `hidden` when there is
 * nothing to say, so this is not optional: the browser's own [hidden] rule is a
 * single attribute selector and loses to the class above it on specificity, which
 * would leave a red "[ ! ]" under the form on every idle page load.
 */
body.fc-ds .fc-gartner-form__error[hidden] {
	display: none;
}

/* The mono face exists for exactly this: a machine's own punctuation. */
body.fc-ds .fc-gartner-form__mark {
	font-family: var(--font-mono);
	font-variant-ligatures: none;
}

/* .fc-btn--dark already supplies the geometry and the colourway. This only strips
   what a UA gives a real <button> and a <div> never had. */
body.fc-ds .fc-gartner-form__submit {
	appearance: none;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE CONFIRMATION
 * A bordered panel where the form was, at the same width — so the column does not
 * change shape between the two states and the page never jumps.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-gartner-form__done {
	display: flex;
	flex-direction: column;
	gap: var(--sp-24);
	padding: var(--sp-32);
	border: var(--stroke-1) solid var(--border-on-light);
}

/* Green, and the only green on the page. It is the eyebrow role in another colour,
   which is why it is bold and tracked rather than semibold like the kicker. */
body.fc-ds .fc-gartner-form__done-title {
	display: flex;
	align-items: center;
	gap: var(--sp-8);
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-bold);
	font-size: var(--type-eyebrow-16);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--fc-green);
}

/* --lh-body, not --lh-loose: two lines of confirmation, not a paragraph of copy. */
body.fc-ds .fc-gartner-form__done-body {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-16);
	line-height: var(--lh-body);
	color: var(--text-on-light-muted);
}

/* The reference is a machine string, so it is set as one. */
body.fc-ds .fc-gartner-form__ref {
	margin: 0;
	font-family: var(--font-mono);
	font-variant-ligatures: none;
	font-size: 16px;
	color: var(--text-on-light);
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE RESOURCES ROW — the Knowledge Hub list on /resources/
 *
 * Twelve hairline rows, each one a label on the left and the kind of thing it is on the
 * right. This was the whole of /resources/ before Build-19; it is now one of that page's
 * five sections, and everything else it needs — the hero, the case rail, the release
 * carousel and the media table — is in assets/ds/resources.css.
 *
 * THESE RULES STAY HERE, in the sheet that loads on twenty pages, for two reasons. The
 * row is the one part of the old page Build-19 kept unchanged, so moving it would be a
 * rename with no change behind it. And assets/ds/blog.css carries a copy of it for
 * /news/ and /media-articles/, with two comments pointing at this block by name as the
 * original — see THE NEWS LIST there. Moving it means updating a cross-reference between
 * two sheets that never load together, to no benefit.
 *
 * The rows are drawn as a shared top border on the <ul> plus a bottom border on every
 * <li>, which is the export's construction and is deliberately not `border-bottom` on
 * the last row only or a `:not(:last-child)` rule. It gives twelve items thirteen
 * hairlines — closed top and bottom — and it survives rows being added, removed or
 * dropped at render time without any rule needing to know which one is last. All twelve
 * resolve to a link now; the muted unlinked state is kept because the next row added
 * might not. See inc/ds-resources.php.
 *
 * THE HOVER MOVES A WHITE PLATE UNDER THE ROW rather than underlining it. That is the
 * export's, and it is the same gesture the nav rows use: on bone, white reads as
 * "lifted" where a grey would read as "disabled". It applies to the linked rows only,
 * because a plate sliding under text that goes nowhere is a promise of a click.
 * ══════════════════════════════════════════════════════════════════════════ */

/*
 * 54px below the section's heading lockup, which is the step Build-19 uses between a
 * headline and the thing it introduces on all four of its sections.
 *
 * The margin is on the list rather than on the placeholder group around it, so a list
 * that resolves to nothing at all leaves no gap behind — the group has no box of its
 * own and collapses.
 */
body.fc-ds .fc-resources__list {
	margin: var(--sp-54) 0 0;
	padding: 0;
	list-style: none;
	border-top: var(--stroke-1) solid var(--border-on-light);
}

body.fc-ds .fc-resources__row {
	border-bottom: var(--stroke-1) solid var(--border-on-light);
}

/*
 * One rule for both the <a> and the <div>, so a row that goes nowhere sits on exactly
 * the same baseline and padding as one that does. Only the colour and the hover differ.
 *
 * align-items: baseline, so the 12px kind on the right sits on the first line of a
 * label that wraps to two rather than centring itself against the whole block.
 *
 * The 16px of side padding is what the white hover plate needs to read as a plate — it
 * gives the type somewhere to sit inside the highlight instead of touching its edge.
 */
body.fc-ds .fc-resources__link {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--sp-32);
	padding: var(--sp-24) var(--sp-16);
	color: var(--text-on-light);
	text-decoration: none;
	transition: background 160ms ease-out, color 160ms ease-out;
}

body.fc-ds a.fc-resources__link:hover,
body.fc-ds a.fc-resources__link:focus-visible {
	background: var(--surface-lightest);
	color: var(--fc-violet);
}

/* An unwritten resource, and the row states it as muted text and nothing else. */
body.fc-ds .fc-resources__link--soon {
	color: var(--fc-ink-a60);
}

/*
 * --type-body-16, which is 18px here. The export writes var(--type-body-18) and means
 * 18px by it; in this theme that token is the 20px display-adjacent step and --type-body-16
 * is the paragraph size on every page. See the token table at the head of layout.css.
 */
body.fc-ds .fc-resources__label {
	display: inline-flex;
	align-items: flex-start;
	gap: var(--sp-8);
	font-size: var(--type-body-16);
	font-weight: var(--fw-medium);
	line-height: var(--lh-body);
	text-wrap: pretty;
}

/*
 * The arrow follows the label's colour, so it turns violet with the row on hover. The
 * 4px nudge sits it on the cap height of an 18px line rather than on the line box top,
 * where align-items: flex-start would otherwise leave it floating.
 */
body.fc-ds .fc-resources__arrow {
	flex: none;
	display: block;
	margin-top: 4px;
}

/* .fc-meta carries the 12px, the weight, the tracking and the uppercase. */
body.fc-ds .fc-resources__kind {
	flex: none;
	color: var(--fc-ink-a60);
}

/*
 * Half a step lighter than a linked row's kind. The label is already muted, so a kind
 * at the same 60% would read as the strongest thing on a row that is the weakest on
 * the page.
 */
body.fc-ds .fc-resources__link--soon .fc-resources__kind {
	color: var(--fc-ink-a50);
}

/*
 * 440 and not 820. The row survives a narrow viewport better than most things in this
 * file — a long label wraps and the kind stays beside it — right down to the point
 * where the kind has nowhere left to be. Below that it goes under the label, which
 * also means the 32px column gap has to become a small row gap.
 */
@container page (max-width: 440px) {
	body.fc-ds .fc-resources__link {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--sp-8);
	}
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE CONTACT PAGE
 *
 * Two words and the form on the left of a bleed hero, the handshake artwork sealing
 * the right of it, a hairline, then the four offices. Bone end to end.
 *
 * IT WAS #0a0a0a END TO END AND IS NOT ANY MORE — Build-11 redesigned the page, and the
 * flip is the one the demo page made first on 2026-08-25. The full checklist is over THE
 * REQUEST A DEMO PAGE below; what it cost here was four things:
 *
 *   · The nav left the dark list in fluent_commerce_ds_nav_tone().
 *   · The FOOTER needed nothing. This page was never in
 *     fluent_commerce_ds_closing_surface()'s dark list, so it already draws the ink
 *     plate — which is what a bone page above it wants, since the footer always opposes
 *     the section above it.
 *   · The form's two on-dark choices in inc/ds-contact.php: the consent block's surface,
 *     and the submit's colourway (.fc-btn--fill is a white fill and is only ever right
 *     on a dark surface; it is .fc-btn--violet now, which is what the design draws).
 *   · Every --*-on-dark token in the form below became its --*-on-light pair, and the
 *     fields took a white fill rather than staying transparent, which is the design's
 *     own call and the thing that separates a field from the bone around it.
 *
 * The signal colours did NOT flip: --fc-red on the invalid border and the error line,
 * --fc-green on the confirmation title. A status colour that changed with the surface
 * would stop being a status colour.
 *
 * The form is injected at request time (inc/ds-contact.php) and every one of its
 * states is drawn by the server. Like both report pages, this page ships no JS.
 * ══════════════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────────────────────────────────────
 * THE HERO
 *
 * .fc-trust-hero's construction, to the pixel and on purpose: the design draws the two
 * heroes as one shape and even asks for the same artwork. The long version of every
 * value here is over THE HERO in security.css.
 *
 * Restated rather than shared because that file loads on the Trust Center alone and this
 * one loads on twenty pages — lifting six rules into layout.css would put them in every
 * page's critical CSS to save two pages from repeating them. If a third page draws this
 * shape, that is the moment to move them.
 *
 * The wrapper is .fc-split and not the design's .fc-hero-split: an even two-column grid
 * that stacks at 1100 is a primitive this site already has, and a second name for it
 * would be a second thing to keep in step.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-contact-hero {
	/* Both the success redirect and the nav's Contact button land on #contact. */
	scroll-margin-top: var(--nav-height);
}

/*
 * The section gives up its own horizontal padding so the artwork can run from the middle
 * of the page to the RIGHT rule; the copy column pays the ruled inset itself, and the
 * vertical padding moves there too because the picture is full-bleed top and bottom and
 * the copy is what sets the height.
 *
 * Written .fc-section.fc-contact-hero to match the specificity of the ruled padding it
 * overrides; it wins on source order, layout.css being the earlier file.
 */
body.fc-ds .fc-section.fc-contact-hero {
	padding: 0 var(--gutter-page) 0 0;
}

/* No gap: the seam between the two columns is a hairline, not a space. */
body.fc-ds .fc-contact-hero__split {
	gap: 0;
	align-items: stretch;
}

/*
 * --sp-32 where the design says 40. 40 is not on the --sp scale, and this pair is a
 * headline over the block that answers it, which is what 32 is for everywhere else on
 * the site.
 */
body.fc-ds .fc-contact-hero__copy {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--sp-32);
	padding: var(--sp-54) var(--sp-54) var(--sp-54) calc(var(--gutter-page) + 40px);
}

/*
 * The artwork's column. min-height is the floor for the whole hero, since the grid
 * stretches both columns to the taller of the two and on a wide screen the copy is the
 * shorter one. border-left is the seam — the design calls this .fc-seam-col — and it
 * becomes a border-top when the columns stack, because a rule between two things has to
 * be on the side they actually meet.
 */
body.fc-ds .fc-contact-hero__figure {
	position: relative;
	min-height: 360px;
	border-left: var(--stroke-1) solid var(--fc-ink-a20);
}

/* Absolute, so the picture takes the column's height from the form beside it rather
   than giving it a height of its own. */
body.fc-ds .fc-contact-hero__img {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * Two words, so there is no measure to set and nothing to wrap: .fc-display-hero carries
 * the face, the size and the ramp, and the only thing left is the colour. Ink and not
 * white, which is the whole of the flip as far as the headline is concerned.
 */
body.fc-ds .fc-contact-hero__title {
	color: var(--text-on-light);
}

/*
 * No cap. The dark page capped this at 560 to keep an even split against a paragraph of
 * measure beside it; here the column's width is set by the hero's own padding, and a cap
 * on top of that would leave the field stopping short of the seam for no reason.
 */
body.fc-ds .fc-contact__form {
	width: 100%;
}

/*
 * Stacked at 1100, where every other split stacks, and the same three things move as on
 * the Trust Center: the seam becomes the top edge of the picture; the copy column pays
 * the inset on both sides now that it is the full width — 40px on the right rather than
 * the inset again, since the section is already indented by the gutter on that side; and
 * the picture is pulled in off the left rule, which .fc-ruled::before paints before the
 * section's content and a full-bleed image would bury.
 */
@container page (max-width: 1100px) {
	body.fc-ds .fc-contact-hero__copy {
		padding: var(--sp-54) 40px var(--sp-54) calc(var(--gutter-page) + 40px);
	}

	body.fc-ds .fc-contact-hero__figure {
		margin-left: calc(var(--gutter-page) + var(--stroke-1));
		border-left: 0;
		border-top: var(--stroke-1) solid var(--fc-ink-a20);
	}
}

/* The 40px inset halves at 768, the same as every other ruled section's. */
@container page (max-width: 820px) {
	body.fc-ds .fc-contact-hero__copy {
		padding: var(--sp-54) 20px var(--sp-54) calc(var(--gutter-page) + 20px);
	}
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE OFFICES
 * Two grids that share a column structure: four maps in a hairline band, four addresses
 * under it. Both are .fc-grid-4, so they step down together — 4→2 at 1100, →1 at 440 —
 * and the address always sits under its own map because they are the same grid and not
 * two grids that agree.
 * ────────────────────────────────────────────────────────────────────────── */

/*
 * Half a section's padding on top, which is the design's own figure. The seam above is
 * doing the work the other half would have done.
 */
body.fc-ds .fc-offices {
	padding-top: calc(var(--pad-section) * 0.5);
}

/*
 * Grotesk at display size and centred — the one centred heading on the page. -0.02em
 * rather than --ls-display's -0.01em is the design's, and the same literal the
 * innovation page's headline already carries: sans at 48px takes more tightening than
 * the serif the token was set for.
 */
body.fc-ds .fc-offices__title {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-h3-48);
	line-height: var(--lh-flush);
	letter-spacing: -0.02em;
	color: var(--text-on-light);
	text-align: center;
}

/*
 * The band runs RULE TO RULE, not edge to edge. The design bleeds it to the viewport,
 * which takes its two horizontal hairlines straight across the ruled column's verticals;
 * pulling it out by exactly the ruled inset instead means the four lines close a box
 * rather than crossing each other. Same picture, one fewer collision — and the tiles
 * still touch the borders top and bottom, because the band has no vertical padding.
 */
body.fc-ds .fc-offices__band {
	margin: var(--sp-54) -40px 0;
	border-top: var(--stroke-1) solid var(--fc-ink-a20);
	border-bottom: var(--stroke-1) solid var(--fc-ink-a20);
}

/* Follows the ruled inset when it halves. */
@container page (max-width: 820px) {
	body.fc-ds .fc-offices__band {
		margin-right: -20px;
		margin-left: -20px;
	}
}

body.fc-ds .fc-offices__list {
	margin-top: var(--sp-32);
}

/* --sp-8 where the design says 12, for the reason the hero's gap is 32 and not 40:
   these are stacked lines of contact detail, which is what 8 is for. */
body.fc-ds .fc-office {
	display: flex;
	flex-direction: column;
	gap: var(--sp-8);
}

/*
 * Violet, which on this page is otherwise only the button, the focus ring and the pins.
 * The office name is a label on a block of details, and the colour is what separates it
 * from the address under it in place of a jump in size.
 */
body.fc-ds .fc-office__name {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-h5-24);
	line-height: var(--lh-snug);
	color: var(--fc-violet);
}

body.fc-ds .fc-office__phone {
	color: var(--fc-ink-a60);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE MAP TILE
 *
 * A dot-grid plate with four violet brackets and one static world in it. The argument
 * for the SVG is over fluent_commerce_ds_contact_office_map(); what matters here is
 * that the pin's position is a PERCENTAGE OF A 2:1 BOX, because the projection that
 * produced it assumed a 2:1 frame. So the box is held at 2:1 whatever shape the tile
 * takes around it.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-officemap {
	position: relative;
	width: 100%;
	aspect-ratio: 5 / 3;
	background-color: var(--diagram-surface);
	background-image: var(--dot-grid);
	background-size: var(--dot-cell) var(--dot-cell);
}

/* One shape rotated four times, which is how the design draws it: an L in the top-left
   corner is a bracket in any corner once it is turned. */
body.fc-ds .fc-officemap__corner {
	position: absolute;
	width: 12px;
	height: 12px;
	border-top: 2px solid var(--fc-violet);
	border-left: 2px solid var(--fc-violet);
}

body.fc-ds .fc-officemap__corner--tl {
	top: 0;
	left: 0;
}

body.fc-ds .fc-officemap__corner--tr {
	top: 0;
	right: 0;
	transform: rotate(90deg);
}

body.fc-ds .fc-officemap__corner--br {
	right: 0;
	bottom: 0;
	transform: rotate(180deg);
}

body.fc-ds .fc-officemap__corner--bl {
	bottom: 0;
	left: 0;
	transform: rotate(270deg);
}

/*
 * Inset 8px, which is the design's inset for the frame this replaces, and centred in the
 * height that leaves. The explicit ratio is load-bearing: without it the SVG would
 * letterbox itself inside a 5/3 plate and the pin's top:% would be measured against a
 * box the map is not filling — every marker would sit high by a tenth of the tile.
 */
body.fc-ds .fc-officemap__globe {
	position: absolute;
	top: 50%;
	right: 8px;
	left: 8px;
	aspect-ratio: 2 / 1;
	transform: translateY(-50%);
}

body.fc-ds .fc-officemap__world {
	display: block;
	width: 100%;
	height: 100%;
}

/*
 * The design's r=5 dot inside its r=11 ring. The negative margins are half the dot, so
 * left/top address the pin's CENTRE rather than its top-left corner — the projection
 * gives a point, not a box.
 */
body.fc-ds .fc-officemap__pin {
	position: absolute;
	width: 10px;
	height: 10px;
	margin: -5px 0 0 -5px;
	border-radius: var(--radius-pill);
	background: var(--fc-violet);
}

body.fc-ds .fc-officemap__pin::after {
	content: "";
	position: absolute;
	inset: -6px;
	border: var(--stroke-1) solid var(--fc-violet);
	border-radius: var(--radius-pill);
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE EMBEDDED FORM
 * What the page draws since 2026-08-26: an iframe of Pardot's contact form where our
 * own form was — see fluent_commerce_ds_contact_uses_pardot().
 *
 * One declaration, because everything else the frame needs is EMBEDDED PARDOT FORMS in
 * layout.css, shared with the demo page. This is the floor for the case where nothing
 * measures the form, and it matches the height fluent_commerce_ds_contact_frame_height()
 * declares — so an unmeasured frame is the tall case, and the failure is whitespace under
 * the button rather than a scrollbar inside a form. assets/ds/pardot-frame.js zeroes it as
 * soon as the form's own document reports a real height.
 *
 * The form INSIDE the frame is styled by assets/ds/pardot-form.css, which is enqueued by
 * nothing and pasted into Pardot's layout template. It matches the block below rule for
 * rule; where the two disagree, this one is right and that one is out of date.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-contact__frame {
	min-height: 760px;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE FORM
 * OURS, and not what the page draws today — the switch above decides. Left whole: it
 * is one filter from being the page's form again.
 *
 * Two fields at the export's 56px, which is taller than .fc-btn's 48 — deliberately,
 * because this form's second field is a paragraph and a 48px box beside a six-row
 * textarea reads as an afterthought. The button stays a real .fc-btn at 48.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-contact-form {
	display: flex;
	flex-direction: column;
	gap: var(--sp-24);
}

/* A <p> per field, so the hidden <label> and its control are one flex item. */
body.fc-ds .fc-contact-form__field {
	margin: 0;
	display: flex;
	min-width: 0;
}

/*
 * WHITE FILL, NOT TRANSPARENT, and that is the design's call rather than a consequence
 * of the flip. A transparent field on ink reads as a box because the hairline is the
 * lightest thing on the page; on bone the same field is bone inside a bone page and only
 * the hairline says it is a control at all. White is the ground that makes it one.
 */
body.fc-ds .fc-contact-form__input {
	width: 100%;
	min-width: 0;
	height: 56px;
	padding: 0 20px;
	background: var(--surface-lightest);
	border: var(--stroke-1) solid var(--border-on-light);
	border-radius: var(--radius-10);
	font-family: var(--font-core);
	/* The form size — see layout.css. */
	font-size: var(--fc-field-size);
	font-weight: var(--fw-medium);
	color: var(--text-on-light);
}

/*
 * The message. height:auto so `rows` decides it, and vertical resize only — a
 * horizontally resizable textarea can be dragged out of the column it sits in.
 */
body.fc-ds .fc-contact-form__input--message {
	height: auto;
	padding: 18px 20px;
	line-height: var(--lh-loose);
	resize: vertical;
}

body.fc-ds .fc-contact-form__input::placeholder {
	color: var(--fc-ink-a40);
}

/*
 * :focus and not :focus-visible, which is the export's own choice and the right one
 * for a text field: a click into an input is a request to type, so it should show
 * where the caret went. Outline outside the border, both violet — the edge reads as
 * one 2px line rather than two rings.
 */
body.fc-ds .fc-contact-form__input:focus {
	outline: var(--stroke-1) solid var(--fc-violet);
	border-color: var(--fc-violet);
}

/*
 * data-invalid, not :invalid — the server decides what is wrong and says so once, on
 * submit. Both fields are required, so styling the error off :invalid would draw two
 * red boxes on an untouched page. See fluent_commerce_ds_contact_form().
 */
body.fc-ds .fc-contact-form__input[data-invalid="true"] {
	border-color: var(--fc-red);
}

/*
 * The consent statement used to be styled here, in .fc-contact-form__consent-group and
 * four rules under it. It is now .fc-consent in layout.css — the same 14px, the same
 * 8px pair, the same 3px optical nudge on the box — because three other forms had each
 * grown their own copy of it and drifted to three different sizes. This form's version
 * was the one they were told to match, so it became the shared one. It takes the
 * component's DEFAULT light colourway now: .fc-consent--on-dark was what this form asked
 * for while the page was ink, and it went with the page. See inc/ds-consent.php.
 *
 * One trap the shared block inherits, worth keeping written down: --type-body-15 is
 * overridden to 17px in layout.css (the +2 reading-size decision), so anything named
 * after a pixel size would say 15 and mean 17. Hence a literal 14.
 */
body.fc-ds .fc-contact-form__error {
	display: flex;
	align-items: center;
	gap: var(--sp-8);
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-label-12);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--fc-red);
}

/*
 * The alert region is in the DOM at all times and carries `hidden` when there is
 * nothing to say, so this is not optional: the browser's own [hidden] rule is a
 * single attribute selector and loses to the class above it on specificity, which
 * would leave a red "[ ! ]" under the form on every idle page load.
 */
body.fc-ds .fc-contact-form__error[hidden] {
	display: none;
}

/* The mono face exists for exactly this: a machine's own punctuation. */
body.fc-ds .fc-contact-form__mark {
	font-family: var(--font-mono);
	font-variant-ligatures: none;
}

/* .fc-btn--violet already supplies the geometry and the colourway — violet fill, white
   label, an ink edge on hover, which is exactly what the design draws. This only strips
   what a UA gives a real <button> and a <div> never had. */
body.fc-ds .fc-contact-form__submit {
	appearance: none;
	/*
	 * The design's align-self, and it needs saying because a flex column stretches its
	 * items: without this the button is as wide as the textarea above it, which is a
	 * violet band and not a button.
	 */
	align-self: flex-start;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * THE CONFIRMATION
 * A bordered panel where the form was, in the same column — so the page does not
 * change shape between the two states.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-contact-form__done {
	display: flex;
	flex-direction: column;
	gap: var(--sp-24);
	padding: var(--sp-32);
	/* White, like the fields it replaces, and for the same reason: on bone a hairline
	   alone is not enough to make a panel read as a panel. */
	background: var(--surface-lightest);
	border: var(--stroke-1) solid var(--border-on-light);
}

/* Green, and the only green on the page. It is the eyebrow role in another colour,
   which is why it is bold and tracked rather than semibold like a kicker. */
body.fc-ds .fc-contact-form__done-title {
	display: flex;
	align-items: center;
	gap: var(--sp-8);
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-bold);
	font-size: var(--type-eyebrow-16);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--fc-green);
}

/* --type-body-18 here where the report pages use 16: this panel is the export's own,
   and it sets the confirmation at the same size as the page's body copy. */
body.fc-ds .fc-contact-form__done-body {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-18);
	line-height: var(--lh-loose);
	color: var(--text-on-light-muted);
}

/*
 * The reference is a machine string, so it is set as one. The export's MachineString
 * is `copyable` — a button and a clipboard write — and this is a plain span, which is
 * what the report pages and the event RSVP already ship. Selecting eight characters
 * is not the friction that panel is worried about; being consistent about which
 * strings are interactive is worth more than saving it.
 */
body.fc-ds .fc-contact-form__ref {
	margin: 0;
	font-family: var(--font-mono);
	font-variant-ligatures: none;
	font-size: 16px;
	color: var(--text-on-light);
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE REQUEST A DEMO PAGE
 *
 * ONE SECTION, REBUILT FROM THE BUILD-16 DESIGN ON 2026-08-26. What was here — a hero,
 * a two-column ask-beside-form split, a "Placeholder design" banner in 48px violet and a
 * dashed diagnostics panel — was scaffolding standing in for a design that had not
 * arrived. It has, and this is the whole of it: a screenshot of the product filling the
 * section behind a blur and a white wash, and one bordered card floating in the middle of
 * it holding one line of type and one lockup.
 *
 * WHITE, NOT BONE. The page went light on 2026-08-25 and bone was the light it went to;
 * the design draws #FFFFFF, and the card is white on white with a 20%-ink border, so the
 * ground under it cannot be bone or the card would read as a panel rather than a plate.
 * Two other files have to agree — the closing-surface audit in inc/ds-chrome.php and the
 * template's own note — and the nav does not, because white and bone both want the light
 * bar.
 *
 * THE BLUR IS THE ARGUMENT, so most of what follows is about a picture rather than a
 * layout: 94% of the section, covered, blurred 3px, under a 35% white veil. Legible enough
 * to be obviously a real order screen and not legible enough to read, which is why the card
 * on top of it can get away with asking for one thing.
 *
 * NO .fc-section HERE. That primitive pays --pad-section on a stated surface and stacks
 * its children in the ruled column; this section is three layers deep with two of them
 * absolutely positioned, and the padding belongs to .fc-demo__inner, which is the only
 * layer that has content in it.
 *
 * THE LOCKUP ITSELF IS NOT HERE — it is a system primitive in layout.css under THE LOCKUP,
 * because the export declares it as one and seven pages use it. What is here is the card
 * around it and the opt-in under it.
 * ────────────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-demo {
	position: relative;
	display: flex;
	align-items: center;
	min-height: 720px;
	/* The blurred picture is inset 3% on every side and its blur spreads past that, so
	   this is load-bearing rather than defensive. */
	overflow: hidden;
	background: var(--fc-white);
	/* The one edge on the page. Without it the section runs into the ink footer plate with
	   nothing between two surfaces that are already opposites. */
	border-bottom: var(--stroke-1) solid var(--border-on-light);
}

/*
 * The product, blurred. 94% rather than 100%: the design leaves a hairline of white on all
 * four sides so the picture reads as something laid on the page rather than as the page's
 * own background, and the blur needs somewhere to fall off.
 */
body.fc-ds .fc-demo__shot {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 94%;
	height: 94%;
	transform: translate(-50%, -50%);
	object-fit: cover;
	filter: blur(3px);
}

/*
 * The veil. 0.35 is the design's, and it is a literal because tokens.css carries no white
 * at that alpha and one veil is not a reason to add a step to the alpha ramp. It exists to
 * do two jobs at once: hold the card's border against a busy picture, and take the
 * screenshot far enough back that nobody tries to read it.
 */
body.fc-ds .fc-demo__veil {
	position: absolute;
	inset: 0;
	background: rgba(255, 255, 255, 0.35);
}

/*
 * --fc-demo-rise MOVES THE CARD UP THE PAGE, asked for on 2026-08-27 alongside the frame's
 * two height increases. It comes off the top padding and goes straight back onto the bottom,
 * so the section keeps its height and the blurred picture band keeps its proportion — the
 * card moves, nothing else does, and the footer below it does not shift.
 *
 * 24px is 10% of where the card was sitting: measured at 242px from the top of the document
 * on live, which is the nav plus this padding. Expressed as a literal rather than a
 * percentage because a percentage in padding resolves against WIDTH, not height, so 10% here
 * would be 144px at 1440 and 39px at 390 — the opposite of what a phone wants.
 *
 * It is zero below 820px. --pad-section is 72 and then 54 down there, and taking a third of
 * that off would push the card into the nav; on a narrow screen the card is nearly the whole
 * section anyway, so there is no "up the page" left to move it.
 */
body.fc-ds .fc-demo__inner {
	--fc-demo-rise: 24px;

	position: relative;
	/* Above both layers above. */
	z-index: 2;
	display: flex;
	justify-content: center;
	box-sizing: border-box;
	width: 100%;
	padding: calc(var(--pad-section) - var(--fc-demo-rise)) var(--gutter-page)
		calc(var(--pad-section) + var(--fc-demo-rise));
}

/*
 * The card. --fc-demo-inset is a custom property rather than a literal in the padding
 * because it is the one value the phone case changes, and 40px is off the spacing scale
 * (there is no 40 step between 32 and 54) — the design's number, kept as the design's
 * number, named so it reads as this card's inset and not as a step anything else may use.
 */
body.fc-ds .fc-demo__card {
	--fc-demo-inset: 40px;

	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-32);
	box-sizing: border-box;
	width: 100%;
	max-width: 640px;
	padding: var(--sp-54) var(--fc-demo-inset);
	background: var(--fc-white);
	border: var(--stroke-1) solid var(--border-on-light);
	text-align: center;
}

/*
 * NOTHING CARRIES THIS CLASS SINCE 2026-08-27. Andrew asked for the card's one line —
 * "See what a good OMS looks like." — off the page, so the H1 in
 * fluent_commerce_ds_demo_card_markup() is .fc-sr now and the card is the form alone. The
 * rule is left standing because these are the design's numbers for that line and the theme
 * has no git history to recover them from; putting the line back is a className away.
 *
 * 24px, and an H1. The size is the design's and it is a card's heading rather than a
 * hero's — this page has no display type on it at all, which is deliberate: a 48px line
 * over a 62px field would make the field look like an afterthought. 460px caps it at two
 * lines, and text-wrap:pretty is what keeps the second one from being one word.
 */
body.fc-ds .fc-demo__title {
	margin: 0;
	max-width: 460px;
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: var(--type-h5-24);
	line-height: var(--lh-snug);
	letter-spacing: var(--ls-display);
	color: var(--text-on-light);
	text-wrap: pretty;
}

/* The placeholder group the live form is rendered into — see
   fluent_commerce_ds_demo_form_render(). It carries no look of its own; it is the width
   the form is allowed. */
body.fc-ds .fc-demo__form {
	width: 100%;
}

/*
 * THE EMBEDDED PARDOT FORM, when it is the one in the card rather than ours — see
 * fluent_commerce_ds_demo_uses_pardot().
 *
 * Everything the frame and its wrapper have in common with the other two embedded forms —
 * the block display, the full width, no chrome, color-scheme:light — is EMBEDDED PARDOT
 * FORMS in layout.css now, on .fc-pardot / .fc-pardot__frame, which this frame also
 * carries. It used to be restated here, and it drifted from the copy in contact-pardot.css.
 *
 * What is left is the one thing that is this page's: the floor, for the case where nothing
 * measures the form. assets/ds/pardot-frame.js is deferred and its counterpart is pasted
 * into a Pardot template by hand, so either half can be missing — and this matches the
 * height fluent_commerce_ds_demo_pardot_height() declares, so a frame nobody measures is
 * the tall case and the failure is whitespace under the button rather than a scrollbar in a
 * form. pardot-frame.js zeroes it as soon as it knows better.
 *
 * ON LIVE IT NEVER KNOWS BETTER, so this floor and that number are what every visitor gets
 * rather than a first frame — the Pardot-side half of the handshake was never pasted in. The
 * measurement is written down in that function's docblock. Keep the two in step: they moved
 * together on 2026-08-27, 480 → 552 → 580.
 */
body.fc-ds .fc-demo__frame {
	min-height: 580px;
}

body.fc-ds .fc-demo-form {
	display: flex;
	flex-direction: column;
	align-items: center;
	/* Between the lockup and the opt-in. Not the card's 32: the two are one thing being
	   asked and one condition on the asking, and 32 twice reads as three separate blocks. */
	gap: var(--sp-24);
	width: 100%;
}

/*
 * The opt-in, and the error above it. Both are left-aligned and capped inside a centred
 * card: two sentences of 14px fine print centred under a centred heading is four ragged
 * edges in a 640px box, and the checkbox has to sit against a text edge to read as being
 * attached to the sentence beside it. The cap matches the heading's, so the card has one
 * measure rather than two.
 */
body.fc-ds .fc-demo-form .fc-consent,
body.fc-ds .fc-demo-form__error {
	width: 100%;
	max-width: 460px;
	text-align: left;
}

body.fc-ds .fc-demo-form__error {
	display: flex;
	align-items: center;
	gap: var(--sp-8);
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-label-12);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--fc-red);
}

/*
 * The alert region is in the DOM at all times and carries `hidden` when there is
 * nothing to say, so this is not optional: the browser's own [hidden] rule is a single
 * attribute selector and loses to the class above it, which would leave a red "[ ! ]"
 * under the form on every idle page load.
 */
body.fc-ds .fc-demo-form__error[hidden] {
	display: none;
}

/* The mono face exists for exactly this: a machine's own punctuation. */
body.fc-ds .fc-demo-form__mark {
	font-family: var(--font-mono);
	font-variant-ligatures: none;
}

/*
 * The confirmation, in place of the form and inside the same card. It had a border and
 * 32px of padding of its own while it sat in a bare column; inside a bordered plate that
 * is a box in a box, so both are gone and it is simply centred where the form was.
 */
body.fc-ds .fc-demo-form__done {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-16);
}

/* Green, and the only green on the page. The eyebrow role in another colour, which is
   why it is bold and tracked rather than semibold like a kicker. */
body.fc-ds .fc-demo-form__done-title {
	display: flex;
	align-items: center;
	gap: var(--sp-8);
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-bold);
	font-size: var(--type-eyebrow-16);
	letter-spacing: var(--ls-eyebrow);
	text-transform: uppercase;
	color: var(--fc-green);
}

body.fc-ds .fc-demo-form__done-body {
	margin: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: var(--type-body-18);
	line-height: var(--lh-loose);
	color: var(--text-on-light-muted);
}

/*
 * TABLETS AND DOWN. The 720px floor is a desktop composition's floor — it exists so the
 * card has picture around it on a wide screen. On a narrow one the picture is mostly
 * behind the card anyway, and 720px of white under a 400px card is a scroll for nothing.
 */
@container page (max-width: 820px) {
	body.fc-ds .fc-demo {
		min-height: 560px;
	}

	/* See the note on --fc-demo-rise: there is not enough padding down here to take 24px out
	   of, and not enough section around the card for the move to mean anything. */
	body.fc-ds .fc-demo__inner {
		--fc-demo-rise: 0px;
	}
}

/*
 * PHONES. 40px of card inset either side of a 62px lockup leaves the field about 200px
 * wide at 375px, so the inset comes back to the scale. The lockup's own stacking is in
 * layout.css, at the same breakpoint.
 */
@container page (max-width: 440px) {
	body.fc-ds .fc-demo__card {
		--fc-demo-inset: var(--sp-24);

		padding-top: var(--sp-32);
		padding-bottom: var(--sp-32);
	}

	/*
	 * The embedded form's floor goes up on a phone, because the pill inside the frame stacks
	 * there: the field and the button stop sharing a row, which is one control's height plus
	 * a gap more than the desktop case. Same reasoning as the 580 above — this is only what
	 * an unmeasured frame gets, and pardot-frame.js removes it as soon as it knows better,
	 * which on live it does not: see fluent_commerce_ds_demo_pardot_height(), where the
	 * measurement is written down. Grew with that number, 560 → 676.
	 */
	body.fc-ds .fc-demo__frame {
		min-height: 676px;
	}
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE STORY CAROUSEL
 *
 * Customer quotes, one at a time, with no script in the mechanism. The export runs it from
 * React state; here it is N radios and one :checked rule, so it works with JS off and gets
 * keyboard arrow-key movement between the dots for free by being a radio group.
 * assets/ds/story.js adds two things and no state: the design's 8-second advance, and the
 * drag that lets the quote be pulled from one story to the next — see THE DRAG below, and
 * the same file's header for why every carousel on this site is draggable.
 *
 * HERE, in pages.css, as of 2026-08-26. It was written in integrations.css and named
 * .fc-story rather than .fc-integrations-story__* on the grounds that a quote carousel is
 * not an Integrations idea — "the second page to want one lifts it as it stands". The
 * pricing page is the second page, so it did. The markup moved the same way, out of
 * inc/ds-integrations.php and into inc/ds-blocks.php, and NOTHING in either the CSS or the
 * script had to change to serve two pages.
 *
 * ── THE FIVE-WAY PAIRING, AND WHY IT IS WRITTEN OUT ─────────────────
 *
 * The radios come first, as siblings of the stage and of the dot row, so a checked radio
 * can reach both with ~. Pairing radio n to slide n and dot n is index work that CSS
 * cannot do generically — there is no way to say "the nth of these matches the nth of
 * those" — so the five are written out three times: once to show a slide, once to fatten
 * its dot, once to ring the dot when the radio has keyboard focus. A sixth story is one
 * more selector in each of those three rules, and that is the component's only ceiling.
 *
 * :has() would have collapsed each rule to one selector. It is not used because the
 * failure mode is the wrong way round: where :has() is unsupported NOTHING is shown,
 * whereas the sibling combinator has been safe for twenty years.
 *
 * ── THE STAGE IS A GRID, NOT ABSOLUTE POSITIONING ───────────────────
 *
 * Every slide occupies the same grid cell, so the stage is as tall as the LONGEST quote
 * at every width and nothing moves as the carousel advances. The export gives its figure
 * min-height:260px instead, which is a guess at the tallest one and is wrong at any width
 * the guess was not made at.
 *
 * Hidden slides are visibility:hidden rather than display:none, because they still have
 * to size the grid — and visibility also takes them out of the accessibility tree and out
 * of the tab order, which opacity alone would not.
 * ═══════════════════════════════════════════════════════════════════════════ */
body.fc-ds .fc-story {
	/*
	 * The positioned ancestor the visually-hidden radios need. .fc-sr is
	 * position:absolute with no offsets, so without this they resolve against the page
	 * and clicking a dot scrolls to the top of it. Same trap the OMS bento documents.
	 */
	position: relative;
	display: flex;
	flex-direction: column;
	/* 40 rather than a scale step: the export's, and the pair reads as one control group
	   at it — --sp-54 separates them and --sp-32 crowds the 28px pill. */
	gap: 40px;
	max-width: 700px;
	margin: var(--sp-54) auto 0;
}

body.fc-ds .fc-story__stage {
	display: grid;
	/*
	 * Nothing here transforms on its own. This is for the drag's spring-back and for the
	 * settle after a commit — story.js writes translateX and opacity inline while the
	 * pointer is down and clears both on release, so the property that has to be animated
	 * is the one going back to zero. .is-dragging below takes the transition off for the
	 * duration of the pull, or the quote would lag behind the hand.
	 */
	transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}

/*
 * ── THE DRAG ────────────────────────────────────────────────────────────────
 *
 * Both classes come from story.js and neither exists without it, which is the point: with
 * no script there is no grab cursor promising a gesture that cannot happen, and the
 * carousel is the dots and the arrow keys, exactly as it was.
 *
 * touch-action: pan-y is the load-bearing declaration. It tells the browser to keep
 * vertical panning for itself and hand horizontal movement to the script, so a swipe
 * across the quote changes the story while a swipe up the page still scrolls it. Without
 * it a phone would either scroll through every attempted swipe or stop scrolling on the
 * quote altogether, depending on which way the gesture resolved first.
 */
body.fc-ds .fc-story.is-grab .fc-story__stage {
	cursor: grab;
	touch-action: pan-y;
}

body.fc-ds .fc-story.is-dragging .fc-story__stage {
	cursor: grabbing;
	/* A pull across four lines of quote would otherwise select all of it. */
	user-select: none;
	transition: none;
}

body.fc-ds .fc-story__slide {
	grid-area: 1 / 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--sp-32);
	margin: 0;
	text-align: center;
	opacity: 0;
	visibility: hidden;
	transition: opacity var(--dur-base) var(--ease-out), visibility var(--dur-base) var(--ease-out);
}

body.fc-ds .fc-story__radio:nth-of-type(1):checked ~ .fc-story__stage > .fc-story__slide:nth-child(1),
body.fc-ds .fc-story__radio:nth-of-type(2):checked ~ .fc-story__stage > .fc-story__slide:nth-child(2),
body.fc-ds .fc-story__radio:nth-of-type(3):checked ~ .fc-story__stage > .fc-story__slide:nth-child(3),
body.fc-ds .fc-story__radio:nth-of-type(4):checked ~ .fc-story__stage > .fc-story__slide:nth-child(4),
body.fc-ds .fc-story__radio:nth-of-type(5):checked ~ .fc-story__stage > .fc-story__slide:nth-child(5) {
	opacity: 1;
	visibility: visible;
}

/*
 * Fraunces at 26, which is the brand's reading size for a quote rather than the 32
 * .fc-quote sets: these run to four lines and a display size would make a paragraph of
 * them. Not a new role, because it is one section's measure.
 */
body.fc-ds .fc-story__quote {
	margin: 0;
	font-family: var(--font-quote);
	font-weight: var(--fw-regular);
	font-size: var(--type-lead-26);
	line-height: var(--lh-body);
	letter-spacing: var(--ls-quote);
	color: var(--text-on-light);
	text-wrap: pretty;
}

body.fc-ds .fc-story__by {
	display: flex;
	align-items: center;
	gap: var(--sp-16);
}

/* 58 round, from 116px files, so it stays sharp at 2×. */
body.fc-ds .fc-story__avatar {
	flex: none;
	width: 58px;
	height: 58px;
	border-radius: var(--radius-pill);
	object-fit: cover;
	display: block;
}

body.fc-ds .fc-story__who {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--sp-8);
	text-align: left;
}

body.fc-ds .fc-story__name {
	font-family: var(--font-core);
	font-size: var(--type-eyebrow-16);
	font-weight: var(--fw-semibold);
	line-height: var(--lh-snug);
	color: var(--text-on-light);
}

body.fc-ds .fc-story__credit {
	font-family: var(--font-core);
	font-size: var(--type-eyebrow-16);
	font-weight: var(--fw-medium);
	line-height: var(--lh-snug);
	color: var(--fc-ink-a60);
}

body.fc-ds .fc-story__dots {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 12px;
}

body.fc-ds .fc-story__dot {
	position: relative;
	display: block;
	width: 8px;
	height: 8px;
	border-radius: var(--radius-pill);
	background: var(--fc-ink-a20);
	cursor: pointer;
	transition: width var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}

/*
 * The hit area, which is 8px of paint and 28 × 20 of target. Drawn as an invisible
 * ::before rather than as padding, because padding would take the background with it and
 * the dot's whole job is to be 8px.
 */
body.fc-ds .fc-story__dot::before {
	content: "";
	position: absolute;
	inset: -12px -10px;
}

body.fc-ds .fc-story__radio:nth-of-type(1):checked ~ .fc-story__dots > .fc-story__dot:nth-child(1),
body.fc-ds .fc-story__radio:nth-of-type(2):checked ~ .fc-story__dots > .fc-story__dot:nth-child(2),
body.fc-ds .fc-story__radio:nth-of-type(3):checked ~ .fc-story__dots > .fc-story__dot:nth-child(3),
body.fc-ds .fc-story__radio:nth-of-type(4):checked ~ .fc-story__dots > .fc-story__dot:nth-child(4),
body.fc-ds .fc-story__radio:nth-of-type(5):checked ~ .fc-story__dots > .fc-story__dot:nth-child(5) {
	width: 28px;
	background: var(--fc-violet);
}

/*
 * The focus ring. tokens.css draws one on :focus-visible for everything, but these radios
 * are 1px and clipped, so the ring has to be moved onto the label the visitor is actually
 * looking at.
 */
body.fc-ds .fc-story__radio:nth-of-type(1):focus-visible ~ .fc-story__dots > .fc-story__dot:nth-child(1),
body.fc-ds .fc-story__radio:nth-of-type(2):focus-visible ~ .fc-story__dots > .fc-story__dot:nth-child(2),
body.fc-ds .fc-story__radio:nth-of-type(3):focus-visible ~ .fc-story__dots > .fc-story__dot:nth-child(3),
body.fc-ds .fc-story__radio:nth-of-type(4):focus-visible ~ .fc-story__dots > .fc-story__dot:nth-child(4),
body.fc-ds .fc-story__radio:nth-of-type(5):focus-visible ~ .fc-story__dots > .fc-story__dot:nth-child(5) {
	outline: var(--stroke-2) solid var(--fc-violet);
	outline-offset: 4px;
}

/*
 * Below 440 the quote is the section. The avatar row centres under it rather than sitting
 * left of centre, and the 40px gap to the dots comes in — at this width it is most of a
 * thumb's reach between the two.
 */
@container page (max-width: 440px) {
	body.fc-ds .fc-story {
		gap: var(--sp-32);
	}

	body.fc-ds .fc-story__by {
		flex-direction: column;
		gap: var(--sp-16);
	}

	body.fc-ds .fc-story__who {
		align-items: center;
		text-align: center;
	}
}

/* ═════════════════════════════════════════════════════════════════════════════
 * THE DIAGRAM SYSTEM
 *
 * Seven fixed-canvas drawings are built out of these. Everything below is in
 * ABSOLUTE PX and stays that way — the panel scales the whole drawing (see
 * .fc-pat-scale), so a diagram that also ramped its own type would be scaled
 * twice and would not be the same drawing at two sizes. That is why these rules
 * use 17px and not var(--type-body-18), and 32px and not var(--type-numeral-32).
 *
 * Colour is load-bearing and the rules are the design system's:
 *   · violet marks THE path that resolved. At most one route per diagram.
 *   · --diagram-noise, dashed, is a route that went nowhere.
 *   · green/red on a MetricCard is the only other colour a diagram gets, because
 *     a movement is data rather than emphasis.
 * ══════════════════════════════════════════════════════════════════════════ */

/*
 * Icons are inline SVG from fluent_commerce_ds_icon(), not a webfont — see
 * inc/ds-icons.php for why. Nothing needs sizing here: the function writes width
 * and height attributes, and stroke="currentColor" means the surrounding rule's
 * colour is the only thing a diagram has to state.
 *
 * flex: none because almost every one of these sits in a flex row next to a label
 * that is allowed to grow, and an SVG with a percentage max-width will otherwise
 * be the thing that gives.
 */
body.fc-ds .fc-dg-icon {
	display: block;
	flex: none;
}

/*
 * THE CANVAS
 * A diagram is not the page — it is a schematic on its own paper. Hence a surface
 * a shade off the bone and the dot grid at the diagram hairline's weight.
 */
body.fc-ds .fc-dg {
	position: relative;
	flex: none;
	width: var(--dg-w, 640px);
	height: var(--dg-h, 560px);
	padding: var(--dg-pad, 40px);
	background: var(--dot-grid) 0 0 / var(--dot-cell) var(--dot-cell), var(--diagram-surface);
}

body.fc-ds .fc-dg--center {
	display: flex;
	align-items: center;
	justify-content: center;
}

body.fc-ds .fc-dg--stack {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 40px;
}

/*
 * Placement. --x/--y are the drawing's own coordinates; --mid centres the element
 * ON them, which is what a node positioned by its centre needs. Nodes are z-index
 * 2 and the wire layer is 1, so a rule never crosses a label.
 */
body.fc-ds .fc-dg-at {
	position: absolute;
	left: var(--x, 0);
	top: var(--y, 0);
	z-index: 2;
}

body.fc-ds .fc-dg-at--mid {
	translate: -50% -50%;
}

/* One SVG holds every connector in a diagram, drawn behind the nodes. */
body.fc-ds .fc-dg-wires {
	position: absolute;
	inset: 0;
	z-index: 1;
	overflow: visible;
	pointer-events: none;
}

/* ── Nodes ────────────────────────────────────────────────────────────────── */
body.fc-ds .fc-dg-node {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	padding: 12px 18px;
	background: var(--fc-white);
	border: 1px solid var(--diagram-edge);
	border-radius: var(--radius-10);
	color: var(--fc-ink-12);
}

body.fc-ds .fc-dg-node--stacked {
	flex-direction: column;
	padding: 20px 24px;
}

body.fc-ds .fc-dg-node--accent {
	border-color: var(--fc-violet-light);
}

body.fc-ds .fc-dg-node__label {
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: 17px;
	line-height: var(--lh-snug);
	white-space: nowrap;
}

body.fc-ds .fc-dg-node--stacked .fc-dg-node__label {
	font-weight: var(--fw-semibold);
	font-size: 20px;
}

/* The hub — the one pill in the system, and the only place the mark appears. */
body.fc-ds .fc-dg-hub {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	padding: 18px 32px;
	background: var(--fc-ink-12);
	border-radius: var(--radius-pill);
}

body.fc-ds .fc-dg-hub__mark {
	flex: none;
	width: 15px;
	height: 22px;
	background: var(--mark) no-repeat left center;
	background-size: auto 100%;
}

body.fc-ds .fc-dg-hub__label {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: 22px;
	line-height: var(--lh-snug);
	color: var(--fc-bone);
	white-space: nowrap;
}

/* ── The attribute plate ──────────────────────────────────────────────────── */
body.fc-ds .fc-dg-plate {
	width: var(--plate-w, 340px);
	padding: 8px;
	background: var(--diagram-plate);
	border: 1px solid var(--diagram-edge);
}

body.fc-ds .fc-dg-plate__inner {
	background: var(--fc-white);
}

body.fc-ds .fc-dg-plate__row {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 10px 14px;
	border-top: 1px solid var(--diagram-edge);
	color: var(--fc-ink-12);
}

body.fc-ds .fc-dg-plate__row:first-child {
	border-top: 0;
}

body.fc-ds .fc-dg-plate__label {
	flex: 1;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: 16px;
	line-height: var(--lh-snug);
}

body.fc-ds .fc-dg-plate__value {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: 16px;
	line-height: var(--lh-snug);
}

/* The one fact that decided the order. Never accent two rows. */
body.fc-ds .fc-dg-plate__value--accent {
	color: var(--fc-violet);
}

body.fc-ds .fc-dg-plate-head {
	display: inline-flex;
	align-items: center;
	gap: 12px;
	width: var(--plate-w, auto);
	padding: 12px 18px;
	background: var(--diagram-plate);
	border: 1px solid var(--diagram-edge);
	color: var(--fc-ink-12);
}

body.fc-ds .fc-dg-plate-head__label {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: 17px;
	line-height: var(--lh-snug);
	white-space: nowrap;
}

/* ── The meter ────────────────────────────────────────────────────────────── */
body.fc-ds .fc-dg-meter {
	display: flex;
	align-items: center;
	gap: 10px;
	width: var(--meter-w, 340px);
	padding: 11px 16px;
	overflow: hidden;
	background: var(--fc-violet-light);
	color: var(--fc-white);
}

body.fc-ds .fc-dg-meter__label {
	flex: 1;
	min-width: 0;
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: 17px;
	line-height: var(--lh-snug);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

body.fc-ds .fc-dg-meter__rule {
	flex: none;
	align-self: stretch;
	width: 1px;
	background: var(--fc-white-a29);
}

body.fc-ds .fc-dg-meter__value {
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: 14px;
	line-height: var(--lh-snug);
}

body.fc-ds .fc-dg-meter__segs {
	display: flex;
	flex: none;
	gap: 2.5px;
}

body.fc-ds .fc-dg-meter__seg {
	width: 8px;
	height: 16px;
	background: var(--fc-white-a29);
}

body.fc-ds .fc-dg-meter__seg--on {
	background: var(--fc-iris);
}

/* ── The metric card and its sparkline ────────────────────────────────────── */

/*
 * Three cards under the sparkline. 176 × 3 + 16 × 2 = 560, which is exactly the
 * chart's width — the row and the chart are one block and the gap is what makes
 * that arithmetic work, so neither number is free to change on its own.
 */
body.fc-ds .fc-dg-metrics {
	display: flex;
	gap: 16px;
}

body.fc-ds .fc-dg-metric {
	display: flex;
	flex-direction: column;
	gap: 14px;
	width: var(--metric-w, 176px);
	padding: 20px 22px;
	background: var(--fc-white);
	border: 1px solid var(--diagram-edge);
	border-radius: var(--radius-10);
	color: var(--fc-ink-12);
}

body.fc-ds .fc-dg-metric__value {
	font-family: var(--font-quote);
	font-weight: var(--fw-semibold);
	font-size: 32px;
	line-height: var(--lh-tight);
	letter-spacing: var(--ls-quote);
	font-variant-numeric: tabular-nums;
}

body.fc-ds .fc-dg-metric__delta {
	display: flex;
	align-items: center;
	gap: 6px;
	font-family: var(--font-core);
	font-weight: var(--fw-medium);
	font-size: 16px;
	line-height: var(--lh-snug);
	color: var(--fc-green);
}

body.fc-ds .fc-dg-metric__delta--down {
	color: var(--fc-red);
}

body.fc-ds .fc-dg-trend {
	display: block;
	overflow: visible;
}

/* ── The engine, and what it feeds ────────────────────────────────────────── */
body.fc-ds .fc-dg-ai {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 12px;
	width: var(--ai-size, 120px);
	height: var(--ai-size, 120px);
	background: linear-gradient(150deg, var(--fc-violet-bright) 0%, var(--fc-violet-light) 100%);
	border-radius: var(--radius-16);
	color: var(--fc-white);
}

body.fc-ds .fc-dg-ai__label {
	font-family: var(--font-core);
	font-weight: var(--fw-semibold);
	font-size: 15px;
	line-height: var(--lh-snug);
}

/*
 * The glyph card. Violet here is a tint of association, not emphasis: these are
 * downstream of the engine, so they inherit its colour. The glyph carries the
 * meaning and there is deliberately no label.
 */
body.fc-ds .fc-dg-glyph {
	display: flex;
	align-items: center;
	justify-content: center;
	width: var(--glyph-size, 96px);
	height: var(--glyph-size, 96px);
	background: var(--fc-white);
	border: 1.5px solid var(--fc-violet-light);
	border-radius: var(--radius-16);
	color: var(--fc-iris);
}

/* The flat grey tile from the icon field. Black glyph; accent picks one out. */
body.fc-ds .fc-dg-tile {
	display: flex;
	align-items: center;
	justify-content: center;
	width: var(--tile-size, 76px);
	height: var(--tile-size, 76px);
	background: var(--diagram-plate);
	border: 1px solid var(--diagram-edge);
	color: var(--fc-ink-12);
}

body.fc-ds .fc-dg-tile--accent {
	color: var(--fc-violet);
}

/* ── The app window ───────────────────────────────────────────────────────── */
body.fc-ds .fc-dg-frame {
	display: flex;
	flex-direction: column;
	width: var(--frame-w, 380px);
	height: var(--frame-h, 330px);
	overflow: hidden;
	background: var(--fc-white);
	border: 1.5px solid var(--fc-violet-light);
	border-radius: var(--radius-16);
}

body.fc-ds .fc-dg-frame__bar {
	display: flex;
	align-items: center;
	gap: 8px;
	flex: none;
	height: 44px;
	padding: 0 16px;
	background: var(--fc-lilac-pale);
	border-bottom: 1.5px solid var(--fc-violet-light);
}

body.fc-ds .fc-dg-frame__dot {
	width: 10px;
	height: 10px;
	border-radius: 50%;
	background: var(--fc-white);
}

body.fc-ds .fc-dg-frame__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	justify-content: space-between;
	padding: 24px 22px;
}

body.fc-ds .fc-dg-frame__icon {
	color: var(--fc-violet-light);
}

body.fc-ds .fc-dg-frame__lines {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

/* Contents stay abstract — bars, never a screenshot. */
body.fc-ds .fc-dg-frame__line {
	width: var(--w, 180px);
	height: 11px;
	background: var(--fc-lilac-pale);
}

body.fc-ds .fc-dg-frame__progress {
	display: flex;
	align-items: center;
}

body.fc-ds .fc-dg-frame__check {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 48px;
	height: 48px;
	border-radius: 50%;
	background: var(--fc-violet-light);
	color: var(--fc-white);
}

body.fc-ds .fc-dg-frame__rule {
	flex: 1;
	height: 5px;
	background: var(--fc-iris);
}

body.fc-ds .fc-dg-frame__arrow {
	margin-left: -9px;
	color: var(--fc-violet-light);
}
