/**
 * StitchPress — customer capture modal.
 *
 * Brand-neutral by design. Every color, radius and spacing value is a custom
 * property with a sane default, so a shop or theme overrides the look by
 * redefining a handful of variables rather than fighting selectors:
 *
 *   .stp-capture { --stp-accent: #c8102e; --stp-radius: 2px; }
 *
 * Everything is namespaced under .stp-capture (and .stp-capture-launcher on the
 * product page) — no bare element selectors escape into the theme. Mobile
 * first: the single-column stack is the base, the two-column layout is the
 * enhancement. Motion is opt-out via prefers-reduced-motion.
 */

.stp-capture,
.stp-capture-launcher,
.stp-cart-edit {
	--stp-accent: #1f5eff;
	--stp-accent-contrast: #ffffff;
	--stp-ink: #1a1a1a;
	--stp-ink-muted: #5b6470;
	--stp-surface: #ffffff;
	--stp-surface-alt: #f5f6f8;
	--stp-border: #d8dce3;
	--stp-border-strong: #858e9b;
	--stp-danger: #b3261e;
	--stp-success: #1a7f4b;
	--stp-notice: #7a5200;
	/*
	 * ADVISORY AMBER — the warning strip and the "Check" bullets in the facts
	 * sheet, and NEVER --stp-danger.
	 *
	 * The distinction is a product rule, not a palette preference: nothing the
	 * estimator says can stop Add to cart. "Your text works out about 0.19"
	 * tall" is something the shop will talk to the customer about before it
	 * runs the job, and painting it the same red as "your customization could
	 * not be saved" tells a customer the order is broken when it is not.
	 * Derived from --stp-notice, which is already this file's amber ink, so a
	 * shop that recolors one gets all three.
	 */
	--stp-notice-bg: rgba( 122, 82, 0, 0.08 );
	--stp-notice-edge: rgba( 122, 82, 0, 0.45 );
	--stp-radius: 6px;
	--stp-radius-sm: 4px;
	--stp-radius-lg: 12px;
	/*
	 * The inline card's own corner. Larger than --stp-radius-lg on purpose: the
	 * card is a product surface sitting in the page's own flow, not a panel
	 * inside a dialog, and it is the one radius a shop is most likely to want to
	 * match against its theme's cards.
	 */
	--stp-radius-card: 14px;
	--stp-gap: 16px;
	--stp-gap-sm: 8px;
	--stp-shadow: 0 12px 40px rgba( 15, 23, 42, 0.24 );
	--stp-focus: 0 0 0 3px rgba( 31, 94, 255, 0.35 );
	--stp-swatch-size: 34px;
	--stp-zone-outline: rgba( 255, 255, 255, 0.9 );
	/*
	 * Visual placement. The halo is what makes the selection frame and the
	 * handles readable on a white tee AND on a black cap — the item sits on a
	 * photograph whose colors we do not control, so every stage affordance is
	 * drawn twice: accent on top of a light halo.
	 */
	--stp-halo: rgba( 255, 255, 255, 0.92 );
	--stp-handle-hit: 44px;
	--stp-handle-dot: 14px;
	/*
	 * Studio layout (visual mode only). A neutral working surface behind the
	 * garment, the height the stage is given, and the size of the panel that
	 * comes up over it — a sheet on a phone, a drawer beside the canvas from
	 * 720px. All four are properties like everything else here, so a shop that
	 * wants a taller stage or a darker studio changes a number, not a selector.
	 */
	/*
	 * WHITE. The canvas is a product photograph on a shop's product page, and
	 * every retail designer worth copying puts it on white — a tinted working
	 * surface is the thing that makes a designer read as an application. Kept a
	 * token because a shop selling white garments may well want the tint back.
	 */
	--stp-canvas-bg: #ffffff;
	--stp-stage-h: 55vh;
	--stp-drawer-w: 320px;
	/*
	 * The card's CHROME — the top bar and the tool bar that frame the garment.
	 *
	 * Deliberately NOT --stp-accent and deliberately not derived from it: the
	 * chrome is a neutral frame whose whole job is to disappear around the
	 * product photograph, while the accent is what marks the customer's own
	 * choices inside it (a selected swatch, a filled area, the item being
	 * dragged). A shop that recolors one almost never means the other, and the
	 * two are the pair most likely to be set to the same value by accident.
	 *
	 * Two tokens rather than a palette: a background and the ink on it. Every
	 * state on the bars — hover, pressed, disabled — is a white overlay at some
	 * opacity over the background, so one value moves the whole surface and
	 * nothing has to be recomputed to stay legible against it.
	 */
	--stp-chrome-bg: #1b2c46;
	--stp-chrome-ink: #ffffff;
	/*
	 * ACCENT AS TEXT. --stp-accent is a fill colour; a skin whose accent is a
	 * light gold cannot set text in it and pass AA. Every text site that wants
	 * the accent reads this instead, and it defaults to the accent itself, so the
	 * neutral look is unchanged and a skin sets a darker ink beside its fill.
	 */
	--stp-accent-ink: var( --stp-accent );
	/*
	 * Fonts by NAME only — the plugin ships none. `inherit` keeps the theme's
	 * type until a skin names a family the theme already loads.
	 */
	--stp-font-ui: inherit;
	--stp-font-display: inherit;
	/*
	 * TINTS AND SHADOWS, one token per literal so no colour is hard-coded
	 * outside this block. Named by family and opacity (light = a white veil,
	 * shade = the slate shadow, dark = a neutral shadow); the defaults are the
	 * exact values the rules used before, so the neutral look is unchanged.
	 */
	--stp-accent-tint-06: rgba( 31, 94, 255, 0.06 );
	--stp-accent-tint-14: rgba( 31, 94, 255, 0.14 );
	--stp-chrome-fade: rgba( 27, 44, 70, 0 );
	--stp-danger-08: rgba( 179, 38, 30, 0.08 );
	--stp-danger-25: rgba( 179, 38, 30, 0.25 );
	--stp-dark-25: rgba( 0, 0, 0, 0.25 );
	--stp-dark-80: rgba( 0, 0, 0, 0.8 );
	--stp-ink-placeholder: #667085;
	--stp-light-08: rgba( 255, 255, 255, 0.08 );
	--stp-light-10: rgba( 255, 255, 255, 0.1 );
	--stp-light-12: rgba( 255, 255, 255, 0.12 );
	--stp-light-14: rgba( 255, 255, 255, 0.14 );
	--stp-light-16: rgba( 255, 255, 255, 0.16 );
	--stp-light-18: rgba( 255, 255, 255, 0.18 );
	--stp-light-22: rgba( 255, 255, 255, 0.22 );
	--stp-light-28: rgba( 255, 255, 255, 0.28 );
	--stp-light-34: rgba( 255, 255, 255, 0.34 );
	--stp-light-42: rgba( 255, 255, 255, 0.42 );
	--stp-light-50: rgba( 255, 255, 255, 0.5 );
	--stp-light-60: rgba( 255, 255, 255, 0.6 );
	--stp-light-85: rgba( 255, 255, 255, 0.85 );
	--stp-light-86: rgba( 255, 255, 255, 0.86 );
	--stp-light-95: rgba( 255, 255, 255, 0.95 );
	--stp-notice-08: rgba( 122, 82, 0, 0.08 );
	--stp-notice-14: rgba( 122, 82, 0, 0.14 );
	--stp-notice-25: rgba( 122, 82, 0, 0.25 );
	--stp-shade-06: rgba( 15, 23, 42, 0.06 );
	--stp-shade-08: rgba( 15, 23, 42, 0.08 );
	--stp-shade-14: rgba( 15, 23, 42, 0.14 );
	--stp-shade-16: rgba( 15, 23, 42, 0.16 );
	--stp-shade-18: rgba( 15, 23, 42, 0.18 );
	--stp-shade-55: rgba( 15, 23, 42, 0.55 );
	--stp-success-08: rgba( 26, 127, 75, 0.08 );
	--stp-success-30: rgba( 26, 127, 75, 0.3 );
	font-family: var( --stp-font-ui );
}

/*
 * BORDER-BOX FOR THE WHOLE CARD, stated rather than inherited.
 *
 * `width: 100%` on a content-box control is 100% PLUS its padding and border,
 * which overflows whatever holds it. Most themes set border-box globally and
 * hid this; Twenty Twenty-Five does not, and on it the sheet's text field ran
 * ~10px past its right edge — found the moment the sheet got big enough to
 * show a field and a swatch grid together (0.9.0).
 *
 * 0.9.0 fixed it on the three form-control classes. That was under-scoped: the
 * same bug survived on `.stp-capture__submit` (width:100% + 40px of padding —
 * Add to cart, four times the overflow of the field that was fixed), on the
 * editor toggle, on every 44px swatch in an auto-fill grid, and on the inline
 * dialog itself (9th review round, MAJOR 4). One rule instead, because the
 * three-class version needs re-doing every time a control is added.
 *
 * SCOPE: every selector is anchored to `.stp-capture`, so this cannot reach a
 * theme element — the card's own subtree and nothing else. It is deliberately
 * NOT applied to `.stp-capture-launcher` or `.stp-cart-edit`, which are small
 * fragments rendered inside the theme's own markup.
 */
.stp-capture,
.stp-capture *,
.stp-capture *::before,
.stp-capture *::after {
	box-sizing: border-box;
}

/* -------------------------------------------------------------------------
 * Launcher (in form.cart)
 * ---------------------------------------------------------------------- */

.stp-capture-launcher {
	margin: 0 0 var( --stp-gap );
}

.stp-capture-launcher__summary {
	margin: var( --stp-gap-sm ) 0 0;
	color: var( --stp-ink-muted );
	font-size: 0.875em;
}

.stp-capture-launcher__summary:empty {
	display: none;
}

/*
 * Post-add confirmation. It replaces a page reload as the customer's only
 * evidence that anything happened, so it does not fade out on a timer and it
 * carries the obvious next step with it.
 */
.stp-capture-launcher__flash {
	margin: var( --stp-gap-sm ) 0 0;
	padding: var( --stp-gap-sm ) 12px;
	color: var( --stp-success );
	background: var( --stp-success-08 );
	border: 1px solid var( --stp-success-30 );
	border-radius: var( --stp-radius-sm );
	font-size: 0.875em;
}

.stp-capture-launcher__flash[hidden] {
	display: none;
}

.stp-capture-launcher__flash-link {
	color: inherit;
	text-decoration: underline;
}

/* -------------------------------------------------------------------------
 * Cart re-edit link (rendered into the cart's product-name cell)
 * ---------------------------------------------------------------------- */

.stp-cart-edit {
	display: inline-block;
	margin-top: 4px;
	font-size: 0.8125em;
	color: var( --stp-accent-ink );
	text-decoration: underline;
}

/* -------------------------------------------------------------------------
 * Overlay + dialog
 * ---------------------------------------------------------------------- */

body.stp-capture-open {
	overflow: hidden;
}

.stp-capture__overlay {
	position: fixed;
	inset: 0;
	z-index: 100000;
	display: flex;
	align-items: flex-end;
	justify-content: center;
	padding: 0;
	background: var( --stp-shade-55 );
	overflow-y: auto;
}

.stp-capture__overlay[hidden] {
	display: none;
}

.stp-capture__dialog {
	position: relative;
	display: flex;
	flex-direction: column;
	width: 100%;
	max-width: 960px;
	max-height: 100%;
	overflow: hidden;
	background: var( --stp-surface );
	border-radius: var( --stp-radius ) var( --stp-radius ) 0 0;
	box-shadow: var( --stp-shadow );
	color: var( --stp-ink );
	font-size: 16px;
	line-height: 1.45;
}

.stp-capture__dialog:focus {
	outline: none;
}

/* -------------------------------------------------------------------------
 * Inline card
 *
 * The same dialog, re-parented into a container the server printed in the
 * product page's own flow (STP_Capture::display_mode). Everything below is
 * about it no longer being an overlay: no viewport cap, no bottom-sheet
 * corners, no close button — there is nothing to close.
 * ---------------------------------------------------------------------- */

.stp-capture-embed {
	display: block;
	width: 100%;
	margin: 0 0 var( --stp-gap );
}

/*
 * ONE ADD-TO-CART. The inline card sits inside form.cart with the theme's own
 * submit button rendered right under it — two same-named primary actions, of
 * which only the card's runs preflight(), the estimate and the spec hand-off.
 * capture.js stamps this class on the form at the moment the card mounts
 * (mountInline), so the theme's button exists exactly when the card does not:
 * no JS, no class, native button, full fallback. display:none is safe for the
 * card's own escape hatch — requestSubmit() accepts a hidden submitter and
 * form.submit() never needed a visible one — and it removes the button from
 * the tab order along with the paint, which a clip-hide would not.
 *
 * The quantity field is left alone: the serialized design is per-unit and the
 * customer's quantity rides the same form either path.
 *
 * `!important`, deliberately — the competition here is other people's CSS.
 * Woo's own woocommerce-blocktheme.css re-asserts the button at
 * `.woocommerce div.product form.cart button.single_add_to_cart_button`
 * (0,4,3), and every theme is free to go higher. This rule exists to switch
 * a control OFF while the card owns the action; it is the same class of
 * deliberate override as the reduced-motion `animation: none !important`
 * below, and out-specifying an open set of themes is a losing game.
 *
 * `visibility`, NOT `display`. Block themes size the product summary column
 * to its content, and the qty+button row is routinely that column's widest
 * item — `display: none` shrank the whole column (and the card in it) from
 * 315px to 239px on Twenty Twenty-Five at 375vw, measured live. visibility
 * keeps the button's box propping the column while removing paint, taps,
 * tab order and the accessibility node; the fallback submit path is
 * indifferent either way (requestSubmit() accepts an invisible submitter).
 */
form.cart.stp-capture-owns-submit .single_add_to_cart_button,
form.cart.stp-capture-owns-submit button[type="submit"][name="add-to-cart"],
form.cart.stp-capture-owns-submit input[type="submit"][name="add-to-cart"] {
	visibility: hidden !important;
}

/*
 * The theme's quantity row goes invisible WITH its button (8th-round finding:
 * hiding only the button left a bare unlabeled "1" spinner floating beside an
 * invisible hole). Same visibility technique, same reason — the row's box
 * keeps propping content-sized block-theme columns. The customer still sets
 * quantity: the card's footer grows its own stepper (mountInline), two-way
 * synced with this hidden input, which remains the value the form actually
 * posts.
 */
form.cart.stp-capture-owns-submit .quantity {
	visibility: hidden !important;
}

.stp-capture__dialog.is-inline {
	/*
	 * THE DESIGNER FILLS THE SCREEN; the standing text is met by scrolling.
	 *
	 * Real-device pass 2026-08-20 (Aramis): "make the designer the full screen
	 * and scroll for the footer with the disclaimer and stuff". So the garment
	 * is sized to what is left of the viewport after the card's own fixed
	 * chrome — top bar, mode pills, tool rail, action bar, borders — which puts
	 * Add to cart at the bottom of the screen and every paragraph below the
	 * fold, where standing text belongs.
	 *
	 * THE CHROME IS MEASURED, NOT GUESSED. 0.9.0 wrote "call it 260px" here and
	 * was wrong by ~180: the real total at 430x932 is ~440 (top bar 52, mode
	 * pills 38, rail 66, action-bar footer ~282, borders). The error was
	 * invisible with a 6:7 garment, which is WIDTH-limited on a phone and never
	 * reaches the cap at all; any view image taller than about 3:4 — a hoodie, a
	 * full-length apron, a 600x1200 product shot — took the full 672px and put
	 * the primary button ~200px BELOW the fold, which is the exact defect the
	 * declaration was written to fix (9th review round, MAJOR 2). capture.js
	 * measures the real parts and writes --stp-chrome-h in pixels on this
	 * element, on build and again on every resize (applyChromeHeight).
	 *
	 * The 260px here is the PRE-JS FRAME only — the value that holds for the
	 * one layout that happens before the measurement lands. It is deliberately
	 * the old constant: too small rather than too large, so the first frame
	 * shows a garment that is slightly too big rather than a strip.
	 *
	 * `svh` is the SMALL viewport height: on a phone that is the height with
	 * the browser's toolbars SHOWN, which is the state the customer is in when
	 * the page loads. Plain `vh` there is the toolbar-hidden height, and sizing
	 * to it would push the button under the toolbar — the one thing that must
	 * stay on screen. The `vh` line above it is the fallback for browsers that
	 * do not know `svh` (they drop the second declaration and keep the first),
	 * and the floor stops a short viewport from squeezing the garment to a
	 * strip. BOTH lines carry the floor: without it, a viewport shorter than
	 * the chrome makes the calc negative, `max-height` rejects a negative
	 * length, the declaration is invalid at computed-value time and the cap
	 * falls back to `none` — the garment then renders at its natural size,
	 * which is the opposite of what a short viewport needs. Inline only: the
	 * modal is capped to the viewport by its own rules and keeps the 55vh stage
	 * it was tuned with.
	 *
	 * THE FLOOR IS A LAST RESORT, NOT A LAYOUT. Once it engages, the garment
	 * has stopped shrinking and the chrome under it is what overflows instead —
	 * so the short-viewport block further down takes the stage off this
	 * expression entirely from the height where that begins. That height is
	 * derived from these two numbers; the arithmetic is written out beside the
	 * media query, and the two move together.
	 */
	--stp-chrome-h: 260px;
	--stp-stage-h: max( 260px, calc( 100vh - var( --stp-chrome-h ) ) );
	--stp-stage-h: max( 260px, calc( 100svh - var( --stp-chrome-h ) ) );
	max-width: none;
	/*
	 * NOT capped to the viewport. The card scrolls with the page like anything
	 * else on it; the stage inside keeps its own --stp-stage-h cap, which is what
	 * stops a tall garment from filling a phone screen on its own.
	 */
	max-height: none;
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-card );
	box-shadow:
		0 1px 2px var( --stp-shade-06 ),
		0 10px 30px var( --stp-shade-08 );
}

/*
 * The header is the dialog's title and its close button. Inline, the first is
 * said by the card's own aria-label and the second closes nothing.
 */
.stp-capture__dialog.is-inline > .stp-capture__header {
	display: none;
}

.stp-capture__dialog :focus-visible {
	outline: 2px solid var( --stp-accent );
	outline-offset: 2px;
	box-shadow: var( --stp-focus );
}

/* -------------------------------------------------------------------------
 * Header
 * ---------------------------------------------------------------------- */

.stp-capture__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var( --stp-gap-sm );
	padding: var( --stp-gap );
	border-bottom: 1px solid var( --stp-border );
}

.stp-capture__title {
	margin: 0;
	font-size: 1.125em;
	line-height: 1.2;
}

.stp-capture__close {
	flex: 0 0 auto;
	width: 40px;
	height: 40px;
	padding: 0;
	font-size: 24px;
	line-height: 1;
	color: var( --stp-ink-muted );
	background: transparent;
	border: 1px solid transparent;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__close:hover {
	color: var( --stp-ink );
	border-color: var( --stp-border );
}

/* -------------------------------------------------------------------------
 * Card chrome: the dark top bar (visual mode only)
 *
 * Preview on the left, undo/redo in the middle, Info on the right. The bar
 * spans the whole card at every width — unlike the tool bar below it, which
 * stands up into a left column from 720px — because what it carries is about
 * the card as a whole rather than about the garment in it.
 * ---------------------------------------------------------------------- */

.stp-capture__topbar {
	display: flex;
	align-items: center;
	gap: var( --stp-gap-sm );
	padding: 4px 8px;
	color: var( --stp-chrome-ink );
	background: var( --stp-chrome-bg );
}

/*
 * THE HISTORY GROUP IS CENTRED, and space-between could not do it.
 *
 * space-between only centers a middle child when the two outside it are the same
 * width, and these never are: "Preview" and "Info" are different words in every
 * locale, and one of them loses its label entirely below the breakpoint further
 * down. So undo/redo sat off-center by half the difference, drifting as the
 * language changed — and drifting AGAIN, visibly, at the width where the labels
 * disappear.
 *
 * The two outer buttons share the leftover space equally instead, which pins the
 * middle to the true center of the bar whatever they contain, and each one keeps
 * its own alignment so Preview still reads from the left edge and Info to the
 * right. `flex-basis: 0` rather than auto, so it is the SPACE that is shared and
 * not the content that is stretched; the 44px min-width on the button still
 * holds the floor.
 */
.stp-capture__topbar > .stp-capture__chrome-button--preview,
.stp-capture__topbar > .stp-capture__chrome-button--info {
	flex: 1 1 0;
}

.stp-capture__topbar > .stp-capture__chrome-button--preview {
	justify-content: flex-start;
}

.stp-capture__topbar > .stp-capture__chrome-button--info {
	justify-content: flex-end;
}

.stp-capture__topbar[hidden] {
	display: none;
}

/*
 * White at 60%, not the accent: the accent is a light blue on a dark navy and
 * the focus ring is the one thing that must be unmistakable at a glance.
 */
.stp-capture__topbar :focus-visible {
	outline: 2px solid var( --stp-chrome-ink );
	outline-offset: 2px;
	box-shadow: 0 0 0 3px var( --stp-light-28 );
}

/* Its natural width, so the space either side of it is what gets shared. */
.stp-capture__chrome-history {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 2px;
}

.stp-capture__chrome-button {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	/* Mobile ship gate: a 44px target on a bar a thumb reaches for. */
	min-width: 44px;
	min-height: 44px;
	padding: 4px 10px;
	font: inherit;
	font-size: 0.8125em;
	line-height: 1.2;
	color: var( --stp-chrome-ink );
	background: transparent;
	border: 1px solid transparent;
	border-radius: var( --stp-radius );
	cursor: pointer;
}

/*
 * Muted rather than gone. An undo button that vanishes at the start of the
 * stack takes the middle of the bar with it and moves everything else; one that
 * is visibly unavailable tells the customer the history exists at all.
 */
.stp-capture__chrome-button[disabled] {
	opacity: 0.38;
	cursor: default;
}

.stp-capture__chrome-button:hover:not( [disabled] ) {
	background: var( --stp-light-12 );
}

.stp-capture__chrome-button.is-on {
	background: var( --stp-light-18 );
	border-color: var( --stp-light-50 );
}

.stp-capture__chrome-icon {
	flex: 0 0 auto;
	width: 20px;
	height: 20px;
}

.stp-capture__chrome-label {
	white-space: nowrap;
}

/*
 * NARROW PHONES. Below this width the words go and the icons stay, which is the
 * same trade the parity row already makes — the accessible name is on the button
 * either way (aria-label), so nothing is lost to anyone who was not reading the
 * pixels.
 *
 * 420px, NOT 360px. The old figure was arrived at from the English labels and
 * nothing else: "Preview" and "Info" are short, and four controls carrying them
 * do fit a 360px bar. They are not short everywhere — German ("Vorschau"),
 * French ("Aperçu"), Spanish ("Vista previa"), and any locale whose word for
 * Info is a phrase — and on the common 375px and 390px phones those overflowed
 * the bar, which is a flex row: the labels did not wrap, they squeezed the
 * history group off center and pushed Info against the edge. Above 420px there
 * is room for a translated pair with the middle group intact, and below it the
 * icons alone are the same four unambiguous targets.
 */
@media ( max-width: 419px ) {

	.stp-capture__chrome-label {
		position: absolute;
		width: 1px;
		height: 1px;
		padding: 0;
		margin: -1px;
		overflow: hidden;
		clip: rect( 0, 0, 0, 0 );
		white-space: nowrap;
		border: 0;
	}
}

/*
 * The Info sheet: the stage instruction and the standing fidelity caveat, which
 * are strings this file already shows. No prose is written here.
 */
.stp-capture__info {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap-sm );
}

.stp-capture__info-line {
	margin: 0;
	color: var( --stp-ink-muted );
	font-size: 0.875em;
}

/* -------------------------------------------------------------------------
 * THE FACTS SHEET
 *
 * One builder (buildFactsPanel), two homes: a studio panel in visual mode and
 * a collapsible region under the price in quick capture. These rules are
 * written for the CONTENT and know nothing about which home it is in, which is
 * what lets the two presentations stay one thing.
 *
 * NO TABLES ANYWHERE IN HERE, and that is a hard constraint rather than a
 * preference: 430x932 is the primary surface, and a table cannot reflow at
 * 430px — a two-column table with "Estimated stitch count" on the left either
 * wraps into a column two characters wide or scrolls the page sideways. Every
 * pair is a `dl` on a 2-column grid instead: the label takes the space it
 * needs and wraps under itself, the value stays pinned to the right edge.
 * ---------------------------------------------------------------------- */

.stp-capture__facts {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap );
}

.stp-capture__facts-sec {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap-sm );
}

.stp-capture__facts-h {
	margin: 0;
	font-size: 0.9375em;
	font-weight: 600;
	color: var( --stp-ink );
}

/*
 * Label left, value right. `1fr auto` rather than two equal columns: the value
 * is a short figure and the label is prose, so the prose gets the slack — at
 * 430px "Screens to burn" and "5 (4 + underbase)" sit on one line, and a
 * translation twice as long wraps the label without ever pushing the number
 * off the edge.
 */
.stp-capture__facts-dl {
	display: grid;
	grid-template-columns: 1fr auto;
	gap: 4px 12px;
	margin: 0;
	font-size: 0.9375em;
}

.stp-capture__facts-dt {
	margin: 0;
	color: var( --stp-ink-muted );
	min-width: 0;
}

.stp-capture__facts-dd {
	margin: 0;
	text-align: right;
	font-weight: 600;
	/*
	 * Lining figures of equal width, so a column of numbers reads as a column.
	 * Harmless where the font has no such set — the browser falls back to
	 * whatever it has.
	 */
	font-variant-numeric: tabular-nums;
}

/*
 * The caveat under a figure ("An estimate. We confirm after digitizing.").
 * A second <dd> under the same <dt>, spanning both columns — which is what a
 * definition list is for, and is why this needs no wrapper element.
 */
.stp-capture__facts-note {
	grid-column: 1 / -1;
	margin: 0;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
	line-height: 1.45;
}

/*
 * The THIRD `<dd>` under a row: whatever the caller wants to hang there as a
 * node rather than as a string. Only the review summary uses it today (the
 * color swatches and the placement's price), and like the note above it, it
 * spans both columns and needs no wrapper element of its own.
 */
.stp-capture__facts-extra {
	grid-column: 1 / -1;
	margin: 0;
}

/* -------------------------------------------------------------------------
 * THE REVIEW STEP (Wave 1)
 *
 * A studio panel like the facts sheet, and deliberately built out of the facts
 * sheet's own parts: the sections, the headings, the `dl` grid and the money
 * block are all the rules above. What is here is only the things a summary has
 * that a facts sheet does not — a row per placement, a color swatch, and the
 * price of that one placement.
 *
 * NOTHING IN HERE HAS A SIZE THAT DEPENDS ON THE STAGE. The panel overlays the
 * canvas and never reflows it (see the sheet's own block), so no rule in this
 * section can move a zone rect or re-fit a text item.
 * ---------------------------------------------------------------------- */

.stp-capture__review,
.stp-capture__review-body {
	min-height: 0;
}

/*
 * The summary is where focus lands when the step opens (studioFocusIn), so it
 * has to show that it has been landed on. Same treatment, same reasoning, as
 * the facts region: it is a summary rather than a form control, so it takes a
 * ring rather than a control's outline.
 */
.stp-capture__review:focus {
	outline: none;
	box-shadow: var( --stp-focus );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__review-body {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap );
}

/*
 * The colors and the price for one placement, on one line: swatches left,
 * money right. `space-between` with `flex-wrap`, so three thread colors and a
 * price still resolve at 430px by wrapping rather than by squeezing.
 */
.stp-capture__review-meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 4px var( --stp-gap-sm );
	margin-top: 2px;
}

.stp-capture__review-colors {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 4px var( --stp-gap-sm );
	min-width: 0;
}

.stp-capture__review-color {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-width: 0;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
}

/*
 * The swatch. A 14px disc with a hairline ring, because a light thread on a
 * light panel is otherwise a color with no edge — the same reason the picker's
 * swatches carry one. It is `aria-hidden` in the markup: the code and the name
 * beside it are the color's name, and a dot is not a thing to read out.
 *
 * `flex: none` so it can never be squeezed into an ellipse by a long name.
 */
.stp-capture__review-dot {
	flex: none;
	width: 14px;
	height: 14px;
	border: 1px solid var( --stp-border );
	border-radius: 50%;
}

.stp-capture__review-color-name {
	min-width: 0;
	overflow-wrap: break-word;
}

.stp-capture__review-price {
	margin-left: auto;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

/*
 * The two borrowed footer fields at the bottom of the panel (the note and the
 * proof choice — openStudioPanel lends them, in that order, after the summary
 * container). They are the FOOTER's nodes, so they arrive with the footer's
 * styling and the only thing missing is a rule that separates them from the
 * summary above.
 *
 * Selected off the summary container rather than off the sheet body, so the
 * line is drawn exactly once and only where there is something above it to be
 * separated from — the adjacent sibling combinator is the whole guard.
 */
.stp-capture__review + .stp-capture__field--note {
	margin-top: var( --stp-gap );
	padding-top: var( --stp-gap );
	border-top: 1px solid var( --stp-border );
}

.stp-capture__field--note + .stp-capture__field--proof {
	margin-top: var( --stp-gap-sm );
}

/*
 * The advisories. AMBER, NEVER RED — see --stp-notice-bg. A left border rather
 * than a filled block: these sit inside a panel a customer opened deliberately,
 * and a wall of color there reads as an error state.
 */
.stp-capture__facts-warn {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap-sm );
}

.stp-capture__facts-warn-item {
	margin: 0;
	padding: 6px 0 6px 12px;
	border-left: 3px solid var( --stp-notice-edge );
	color: var( --stp-ink );
	font-size: 0.875em;
	/*
	 * 1.45 because these are two and three lines of prose on a phone, not
	 * labels; and `text-wrap: pretty` where the browser has it, so a warning
	 * does not end on a one-word last line. Both degrade to nothing.
	 */
	line-height: 1.45;
	text-wrap: pretty;
}

.stp-capture__facts-money {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap-sm );
}

/*
 * The price breakdown. The same grid as the figures above it, deliberately: a
 * customer scanning the sheet meets one column of labels and one of numbers,
 * not two layouts that happen to look similar.
 */
.stp-capture__bd {
	display: grid;
	grid-template-columns: 1fr auto;
	gap: 4px 12px;
	margin: 0;
	font-size: 0.9375em;
}

.stp-capture__bd-dt {
	margin: 0;
	color: var( --stp-ink-muted );
	min-width: 0;
}

.stp-capture__bd-dd {
	margin: 0;
	text-align: right;
	font-variant-numeric: tabular-nums;
}

/* The next quantity break: an opportunity, so it reads as one. */
.stp-capture__facts-tier {
	margin: 0;
	color: var( --stp-success );
	font-size: 0.875em;
	line-height: 1.45;
}

.stp-capture__facts-disclaimer {
	margin: 0;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
	line-height: 1.4;
}

/*
 * The sheet's own container inside the studio panel. The panel scrolls; this
 * only has to not add a second scroll box inside it.
 */
.stp-capture__facts-body {
	min-height: 0;
}

/*
 * The MODAL presentation's collapsible region, under the price.
 *
 * A FOOTER CHILD since 0.10.0 (see buildModal), so it has lines in every map
 * below. It used to be a child of .stp-capture__price, which put it inside a
 * block refreshPrice() hides whenever there is no estimate to show — and the
 * warning strip stays up through exactly that state, so pressing "2 things to
 * check" opened a `display: none` region and `aria-expanded` said "true" about
 * nothing. Its top rule is what separates it from the number it belongs to;
 * without one the sheet reads as more price.
 */
.stp-capture__facts-region {
	margin-top: var( --stp-gap-sm );
	padding-top: var( --stp-gap-sm );
	border-top: 1px solid var( --stp-border );
}

.stp-capture__facts-region[hidden] {
	display: none;
}

/*
 * A disclosure is not a form control and should not take a focus ring shaped
 * like one — but it IS focused programmatically when it opens, so it needs to
 * show where the customer has been put.
 */
.stp-capture__facts-region:focus {
	outline: none;
	box-shadow: var( --stp-focus );
	border-radius: var( --stp-radius-sm );
}

/* -------------------------------------------------------------------------
 * Error region
 * ---------------------------------------------------------------------- */

.stp-capture__error {
	margin: 0;
	padding: var( --stp-gap-sm ) var( --stp-gap );
	color: var( --stp-danger );
	background: var( --stp-danger-08 );
	border-bottom: 1px solid var( --stp-danger-25 );
	font-size: 0.9375em;
}

.stp-capture__error[hidden] {
	display: none;
}

/*
 * The server can refuse an add for more than one reason at a time; they are
 * listed rather than run together into one sentence, because each one is a
 * separate thing the customer has to go and fix.
 */
.stp-capture__error-list {
	margin: 0;
	padding-left: 1.25em;
}

.stp-capture__error-list li + li {
	margin-top: 4px;
}

/* -------------------------------------------------------------------------
 * Notice region (not a failure — currently "you are editing a saved design")
 * ---------------------------------------------------------------------- */

.stp-capture__notice {
	margin: 0;
	padding: var( --stp-gap-sm ) var( --stp-gap );
	color: var( --stp-notice );
	background: var( --stp-notice-08 );
	border-bottom: 1px solid var( --stp-notice-25 );
	font-size: 0.9375em;
}

.stp-capture__notice[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * Capture-mode pills (quick / visual placement)
 *
 * Rendered only when the template offers both, so this bar is either two
 * buttons or absent — never one button pretending to be a choice.
 * ---------------------------------------------------------------------- */

.stp-capture__capture-modes {
	display: flex;
	flex-wrap: wrap;
	gap: var( --stp-gap-sm );
	padding: var( --stp-gap ) var( --stp-gap ) 0;
}

.stp-capture__capture-modes[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * Screen-reader-only region (throttled placement announcements)
 * ---------------------------------------------------------------------- */

.stp-capture__sr {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
	border: 0;
}

/* -------------------------------------------------------------------------
 * Body: stage + panel
 * ---------------------------------------------------------------------- */

.stp-capture__body {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap );
	padding: var( --stp-gap );
	overflow-y: auto;
}

.stp-capture__views {
	display: flex;
	flex-wrap: wrap;
	gap: var( --stp-gap-sm );
	margin-bottom: var( --stp-gap-sm );
}

.stp-capture__views[hidden] {
	display: none;
}

.stp-capture__view {
	padding: 6px 12px;
	font-size: 0.875em;
	color: var( --stp-ink );
	background: var( --stp-surface-alt );
	border: 1px solid var( --stp-border );
	border-radius: 999px;
	cursor: pointer;
}

/*
 * aria-pressed, not aria-selected: the view switcher is a group of toggle
 * buttons, not a tablist — every view paints the same canvas, so there are no
 * tabpanels for tab semantics to point at. Matches the method pills.
 */
.stp-capture__view[aria-pressed="true"] {
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
	border-color: var( --stp-accent );
}

/*
 * The labeled pills name the views and stay in quick capture, where there is
 * no garment to read a bare "2/4" against. Visual mode gets the floating pager
 * over the canvas instead — one control instead of a row that grows with the
 * template, and the garment keeps the width.
 */
.stp-capture.is-visual .stp-capture__views {
	display: none;
}

.stp-capture__canvas {
	position: relative;
	overflow: hidden;
	background: var( --stp-surface-alt );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-sm );
}

/* -------------------------------------------------------------------------
 * View pager (visual mode) — floats over the bottom center of the garment
 *
 * IT USED TO SIT IN THE TOP-RIGHT CORNER, and that is where the zones are.
 * Every garment template this plugin ships for — a cap, a tee, a polo — is
 * photographed with its decoratable areas in the upper half of the frame:
 * front center, left and right chest, the back yoke. A floating pill in that
 * corner sat ON TOP of them. It swallowed taps meant for a right-chest hotspot,
 * it covered a decoration the customer had just placed there, and on a phone it
 * was the far corner from the thumb holding the device.
 *
 * The hem is the one part of a garment photograph that is reliably empty, the
 * pill is a fixed height so it cannot grow back up into the artwork, and bottom
 * center is where a thumb already is. Nothing else about it changes: same
 * stacking context, same z-index above the item layer, same disabled-at-the-ends
 * behavior.
 * ---------------------------------------------------------------------- */

.stp-capture__pager {
	position: absolute;
	bottom: 8px;
	/* Centered on the GARMENT, not on the stage: the pager is a child of the
	 * canvas, so 50% is half the image's width whatever the stage does with the
	 * space around it. */
	left: 50%;
	transform: translateX( -50% );
	/*
	 * Above the hotspot layer (which contains a stacking context capped at its
	 * own auto slot) and above the item layer, so a decoration dragged over the
	 * hem passes under the pager rather than over it.
	 */
	z-index: 40;
	display: flex;
	align-items: center;
	gap: 2px;
	padding: 2px;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: 999px;
	box-shadow: 0 2px 10px var( --stp-shade-18 );
}

.stp-capture__pager[hidden] {
	display: none;
}

/*
 * An open studio sheet and the pager both claim the bottom-center of a phone,
 * and the sheet used to simply cover it (HANDOFF known-cosmetic). While a
 * sheet is up (capture.js stamps `.is-sheet-open` on the root for exactly its
 * lifetime) the pager fades and stops taking taps; switching views mid-panel
 * was never a meaningful action. Opacity rather than `hidden`, so closing the
 * sheet brings it back without re-entering layout.
 *
 * Phones only: from 720px the sheet is a LEFT DRAWER (see the studio desktop
 * block) and the pager's bottom-center is unobstructed — hiding it there
 * would remove a working control to solve a collision that cannot happen.
 */
@media ( max-width: 719.98px ) {

	.stp-capture.is-sheet-open .stp-capture__pager {
		opacity: 0;
		pointer-events: none;
		transition: opacity 0.15s ease-out;
	}
}

.stp-capture__pager-button {
	display: flex;
	align-items: center;
	justify-content: center;
	/* The same 44px the drag handles use — this sits on the same canvas. */
	width: var( --stp-handle-hit );
	height: var( --stp-handle-hit );
	padding: 0;
	color: inherit;
	background: transparent;
	border: 0;
	border-radius: 50%;
	cursor: pointer;
}

.stp-capture__pager-button:hover:not( [disabled] ) {
	background: var( --stp-surface-alt );
}

/* Disabled at the ends of the run: the pager does not wrap, and saying so is
 * how a customer knows they have seen every view. */
.stp-capture__pager-button[disabled] {
	opacity: 0.35;
	cursor: default;
}

.stp-capture__pager-arrow {
	width: 20px;
	height: 20px;
}

.stp-capture__pager-glyph {
	width: 16px;
	height: 16px;
	color: var( --stp-ink-muted );
}

/* Tabular figures so the pill does not resize as the count passes 9 to 10. */
.stp-capture__pager-count {
	min-width: 3ch;
	font-size: 0.8125em;
	font-variant-numeric: tabular-nums;
	text-align: center;
}

/* -------------------------------------------------------------------------
 * THE EMPTY-DESIGN INVITATION (Wave 1)
 *
 * "What would you like on it?" plus the two add actions, floating over the top
 * of the garment for exactly as long as there is nothing on it.
 *
 * ABSOLUTE, LIKE THE PAGER, AND FOR A HARDER REASON THAN TIDINESS. The canvas
 * shrink-wraps the view image and the image is capped by --stp-stage-h; a child
 * in the normal flow would grow the canvas — which is the coordinate frame every
 * zone rect and every stored placement is a percentage OF (SPEC-SCHEMA) — and
 * would push the footer's action bar back below the fold. Out of flow, it can do
 * neither. No rule in this block gives the canvas, the image or the stage a size.
 *
 * TOP, not center. The zones are already outlined while the design is empty
 * (.is-empty-design, 0.8.0) and most of them are in the upper-middle of a
 * garment photograph; a card in the middle of the frame would sit on top of the
 * front-center outline it is inviting the customer towards. The top strip is the
 * one part of a cap or a tee shot that is reliably empty.
 *
 * DISPLAY: NONE AT REST, which is also what keeps its two buttons out of the
 * focus order (focusables() filters on offsetParent). Four classes decide it and
 * all four are already maintained by capture.js: the studio layout, an empty
 * design, no proof lens, no open sheet.
 * ---------------------------------------------------------------------- */

.stp-capture__invite {
	display: none;
}

.stp-capture.is-studio.is-empty-design .stp-capture__invite {
	position: absolute;
	top: 8px;
	left: 50%;
	transform: translateX( -50% );
	/*
	 * Above the hotspot layer and the item layer, below nothing — the same slot
	 * the pager takes, and for the same reason: it is chrome over the garment,
	 * not something on it.
	 */
	z-index: 40;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	max-width: calc( 100% - 16px );
	padding: 8px 10px;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius );
	box-shadow: 0 2px 10px var( --stp-shade-18 );
	text-align: center;
	/*
	 * TAPS PASS THROUGH THE CARD TO THE ZONES UNDER IT; only its buttons take
	 * them (the buttons restore pointer-events below — the pager pattern).
	 * 13th-round BLOCKER: without this, on the max-height:760px stage
	 * (34vh — iPhone SE portrait, any phone in landscape) the card covered up
	 * to 81% of the canvas and the back view's only zone was unreachable by
	 * tap — in exactly the empty-design state where tapping a zone is the
	 * point. "The top strip is reliably empty" was false against the repo's
	 * own reference geometry (back_yoke starts at 24% of canvas height).
	 */
	pointer-events: none;
}

.stp-capture__invite-button {
	pointer-events: auto;
}

.stp-capture__invite-lead {
	margin: 0;
	font-size: 0.8125em;
	font-weight: 600;
	line-height: 1.3;
}

.stp-capture__invite-actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 6px;
}

/*
 * The two actions. Deliberately NOT the accent-filled primary style: the one
 * filled button on this card is Add to cart, and a garment wearing two more of
 * them tells a customer nothing about which is the way forward.
 *
 * 36px tall rather than the 44 the canvas handles use — this is a floating card
 * on top of the garment, and a 44px pair pushed it over the front-center zone at
 * 375px. Above the 24px floor ARCHITECTURE sets for a tap target, and it is not
 * a target a customer has to hit under time pressure or with a moving finger.
 */
.stp-capture__invite-button {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-height: 36px;
	padding: 6px 12px;
	font: inherit;
	font-size: 0.8125em;
	font-weight: 600;
	color: var( --stp-ink );
	background: var( --stp-surface-alt );
	border: 1px solid var( --stp-border );
	border-radius: 999px;
	cursor: pointer;
}

.stp-capture__invite-button:hover {
	border-color: var( --stp-border-strong );
}

.stp-capture__invite-button[hidden] {
	display: none;
}

.stp-capture__invite-button .stp-capture__tool-icon {
	width: 16px;
	height: 16px;
}

/*
 * It goes when the proof lens goes on — there is nothing to add to under a lens
 * that exists to show the finished thing — and while a sheet is up, because the
 * customer is already inside the panel it would have opened.
 *
 * BOTH RULES REPEAT THE OTHER TWO CLASSES ON PURPOSE. The rule that shows
 * the invitation is `.is-studio.is-empty-design` — 0-3-0 — so a 0-2-0 override
 * here would lose to it at every width and the invitation would sit on the
 * proof view. Specificity is the mechanism, so it is stated rather than assumed.
 */
.stp-capture.is-studio.is-empty-design.is-preview .stp-capture__invite {
	display: none;
}

/*
 * DISPLAY, NOT THE PAGER'S OPACITY, and the difference is the focus order.
 *
 * `opacity: 0` leaves an element laid out, in the accessibility tree and — as
 * far as focusables() is concerned, which asks `offsetParent !== null` — in the
 * modal's focus trap. The pager gets away with that because its buttons stay
 * meaningful behind a panel; two invitation buttons a keyboard customer can
 * reach but not see are not the same thing, and there is nothing to fade in a
 * transition nobody asked for. Specificity is stated rather than assumed: the
 * rule that shows the invitation carries three classes.
 */
.stp-capture.is-studio.is-empty-design.is-sheet-open .stp-capture__invite {
	display: none;
}

.stp-capture__image {
	display: block;
	width: 100%;
	height: auto;
}

.stp-capture__zones {
	position: absolute;
	inset: 0;
	/*
	 * A STACKING CONTEXT, deliberately, and the value must stay 0.
	 *
	 * paintZones() writes a per-hotspot z-index derived from zone area (small
	 * zone on top, so the left chest inside a full front is still tappable),
	 * and those values run as high as 1000. Without a context here they would
	 * compete with .stp-capture__items in the canvas's stacking order and win,
	 * putting the hotspot layer over the decorations the customer is dragging.
	 * Contained here, the whole hotspot layer keeps its single auto-height slot
	 * under the placement layer and the ranking only orders hotspots against
	 * each other, which is all it was ever meant to do.
	 */
	z-index: 0;
}

/*
 * Zone hotspots are positioned with the percentages stored on the template.
 * There is no correction pass anywhere in this stylesheet or in capture.js —
 * if a zone looks wrong, the stored geometry is wrong (capture-integrity D1).
 */
.stp-capture__zone {
	position: absolute;
	display: flex;
	align-items: center;
	justify-content: center;
	/*
	 * Visual floor only — the stored percentages are untouched (D1). A 1%-wide
	 * zone on a 320px phone canvas is a 3px tap target; the hotspot is grown to
	 * something a finger can hit while the geometry it represents stays exactly
	 * as configured.
	 */
	min-width: 24px;
	min-height: 24px;
	padding: 0;
	color: var( --stp-ink );
	background: var( --stp-light-16 );
	border: 2px dashed var( --stp-zone-outline );
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

/*
 * The TAP target outgrows the VISUAL target by 10px a side (an invisible
 * pseudo-element participates in the button's hit-testing). The 24px visual
 * floor above is honest about where the zone is; this is the concession to
 * real thumbs — a small left-chest zone gains ~20px of effective target with
 * not one stored percentage or painted pixel moved (capture-integrity D1).
 * Adjacent zones can overlap by that margin; paintZones() already ranks the
 * smaller zone's hotspot on top, so the harder target wins the contested
 * strip.
 */
.stp-capture__zone::before {
	content: "";
	position: absolute;
	top: -10px;
	right: -10px;
	bottom: -10px;
	left: -10px;
}

.stp-capture__zone:hover,
.stp-capture__zone:focus-visible {
	background: var( --stp-light-34 );
	border-style: solid;
}

.stp-capture__zone.is-filled {
	border-color: var( --stp-accent );
	border-style: solid;
	background: var( --stp-accent-tint-14 );
}

.stp-capture__zone-label {
	padding: 2px 6px;
	font-size: 11px;
	line-height: 1.2;
	text-align: center;
	background: var( --stp-light-85 );
	border-radius: 3px;
}

/*
 * How many decorations are in this zone. Only rendered above one — a badge
 * reading "1" on every filled hotspot says nothing — and pinned to the corner
 * rather than laid out in flow, so it does not push the zone's own label off
 * center. The count is repeated in the button's accessible name, so the badge
 * itself is aria-hidden and is pure signal for the eye.
 */
.stp-capture__zone-count {
	position: absolute;
	top: -8px;
	right: -8px;
	min-width: 18px;
	height: 18px;
	padding: 0 4px;
	font-size: 11px;
	font-weight: 700;
	line-height: 18px;
	text-align: center;
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
	border: 1px solid var( --stp-halo );
	border-radius: 999px;
	box-sizing: border-box;
}

.stp-capture__zone-count[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * Zones are INVISIBLE AT REST in visual mode
 *
 * The single biggest difference between a product page and a tool: a garment
 * covered in dashed rectangles reads as software, and the customer came to see
 * their name on a cap. So in visual mode the outlines, the labels and the count
 * badges are painted out — and NOTHING ELSE CHANGES. The hotspots are still
 * buttons of exactly the size the stored geometry gives them (no rule here
 * touches a percentage — capture-integrity D1), still tappable, still in the tab
 * order, still carrying their accessible names.
 *
 * They come back for five reasons and no others:
 *
 *   1. a one-off pulse when the card first becomes interactive, so the customer
 *      is shown where they may go (JS adds .is-zones-pulse once and drops it on
 *      the animation's own end event — no timer);
 *   2. .is-hot — the zone a gesture is currently bounded by, for the length of
 *      that gesture, because that boundary is the explanation for what the item
 *      is doing;
 *   3. :hover / :focus-visible on the hotspot itself — a pointer or a keyboard
 *      arriving on a target has to be told it is a target;
 *   4. .is-areas — every zone, while the Areas panel is the thing on screen,
 *      because that panel's whole subject is the areas;
 *   5. .is-empty-design — every zone, quietly, while NOTHING is decorated yet.
 *      The pulse of reason 1 is gone in 1.2s, and a customer who loaded the
 *      page while reading elsewhere met a plain photograph with no visible way
 *      in. The rest state returns the moment the first decoration lands
 *      (refreshLauncher() drops the class), which is exactly when the garment
 *      has to stop looking like software again.
 *
 * Quick capture is untouched: it has no stage to keep clean and the outlines
 * ARE its interface.
 * ---------------------------------------------------------------------- */

.stp-capture.is-visual .stp-capture__zone,
.stp-capture.is-visual .stp-capture__zone.is-filled {
	background: transparent;
	border-color: transparent;
	transition:
		background-color 0.18s ease-out,
		border-color 0.18s ease-out;
}

.stp-capture.is-visual .stp-capture__zone-label,
.stp-capture.is-visual .stp-capture__zone-count {
	opacity: 0;
	transition: opacity 0.18s ease-out;
}

/*
 * Reason 5: the empty design. Softer than the interaction states below — a
 * dashed invitation, not a highlight — because these zones are scenery until
 * the customer commits to one. Labels ride along at full opacity; an outline
 * with no name is a puzzle, not an affordance.
 *
 * PLACED ABOVE the hover/focus/.is-hot/.is-areas reveal rules on purpose: the
 * selectors tie at (0,4,0), so source order decides, and the reveal states
 * must still win while the design is empty — a finger arriving on a zone
 * upgrades the soft invitation to the full highlight (8th-round finding: the
 * first draft sat below them and deadened reveals exactly for first-timers).
 */
.stp-capture.is-visual.is-empty-design .stp-capture__zone {
	background: var( --stp-light-10 );
	border-color: var( --stp-zone-outline );
	border-style: dashed;
}

.stp-capture.is-visual.is-empty-design .stp-capture__zone-label {
	opacity: 1;
}

.stp-capture.is-visual .stp-capture__zone:hover,
.stp-capture.is-visual .stp-capture__zone:focus-visible,
.stp-capture.is-visual .stp-capture__zone.is-hot,
.stp-capture.is-visual.is-areas .stp-capture__zone {
	background: var( --stp-light-22 );
	border-color: var( --stp-zone-outline );
	border-style: solid;
}

/* A filled zone keeps its accent identity when it is shown at all. */
.stp-capture.is-visual .stp-capture__zone.is-filled:hover,
.stp-capture.is-visual .stp-capture__zone.is-filled:focus-visible,
.stp-capture.is-visual .stp-capture__zone.is-filled.is-hot,
.stp-capture.is-visual.is-areas .stp-capture__zone.is-filled {
	background: var( --stp-accent-tint-14 );
	border-color: var( --stp-accent );
}

.stp-capture.is-visual .stp-capture__zone:hover .stp-capture__zone-label,
.stp-capture.is-visual .stp-capture__zone:focus-visible .stp-capture__zone-label,
.stp-capture.is-visual .stp-capture__zone.is-hot .stp-capture__zone-label,
.stp-capture.is-visual.is-areas .stp-capture__zone .stp-capture__zone-label,
.stp-capture.is-visual .stp-capture__zone:hover .stp-capture__zone-count,
.stp-capture.is-visual .stp-capture__zone:focus-visible .stp-capture__zone-count,
.stp-capture.is-visual .stp-capture__zone.is-hot .stp-capture__zone-count,
.stp-capture.is-visual.is-areas .stp-capture__zone .stp-capture__zone-count {
	opacity: 1;
}

/*
 * The introduction. One run, and the class that carries it is removed by JS on
 * this animation's own `animationend` — so it cannot replay when a view switch
 * rebuilds every hotspot, and there is no timer deciding when it is over.
 *
 * DRAWN ON A PSEUDO-ELEMENT, not on the hotspot's own border, and that is a
 * correctness point rather than a style one. An animation beats every normal
 * declaration in the cascade for as long as it is applied — so a pulse that
 * animated `border-color` would hold the four reveal rules above closed while
 * it ran, and a browser that PAUSES it (a background tab is the ordinary case)
 * would hold them closed indefinitely: hover, focus, a drag and the Areas panel
 * would all silently stop showing the zone. On its own layer it cannot reach
 * them; a stuck pulse is an invisible overlay and nothing else.
 *
 * `opacity: 0` is the resting value as well as the first keyframe, because the
 * animation carries no fill-mode: when it ends the pseudo-element goes back to
 * the cascade, and the cascade has to say "not visible".
 */
@keyframes stp-zone-pulse {

	0%,
	100% {
		opacity: 0;
	}

	12%,
	68% {
		opacity: 1;
	}
}

.stp-capture.is-visual.is-zones-pulse .stp-capture__zone::after {
	content: "";
	position: absolute;
	inset: -2px;
	opacity: 0;
	background: var( --stp-light-22 );
	border: 2px dashed var( --stp-zone-outline );
	border-radius: var( --stp-radius-sm );
	pointer-events: none;
	animation: stp-zone-pulse 1.2s ease-in-out 1;
}

/* -------------------------------------------------------------------------
 * Preview — the customer proof view
 *
 * Everything the customer used to get here, gone: zone outlines, the selection
 * frame, the handles, and the pointer targets under all three. What is left is
 * the garment and the decorations on it, which is the thing being bought.
 *
 * A LENS, NOT A STATE. Nothing here is stored, nothing moves, and the payload
 * cannot tell the difference — capture.js clears the selection on the way in
 * (so the ring does not survive the class that hides it) and that is the whole
 * of what it touches.
 * ---------------------------------------------------------------------- */

.stp-capture.is-preview .stp-capture__zones {
	pointer-events: none;
}

.stp-capture.is-visual.is-preview .stp-capture__zone,
.stp-capture.is-visual.is-preview .stp-capture__zone:hover,
.stp-capture.is-visual.is-preview .stp-capture__zone:focus-visible,
.stp-capture.is-visual.is-preview .stp-capture__zone.is-hot {
	background: transparent;
	border-color: transparent;
}

.stp-capture.is-visual.is-preview .stp-capture__zone .stp-capture__zone-label,
.stp-capture.is-visual.is-preview .stp-capture__zone .stp-capture__zone-count {
	opacity: 0;
}

.stp-capture.is-preview .stp-capture__item {
	pointer-events: none;
	cursor: default;
}

.stp-capture.is-preview .stp-capture__item::before,
.stp-capture.is-preview .stp-capture__handle {
	display: none;
}

.stp-capture__hint {
	display: block;
	margin: var( --stp-gap-sm ) 0 0;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
}

/* -------------------------------------------------------------------------
 * Visual placement: items on the garment
 *
 * The layer is pointer-transparent and the items in it are not, so a tap on
 * bare garment still reaches the zone hotspot underneath it. Every offset in
 * here is a percentage of the ZONE box (the wrapper), which is the only
 * coordinate frame the stored placement knows about.
 * ---------------------------------------------------------------------- */

/*
 * The canvas clips in quick mode (nothing should ever paint outside the
 * garment) but must not in visual mode: the rotate handle deliberately hangs
 * above the item, and a zone near the top of the garment would otherwise have
 * a handle that is half there and impossible to grab.
 */
.stp-capture.is-visual .stp-capture__canvas {
	overflow: visible;
}

.stp-capture__items {
	position: absolute;
	inset: 0;
	pointer-events: none;
}

.stp-capture__zone-items {
	position: absolute;
	/* Handles hang outside the item, and a rotated item outside its own box. */
	overflow: visible;
	/*
	 * --stp-layer is written by orderItems() from the element's stored `layer`
	 * (0..11). A zone with several decorations stacks them here; the wrappers
	 * are also kept in record order in the DOM, which is what breaks a tie
	 * between two elements on the same layer (SPEC-SCHEMA).
	 *
	 * It arrives as a custom property rather than as an inline z-index so that
	 * the .is-lifted rule below can still win. An inline z-index outranks any
	 * stylesheet selector; this way both values sit in the cascade and the more
	 * specific class takes it, which is the whole point of the lift.
	 */
	z-index: var( --stp-layer, 0 );
}

/*
 * The selected element, lifted clear of its neighbors for as long as it is the
 * one being worked on. TEMPORARY and never stored — `layer` is the customer's
 * own choice and what the shop will produce, and it must not drift upwards
 * every time something is touched.
 */
.stp-capture__zone-items.is-lifted {
	z-index: 30;
}

.stp-capture__item {
	position: absolute;
	pointer-events: auto;
	/*
	 * THE PAGE MUST STILL SCROLL PAST A DECORATION THE CUSTOMER IS NOT EDITING.
	 *
	 * `touch-action: none` on every item was right for the modal and wrong the
	 * moment the card became a block in a product page. A decoration can cover a
	 * good part of the garment, the garment is most of the card, and the card is
	 * most of a phone screen — so a customer scrolling down the page who happened
	 * to start the swipe on a decoration found the page simply would not move.
	 * Nothing on screen explained it: the finger was on a photograph of a cap.
	 *
	 * `pan-y` gives the browser the vertical gesture and keeps the horizontal one,
	 * which is the correct trade for an item at rest — a decoration nobody has
	 * selected is scenery. The moment it IS the one being worked on (below) the
	 * whole gesture comes back, and that state is one tap away and visibly marked
	 * by the selection ring.
	 */
	touch-action: pan-y;
	cursor: move;
	/* The stored value is a percentage of the zone; JS writes it. */
	width: 80%;
	transform-origin: 50% 50%;
	-webkit-user-select: none;
	user-select: none;
}

/*
 * Selection frame. Drawn as a ring OUTSIDE the content so it never covers the
 * decoration, dashed accent over a light halo so it reads on a dark garment
 * and on a light one.
 */
.stp-capture__item::before {
	content: "";
	position: absolute;
	inset: -4px;
	border: 1px dashed var( --stp-accent );
	border-radius: 2px;
	box-shadow:
		0 0 0 1px var( --stp-halo ),
		inset 0 0 0 1px var( --stp-halo );
	pointer-events: none;
}

/*
 * ...and the selected item takes the gesture back in full.
 *
 * `is-selected` is the state a tap, a Tab or a click in the editor puts an
 * element into, and it is drawn with the emphasized ring below — so by the time
 * a customer is dragging, the thing under their finger is visibly the thing they
 * chose. `is-active` is the drag itself, which must not be interruptible by a
 * scroll it has already started outrunning.
 */
.stp-capture__item.is-selected,
.stp-capture__item.is-active {
	touch-action: none;
}

.stp-capture__item:focus {
	outline: none;
}

/*
 * SELECTED, ACTIVE and FOCUSED all draw the same emphasized ring.
 *
 * `is-selected` is the one that was missing: paintSelection() has always
 * written it, the tools row has always acted on it, and nothing on the garment
 * showed it — so on a zone with two decorations the customer could not tell
 * which one the Curve slider and the Delete button were about to change.
 * `is-active` (a drag in progress) and focus-visible are moments; selection is
 * a state that outlives both, and it earns the same ring.
 */
.stp-capture__item:focus-visible::before,
.stp-capture__item.is-selected::before,
.stp-capture__item.is-active::before {
	border-style: solid;
	border-width: 2px;
	box-shadow:
		0 0 0 2px var( --stp-halo ),
		inset 0 0 0 2px var( --stp-halo );
}

.stp-capture__item-text {
	display: block;
	width: 100%;
	line-height: 1;
	text-align: center;
}

/*
 * inline-block + nowrap is load-bearing, not cosmetic: capture.js reads this
 * span's offsetWidth at a reference font-size to derive the size that fills
 * the item exactly. Let it wrap and that measurement becomes meaningless.
 */
.stp-capture__item-line {
	display: inline-block;
	line-height: 1;
	white-space: nowrap;
}

.stp-capture__item-art {
	display: block;
	width: 100%;
	height: auto;
	-webkit-user-drag: none;
}

/*
 * The mirror sits INSIDE the rotated element rather than being multiplied into
 * its transform. scaleX(-1) then rotate(θ) is not rotate(θ) then scaleX(-1),
 * and the one a customer means by "flip" is "mirror the artwork, then turn the
 * whole thing" — which is what nesting produces, for free, in the right order.
 */
.stp-capture__item-art.is-flipped {
	transform: scaleX( -1 );
}

/*
 * Curved text. The SVG carries a viewBox sized to the glyphs actually drawn, so
 * it fills the item's width and takes whatever height its own aspect ratio
 * implies — which is how a curve makes the item's box taller without a single
 * pixel measurement in the arc code. overflow visible because the viewBox is
 * padded by eye, and a font with unusually long descenders should spill rather
 * than be cut.
 */
.stp-capture__item-svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
}

/*
 * NO THREAD COLOR CHOSEN YET (capture.js toggles `.is-unset` on the item).
 *
 * The unstyled state inherited the page's ink, which on a dark garment photo
 * was black-on-black — a customer typed their name and saw nothing. Until a
 * color is picked the text is drawn in a neutral mid-gray inside a light
 * halo, a combination that reads on any fabric the shop photographs. This is
 * presentation only: the record carries no color until the customer chooses
 * one, and the validator still blocks add-to-cart without that choice. The
 * moment a real thread is picked, updateItem() removes the class and the
 * chosen hex takes over — including a black thread on a black cap, which is
 * then an honest preview of exactly what was ordered.
 */
.stp-capture__item.is-unset .stp-capture__item-text {
	color: var( --stp-ink-placeholder );
	text-shadow:
		0 0 1px var( --stp-light-95 ),
		0 1px 2px var( --stp-light-85 ),
		0 0 6px var( --stp-light-60 );
}

/* The arc's fill attribute is `currentColor` while unset, so the same color
 * arrives through `color`; the halo needs filter because text-shadow does not
 * reach SVG glyphs. */
.stp-capture__item.is-unset .stp-capture__item-svg {
	color: var( --stp-ink-placeholder );
	filter:
		drop-shadow( 0 0 1px var( --stp-light-95 ) )
		drop-shadow( 0 0 3px var( --stp-light-60 ) );
}

/*
 * Handles: a 44px touch target with a small visible dot, per the mobile-first
 * ship gate. The hit area is the element; the dot is a pseudo-element, so the
 * target does not have to look like a 44px blob on a phone-sized garment.
 */
.stp-capture__handle {
	position: absolute;
	display: block;
	width: var( --stp-handle-hit );
	height: var( --stp-handle-hit );
	pointer-events: auto;
	touch-action: none;
}

.stp-capture__handle::after {
	content: "";
	position: absolute;
	left: 50%;
	top: 50%;
	width: var( --stp-handle-dot );
	height: var( --stp-handle-dot );
	margin: calc( var( --stp-handle-dot ) / -2 ) 0 0 calc( var( --stp-handle-dot ) / -2 );
	background: var( --stp-accent );
	border: 2px solid var( --stp-halo );
	border-radius: 50%;
	box-sizing: border-box;
}

.stp-capture__handle--scale {
	right: calc( var( --stp-handle-hit ) / -2 );
	bottom: calc( var( --stp-handle-hit ) / -2 );
	cursor: nwse-resize;
}

/*
 * Above the item, and no further above it than it has to be: the hit area
 * hangs outside the garment image and the modal body is a scroll container,
 * so every extra pixel here is a pixel of handle a top-edge zone can push out
 * of view.
 */
.stp-capture__handle--rotate {
	left: 50%;
	top: calc( ( var( --stp-handle-hit ) + 6px ) * -1 );
	margin-left: calc( var( --stp-handle-hit ) / -2 );
	cursor: grab;
}

/* The tether, so the rotate dot reads as belonging to this item. */
.stp-capture__handle--rotate::before {
	content: "";
	position: absolute;
	left: 50%;
	top: 50%;
	width: 2px;
	height: calc( ( var( --stp-handle-hit ) / 2 ) + 6px );
	margin-left: -1px;
	background: var( --stp-accent );
	box-shadow: 0 0 0 1px var( --stp-halo );
}

/*
 * With something drawn in the zone, the zone's own name label is in the way of
 * the thing it names. The hotspot keeps its accessible name either way.
 */
.stp-capture.is-visual .stp-capture__zone.is-filled .stp-capture__zone-label {
	display: none;
}

/*
 * Fidelity honesty (SPEC-SCHEMA): the stage is an approximation drawn with the
 * customer's own screen metrics. Permanent while visual mode is on — not a
 * dismissible tooltip.
 */
.stp-capture__disclaimer {
	display: block;
	margin: 4px 0 0;
	color: var( --stp-ink-muted );
	font-size: 0.75em;
}

.stp-capture__disclaimer[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * Panel: methods + editors
 * ---------------------------------------------------------------------- */

.stp-capture__methods {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var( --stp-gap-sm );
	margin-bottom: var( --stp-gap );
}

.stp-capture__methods[hidden] {
	display: none;
}

.stp-capture__pill {
	padding: 6px 12px;
	font-size: 0.875em;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border-strong );
	border-radius: 999px;
	cursor: pointer;
}

.stp-capture__pill[aria-pressed="true"] {
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
	border-color: var( --stp-accent );
}

.stp-capture__editor {
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-sm );
	margin-bottom: var( --stp-gap-sm );
	background: var( --stp-surface );
}

.stp-capture__editor.is-filled {
	border-color: var( --stp-accent );
}

.stp-capture__editor-heading {
	margin: 0;
	font-size: 1em;
}

.stp-capture__editor-toggle {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var( --stp-gap-sm );
	width: 100%;
	padding: 12px var( --stp-gap );
	font: inherit;
	text-align: left;
	color: var( --stp-ink );
	background: transparent;
	border: 0;
	cursor: pointer;
}

.stp-capture__editor-name {
	font-weight: 600;
}

.stp-capture__editor-status {
	flex: 1 1 auto;
	overflow: hidden;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
	text-align: right;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__editor-body {
	padding: 0 var( --stp-gap ) var( --stp-gap );
	border-top: 1px solid var( --stp-border );
}

.stp-capture__editor-body[hidden] {
	display: none;
}

.stp-capture__modes {
	display: flex;
	flex-wrap: wrap;
	gap: var( --stp-gap-sm );
	margin: var( --stp-gap ) 0;
}

.stp-capture__editor-actions {
	margin-top: var( --stp-gap );
}

/* -------------------------------------------------------------------------
 * Element repeater (several decorations in one zone)
 *
 * A zone whose max_items is 1 renders exactly one block with no heading and no
 * "add another", so this whole section is invisible on the templates that
 * existed before it — the separator only appears once there is something to
 * separate.
 * ---------------------------------------------------------------------- */

.stp-capture__element + .stp-capture__element {
	margin-top: var( --stp-gap );
	padding-top: var( --stp-gap );
	border-top: 1px dashed var( --stp-border );
}

.stp-capture__element-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var( --stp-gap-sm );
	margin-top: var( --stp-gap );
}

.stp-capture__element-name {
	font-size: 0.8125em;
	font-weight: 700;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var( --stp-ink-muted );
}

.stp-capture__add {
	margin-top: var( --stp-gap );
}

.stp-capture__add[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * Parity controls (duplicate, delete, center, flip, layer, arc)
 *
 * In the panel rather than floating over the garment, on purpose: a toolbar
 * pinned to a rotated 40px item on a phone overlaps the thing it acts on and
 * falls off the canvas near the edges. Here the controls are finger-sized,
 * tabbable and beside the fields that own the same element.
 * ---------------------------------------------------------------------- */

.stp-capture__element-tools {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap-sm );
	margin-top: var( --stp-gap );
}

.stp-capture__tools {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

.stp-capture__tools[hidden] {
	display: none;
}

.stp-capture__tool {
	display: flex;
	align-items: center;
	justify-content: center;
	/* Mobile ship gate: never below a 24px target, and comfortably above it. */
	width: 36px;
	height: 36px;
	padding: 0;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border-strong );
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__tool[hidden] {
	display: none;
}

.stp-capture__tool:hover {
	border-color: var( --stp-accent );
	color: var( --stp-accent-ink );
}

.stp-capture__tool[disabled] {
	opacity: 0.4;
	cursor: default;
}

.stp-capture__tool[aria-pressed="true"] {
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
	border-color: var( --stp-accent );
}

/* Destructive, and marked as such before it is pressed rather than after. */
.stp-capture__tool--delete:hover {
	color: var( --stp-danger );
	border-color: var( --stp-danger );
}

/*
 * The studio's Elements rows are the one place these 36px buttons are a FINGER
 * target rather than a mouse one, and the one place they are stacked in a
 * column: three of them — back, forward, delete — sit side by side at the right
 * edge of every row, one of them destructive, on a panel a customer scrolls
 * with the same thumb. 36px is under the 44px the rest of this file ships (the
 * rail buttons, the drag handles), and a mis-tap here deletes work.
 *
 * Scoped to a coarse pointer so a mouse keeps the compact row, and scoped to
 * the row so the parity toolbar inside an editor — seven buttons wrapping in a
 * narrow column — is not blown up with it.
 */
@media ( pointer: coarse ) {

	.stp-capture__row-acts .stp-capture__tool {
		width: 44px;
		height: 44px;
	}
}

/*
 * The 44px floor for the card's text-sized buttons on a phone: the mode and
 * method pills, the view switcher, the empty-design invitation and the quiet
 * footer links (Start over, Save design). Measured on staging at 390px they
 * were 30-36px tall — under the 44px every other finger target in this file
 * ships (rail, pager, chrome, drag handles) and under WCAG 2.5.5 / Apple HIG.
 *
 * Box only, not weight: min-height/min-width grow the hit area while the ink,
 * the font size and the padding stay what they were, so the links still read
 * as text and the invitation pills stay the quiet secondary style. Scoped to a
 * coarse pointer OR a phone-width card, so a desktop mouse keeps the compact
 * rows. Every selector is (0,2,0) so it out-ranks the (0,1,0) rules that set
 * the smaller floors above and below it (.stp-capture__startover's 24px)
 * regardless of source order. No zone hotspot is touched — those stay the
 * size their stored geometry gives them (capture-integrity D1).
 */
@media ( pointer: coarse ), ( max-width: 719.98px ) {

	.stp-capture .stp-capture__pill,
	.stp-capture .stp-capture__view,
	.stp-capture .stp-capture__invite-button,
	.stp-capture .stp-capture__link {
		min-width: 44px;
		min-height: 44px;
	}

	.stp-capture .stp-capture__invite-button {
		justify-content: center;
	}
}

/*
 * Strokes in currentColor: the icon follows the button's state — muted, accent
 * on hover, contrast when pressed — with no second set of color rules.
 */
.stp-capture__tool-icon {
	display: block;
	width: 20px;
	height: 20px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
	pointer-events: none;
}

.stp-capture__arc[hidden] {
	display: none;
}

.stp-capture__arc-row {
	display: flex;
	align-items: center;
	gap: var( --stp-gap-sm );
}

.stp-capture__range {
	flex: 1 1 auto;
	min-width: 0;
	/* The native control: it is keyboard-operable, announces its own value and
	 * is the one slider a screen-reader user already knows. */
	height: 32px;
	accent-color: var( --stp-accent );
}

/*
 * A number, not a sentence. Tabular figures so the row does not jitter as the
 * slider passes 9 to 10 and 99 to 100.
 */
.stp-capture__arc-value {
	flex: 0 0 auto;
	min-width: 3.5ch;
	font-size: 0.875em;
	font-variant-numeric: tabular-nums;
	text-align: right;
	color: var( --stp-ink-muted );
}

/* -------------------------------------------------------------------------
 * Fields
 * ---------------------------------------------------------------------- */

.stp-capture__fields {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap );
}

.stp-capture__field {
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.stp-capture__label {
	font-size: 0.8125em;
	font-weight: 600;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var( --stp-ink-muted );
}

.stp-capture__input,
.stp-capture__select,
.stp-capture__textarea {
	width: 100%;
	padding: 10px 12px;
	font: inherit;
	/*
	 * THE iOS ZOOM FLOOR, stated in pixels on purpose. Same rule the notice
	 * button carries: iOS Safari zooms the whole page when a form control
	 * under 16px takes focus, and it does not zoom back out. `font: inherit`
	 * alone is not enough here — these controls sit inside panels whose base
	 * is 0.875em, so the select and the textarea measured 14px and 13.3px on
	 * staging while the text input beside them measured 16px purely because
	 * its own container happened to be full size. A customer picking a size
	 * or typing a note got the page thrown out of scale; the one typing their
	 * text did not. Not `1em` (that re-inherits the 14px) and not `1rem` (the
	 * theme owns the root); the card's own base is 16px and this pins to it.
	 */
	font-size: 16px;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border-strong );
	border-radius: var( --stp-radius-sm );
}

/*
 * The same floor, out-ranking the host page instead of overriding it. The
 * class rule above is (0,1,0), and WooCommerce core's
 * `woocommerce-blocktheme.css` sets `.woocommerce-page select { font-size:
 * var(--wp--preset--font-size--small) }` at (0,1,1) — on staging
 * (Twenty Twenty-Five) the size selects measured 14px at 390px while every
 * other field held 16px, and a select under 16px zooms the whole page on
 * iOS. Each selector here is at least (0,1,1) and the class pairs are
 * (0,2,0), which beats that rule on specificity alone, without depending on
 * stylesheet order and without `!important`. A theme that styles form
 * controls above (0,2,0) can still win; MB-03 on staging is the check.
 */
.stp-capture .stp-capture__input,
.stp-capture .stp-capture__select,
.stp-capture .stp-capture__textarea,
.stp-capture-launcher .stp-capture__input,
.stp-capture-launcher .stp-capture__select,
.stp-capture-launcher .stp-capture__textarea,
.stp-capture select,
.stp-capture textarea,
.stp-capture input:not([type="hidden"]):not([type="file"]):not([type="range"]):not([type="checkbox"]):not([type="radio"]),
.stp-capture-launcher select,
.stp-capture-launcher textarea,
.stp-capture-launcher input:not([type="hidden"]):not([type="file"]):not([type="range"]):not([type="checkbox"]):not([type="radio"]) {
	font-size: 16px;
}

.stp-capture__textarea {
	resize: vertical;
}

.stp-capture__preview {
	min-height: 64px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 10px;
	font-size: 26px;
	line-height: 1.2;
	text-align: center;
	word-break: break-word;
	background: var( --stp-surface-alt );
	border: 1px dashed var( --stp-border );
	border-radius: var( --stp-radius-sm );
}

/*
 * The empty-preview placeholder is TEXT the customer reads ("Your text"), not a
 * border, so it takes the muted-ink token. --stp-border-strong is a line color;
 * used as type it fell below 4.5:1 on the panel background.
 */
.stp-capture__preview.is-empty {
	color: var( --stp-ink-muted );
}

/*
 * `font: inherit` DOES NOT CARRY text-transform OR letter-spacing — neither is
 * part of the `font` shorthand, so both survive the reset and come through from
 * whatever the theme puts on `button`. A great many of them uppercase and track
 * out every button on the page, which turned this into "START OVER" and, in
 * 0.10.0, the warning strip into "2 THINGS TO CHECK" — a sentence shouted at a
 * customer about an advisory that is deliberately not an error. Stated on every
 * button in the card that is meant to read as text.
 */
.stp-capture__link {
	align-self: flex-start;
	padding: 0;
	font: inherit;
	font-size: 0.875em;
	text-transform: none;
	letter-spacing: normal;
	color: var( --stp-accent-ink );
	background: none;
	border: 0;
	text-decoration: underline;
	cursor: pointer;
}

.stp-capture__link[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * Swatches
 * ---------------------------------------------------------------------- */

.stp-capture__swatches {
	display: grid;
	grid-template-columns: repeat( auto-fill, minmax( var( --stp-swatch-size ), 1fr ) );
	gap: 6px;
}

/*
 * PHONE SWATCHES ARE FINGER-SIZED. 34px was a mouse's target on a chart of 141
 * threads, and picking a color was the other half of the real-device
 * complaint ("hard to use the options to change colors and all that as its soo
 * small"). 44px is the ship-gate target, and with the resizable sheet there is
 * finally room to show a useful number of them at that size.
 */
@media ( max-width: 719.98px ) {

	.stp-capture {
		--stp-swatch-size: 44px;
	}
}

.stp-capture__swatch {
	position: relative;
	width: 100%;
	aspect-ratio: 1 / 1;
	min-height: var( --stp-swatch-size );
	padding: 0;
	background-clip: padding-box;
	border: 1px solid var( --stp-dark-25 );
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__swatch.is-on {
	box-shadow: 0 0 0 2px var( --stp-surface ), 0 0 0 4px var( --stp-accent );
}

/* A tick that reads on both light and dark swatches without relying on color
 * alone — selection must not be conveyed by hue only. */
.stp-capture__swatch.is-on::after {
	content: "";
	position: absolute;
	left: 50%;
	top: 46%;
	width: 30%;
	height: 55%;
	transform: translate( -50%, -50% ) rotate( 45deg );
	border: solid var( --stp-surface );
	border-width: 0 3px 3px 0;
	filter: drop-shadow( 0 0 1px var( --stp-dark-80 ) );
}

.stp-capture__chosen {
	margin: 0;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
}

/* -------------------------------------------------------------------------
 * Upload
 * ---------------------------------------------------------------------- */

.stp-capture__drop {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var( --stp-gap-sm );
	padding: var( --stp-gap );
	text-align: center;
	background: var( --stp-surface-alt );
	border: 2px dashed var( --stp-border-strong );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__drop.is-over {
	border-color: var( --stp-accent );
	background: var( --stp-accent-tint-06 );
}

.stp-capture__drop-button {
	padding: 10px 16px;
	font: inherit;
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
	border: 0;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__drop-button[disabled] {
	opacity: 0.6;
	cursor: default;
}

.stp-capture__file {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
	border: 0;
}

.stp-capture__progress {
	width: 100%;
	height: 6px;
}

.stp-capture__thumb {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var( --stp-gap-sm );
}

.stp-capture__thumb:empty {
	display: none;
}

.stp-capture__thumb-image {
	width: 56px;
	height: 56px;
	object-fit: contain;
	background: var( --stp-surface-alt );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__chip {
	padding: 4px 10px;
	font-size: 0.8125em;
	word-break: break-all;
	background: var( --stp-surface-alt );
	border: 1px solid var( --stp-border );
	border-radius: 999px;
}

/* -------------------------------------------------------------------------
 * Footer
 * ---------------------------------------------------------------------- */

.stp-capture__footer {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap );
	padding: var( --stp-gap );
	background: var( --stp-surface-alt );
	border-top: 1px solid var( --stp-border );
}

.stp-capture__checkbox {
	display: flex;
	align-items: flex-start;
	gap: var( --stp-gap-sm );
	font-size: 0.9375em;
	cursor: pointer;
}

.stp-capture__checkbox input {
	margin-top: 3px;
}

/* -------------------------------------------------------------------------
 * THE WARNING STRIP — footer row 1.
 *
 * "2 things to check", above the action bar, tappable, and it opens the same
 * facts sheet the price disclosure opens.
 *
 * AMBER AND NEVER RED. Nothing behind this strip can block Add to cart: these
 * are advisories about how a design will PRODUCE, the shop confirms every job,
 * and a customer who wants 0.15" text after being told it will not stitch is a
 * conversation rather than an error. Painted in --stp-danger it would read as
 * "your order is broken" and cost the sale it exists to improve.
 *
 * 44px minimum, like every other tap target in the card, and 1em type so iOS
 * does not zoom the page when it is focused.
 * ---------------------------------------------------------------------- */

.stp-capture__warnstrip {
	display: flex;
	align-items: center;
	gap: var( --stp-gap-sm );
	width: 100%;
	min-height: 44px;
	margin: 0;
	padding: 8px 12px;
	font: inherit;
	/*
	 * FULL SIZE, not the 0.9375em the card's secondary text uses. Two reasons,
	 * and the second is the binding one: this is the only advisory a customer
	 * sees without opening anything, and iOS Safari zooms the whole page when a
	 * control under 16px is focused — measured here at 15px before this line,
	 * against a 16px card base.
	 */
	font-size: 1em;
	/*
	 * Neither is in the `font` shorthand, so `font: inherit` leaves both at
	 * whatever the theme put on `button` — and an uppercasing theme turned this
	 * into "2 THINGS TO CHECK", which reads as an alarm about something that is
	 * expressly not one. Same reset as .stp-capture__link.
	 */
	text-transform: none;
	letter-spacing: normal;
	text-align: left;
	color: var( --stp-notice );
	background: var( --stp-notice-bg );
	border: 0;
	border-left: 3px solid var( --stp-notice-edge );
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
	/*
	 * The card's own box-sizing rule covers this too, but the strip is
	 * `width: 100%` with 24px of horizontal padding — the exact shape that
	 * overflowed its container on Twenty Twenty-Five before 0.9.1 fixed the
	 * class of bug. Stated rather than inherited.
	 */
	box-sizing: border-box;
}

.stp-capture__warnstrip[hidden] {
	display: none;
}

.stp-capture__warnstrip:hover,
.stp-capture__warnstrip:focus-visible {
	background: var( --stp-notice-14 );
}

.stp-capture__warnstrip:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

/*
 * The mark is a "!" in the markup rather than an ::after glyph, so it survives
 * a copy-paste and is not invented by the stylesheet. Circled, at a fixed size,
 * so a two-line label does not stretch it into an oval.
 */
.stp-capture__warnstrip-mark {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 20px;
	height: 20px;
	font-size: 0.8125em;
	font-weight: 700;
	line-height: 1;
	color: var( --stp-notice );
	border: 2px solid currentColor;
	border-radius: 50%;
}

.stp-capture__warnstrip-label {
	flex: 1 1 auto;
	min-width: 0;
	font-weight: 600;
}

/*
 * The chevron. A CSS shape rather than a character, because ">" in the markup
 * is read aloud by a screen reader and "2 things to check greater than" is not
 * a thing anyone should hear.
 */
.stp-capture__warnstrip-chevron,
.stp-capture__price-chevron {
	flex: 0 0 auto;
	width: 8px;
	height: 8px;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: rotate( -45deg );
}

.stp-capture__warnstrip-chevron {
	/* Points right: this one navigates INTO a sheet rather than expanding. */
	transform: rotate( -45deg );
	margin-right: 2px;
}

.stp-capture__price {
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.stp-capture__price[hidden] {
	display: none;
}

.stp-capture__price-value {
	font-size: 1.0625em;
}

/* -------------------------------------------------------------------------
 * THE PRICE AS A DISCLOSURE
 *
 * A <button> only where there is a facts sheet behind it; with estimates off
 * the price is still the plain <strong> above and none of this applies.
 *
 * It must not LOOK like a button — it is the price, and turning the headline
 * figure of the action bar into a second call to action competes with Add to
 * cart. So: the same weight and size it had, plus a chevron, plus the ordinary
 * affordances of something pressable.
 * ---------------------------------------------------------------------- */

.stp-capture__price-value--button {
	display: inline-flex;
	align-items: center;
	gap: var( --stp-gap-sm );
	/*
	 * Self-start, or the button stretches to the footer column's full width and
	 * its 44px hit area becomes a 44px-tall band the customer taps by accident
	 * on the way to the quantity stepper.
	 */
	align-self: flex-start;
	min-height: 44px;
	margin: 0;
	padding: 0;
	font: inherit;
	font-size: 1.0625em;
	font-weight: 700;
	/*
	 * Outside the `font` shorthand, so `font: inherit` does not reach them and
	 * an uppercasing theme would set the price in tracked-out capitals. It is
	 * the price: it has to look like the number, not like a call to action.
	 * Same reset as the warning strip and .stp-capture__link.
	 */
	text-transform: none;
	letter-spacing: normal;
	text-align: left;
	color: inherit;
	background: none;
	border: 0;
	cursor: pointer;
}

.stp-capture__price-value--button:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__price-number {
	min-width: 0;
}

/* Down when closed, up when open — the standing convention for a disclosure. */
.stp-capture__price-chevron {
	transform: rotate( 45deg );
	margin-top: -4px;
}

.stp-capture__price-value--button[aria-expanded="true"] .stp-capture__price-chevron {
	transform: rotate( -135deg );
	margin-top: 4px;
}

/*
 * The setup fee is its own line and never folded into the per-unit figure: it
 * is charged once per job across the whole cart and may be waived.
 */
.stp-capture__price-setup {
	font-size: 0.875em;
}

.stp-capture__price-setup[hidden] {
	display: none;
}

/*
 * The price's scope line. A sibling of the price block since 0.9.0 (it is
 * footnote text, and the action bar is for the number and the button), so it
 * carries its own margin reset — it is a <p> in someone else's theme now.
 */
.stp-capture__price-note {
	margin: 0;
	color: var( --stp-ink-muted );
	font-size: 0.75em;
	line-height: 1.4;
	text-align: center;
}


/*
 * The trust strip: the counterweight to the fidelity disclaimer, sitting with
 * the primary action because that is the moment the reassurance is for.
 */
.stp-capture__trust {
	margin: 6px 0 0;
	color: var( --stp-ink-muted );
	font-size: 0.75em;
	line-height: 1.4;
	text-align: center;
}

.stp-capture__trust[hidden] {
	display: none;
}

/*
 * The card's quantity stepper (inline presentation only — revealed by
 * mountInline when it finds a native input to mirror).
 */
.stp-capture__field--qty {
	display: flex;
	align-items: center;
	gap: var( --stp-gap-sm );
}

.stp-capture__field--qty[hidden] {
	display: none;
}

.stp-capture__qty {
	width: 5.5em;
	text-align: center;
}

.stp-capture__submit {
	width: 100%;
	padding: 14px 20px;
	font: inherit;
	font-weight: 600;
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
	border: 0;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__submit[disabled] {
	opacity: 0.65;
	cursor: default;
}

/*
 * The way out of a resumed design. Deliberately a quiet text button in the
 * muted ink and deliberately NOT next to the primary one on a phone: it throws
 * work away, so it must not be what a thumb lands on by accident.
 */
.stp-capture__startover {
	align-self: center;
	/*
	 * ...but quiet is not the same as small. It measured 62x20 on a phone,
	 * under both the 24px floor in ARCHITECTURE.md and WCAG 2.5.8 AA, which is
	 * the worst of both worlds for a destructive control: hard to hit on
	 * purpose AND hard to hit accurately, so a miss lands on it exactly when
	 * the customer meant something else. The padding grows the target without
	 * raising the visual weight one bit — the ink, the size and the position
	 * are all unchanged, so the note above still holds.
	 */
	min-height: 24px;
	padding: 6px 12px;
	color: var( --stp-ink-muted );
}

.stp-capture__startover:hover {
	color: var( --stp-danger );
}

/* -------------------------------------------------------------------------
 * Studio layout — VISUAL MODE ONLY
 *
 * Canvas first: the garment across the top of the modal, an icon rail under it,
 * and one panel at a time coming up over the lower half of the stage. Quick
 * capture never sees any of this — every rule below is behind
 * `.stp-capture.is-studio`, and the rail and the sheet carry `hidden` there.
 *
 * The body becomes a two-area grid rather than a column of boxes, and the sheet
 * is a grid item placed in the STAGE area, aligned to its end. That is what
 * makes the panel an overlay with no absolute positioning to maintain and no
 * measurement of the rail's height: the rail keeps its own row and stays
 * reachable, the sheet sits over the canvas, and the canvas box does not change
 * size when a panel opens. It must not — a text item's font-size is fitted in
 * PIXELS against the measured canvas (fitLine), so a canvas that reflowed under
 * an opening panel would leave every fitted size describing a stage that is no
 * longer there.
 * ---------------------------------------------------------------------- */

.stp-capture.is-studio .stp-capture__body {
	display: grid;
	grid-template-columns: 1fr;
	grid-template-rows: 1fr auto;
	grid-template-areas:
		"stage"
		"rail";
	gap: 0;
	padding: 0;
	min-height: 0;
	/*
	 * STRETCH, explicitly. The 720px flex layout above sets align-items:
	 * flex-start for the two-column modal, and a grid inherits it — which left
	 * the tool bar sized to its own five buttons and a band of stage showing
	 * beneath it where the dark column should have run the full height. The
	 * chrome only reads as a frame if it reaches the edges.
	 */
	align-items: stretch;
	/*
	 * The BODY does not scroll here; the stage inside it does. An overlay
	 * anchored to a scrolling box scrolls away from the rail it is supposed to
	 * sit on top of.
	 *
	 * `clip` rather than `hidden`, and the PAIR is load-bearing. `hidden` still
	 * makes this a SCROLL CONTAINER - it only refuses the user a scrollbar, so
	 * the browser remains free to scroll it programmatically. The sheet is a
	 * grid item that spends the first frames of its open at translateY( 100% ),
	 * a transform contributes to scrollable overflow, and so any focus() that
	 * lands inside the sheet gets "revealed" by scrolling this box: measured
	 * 317px on a rail open and 361px entering the zone editor, which is the
	 * blank panel Aramis photographed on 0.9.0 (iPhone, 2026-08-20). `clip`
	 * clips WITHOUT creating a scroll container, so there is nothing left to
	 * scroll and the whole class of bug is gone rather than patched one caller
	 * at a time.
	 *
	 * `hidden` stays as the first declaration for Safari below 16, which drops
	 * the `clip` line; those browsers are covered behaviorally instead, by the
	 * preventScroll + assertion in focusInCard().
	 */
	overflow: hidden;
	overflow: clip;
}

.stp-capture.is-studio .stp-capture__stage {
	grid-area: stage;
	/* The 720px layout makes this sticky for the two-column view; a grid item
	 * in the studio is placed by the grid and nothing else. */
	position: static;
	/*
	 * LOAD-BEARING. Do not remove this line; it is not a leftover.
	 *
	 * The stage and the sheet are two items of the SAME grid. The sheet carries
	 * `z-index: 20` so a panel paints over the canvas it overlays. Inside the
	 * stage, the selected decoration is lifted with `z-index: 30`
	 * (.stp-capture__zone-items.is-lifted) so the thing being dragged is the
	 * thing you can see and grab. With no stacking context on the stage those
	 * two numbers are compared against each other in the body's context — 30
	 * beats 20 — and the lifted decoration paints straight THROUGH an open
	 * panel, on top of the very fields that are editing it.
	 *
	 * `z-index` applies to a grid item even at `position: static`, and any value
	 * other than `auto` makes it a stacking context. That is what makes one
	 * declaration enough: the lift still wins against its siblings inside the
	 * stage, and the stage as a whole now loses to the sheet, always. `0` is
	 * chosen because it changes nothing about where the stage itself paints.
	 */
	z-index: 0;
	min-width: 0;
	min-height: 0;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	padding: var( --stp-gap-sm ) var( --stp-gap-sm ) var( --stp-gap );
	background: var( --stp-canvas-bg );
}

/*
 * THE CANVAS BOX IS THE IMAGE BOX, and stays that way.
 *
 * Zone rects and every stored placement are percentages of the view image
 * (SPEC-SCHEMA), and both the hotspot layer and the item layer are inset:0 of
 * this element — so giving the canvas a height of its own and letterboxing the
 * garment inside it would put dead bars inside the coordinate frame and move
 * every zone. Instead the IMAGE is capped and the canvas shrink-wraps it, which
 * is the only way to get a ~55vh stage without touching the geometry.
 */
.stp-capture.is-studio .stp-capture__canvas {
	display: block;
	width: -webkit-fit-content;
	width: fit-content;
	max-width: 100%;
	margin: 0 auto;
	background: none;
	border: 0;
	border-radius: 0;
}

.stp-capture.is-studio .stp-capture__image {
	width: auto;
	height: auto;
	max-width: 100%;
	max-height: var( --stp-stage-h, 55vh );
}

.stp-capture.is-studio .stp-capture__views {
	justify-content: center;
}

/*
 * The mode pills sit between the dark top bar and the canvas. Centered and
 * tightened so they read as a small switch under the chrome rather than as a
 * second row of navigation.
 */
.stp-capture.is-studio .stp-capture__capture-modes {
	justify-content: center;
	padding: var( --stp-gap-sm ) var( --stp-gap-sm ) 0;
}

.stp-capture.is-studio .stp-capture__disclaimer {
	text-align: center;
}

/*
 * The stage instruction goes SR-ONLY in the studio layout, and stays in the DOM.
 *
 * It is the target of every stage item's aria-describedby — the keyboard
 * contract for moving a decoration is described by this string and nowhere else
 * — so it cannot be removed or `hidden`. What it can stop being is a line of
 * help text printed under the garment: the card's whole claim is that it looks
 * like a product, and the same sentence is one press of Info away for anyone who
 * wants it. Quick capture keeps it visible; it has no Info button and its zone
 * outlines are its interface.
 */
.stp-capture.is-studio .stp-capture__hint {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
	border: 0;
}

/*
 * SHORT VIEWPORTS — a phone in landscape, a small laptop, a browser with three
 * toolbars open.
 *
 * The stage was tuned against a portrait phone: 55vh is the garment before the
 * modal's own header, the mode pills, the rail and the footer have taken
 * anything. Under the height below that collapses — the garment is squeezed to
 * a strip. The stage comes down, which leaves the fixed chrome the room it
 * needs.
 *
 * WHERE 760 COMES FROM, and why it is not 620 any more. The inline card sizes
 * its stage as `max( 260px, 100svh - --stp-chrome-h )`, so the FLOOR takes over
 * — and the chrome starts being squeezed instead of the garment — as soon as
 * the viewport drops under `chrome + 260`. The chrome measured at 430x932 was
 * ~440 (top bar 52, mode pills 38, rail 66, action-bar footer ~282, borders),
 * which put that crossover at ~700 and made 620 an under-estimate even before
 * this wave. 0.10.0 then adds a row to the action bar: the warning strip is
 * 44px of min-height plus one 8px row-gap, ~52px, call the chrome ~500. 500 +
 * 260 = 760, and every viewport from there down is one where the floor is
 * holding the garment up while the chrome overflows. 621–760px — the iPhone SE
 * class, and a phone in landscape with a toolbar open — was the band that fell
 * between the old number and the real one.
 *
 * The interior values are unchanged: 34vh was tuned against the layout, not
 * against the breakpoint, and it stays the stage this block asks for.
 *
 * The SHEET is not in here any more: it is a percentage of the stage area and
 * the customer sizes it themselves (--stp-sheet-size, written by the grip), so
 * it follows the stage down without a second number to keep in step.
 *
 * A custom property rather than a new rule, so this is the same knob a shop
 * already overrides, set to a different number at a different height.
 *
 * TWO SELECTORS, and that is not redundancy. A custom property is resolved by
 * INHERITANCE — nearest declaration wins, and specificity only breaks ties
 * between declarations on the SAME element. `.stp-capture` is the overlay/embed
 * root; the inline card declares --stp-stage-h on `.stp-capture__dialog
 * .is-inline`, which is its descendant, so from 0.9.0 until this line the whole
 * media query was dead on the primary surface no matter how it was written
 * (9th review round, MAJOR 1: measured at 430x600, the dialog was still
 * resolving max(260px, 100svh - chrome) while the root said 34vh). The second
 * rule is the same knob declared where the card can actually see it, at equal
 * specificity and later in source than the block at the top of this file.
 */
@media ( max-height: 760px ) {

	.stp-capture {
		--stp-stage-h: 34vh;
	}

	.stp-capture__dialog.is-inline {
		--stp-stage-h: 34vh;
	}
}

/*
 * The accordion column, the note field and the proof choice are LENT to their
 * panels — the same nodes, re-parented, never a second copy. All three rules use
 * a child combinator on purpose: once the node is inside the sheet its parent is
 * different and the rule stops applying to it, with no class to add and remove
 * in step with the move.
 */
.stp-capture.is-studio .stp-capture__body > .stp-capture__panel {
	display: none;
}

.stp-capture.is-studio .stp-capture__footer > .stp-capture__field--note {
	display: none;
}

/*
 * ...AND THE PROOF CHOICE JOINS IT (Wave 1).
 *
 * The studio footer is an ACTION BAR: the price, the quantity, Add to cart, and
 * the standing fine print under them. "Email me a proof before production" is
 * not fine print and it is not an action — it is a DECISION, and a decision
 * belongs with the other decisions, in the review step, next to the note field
 * that has been there since the studio layout shipped (openStudioPanel lends
 * both). Left in the footer it sat on row 3, below the fold, where a customer
 * met it after they had already been offered the button.
 *
 * Same technique, same reason, same consequence: the child-combinator scopes
 * this to the footer, so the field is fully visible the moment the review panel
 * takes it, and (see the footer map, "Designer UX 1.1") a hidden child needs no row.
 */
.stp-capture.is-studio .stp-capture__footer > .stp-capture__field--proof {
	display: none;
}


/* -------------------------------------------------------------------------
 * Studio: the icon rail
 * ---------------------------------------------------------------------- */

/*
 * The tool bar. Dark chrome, large icons, white labels — the frame around the
 * garment, matching the top bar above it.
 *
 * SCROLLS RATHER THAN SHRINKS. The buttons used to divide the width between
 * them (`flex: 1 1 0`), which meant a sixth tool, or a locale whose words are
 * longer, silently squeezed every label past the point of being readable. At a
 * fixed minimum they overflow instead, and the bar scrolls — the same answer
 * the reference toolbar gives, and the only one that keeps a label a label.
 *
 * ...and once it can overflow, `justify-content` has to be `flex-start`.
 * `space-around` distributes free space by putting half a share OUTSIDE the
 * first and last items — which is fine while everything fits and is a bug the
 * moment it does not: in an overflowing scroll container that leading half-share
 * becomes negative offset, and the first tool is unreachable in the scroll
 * direction. It cannot be scrolled to, because there is nothing before the
 * origin to scroll back to. The rail is packed from the start instead, so the
 * first tool is where a rail's first tool has always been and the overflow all
 * happens at the end, where the scroll can reach it.
 */
.stp-capture__rail {
	grid-area: rail;
	display: flex;
	align-items: stretch;
	justify-content: flex-start;
	gap: 2px;
	padding: 4px var( --stp-gap-sm );
	overflow-x: auto;
	scrollbar-width: thin;
	color: var( --stp-chrome-ink );
	background: var( --stp-chrome-bg );
	border-top: 1px solid var( --stp-light-12 );
}

/*
 * THE SCROLL AFFORDANCE. A row of tools that stops flush at the card's edge
 * looks like the whole set — nothing about it says a sixth one is a swipe away,
 * and on a phone with no visible scrollbar (every phone) the tools past the edge
 * were simply not discovered.
 *
 * A STICKY FLEX ITEM, which is what makes this self-hiding without a line of JS
 * or a wrapper element. The pseudo-element is the last item in the flex row, so
 * it takes part in the layout rather than covering it; `position: sticky` with
 * `right: 0` pins it to the right edge of the SCROLLPORT while the buttons pass
 * underneath. The gradient runs to the rail's own background color, so:
 *
 *   nothing to scroll  — it sits in flow at the end of the row, painting a fade
 *                        from transparent to the background ON the background.
 *                        Invisible, exactly as it should be.
 *   more to the right  — it is pinned at the edge with buttons under it, and
 *                        those buttons fade out into the card edge.
 *   scrolled to the end — its flow position IS the right edge, so it is back
 *                        over empty rail and disappears again.
 *
 * No negative margin, deliberately: it adds its own width to the row instead of
 * overlapping the last button, which is why the "nothing to scroll" case can be
 * genuinely invisible rather than a permanent smudge on the last label.
 */
.stp-capture__rail::after {
	content: "";
	position: sticky;
	right: 0;
	flex: 0 0 28px;
	align-self: stretch;
	/*
	 * The transparent end is the chrome color AT ZERO ALPHA, not the
	 * `transparent` keyword and not a white — either of those drags the ramp
	 * through a color the bar does not contain (transparent is transparent
	 * BLACK, and white would gray the middle), which reads as a dirty smear
	 * rather than a fade.
	 *
	 * Written out because a custom property cannot be given an alpha in a
	 * browser this file still supports (no color-mix, no relative color
	 * syntax). --stp-chrome-fade is --stp-chrome-bg (#1b2c46) at zero alpha; a
	 * skin that moves the chrome moves the fade with it.
	 */
	background: linear-gradient( to right, var( --stp-chrome-fade ), var( --stp-chrome-bg ) );
	pointer-events: none;
}

.stp-capture__rail[hidden] {
	display: none;
}

.stp-capture__rail-button {
	display: flex;
	/*
	 * GROW to share a wide rail, SHRINK no further than the 50px floor. At
	 * the old fixed 68px the five-tool set plus the 28px fade item overflowed
	 * the ~315px the inline card actually gives the rail on a 375px phone —
	 * the fifth tool sat half-clipped under the fade on the most common
	 * viewport there is, looking cut off rather than scrollable. At the 50px
	 * floor all five fit that rail (5×50 + 4×2 gap + 24 padding + 28 fade =
	 * 310 ≤ 315) and still clear the 44px target; a sixth tool overflows and
	 * the fade affordance takes over, which is what it is for. The narrow
	 * floor needs the two-line label clamp below, widened to cover every
	 * phone this floor can occur on.
	 */
	flex: 1 1 56px;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 4px;
	/* Mobile ship gate: a 44px target, reachable one-handed at the bottom of
	 * the modal. Wider than that so the (possibly two-line) label fits. */
	min-width: 50px;
	min-height: 56px;
	padding: 6px 6px;
	font: inherit;
	font-size: 11px;
	line-height: 1.15;
	text-align: center;
	color: var( --stp-light-86 );
	background: transparent;
	border: 1px solid transparent;
	border-radius: var( --stp-radius-lg );
	cursor: pointer;
}

.stp-capture__rail-button[hidden] {
	display: none;
}

.stp-capture__rail-button:hover {
	color: var( --stp-chrome-ink );
	background: var( --stp-light-08 );
}

/*
 * The open tool. A white wash rather than the accent: the accent belongs to the
 * customer's choices on the garment, and a rail button lit in the same color
 * as a selected swatch reads as another one of them.
 */
.stp-capture__rail-button.is-on {
	color: var( --stp-chrome-ink );
	background: var( --stp-light-14 );
	border-color: var( --stp-light-42 );
}

.stp-capture__rail-button :focus-visible,
.stp-capture__rail-button:focus-visible {
	outline: 2px solid var( --stp-chrome-ink );
	outline-offset: -2px;
	box-shadow: none;
}

/* Large enough to be read as a picture rather than a mark. */
.stp-capture__rail-icon {
	width: 26px;
	height: 26px;
}

/*
 * The label is visible AND is the accessible name. Five icons a customer meets
 * once are five guesses without it, and unlike the seven-wide parity row this
 * rail has the width for words.
 */
.stp-capture__rail-label {
	display: block;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * PHONES (and any locale whose words are longer than English's).
 *
 * With the 50px shrink floor above, five rail buttons in a ~315px inline-card
 * rail leave about 51px each, and one line of nowrap text in that space
 * ellipsises "Add artwork" down to "Add…" — a visible label that has stopped
 * being a label. On phone widths the label is allowed two lines instead,
 * clamped so a three-word translation cannot grow the rail into the canvas.
 * The ellipsis still lands, on the second line, where it costs a word rather
 * than the meaning. 719px matches the studio desktop breakpoint, where the
 * rail stands up into a column and the squeeze cannot happen.
 */
@media ( max-width: 719.98px ) {

	.stp-capture__rail-label {
		display: -webkit-box;
		-webkit-box-orient: vertical;
		-webkit-line-clamp: 2;
		line-clamp: 2;
		white-space: normal;
	}
}

/* -------------------------------------------------------------------------
 * Studio: the panel (bottom sheet on a phone, left drawer from 720px)
 * ---------------------------------------------------------------------- */

.stp-capture__sheet {
	/* The SAME grid area as the stage, aligned to its end — so it overlays the
	 * lower part of the canvas without taking the rail's row away from it. */
	grid-area: stage;
	align-self: end;
	z-index: 20;
	display: flex;
	flex-direction: column;
	width: 100%;
	/*
	 * THE CUSTOMER SETS THIS. capture.js writes --stp-sheet-size from the grip
	 * (drag, tap to cycle, arrow keys) and remembers the choice for the page.
	 *
	 * A LENGTH, not a percentage, and that is load-bearing. The sheet is a grid
	 * item in the stage's area, and a percentage height against an area whose
	 * own height is indefinite (this card is a block in a page, not a
	 * viewport-capped modal) is treated as `auto` while the row is being sized
	 * — so the row grew to the sheet's CONTENT, which for a 141-swatch thread
	 * chart is thousands of pixels. Measured on staging: the stage area went to
	 * 1129px around a 354px garment. A definite length contributes itself and
	 * nothing else.
	 *
	 * `svh` is the viewport with the phone's browser toolbars showing, which is
	 * the state the customer is actually in; the `vh` line before each is the
	 * fallback for browsers that do not know `svh`.
	 *
	 * The old flat cap (half the stage) was the complaint on a real phone: with
	 * the stage at 55vh the editor got about a quarter of the screen, which is
	 * not enough to work a thread chart in.
	 *
	 * ABOVE THE GARMENT'S HEIGHT THE STAGE ROW GROWS, and as of 0.9.1 that is
	 * deliberate. This is a grid item in a `1fr` row of a container with an
	 * indefinite block size, so the row is sized from its items' max-content
	 * contributions and a definite length contributes itself: a sheet taller
	 * than the canvas makes the row — and the card — taller, and shrinks them
	 * again on close. That is the only way this layout can produce the
	 * full-screen panel Aramis asked for without going `position: fixed`, which
	 * lands behind the iOS keyboard (see the 3.7 rejection in
	 * docs/reviews/2026-08-20-ios-sheet-diagnosis.md). The canvas does NOT move
	 * while it happens — the row grows by adding empty space below a
	 * `fit-content` canvas — and capture.js scrolls the grown card back into
	 * view on open (revealSheet).
	 */
	height: var( --stp-sheet-size, 58vh );
	height: var( --stp-sheet-size, 58svh );
	overflow: hidden;
	background: var( --stp-surface );
	border-radius: var( --stp-radius-lg ) var( --stp-radius-lg ) 0 0;
	box-shadow: 0 -6px 24px var( --stp-shade-16 );
	/*
	 * Hidden by VISIBILITY as well as by the attribute: the panel is unhidden a
	 * frame before it is told to slide, so that the transition has a starting
	 * state to run from, and without this that frame would show the closed
	 * sheet sitting over the rail.
	 */
	visibility: hidden;
	transform: translateY( 100% );
	transition:
		transform 0.24s ease-out,
		height 0.18s ease-out;
}

/*
 * A height being DRIVEN BY A FINGER is not animated: the panel has to sit under
 * the grip, not chase it. capture.js adds this for the length of the drag only.
 */
.stp-capture__sheet.is-resizing {
	transition: none;
}

.stp-capture__sheet[hidden] {
	display: none;
}

.stp-capture__sheet.is-open {
	visibility: visible;
	transform: translateY( 0 );
}

.stp-capture__sheet:focus {
	outline: none;
}

/* Affordance only — it says the panel came up from the bottom. The close button
 * and Escape are the two ways out, and both are real controls. */
/*
 * The grip: a real resize control, and sized like one.
 *
 * The BAR is 40×4; the TARGET is the full width of the sheet and 28px tall,
 * because this is the first thing a thumb reaches for when the panel is too
 * short and it used to be a 4px decal that did nothing. The bar itself is the
 * pseudo-element, so the target can be finger-sized without drawing a
 * finger-sized slab across the top of the editor.
 *
 * `touch-action: none` because the gesture IS the resize — without it the
 * browser takes the vertical drag for a page scroll before the first
 * pointermove arrives.
 */
.stp-capture__sheet-grip {
	position: relative;
	flex: 0 0 auto;
	width: 100%;
	height: 28px;
	touch-action: none;
	cursor: row-resize;
}

.stp-capture__sheet-grip::before {
	content: "";
	position: absolute;
	top: 10px;
	left: 50%;
	width: 40px;
	height: 4px;
	margin-left: -20px;
	background: var( --stp-border-strong );
	border-radius: 999px;
}

/* Bigger and darker while it is being used, so the bar reads as the thing the
 * finger has hold of. */
.stp-capture__sheet-grip:active::before,
.stp-capture__sheet-grip:focus-visible::before {
	width: 56px;
	margin-left: -28px;
	background: var( --stp-ink-muted );
}

.stp-capture__sheet-grip:focus-visible {
	outline: 2px solid var( --stp-accent );
	outline-offset: -4px;
	border-radius: var( --stp-radius-sm );
}

/*
 * The desktop drawer is full height already — there is no vertical size to
 * choose, and capture.js declines to write one there (isSheetResizable).
 */
@media ( min-width: 720px ) {

	.stp-capture__sheet-grip {
		display: none;
	}
}

.stp-capture__sheet-head {
	display: flex;
	flex: 0 0 auto;
	align-items: center;
	justify-content: space-between;
	gap: var( --stp-gap-sm );
	padding: 4px var( --stp-gap ) 8px;
	border-bottom: 1px solid var( --stp-border );
}

.stp-capture__sheet-title {
	margin: 0;
	overflow: hidden;
	font-size: 0.9375em;
	font-weight: 700;
	line-height: 1.2;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__sheet-close {
	flex: 0 0 auto;
	width: 36px;
	height: 36px;
	padding: 0;
	font-size: 22px;
	line-height: 1;
	color: var( --stp-ink-muted );
	background: transparent;
	border: 1px solid transparent;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__sheet-close:hover {
	color: var( --stp-ink );
	border-color: var( --stp-border );
}

/* The panel scrolls INSIDE itself; the stage behind it never moves. */
.stp-capture__sheet-body {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	padding: var( --stp-gap );
}

.stp-capture__sheet-body > .stp-capture__panel {
	width: 100%;
}

/*
 * A zone's editor body arrives without the section that framed it, so the
 * padding and the rule that separated it from its own heading go with the
 * frame. The node itself is untouched.
 */
.stp-capture__sheet-body > .stp-capture__editor-body {
	padding: 0;
	border-top: 0;
}

/* -------------------------------------------------------------------------
 * Studio: list panels (Elements, and the "which area?" chooser)
 * ---------------------------------------------------------------------- */

.stp-capture__stack {
	display: flex;
	flex-direction: column;
	gap: var( --stp-gap-sm );
}

.stp-capture__empty {
	margin: 0;
	padding: var( --stp-gap ) 0;
	color: var( --stp-ink-muted );
	font-size: 0.875em;
	text-align: center;
}

.stp-capture__row {
	display: flex;
	align-items: center;
	gap: 6px;
	padding: 4px 6px 4px 0;
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-lg );
}

/* The row for the decoration the stage has selected — the same state the ring
 * on the garment is showing, said twice so the two surfaces agree. */
.stp-capture__row.is-on {
	border-color: var( --stp-accent );
}

.stp-capture__row-pick {
	display: flex;
	flex: 1 1 auto;
	align-items: center;
	gap: var( --stp-gap-sm );
	min-width: 0;
	min-height: 44px;
	padding: 6px 10px;
	font: inherit;
	text-align: left;
	color: var( --stp-ink );
	background: transparent;
	border: 0;
	border-radius: var( --stp-radius-lg );
	cursor: pointer;
}

.stp-capture__row-pick--zone {
	width: 100%;
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
}

/* At its cap and everything in it decorated. Still openable — editing what is
 * there is a reasonable thing to want — but not offered as if it were empty. */
.stp-capture__row-pick--zone.is-full {
	opacity: 0.72;
}

.stp-capture__row-icon {
	flex: 0 0 auto;
	width: 18px;
	height: 18px;
	color: var( --stp-ink-muted );
}

/* The area picker's thumbnail: the garment, with THIS area lit on it.
 *
 * TWO boxes, and the split is load-bearing.
 *
 * The TILE is fixed, so four rows put their four labels at the same x. Sizing
 * each tile to its own view's aspect (a cap front is 546x540, its back 926x716)
 * left the column ragged, which is the opposite of the point.
 *
 * The FIT is the image's own box, sized by JS (`sizeRowThumb`) to a contain-fit
 * inside the tile. The highlight lives in there and NOT in the tile, because
 * the zone rect is a percentage OF THE IMAGE: measured against a padded tile it
 * would land off the area, subtly and differently for every view.
 *
 * `object-fit: contain` cannot do this job — it fits the picture but leaves the
 * letterbox inside the element, so percentages still resolve against the padded
 * box. Nor can the CSS-only `height:100%; width:auto`: that is circular, and
 * browsers resolve it by contributing the image's NATURAL width to the
 * shrink-to-fit parent. Measured here before it was fixed: 546, 926 and 1000px
 * wide tiles inside a 44px row, the overflow merely hidden.
 *
 * 44px is the row's own min-height, so the picture rides inside the row the
 * customer already had rather than making the sheet taller. This list is the
 * first thing on a phone and its height is the whole reason it works there.
 *
 * ⚠ 64 and 44 are `ROW_THUMB_W` and `ROW_THUMB_H` in capture.js. They must
 * move together.
 */
.stp-capture__row-thumb {
	display: flex;
	flex: 0 0 auto;
	align-items: center;
	justify-content: center;
	width: 64px;
	height: 44px;
	overflow: hidden;
	background: var( --stp-surface-alt );
	border-radius: var( --stp-radius-sm );
}

/* Before the image resolves this has no size, and the tile's own background is
 * what shows: a tidy blank chip rather than a collapsed sliver. */
.stp-capture__row-thumb-fit {
	position: relative;
	display: block;
}

.stp-capture__row-thumb-img {
	display: block;
	width: 100%;
	height: 100%;
}

/* Lit, and ringed in the same white the stage rings its zones with.
 *
 * The stage draws a zone as `2px dashed var( --stp-zone-outline )`. At 44px a
 * dashed 2px border is mush, so the patch is filled with the accent and keeps
 * the WHITE EDGE — the part of that language that survives the size, and the
 * same token, so a shop that recolors its zone outline recolors this too.
 *
 * box-shadow rather than a border: the ring must not grow the patch. A 1px
 * border on each side is two pixels at a size where two pixels is a visible
 * lie about where the area sits on the garment.
 */
.stp-capture__row-thumb-zone {
	position: absolute;
	background: var( --stp-accent );
	box-shadow: 0 0 0 1px var( --stp-zone-outline );
	opacity: 0.55;
}

@media ( forced-colors: active ) {
	/* The wash is a color the mode drops. An outline is the one thing it keeps,
	 * and at that point the two-pixel growth is the lesser wrong. */
	.stp-capture__row-thumb-zone {
		background: transparent;
		border: 1px solid CanvasText;
		box-shadow: none;
		opacity: 1;
	}
}

.stp-capture__row-text {
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	min-width: 0;
}

.stp-capture__row-zone {
	overflow: hidden;
	font-size: 0.875em;
	font-weight: 600;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__row-summary {
	overflow: hidden;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__row-summary:empty {
	display: none;
}

/* Tabular figures so a column of layer numbers does not jitter at 9 to 10. */
.stp-capture__row-layer {
	flex: 0 0 auto;
	min-width: 2.5ch;
	color: var( --stp-ink-muted );
	font-size: 0.75em;
	font-variant-numeric: tabular-nums;
	text-align: right;
}

.stp-capture__row-acts {
	display: flex;
	flex: 0 0 auto;
	gap: 4px;
}

/* -------------------------------------------------------------------------
 * Tablet and up
 * ---------------------------------------------------------------------- */

@media ( min-width: 720px ) {

	.stp-capture__overlay {
		align-items: center;
		padding: 24px;
	}

	.stp-capture__dialog {
		max-height: calc( 100vh - 48px );
		border-radius: var( --stp-radius );
	}

	.stp-capture__body {
		flex-direction: row;
		align-items: flex-start;
	}

	.stp-capture__stage {
		flex: 1 1 45%;
		position: sticky;
		top: 0;
		min-width: 0;
	}

	.stp-capture__panel {
		flex: 1 1 55%;
		min-width: 0;
	}

	/* ---------------------------------------------------------------------
	 * Studio at desktop width: the rail stands up on the left and the panel
	 * becomes a drawer beside the canvas instead of a sheet under it.
	 * ------------------------------------------------------------------ */

	.stp-capture.is-studio .stp-capture__body {
		grid-template-columns: auto 1fr;
		grid-template-rows: 1fr;
		grid-template-areas: "rail stage";
	}

	/*
	 * The tool bar stands up into a left column and keeps the dark skin — the
	 * top bar still spans the card above it, so the two read as one frame around
	 * the garment rather than as a bar and a sidebar.
	 */
	.stp-capture.is-studio .stp-capture__rail {
		flex-direction: column;
		align-items: stretch;
		justify-content: flex-start;
		gap: 4px;
		/* Wider than it was: 26px icons over a full label. */
		width: 94px;
		padding: var( --stp-gap-sm );
		overflow-x: hidden;
		overflow-y: auto;
		border-top: 0;
		border-right: 1px solid var( --stp-light-12 );
	}

	/*
	 * The horizontal scroll affordance goes with the horizontal scroll. The rail
	 * is a column here and overflows downwards, where the fade would be a 28px
	 * band across the middle of the last tool — an artefact, not an affordance.
	 */
	.stp-capture.is-studio .stp-capture__rail::after {
		display: none;
	}

	.stp-capture.is-studio .stp-capture__rail-button {
		flex: 0 0 auto;
		min-width: 0;
		min-height: 66px;
		font-size: 11px;
	}

	/*
	 * STILL AN OVERLAY. "Beside the canvas" describes where it reads from, not
	 * a column the canvas gives up: the drawer shares the stage's grid area and
	 * slides in over it, so the canvas box is the same size open or closed. A
	 * drawer that took a column would resize the canvas, and every fitted text
	 * size on the stage is measured against that box.
	 */
	.stp-capture.is-studio .stp-capture__sheet {
		align-self: stretch;
		justify-self: start;
		width: var( --stp-drawer-w, 320px );
		max-width: 100%;
		height: auto;
		max-height: none;
		border-right: 1px solid var( --stp-border );
		border-radius: 0;
		box-shadow: 6px 0 24px var( --stp-shade-14 );
		transform: translateX( -100% );
	}

	.stp-capture.is-studio .stp-capture__sheet.is-open {
		transform: translateX( 0 );
	}

	/* A drawer does not come up from anywhere, so it has nothing to grip. */
	.stp-capture.is-studio .stp-capture__sheet-grip {
		display: none;
	}

	/*
	 * ...and the grip was carrying the panel's top spacing. On a phone it
	 * contributes 14px (8px margin, a 4px bar, 2px more) above a head whose own
	 * top padding is 4px. Hiding it here left the heading 13px from the drawer's
	 * top edge — and that edge sits flush against the mode-pill row, both
	 * surfaces white, the drawer's shadow horizontal only, so "Which area?" read
	 * as tucked up under the pills rather than as the title of the panel below
	 * them. Measured, not guessed: the gap between the two boxes is 0.
	 *
	 * The head takes the spacing back, at the same 16px the drawer already uses
	 * horizontally, so the heading sits inside a consistent inset instead of
	 * against the join.
	 */
	.stp-capture.is-studio .stp-capture__sheet-head {
		padding-top: var( --stp-gap );
	}

	.stp-capture.is-studio .stp-capture__stage {
		padding: var( --stp-gap );
	}
}

/* -------------------------------------------------------------------------
 * Motion + contrast preferences
 * ---------------------------------------------------------------------- */

@media ( prefers-reduced-motion: reduce ) {

	.stp-capture *,
	.stp-capture *::before,
	.stp-capture *::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}

	/*
	 * The panel does not slide. With the duration flattened above, a transform
	 * that still ran would be a jump from off-screen to in-place — the worst of
	 * both. Dropping the transform entirely leaves the visibility flip, which is
	 * the panel simply being there: no travel, no motion to sit through.
	 */
	.stp-capture__sheet {
		transform: none !important;
	}

	/*
	 * The zone pulse does not run at all. capture.js already declines to add the
	 * class under this preference — a 1.2s introduction flattened to a
	 * one-frame flash is worse for the people the setting is for than no
	 * introduction — and this is the belt to that braces: a class arriving from
	 * anywhere else still animates nothing.
	 *
	 * The zones themselves are unaffected. They are still shown on hover, on
	 * focus, while a gesture runs and while the Areas panel is open; only the
	 * unprompted movement is gone.
	 */
	.stp-capture.is-visual.is-zones-pulse .stp-capture__zone::after {
		display: none;
		animation: none !important;
	}
}

@media ( forced-colors: active ) {

	.stp-capture__zone,
	.stp-capture__swatch,
	.stp-capture__pill,
	.stp-capture__tool,
	.stp-capture__rail-button,
	.stp-capture__sheet,
	.stp-capture__row,
	.stp-capture__row-pick--zone,
	.stp-capture__chrome-button,
	.stp-capture__pager-button {
		border: 1px solid CanvasText;
	}

	/*
	 * Forced colors drop --stp-chrome-bg and --stp-chrome-ink both: the bars
	 * become Canvas with CanvasText on them, which is right — the dark chrome
	 * was contrast, and this mode supplies its own. What they lose with it is the
	 * edge that separated them from the canvas, so it is drawn explicitly.
	 */
	.stp-capture__topbar,
	.stp-capture__rail,
	.stp-capture__pager,
	.stp-capture__dialog.is-inline {
		border: 1px solid CanvasText;
	}

	.stp-capture__chrome-button.is-on {
		outline: 2px solid Highlight;
		outline-offset: -1px;
	}

	/*
	 * ZONES ARE VISIBLE HERE, and deliberately so. Hiding them at rest is a
	 * decorative choice made against a photograph; forced colors is a
	 * declaration that decoration is not what this user is here for, so every
	 * outline and every label comes back. Nothing about where they are or what
	 * they do changes.
	 *
	 * THE SELECTOR HAS TO MATCH THE HIDING RULE'S, EXACTLY. The bare
	 * `.stp-capture__zone` in the border block above is one class; the rule that
	 * paints the outlines out at rest is a PAIR of selectors —
	 * `.stp-capture.is-visual .stp-capture__zone` (two classes) and
	 * `.stp-capture.is-visual .stp-capture__zone.is-filled` (three) — and
	 * specificity is not something a media query changes. So the restore silently
	 * lost, in the one mode where it mattered, and a forced-colors user in visual
	 * capture was looking at a garment with no visible targets on it at all:
	 * `border-color: transparent` is one of the few colors forced colors leaves
	 * alone.
	 *
	 * Both selectors are repeated here rather than just the shorter one, because
	 * dropping the `.is-filled` half would have left exactly the zones that
	 * already HAVE a decoration in them invisible — the worst half to lose.
	 * Matched at the same specificity and placed later in the file, this wins on
	 * source order, which is where a deliberate override belongs.
	 *
	 * Preview is still excepted — `.is-visual.is-preview` is three classes on the
	 * ancestor and still outranks both of these — because a proof with the
	 * apparatus drawn back over it is not a proof.
	 */
	.stp-capture.is-visual .stp-capture__zone,
	.stp-capture.is-visual .stp-capture__zone.is-filled {
		border: 1px solid CanvasText;
	}

	.stp-capture.is-visual .stp-capture__zone-label,
	.stp-capture.is-visual .stp-capture__zone-count {
		opacity: 1;
	}

	/*
	 * The rail's scroll fade is a gradient, and a gradient in forced colors is
	 * either ignored or painted as a flat system color over the tools it was
	 * meant to hint at. The overflow is still there and still scrollable; this
	 * mode has its own scrollbars to say so.
	 */
	.stp-capture__rail::after {
		display: none;
	}

	/*
	 * Forced colors drop the accent, so the open tool and the selected row are
	 * told apart by the system highlight — the same substitution the pressed
	 * parity tool makes below.
	 */
	.stp-capture__rail-button.is-on,
	.stp-capture__row.is-on {
		outline: 2px solid Highlight;
		outline-offset: -1px;
	}

	/*
	 * THE BAR IS THE DECAL; THE GRIP IS THE TARGET.
	 *
	 * This rule was written when the grip WAS the 40x4 bar. It is now a
	 * full-width, 28px-tall hit target with the bar as its ::before, so painting
	 * the container CanvasText drew a solid slab across the whole top of the
	 * sheet with the bar invisible inside it (9th review round, MINOR 11). The
	 * fill belongs on the pseudo-element that is meant to be seen; the target
	 * stays transparent, exactly as it is outside forced colors.
	 */
	.stp-capture__sheet-grip {
		background: transparent;
	}

	.stp-capture__sheet-grip::before {
		background: CanvasText;
		forced-color-adjust: none;
	}

	/*
	 * Forced colors drop the accent, so a pressed tool is told apart by its
	 * border and its system highlight rather than by a fill nobody will see.
	 */
	.stp-capture__tool[aria-pressed="true"] {
		outline: 2px solid Highlight;
		outline-offset: 1px;
	}

	.stp-capture__zone-count {
		color: CanvasText;
		background: Canvas;
		border-color: CanvasText;
		forced-color-adjust: none;
	}

	.stp-capture__swatch.is-on {
		outline: 3px solid Highlight;
		outline-offset: 1px;
	}

	/*
	 * Forced colors drop the accent and the halo both, so the stage
	 * affordances are redrawn in system colors: the frame in CanvasText, the
	 * handles filled with Highlight so the two things you can grab are the two
	 * things that stand out.
	 */
	.stp-capture__item::before {
		border-color: CanvasText;
		box-shadow: none;
	}

	.stp-capture__item:focus-visible::before,
	.stp-capture__item.is-selected::before,
	.stp-capture__item.is-active::before {
		border-color: Highlight;
		box-shadow: none;
	}

	.stp-capture__handle::after {
		background: Highlight;
		border-color: CanvasText;
		forced-color-adjust: none;
	}

	.stp-capture__handle--rotate::before {
		background: CanvasText;
		box-shadow: none;
		forced-color-adjust: none;
	}
}

/* Drawn size (wave 3): the read-only line that replaces the size menu when the
   drag decides the size. Same box as a field value so the panel does not jump. */
.stp-capture .stp-capture__size-drawn {
	display: block;
	padding: 0.5em 0;
	font-size: 16px;
	line-height: 1.4;
}

.stp-capture .stp-capture__size-drawn[hidden] {
	display: none;
}

/* ==========================================================================
 * Free placement (template setting `placement: free`, 1.0.0-rc4)
 *
 * No zone boxes at rest. One transparent button covers the garment; the
 * template's zones become soft areas that appear only while a decoration is
 * being dragged (.is-free-dragging), the nearest one lit; a vertical centre
 * guide shows while the drag is snapped to it; a quiet status line says when
 * the selected decoration is outside every usual area. Tokens only.
 * ======================================================================= */

.stp-capture__free {
	position: absolute;
	inset: 0;
	z-index: 1;
	width: 100%;
	height: 100%;
	min-height: 0;
	padding: 0;
	margin: 0;
	background: transparent;
	border: 0;
	border-radius: 0;
	cursor: crosshair;
	-webkit-tap-highlight-color: transparent;
}

.stp-capture__free:focus-visible {
	outline: 2px solid var( --stp-focus );
	outline-offset: -2px;
}

.stp-capture__area {
	position: absolute;
	z-index: 2;
	pointer-events: none;
	background: var( --stp-accent-tint-06 );
	border: 1px solid transparent;
	border-radius: var( --stp-radius-sm );
	opacity: 0;
	transition: opacity 0.15s ease;
}

.stp-capture.is-free-dragging .stp-capture__area {
	opacity: 1;
}

.stp-capture.is-free-dragging .stp-capture__area.is-near {
	background: var( --stp-accent-tint-14 );
	border-color: var( --stp-accent );
}

.stp-capture__guide {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 50%;
	z-index: 3;
	width: 0;
	pointer-events: none;
	border-left: 1px solid var( --stp-accent );
	opacity: 0;
}

.stp-capture.is-free-snapped .stp-capture__guide {
	opacity: 0.9;
}

.stp-capture__free-hint {
	position: absolute;
	top: 8px;
	left: 50%;
	z-index: 4;
	max-width: calc( 100% - 24px );
	margin: 0;
	padding: 6px 10px;
	font-family: var( --stp-font-ui );
	font-size: 13px;
	line-height: 1.35;
	color: var( --stp-ink );
	text-align: center;
	pointer-events: none;
	background: var( --stp-light-95 );
	border: 1px solid var( --stp-notice-edge );
	border-radius: var( --stp-radius );
	box-shadow: var( --stp-shadow );
	transform: translateX( -50% );
}

.stp-capture__free-hint[hidden] {
	display: none;
}

.stp-capture.is-preview .stp-capture__free,
.stp-capture.is-preview .stp-capture__free-hint {
	display: none;
}

@media ( prefers-reduced-motion: reduce ) {
	.stp-capture__area {
		transition: none;
	}
}

/* ==========================================================================
 * Gallery presentation (1.0.0-rc4): the designer in the product gallery's slot.
 * The gallery element keeps its place in the layout; while it holds the
 * designer its own size limits are lifted so the card is never clipped.
 * ======================================================================= */

.stp-gallery-swapped {
	/* A sticky media column (common in product themes) taller than the window
	   would pin the designer's top out of reach; the swapped gallery scrolls
	   with the page instead. */
	position: relative !important;
	top: auto !important;
	height: auto !important;
	max-height: none !important;
	min-height: 0 !important;
	aspect-ratio: auto !important;
	overflow: visible !important;
	opacity: 1 !important;
}

.stp-gallery-swapped > :not( .stp-capture-gallery ) {
	display: none !important;
}

.stp-capture-gallery {
	position: relative;
	display: block;
	width: 100%;
	max-width: 100%;
	box-sizing: border-box;
}

.stp-capture-gallery[hidden] {
	display: none;
}

.stp-capture__gallery-back {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-height: 44px;
	margin: 0 0 8px;
	padding: 8px 14px;
	font-family: var( --stp-font-ui );
	font-size: 16px;
	line-height: 1.2;
	color: var( --stp-ink );
	cursor: pointer;
	background: var( --stp-surface );
	border: 1px solid var( --stp-border-strong );
	border-radius: var( --stp-radius );
}

.stp-capture__gallery-back::before {
	content: "\2039";
	font-size: 20px;
	line-height: 1;
}

.stp-capture__gallery-back:focus-visible {
	outline: 2px solid var( --stp-focus );
	outline-offset: 2px;
}

.stp-capture__thumbs {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-top: 8px;
}

.stp-capture__thumb {
	width: 64px;
	height: 64px;
	padding: 2px;
	cursor: pointer;
	background: var( --stp-surface );
	border: 2px solid var( --stp-border );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__thumb[aria-pressed="true"] {
	border-color: var( --stp-accent );
}

.stp-capture__thumb:focus-visible {
	outline: 2px solid var( --stp-focus );
	outline-offset: 2px;
}

.stp-capture__thumb img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain;
}

.stp-capture-gallery.has-thumbs .stp-capture__pager {
	display: none;
}

/* =========================================================================
 * DESIGNER UX 1.1 (2026-10-08, owner feedback on the Hat Store clone)
 *
 * One section for the four things the owner reported: the panel covered the
 * garment, the upload row overlapped the Size label, the summary under the
 * designer was crowded and unaligned, and the price and Add to cart sat below
 * the fold. Spacing in this section uses one scale: 4, 8, 12, 16, 24 px
 * (--stp-gap-sm is 8, --stp-gap is 16). Every control a finger uses is at
 * least 44px tall (--stp-handle-hit). Merge note: docs/DESIGNER-UX.md.
 * ====================================================================== */

/* -------------------------------------------------------------------------
 * The footer: one map for every mode.
 *
 *   price ................................ [! 2 things to check >]
 *   [- 1 +]                                          [ Add to cart ]
 *   Added at checkout. The shop confirms ...           Details v
 *   (Details open: the full price note, disclaimer, trust line, then
 *    Save design · Start over in one row)
 *
 * The four columns are: quantity, two spacer tracks, and the button / pill.
 * Every child is placed by name, so a node the JS hides costs no row and a
 * node it adds cannot fall into an unplanned cell (the old maps shifted every
 * row by one when the warning strip appeared; this one does not need to).
 * ---------------------------------------------------------------------- */

.stp-capture .stp-capture__dialog > .stp-capture__footer {
	display: grid;
	grid-template-columns: auto auto minmax( 0, 1fr ) auto;
	grid-template-areas:
		"price price price warn"
		"facts facts facts facts"
		"qty   buy   buy   buy"
		"proof proof proof proof"
		"note  note  note  note"
		"fine  fine  fine  fine"
		"pnote pnote pnote pnote"
		"disc  disc  disc  disc"
		"trust trust trust trust"
		"acts  acts  acts  acts";
	align-items: center;
	gap: var( --stp-gap-sm ) 12px;
	padding: 12px var( --stp-gap );
}

.stp-capture__footer > .stp-capture__warnstrip { grid-area: warn; }
.stp-capture__footer > .stp-capture__price { grid-area: price; }
.stp-capture__footer > .stp-capture__facts-region { grid-area: facts; }
.stp-capture__footer > .stp-capture__field--qty { grid-area: qty; }
.stp-capture__footer > .stp-capture__submit { grid-area: buy; }
.stp-capture__footer > .stp-capture__field--proof { grid-area: proof; }
.stp-capture__footer > .stp-capture__field--note { grid-area: note; }
.stp-capture__footer > .stp-capture__fine { grid-area: fine; }
.stp-capture__footer > .stp-capture__price-note { grid-area: pnote; }
.stp-capture__footer > .stp-capture__disclaimer { grid-area: disc; }
.stp-capture__footer > .stp-capture__trust { grid-area: trust; }
.stp-capture__footer > .stp-capture__actions { grid-area: acts; }

/* The price row: the estimate, its chevron, and setup on the same line. */
.stp-capture .stp-capture__footer > .stp-capture__price {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0 12px;
	min-width: 0;
	margin: 0;
	text-align: left;
}

.stp-capture .stp-capture__footer > .stp-capture__price[hidden] {
	display: none;
}

.stp-capture .stp-capture__footer .stp-capture__price-value {
	font-size: 1em;
	text-align: left;
}

.stp-capture .stp-capture__footer > .stp-capture__price > * {
	flex: 0 1 auto;
	min-width: 0;
}

.stp-capture .stp-capture__footer .stp-capture__price-value--button {
	min-height: 44px;
}

.stp-capture .stp-capture__footer .stp-capture__price-setup {
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
}

/* The warning strip becomes a pill at the end of the price row. */
.stp-capture .stp-capture__footer > .stp-capture__warnstrip {
	justify-self: end;
	width: auto;
	max-width: 100%;
	min-height: 44px;
	padding: 4px 12px;
	font-size: 0.8125em;
	border: 1px solid var( --stp-notice-edge );
	border-radius: 999px;
	white-space: nowrap;
}

.stp-capture .stp-capture__footer > .stp-capture__warnstrip[hidden] {
	display: none;
}

.stp-capture__warnstrip-count {
	display: none;
	font-weight: 700;
}

/*
 * The pill shows its count; the sentence stays for screen readers and as the
 * tooltip. A full sentence beside the price pushed the estimate and its setup
 * fee onto two lines in a 560px gallery column.
 */
.stp-capture .stp-capture__footer > .stp-capture__warnstrip .stp-capture__warnstrip-label {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
}

.stp-capture .stp-capture__footer > .stp-capture__warnstrip .stp-capture__warnstrip-count {
	display: inline;
}

.stp-capture.is-narrow .stp-capture__footer > .stp-capture__warnstrip {
	gap: 6px;
	padding: 4px 10px;
}

.stp-capture.is-narrow .stp-capture__footer > .stp-capture__warnstrip .stp-capture__warnstrip-chevron {
	display: none;
}

/* Quantity: a stepper on the left, its label kept for screen readers. */
.stp-capture .stp-capture__footer > .stp-capture__field--qty {
	display: block;
	margin: 0;
}

.stp-capture .stp-capture__footer > .stp-capture__field--qty[hidden] {
	display: none;
}

.stp-capture .stp-capture__footer > .stp-capture__field--qty > .stp-capture__label {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
}

.stp-capture__stepper {
	display: inline-flex;
	align-items: stretch;
	height: 48px;
	background: var( --stp-surface );
	border: 1px solid var( --stp-border-strong );
	border-radius: var( --stp-radius );
	overflow: hidden;
}

.stp-capture__stepper-button {
	flex: 0 0 auto;
	width: 44px;
	padding: 0;
	font: inherit;
	font-size: 1.25em;
	line-height: 1;
	color: var( --stp-ink );
	background: transparent;
	border: 0;
	cursor: pointer;
}

.stp-capture__stepper-button:hover {
	background: var( --stp-surface-alt );
}

.stp-capture__stepper-button:focus-visible {
	outline: none;
	box-shadow: inset var( --stp-focus );
}

.stp-capture .stp-capture__stepper .stp-capture__qty {
	width: 3.25em;
	min-height: 0;
	height: auto;
	margin: 0;
	padding: 0 4px;
	text-align: center;
	border: 0;
	border-left: 1px solid var( --stp-border );
	border-right: 1px solid var( --stp-border );
	border-radius: 0;
	-moz-appearance: textfield;
}

.stp-capture .stp-capture__stepper .stp-capture__qty::-webkit-outer-spin-button,
.stp-capture .stp-capture__stepper .stp-capture__qty::-webkit-inner-spin-button {
	margin: 0;
	-webkit-appearance: none;
}

/* Add to cart: right-aligned on the quantity row; fills the row on a phone. */
.stp-capture .stp-capture__footer > .stp-capture__submit {
	justify-self: end;
	width: auto;
	min-width: 10em;
	min-height: 48px;
	margin: 0;
	padding: 12px 24px;
}

.stp-capture.is-narrow .stp-capture__footer > .stp-capture__submit {
	justify-self: stretch;
	min-width: 0;
}

/* The fine print: one line and a Details toggle, left-aligned with the price. */
.stp-capture__fine {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	min-width: 0;
}

.stp-capture__fine-line {
	flex: 1 1 auto;
	min-width: 0;
	margin: 0;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
	line-height: 1.4;
	text-align: left;
}

.stp-capture__fine-toggle {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	min-height: 44px;
	padding: 0 4px;
	font: inherit;
	font-size: 0.8125em;
	font-weight: 600;
	color: var( --stp-accent-ink );
	background: none;
	border: 0;
	cursor: pointer;
}

.stp-capture__fine-toggle:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__fine-chevron {
	width: 7px;
	height: 7px;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: translateY( -2px ) rotate( 45deg );
}

.stp-capture__fine-toggle[aria-expanded="true"] .stp-capture__fine-chevron {
	transform: translateY( 2px ) rotate( -135deg );
}

.stp-capture .stp-capture__footer > .stp-capture__price-note,
.stp-capture .stp-capture__footer > .stp-capture__disclaimer,
.stp-capture .stp-capture__footer > .stp-capture__trust,
.stp-capture .stp-capture__footer > .stp-capture__actions {
	display: none;
	margin: 0;
	text-align: left;
}

.stp-capture .stp-capture__footer.is-details-open > .stp-capture__price-note,
.stp-capture .stp-capture__footer.is-details-open > .stp-capture__disclaimer:not( [hidden] ),
.stp-capture .stp-capture__footer.is-details-open > .stp-capture__trust:not( [hidden] ) {
	display: block;
	color: var( --stp-ink-muted );
	font-size: 0.8125em;
	line-height: 1.45;
}

.stp-capture .stp-capture__footer.is-details-open > .stp-capture__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0 24px;
}

.stp-capture .stp-capture__actions > .stp-capture__link {
	min-height: 44px;
	margin: 0;
	padding: 0;
	text-align: left;
}

.stp-capture .stp-capture__actions > .stp-capture__link[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * The upload row: thumbnail, file name (one line, ellipsis), Remove.
 * ---------------------------------------------------------------------- */

.stp-capture__upload-result {
	display: flex;
	align-items: center;
	gap: 12px;
	margin-top: 12px;
	padding: 8px 8px 8px 12px;
	background: var( --stp-surface-alt );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius );
}

.stp-capture__upload-result:empty {
	display: none;
}

.stp-capture__upload-result .stp-capture__thumb-image {
	flex: 0 0 auto;
	width: 44px;
	height: 44px;
}

.stp-capture__upload-name {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	font-size: 0.875em;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__upload-result .stp-capture__link {
	flex: 0 0 auto;
	min-height: 44px;
	padding: 0 8px;
}

/* Done: closes the panel and selects the decoration (studio only). */
.stp-capture__done {
	display: none;
}

.stp-capture.is-studio .stp-capture__sheet .stp-capture__done {
	display: block;
	width: 100%;
	min-height: 48px;
	margin: var( --stp-gap ) 0 0;
	padding: 12px 16px;
	font: inherit;
	font-weight: 600;
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
	border: 0;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture.is-studio .stp-capture__sheet .stp-capture__done:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

/* -------------------------------------------------------------------------
 * The item bar: floats next to the selected decoration on the stage.
 * ---------------------------------------------------------------------- */

.stp-capture__itembar {
	position: absolute;
	z-index: 40;
	display: flex;
	align-items: center;
	gap: 4px;
	padding: 4px;
	white-space: nowrap;
	pointer-events: auto;
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-lg );
	box-shadow: 0 6px 20px var( --stp-shade-16 );
}

.stp-capture__itembar[hidden],
.stp-capture__itembar-size[hidden],
.stp-capture__itembar-button[hidden] {
	display: none;
}

.stp-capture.is-preview .stp-capture__itembar {
	display: none;
}

.stp-capture__itembar-size {
	display: block;
	margin: 0;
}

.stp-capture .stp-capture__itembar-select {
	height: 44px;
	min-height: 44px;
	max-width: 10em;
	margin: 0;
	padding: 0 28px 0 12px;
	font: inherit;
	font-size: 0.875em;
	color: var( --stp-ink );
	background-color: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__itembar-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	min-width: 44px;
	height: 44px;
	padding: 0 12px;
	font: inherit;
	font-size: 0.875em;
	font-weight: 600;
	color: var( --stp-ink );
	background: transparent;
	border: 0;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__itembar-button:hover {
	background: var( --stp-surface-alt );
}

.stp-capture__itembar-button:focus-visible,
.stp-capture .stp-capture__itembar-select:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

.stp-capture__itembar-button[aria-pressed="true"] {
	background: var( --stp-accent-tint-14 );
}

.stp-capture__itembar-button--delete {
	color: var( --stp-danger );
}

.stp-capture__itembar-button--flip,
.stp-capture__itembar-button--delete {
	padding: 0;
}

/* The three extra resize corners show on the selected decoration only. */
.stp-capture__handle--nw,
.stp-capture__handle--ne,
.stp-capture__handle--sw {
	display: none;
}

.stp-capture__handle--nw {
	top: calc( var( --stp-handle-hit ) / -2 );
	right: auto;
	bottom: auto;
	left: calc( var( --stp-handle-hit ) / -2 );
	cursor: nwse-resize;
}

.stp-capture__handle--ne {
	top: calc( var( --stp-handle-hit ) / -2 );
	right: calc( var( --stp-handle-hit ) / -2 );
	bottom: auto;
	left: auto;
	cursor: nesw-resize;
}

.stp-capture__handle--sw {
	top: auto;
	right: auto;
	bottom: calc( var( --stp-handle-hit ) / -2 );
	left: calc( var( --stp-handle-hit ) / -2 );
	cursor: nesw-resize;
}

.stp-capture.is-studio:not( .is-preview ) .stp-capture__item.is-selected > .stp-capture__handle--nw,
.stp-capture.is-studio:not( .is-preview ) .stp-capture__item.is-selected > .stp-capture__handle--ne,
.stp-capture.is-studio:not( .is-preview ) .stp-capture__item.is-selected > .stp-capture__handle--sw {
	display: block;
}

/* -------------------------------------------------------------------------
 * Under the garment: view thumbnails, then the garment colour (gallery mode).
 * ---------------------------------------------------------------------- */

.stp-capture__understage {
	display: flex;
	align-items: center;
	gap: var( --stp-gap-sm );
	margin-top: var( --stp-gap-sm );
}

.stp-capture__understage > .stp-capture__thumbs {
	flex: 0 1 auto;
	flex-wrap: nowrap;
	gap: 8px;
	min-width: 0;
	margin: 0;
	overflow-x: auto;
}

.stp-capture__understage .stp-capture__thumb {
	flex: 0 0 auto;
	width: 52px;
	height: 52px;
}

/*
 * The garment colour control (owner round 2, 2026-10-08: "the round circles
 * around the colours look bad"). No rings: the trigger is a small square
 * swatch and the name; the list is a tight grid of 44px square swatches with
 * a 1px hairline, the chosen one outlined 2px in the accent with a check. The
 * colour name is a caption line inside the list (hovered, focused or chosen),
 * never a floating tooltip over the garment. The list opens upward from the
 * trigger, right-aligned with the end of the thumbnail row.
 */
.stp-capture__colour {
	position: relative;
	flex: 0 0 auto;
	margin-left: auto;
}

.stp-capture__colour[hidden] {
	display: none;
}

.stp-capture__colour-toggle {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	max-width: 15em;
	min-height: 44px;
	padding: 4px 12px 4px 8px;
	font: inherit;
	font-size: 0.875em;
	font-weight: 600;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius );
	cursor: pointer;
}

.stp-capture__colour-toggle:hover {
	border-color: var( --stp-border-strong );
}

.stp-capture__colour-toggle:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

.stp-capture__colour-dot {
	flex: 0 0 auto;
	width: 24px;
	height: 24px;
	background-color: var( --stp-surface-alt );
	background-position: center;
	background-size: cover;
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__colour-dot[hidden] {
	display: none;
}

.stp-capture__colour-name {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture.is-narrow .stp-capture__colour-name {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
}

.stp-capture.is-narrow .stp-capture__colour-dot[hidden] + .stp-capture__colour-name {
	position: static;
	width: auto;
	height: auto;
	margin: 0;
	clip: auto;
}

.stp-capture__colour-chevron {
	flex: 0 0 auto;
	width: 7px;
	height: 7px;
	margin: 0 2px;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: translateY( 2px ) rotate( -135deg );
}

.stp-capture__colour-toggle[aria-expanded="false"] .stp-capture__colour-chevron {
	transform: translateY( -2px ) rotate( 45deg );
}

.stp-capture__colours {
	position: absolute;
	right: 0;
	bottom: calc( 100% + 8px );
	z-index: 45;
	width: max-content;
	max-width: min( 300px, 86vw );
	padding: 8px;
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius );
	box-shadow: 0 8px 24px var( --stp-shade-16 );
}

.stp-capture__colours[hidden] {
	display: none;
}

.stp-capture__colours-caption {
	display: block;
	min-height: 1.4em;
	margin: 0 0 8px;
	overflow: hidden;
	font-size: 0.8125em;
	font-weight: 600;
	line-height: 1.4;
	color: var( --stp-ink );
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__colours-grid {
	display: grid;
	grid-template-columns: repeat( 6, 44px );
	gap: 4px;
	max-height: 196px;
	overflow-y: auto;
}

.stp-capture__colours-grid.has-text {
	grid-template-columns: repeat( 2, minmax( 0, 1fr ) );
	width: 260px;
	max-width: 100%;
}

.stp-capture__colour-option {
	position: relative;
	width: 44px;
	height: 44px;
	padding: 0;
	background: none;
	border: 0;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__colour-swatch {
	display: block;
	width: 100%;
	height: 100%;
	background-color: var( --stp-surface-alt );
	background-position: center;
	background-size: cover;
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__colour-option:hover .stp-capture__colour-swatch {
	border-color: var( --stp-border-strong );
}

.stp-capture__colour-option[aria-pressed="true"] .stp-capture__colour-swatch {
	outline: 2px solid var( --stp-accent );
	outline-offset: 1px;
}

/* The check: a small accent badge in the corner of the chosen swatch. */
.stp-capture__colour-option[aria-pressed="true"]::after {
	content: "";
	position: absolute;
	right: -3px;
	bottom: -3px;
	width: 16px;
	height: 16px;
	background: var( --stp-accent ) no-repeat center / 10px 10px url( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M1.5 5.2l2.2 2.2 4.8-4.8' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E" );
	border: 2px solid var( --stp-surface );
	border-radius: 50%;
}

.stp-capture__colour-option:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

.stp-capture__colour-option.is-text {
	width: auto;
	min-height: 44px;
	height: auto;
	padding: 0 12px;
	font: inherit;
	font-size: 0.8125em;
	text-align: left;
	color: var( --stp-ink );
	border: 1px solid var( --stp-border );
}

.stp-capture__colour-option.is-text .stp-capture__colour-swatch {
	width: auto;
	height: auto;
	background: none;
	border: 0;
}

.stp-capture__colour-option.is-text[aria-pressed="true"] {
	outline: 2px solid var( --stp-accent );
	outline-offset: 1px;
}

.stp-capture__colour-option.is-text[aria-pressed="true"] .stp-capture__colour-swatch {
	outline: 0;
}

/* The theme's own colour row, while the designer carries the control. */
.stp-attr-in-designer {
	display: none !important;
}

/* -------------------------------------------------------------------------
 * The panel never covers the garment (720px and up).
 *
 * The drawer was an overlay on the stage. It is now a COLUMN of the body
 * grid: open, the body is "panel stage" with the stage keeping 55% of the
 * card; a roomy card (900px+, e.g. the modal) keeps the tool rail too. The
 * rail steps aside in a narrow card because a 560px gallery column cannot
 * hold rail + panel + a 55% stage; the panel's own close button and the
 * Text / Artwork switch inside it cover what the rail offered meanwhile.
 *
 * The stage shrinking is safe: the image ResizeObserver re-lays every item
 * from its stored percentages (capture-integrity rule: geometry lives in the
 * data, the stage only draws it). The panel contributes no height to the row
 * (contain: size), so its content scrolls inside it instead of stretching
 * the card.
 * ---------------------------------------------------------------------- */

@media ( min-width: 720px ) {

	.stp-capture.is-studio.is-sheet-open .stp-capture__body {
		grid-template-columns: minmax( 0, 45fr ) minmax( 0, 55fr );
		grid-template-areas: "panel stage";
		min-height: 440px;
	}

	.stp-capture.is-studio.is-sheet-open:not( .is-roomy ) .stp-capture__rail {
		display: none;
	}

	.stp-capture.is-studio.is-roomy.is-sheet-open .stp-capture__body {
		grid-template-columns: auto minmax( 280px, 340px ) minmax( 0, 1fr );
		grid-template-areas: "rail panel stage";
	}

	.stp-capture.is-studio .stp-capture__sheet.is-open {
		grid-area: panel;
		align-self: stretch;
		justify-self: stretch;
		width: auto;
		max-width: none;
		height: auto;
		contain: size;
		border-right: 1px solid var( --stp-border );
		border-radius: 0;
		box-shadow: none;
		transform: none;
		transition: none;
	}

	.stp-capture.is-studio .stp-capture__sheet.is-open .stp-capture__sheet-body {
		flex: 1 1 auto;
		min-height: 0;
		overflow-y: auto;
	}
}

/* -------------------------------------------------------------------------
 * A smaller view-tab and thumbnail footprint, so the garment gets the height.
 * ---------------------------------------------------------------------- */

.stp-capture.is-studio .stp-capture__stage {
	padding-bottom: 12px;
}

@media ( prefers-reduced-motion: reduce ) {

	.stp-capture__fine-chevron,
	.stp-capture__colour-chevron {
		transition: none;
	}
}

/* -------------------------------------------------------------------------
 * Tap targets: 44px for every control a finger uses in the designer.
 * ---------------------------------------------------------------------- */

.stp-capture .stp-capture__pill {
	min-height: 44px;
	padding: 8px 16px;
}

.stp-capture .stp-capture__sheet-close,
.stp-capture .stp-capture__tool {
	width: 44px;
	height: 44px;
}

.stp-capture .stp-capture__input,
.stp-capture .stp-capture__select,
.stp-capture .stp-capture__drop-button {
	min-height: 44px;
}

.stp-capture .stp-capture__sheet-grip {
	height: 44px;
}

/*
 * The mouse-sized floors (34px swatches, 36px buttons) were kept for desktop
 * pointers by an earlier round. The owner's brief for this pass is 44px for
 * every control, on every pointer: a 768px tablet reports a fine pointer in
 * some browsers, and the swatch chart is now inside a panel that has room.
 */
.stp-capture .stp-capture__swatches {
	--stp-swatch-size: 44px;
}

.stp-capture .stp-capture__invite-button,
.stp-capture .stp-capture__view,
.stp-capture .stp-capture__link,
.stp-capture .stp-capture__editor-toggle {
	min-height: 44px;
}

.stp-capture .stp-capture__sheet-grip::before {
	top: 20px;
}

.stp-capture .stp-capture__editor-actions .stp-capture__link,
.stp-capture .stp-capture__add {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

/* -------------------------------------------------------------------------
 * Back to photos, in the top bar (gallery mode).
 * ---------------------------------------------------------------------- */

.stp-capture .stp-capture__topbar .stp-capture__gallery-back {
	margin: 0;
	padding: 4px 12px 4px 6px;
	font-family: inherit;
	font-size: inherit;
	color: var( --stp-chrome-ink );
	background: transparent;
	border: 0;
	border-radius: var( --stp-radius-sm );
}

.stp-capture .stp-capture__topbar .stp-capture__gallery-back::before {
	content: none;
}

.stp-capture.is-narrow .stp-capture__chrome-button--preview .stp-capture__chrome-label,
.stp-capture.is-narrow .stp-capture__chrome-button--info .stp-capture__chrome-label {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
}

/* A narrow stage keeps the thumbnails and shows the colour as a dot. */
.stp-capture__understage {
	container-type: inline-size;
}

@container ( max-width: 400px ) {

	.stp-capture__understage .stp-capture__thumb {
		width: 46px;
		height: 46px;
	}

	.stp-capture__understage > .stp-capture__thumbs {
		gap: 6px;
	}

	.stp-capture__colour-dot:not( [hidden] ) + .stp-capture__colour-name {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		overflow: hidden;
		clip: rect( 0, 0, 0, 0 );
	}
}

/* -------------------------------------------------------------------------
 * The launcher: one primary button (Customize) and an optional Quick setup
 * button beside it, both styled by the theme's own `.button` / `.button.alt`.
 * A theme button named in the hand-over setting replaces them.
 * ---------------------------------------------------------------------- */

.stp-capture-launcher__buttons {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px;
}

.stp-capture-launcher__button[hidden],
.stp-capture-launcher__button--handed-over {
	display: none !important;
}

/* -------------------------------------------------------------------------
 * The invitation, under the garment (owner round 2): nothing but the
 * customer's own decorations sits on the photo.
 * ---------------------------------------------------------------------- */

.stp-capture.is-studio.is-empty-design .stp-capture__stage > .stp-capture__invite {
	position: static;
	z-index: auto;
	flex-direction: row;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: 8px 12px;
	max-width: none;
	margin: 12px 0 0;
	padding: 0;
	background: none;
	border: 0;
	box-shadow: none;
	transform: none;
	pointer-events: auto;
}

.stp-capture.is-studio .stp-capture__stage > .stp-capture__invite .stp-capture__invite-lead {
	font-size: 0.875em;
}

.stp-capture.is-studio .stp-capture__stage > .stp-capture__invite .stp-capture__invite-actions {
	flex-wrap: nowrap;
	gap: 8px;
}

.stp-capture.is-studio .stp-capture__stage > .stp-capture__invite .stp-capture__invite-button {
	min-height: 44px;
	padding: 8px 16px;
}

/* -------------------------------------------------------------------------
 * Quick setup / Place it yourself: a segmented switch in the top bar.
 * ---------------------------------------------------------------------- */

.stp-capture .stp-capture__topbar > .stp-capture__capture-modes {
	flex: 0 0 auto;
	flex-wrap: nowrap;
	justify-content: center;
	gap: 0;
	padding: 0;
	background: var( --stp-light-12 );
	border-radius: 999px;
}

.stp-capture .stp-capture__topbar > .stp-capture__capture-modes[hidden] {
	display: none;
}

.stp-capture .stp-capture__topbar .stp-capture__mode {
	min-height: 44px;
	padding: 0 12px;
	font-size: 0.8125em;
	font-weight: 600;
	white-space: nowrap;
	color: var( --stp-chrome-ink );
	background: transparent;
	border: 0;
	border-radius: 999px;
}

.stp-capture .stp-capture__topbar .stp-capture__mode[aria-pressed="true"] {
	color: var( --stp-accent-contrast );
	background: var( --stp-accent );
}

.stp-capture .stp-capture__topbar .stp-capture__mode:hover:not( [aria-pressed="true"] ) {
	background: var( --stp-light-14 );
}

/* Compact card: icon-only Preview, Info and Back, so the switch fits. */
.stp-capture.is-compact .stp-capture__topbar .stp-capture__chrome-label {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
}

.stp-capture.is-compact .stp-capture__topbar .stp-capture__gallery-back {
	padding: 4px 8px;
}

/* Narrow card: only the OTHER mode shows, as a switch button. */
.stp-capture.is-narrow .stp-capture__topbar .stp-capture__mode[aria-pressed="true"] {
	display: none;
}

.stp-capture.is-narrow .stp-capture__topbar .stp-capture__mode::before {
	content: "\21C4";
	margin-right: 6px;
	font-weight: 400;
}

/* The top bar's hidden tools (Quick setup hides Preview, undo/redo, Info). */
.stp-capture .stp-capture__topbar > [hidden],
.stp-capture .stp-capture__topbar .stp-capture__chrome-button[hidden] {
	display: none;
}

/* -------------------------------------------------------------------------
 * Quick setup, guided (owner round 2): one question per step, a step bar,
 * Back top-left, the step's next action at the bottom of the panel, Add to
 * cart in the footer as always. Same spacing scale as the studio panel.
 * ---------------------------------------------------------------------- */

.stp-capture__qs,
.stp-capture__qs-nav {
	display: none;
}

.stp-capture.is-guided .stp-capture__qs {
	display: flex;
	flex-direction: column;
	gap: 12px;
	margin: 0 0 var( --stp-gap );
}

.stp-capture.is-guided .stp-capture__qs[hidden],
.stp-capture.is-guided .stp-capture__qs-nav[hidden] {
	display: none;
}

.stp-capture__qs-head {
	display: flex;
	align-items: center;
	gap: 8px;
	min-height: 44px;
}

.stp-capture__qs-back {
	display: inline-flex;
	flex: 0 0 auto;
	align-items: center;
	gap: 4px;
	min-height: 44px;
	padding: 0 8px 0 4px;
	font: inherit;
	font-size: 0.875em;
	font-weight: 600;
	color: var( --stp-ink );
	background: none;
	border: 0;
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__qs-back[hidden] {
	display: none;
}

.stp-capture__qs-back:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

.stp-capture__steps {
	display: flex;
	flex: 1 1 auto;
	gap: 4px;
	min-width: 0;
	margin: 0;
	padding: 0;
	list-style: none;
}

.stp-capture__step {
	display: flex;
	flex: 1 1 0;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
	margin: 0;
	font-size: 0.75em;
	color: var( --stp-ink-muted );
}

.stp-capture__step::before {
	content: "";
	display: block;
	height: 4px;
	background: var( --stp-border );
	border-radius: 2px;
}

.stp-capture__step.is-done::before,
.stp-capture__step.is-current::before {
	background: var( --stp-accent );
}

.stp-capture__step.is-current {
	font-weight: 700;
	color: var( --stp-ink );
}

.stp-capture__step-n {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
}

.stp-capture__step-label {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__qs-question {
	margin: 0;
	font-size: 1.0625em;
	font-weight: 700;
	line-height: 1.3;
	color: var( --stp-ink );
}

.stp-capture__qs-question:focus {
	outline: none;
}

.stp-capture__qs-choices {
	display: grid;
	grid-template-columns: repeat( auto-fit, minmax( 140px, 1fr ) );
	gap: 12px;
}

.stp-capture__qs-choice {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 8px;
	min-height: 96px;
	padding: 16px;
	font: inherit;
	font-weight: 600;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border );
	border-radius: var( --stp-radius );
	cursor: pointer;
}

.stp-capture__qs-choice:hover,
.stp-capture__qs-choice[aria-pressed="true"] {
	border-color: var( --stp-accent );
	background: var( --stp-accent-tint-06 );
}

.stp-capture__qs-choice:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

.stp-capture__qs-choice .stp-capture__tool-icon {
	width: 28px;
	height: 28px;
}

.stp-capture__qs-zones {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.stp-capture .stp-capture__qs-zone {
	display: flex;
	align-items: center;
	gap: 12px;
	width: 100%;
	min-height: 56px;
	padding: 6px 12px 6px 6px;
	text-align: left;
}

.stp-capture__qs-zone-thumb {
	flex: 0 0 auto;
	width: 44px;
	height: 44px;
	object-fit: contain;
	background: var( --stp-surface-alt );
	border-radius: var( --stp-radius-sm );
}

.stp-capture__qs-zone-text {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.stp-capture__qs-zone-name {
	font-weight: 600;
}

.stp-capture__qs-zone-status {
	overflow: hidden;
	font-size: 0.8125em;
	color: var( --stp-ink-muted );
	text-overflow: ellipsis;
	white-space: nowrap;
}

.stp-capture__qs-edits {
	display: flex;
	flex-wrap: wrap;
	gap: 0 16px;
	margin-top: 12px;
}

.stp-capture.is-guided .stp-capture__qs-nav {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px 16px;
	margin-top: var( --stp-gap );
	padding-top: var( --stp-gap );
	border-top: 1px solid var( --stp-border );
}

.stp-capture__qs-next {
	min-height: 48px;
	padding: 12px 20px;
	font: inherit;
	font-weight: 600;
	color: var( --stp-ink );
	background: var( --stp-surface );
	border: 1px solid var( --stp-border-strong );
	border-radius: var( --stp-radius-sm );
	cursor: pointer;
}

.stp-capture__qs-next:focus-visible {
	outline: none;
	box-shadow: var( --stp-focus );
}

/* Steps show one thing: no editors before Details, one editor at Details. */
.stp-capture.is-guided .stp-capture__panel:not( [data-quick-step="details"] ) > .stp-capture__editors {
	display: none;
}

.stp-capture.is-guided .stp-capture__editor:not( .is-open ),
.stp-capture.is-guided .stp-capture__editor-heading {
	display: none;
}

.stp-capture.is-guided .stp-capture__editor {
	border: 0;
}

.stp-capture.is-guided .stp-capture__editor-body {
	padding: 0;
}

.stp-capture.is-guided .stp-capture__stage .stp-capture__hint {
	display: none;
}

/* Proof and note live in the Review step in the guide. */
.stp-capture.is-guided .stp-capture__footer > .stp-capture__field--proof,
.stp-capture.is-guided .stp-capture__footer > .stp-capture__field--note {
	display: none;
}

/* A compact card stacks: the garment as a preview on top, the step below. */
.stp-capture.is-guided.is-compact .stp-capture__body {
	flex-direction: column;
	align-items: stretch;
}

.stp-capture.is-guided.is-compact .stp-capture__stage {
	position: static;
	flex: 0 0 auto;
}

.stp-capture.is-guided .stp-capture__canvas {
	width: -webkit-fit-content;
	width: fit-content;
	max-width: 100%;
	margin: 0 auto;
}

.stp-capture.is-guided .stp-capture__image {
	width: auto;
	max-width: 100%;
	max-height: 32vh;
}

.stp-capture.is-guided.is-compact .stp-capture__panel {
	flex: 0 0 auto;
	padding: var( --stp-gap );
}

/* Guided Quick setup: Add to cart stays in view (the card is sized to the
 * screen like the studio and the step scrolls inside it), the gallery's
 * thumbnails replace the view tabs, and the garment preview is smaller. */
.stp-capture.is-guided .stp-capture__dialog.is-inline > .stp-capture__body {
	max-height: max( 320px, calc( 100vh - var( --stp-chrome-h, 260px ) ) );
	max-height: max( 320px, calc( 100svh - var( --stp-chrome-h, 260px ) ) );
	overflow-y: auto;
}

.stp-capture.is-guided.has-thumbs .stp-capture__views {
	display: none;
}

.stp-capture.is-guided .stp-capture__image {
	max-height: 26vh;
}

/* A checkbox is tapped through its whole label. */
.stp-capture .stp-capture__checkbox {
	display: flex;
	align-items: center;
	gap: 12px;
	min-height: 44px;
}

.stp-capture .stp-capture__checkbox input {
	flex: 0 0 auto;
	width: 20px;
	height: 20px;
	margin: 0;
}

/* The What step owns the text-or-logo choice in the guide. */
.stp-capture.is-guided .stp-capture__element > .stp-capture__modes {
	display: none;
}

/* Narrow card: the quantity row keeps Add to cart on one line. */
.stp-capture.is-narrow .stp-capture__dialog > .stp-capture__footer {
	column-gap: 8px;
}

.stp-capture.is-narrow .stp-capture__footer > .stp-capture__submit {
	padding: 12px 16px;
	white-space: nowrap;
}

.stp-capture.is-narrow .stp-capture__stepper .stp-capture__qty {
	width: 2.75em;
}
