/**
 * WS16 — Immersive category storefront.
 *
 * The room carries the identity through --room-accent; the chrome stays quiet
 * so the scene and the single product do the talking. Sized for the planned
 * cinematic assets (3:2 landscape, native 9:16 portrait on phones) so the
 * approved renders drop in without a layout change.
 */

/*
 * The palette is OWNER-EDITABLE: wp-admin → KoolAllure → Immersive Store emits
 * these same custom properties inline after this file, at equal specificity, so
 * whatever the owner picks wins. Declared on `.ka-imm` they would out-specify
 * the admin block and silently ignore it — :root is load-bearing here.
 *
 * Nothing below repeats a colour literal. Every tint is derived from these five
 * by color-mix, so changing one value here changes every place it is used and
 * there is no second copy to keep in step.
 *
 * 60 ivory / 20 stone / 12 caramel / 8 charcoal. The architecture is light; the
 * products supply the colour.
 */
/* --------------------------------------------------------------------------
   The --imm-* palette MOVED to assets/css/tokens.css.

   IMPORTANT: these tokens are OWNER-EDITABLE in wp-admin (KoolAllure →
   Immersive Store). The plugin emits an inline :root block AFTER this
   stylesheet, so a configured palette still wins — that is the feature, and
   unifying the defaults must not take it away. tokens.css now supplies the
   default the shop starts from, drawn from the same source as every other
   surface instead of being a fifth opinion about warm ivory.

   --imm-ink-rgb / --imm-bg-rgb stay HERE and stay literal: the inline palette
   overwrites them with hex_to_rgb_triplet() output, and --imm-glass/--imm-muted
   are composed from them. --imm-dock-gap is a layout measurement, not a colour.
   -------------------------------------------------------------------------- */
:root {
	--imm-ink-rgb: 38, 33, 29;
	--imm-bg-rgb: 247, 243, 234;
	--imm-glass: rgba(var(--imm-ink-rgb), 0.68);
	--imm-muted: rgba(var(--imm-ink-rgb), 0.66);
	--imm-dock-gap: clamp(16px, 2vw, 28px);
}

.ka-imm {
	position: relative;
	background: var(--imm-bg);
	color: var(--imm-ink);
	min-height: 100svh;
}

/* ----------------------------------------------------------------- flight */

/* A tall track gives the scroll its travel; the stick holds the camera in
   place while that travel is consumed. 260vh reads as a deliberate journey
   without becoming a chore — roughly two flicks on a trackpad. */
.ka-imm__flight-track { height: 260vh; position: relative; }

.ka-imm__flight-stick {
	position: sticky;
	top: 0;
	height: 100svh;
	overflow: hidden;
}

/*
 * The poster as a real layer underneath the film. A video element with no
 * decoded frame at its current position — settled by a search before
 * metadata arrived, scrubbed into an unbuffered range, or stalled on a
 * byte-range request — paints NOTHING, and its background used to be the
 * flat wall the customer saw as "the video disappeared". The film's own
 * first frame now holds that space instead; the moment the video has a
 * frame to paint, it covers the poster exactly (same box, same fit).
 */
.ka-imm__flight-poster {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	pointer-events: none;
}

.ka-imm__flight-video {
	position: relative;   /* stacks above the poster layer in DOM order */
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	background: transparent;   /* unpainted frames reveal the poster, not a wall */
	pointer-events: none;   /* the film is scenery; it never eats a click */
}

/* --------------------------------------------------- Ask AI (design port)
 * Values taken from the approved Claude Design artifact. Dark glass over the
 * film, not a light card: the clip is bright limestone end to end, so a
 * charcoal panel separates where an ivory one competed with the floor.
 * Square corners are deliberate — the design carries no radius anywhere.
 */
/*
 * Ask AI holds the centre of the screen and does not move.
 *
 * It used to travel a four-stage path across the frame, chasing the camera to
 * stay clear of whoever was on screen. Owner decision: a panel crossing the
 * frame is more distracting than a panel overlapping a figure, and the input
 * has to be somewhere known and reachable at every moment of the film — you
 * should never have to find it. One fixed mark, always in the same place.
 *
 * The centring translate is why nothing else may write `transform` on this
 * element: a scale from JS would drop the translate and throw the box to the
 * lower right.
 */
.ka-imm__hero-ai {
	/* Fixed, not absolute: it must still be here after the visitor has scrolled
	   out of the film. Absolute inside the sticky stick tied its life to the
	   hero, which is the opposite of being reachable at any time. */
	position: fixed;
	/* Centre while the film is playing and nothing has been asked. Percentages
	   plus a transform, never vw: `left:100%` is the viewport's right edge
	   exactly, where `100vw` would include the scrollbar and push the docked
	   panel under it. Both properties animate, so the move to the corner is one
	   continuous gesture rather than a jump. */
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	transition: left 720ms cubic-bezier(.4, 0, .2, 1),
	            top 720ms cubic-bezier(.4, 0, .2, 1),
	            transform 720ms cubic-bezier(.4, 0, .2, 1),
	            opacity 320ms ease;
	/*
	 * 400px, up from 335: the row carries the full placeholder ("Describe
	 * what you're looking for"), the microphone and the send button, and at
	 * 335px the placeholder was clipped to an ellipsis even before anyone
	 * typed. Mobile is unaffected — it already spans edge to edge.
	 */
	width: 400px;
	max-height: min(78vh, 40rem);
	overflow-y: auto;
	z-index: 30;
	max-width: calc(100% - 88px);
	display: flex;
	flex-direction: column;
	gap: 11px;
	padding: 16px 18px 18px;
	/* WS17 visual closure: warm ivory glass instead of charcoal. Every ink
	   value inside the panel derives from --imm-bg/--imm-stone, so redefining
	   the pair on the panel flips text, hairlines, chips, the field and the
	   history to warm charcoal on ivory in one move; the caramel accent and
	   the blur stay.
	   Owner review 2026-08-10: 0.94 → 0.85 opacity — just enough transparency
	   for the film to read softly through the glass (asked "even on desktop");
	   the 16px blur keeps it a glow, never a distraction under the text. */
	/* A SCOPED redefinition, not a palette. Flipping the pair on the panel
	   turns its text, hairlines, chips, field and history warm-charcoal-on-
	   ivory in one move; both values now come from the shared tokens instead
	   of two hexes that happened to be close to them. */
	--imm-bg: hsl(var(--ka-foreground));
	--imm-stone: hsl(var(--ka-muted-foreground));
	/* One of the three surfaces glass is for: this floats over the film.
	   The solid value is not a downgrade — it is what a browser without
	   backdrop-filter must get, or the text sits on the video. */
	background: var(--ka-glass-bg-solid);
	border: var(--ka-hairline-warm);
	box-shadow: var(--ka-shadow-panel);
	font-family: var(--ka-font-body);
	will-change: left, top, transform;
}
@supports ((backdrop-filter: blur(16px)) or (-webkit-backdrop-filter: blur(16px))) {
	.ka-imm__hero-ai {
		/* Owner review 2026-08-10: the film must read softly through the glass
		   ("even on desktop"), and the blur keeps it a glow rather than a
		   distraction under the text. */
		background: rgba(var(--imm-bg-rgb), 0.85);
		backdrop-filter: var(--ka-glass-blur);
		-webkit-backdrop-filter: var(--ka-glass-blur);
	}
}

/*
 * Asked something? Then get out of the way.
 *
 * The panel holds the centre over the film because that is where the one
 * question lives. The moment the visitor actually asks, the centre belongs to
 * the answer instead, so the panel retires to the bottom right and stays there
 * — still on screen, still usable, no longer standing in front of the product
 * it just found.
 *
 * This is the ONLY movement it makes. It does not drift during the film.
 */
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai {
	left: 100%;
	top: 100%;
	transform: translate(calc(-100% - var(--imm-dock-gap)), calc(-100% - var(--imm-dock-gap)));
}

/*
 * HERO_CHAT reading order: HEADER -> HISTORY -> COMPOSER.
 *
 * The markup declares the composer (line 197 of the template) before the
 * conversation panel (line 291), because in the centred hero there is no
 * history yet and the input is the whole surface. Once the panel docks that
 * source order reads backwards: the customer's turns appeared UNDER the box
 * they had just typed into, which is not how a conversation reads.
 *
 * Reordered visually rather than by moving the markup. The template order is
 * still correct for the centred state, screen-reader/tab order follows the DOM,
 * and no renderer had to learn a new insertion point — askAppend() keeps
 * appending to the same panel and keeps scrolling it to the newest turn.
 *
 * The panel already carries its own max-height and overflow-y:auto, so the
 * history stays bounded and scrolls internally while the composer below it
 * remains visible.
 */
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai {
	display: flex;
	flex-direction: column;
	/*
	 * The panel itself must NOT scroll once docked.
	 *
	 * The base rule gives it max-height + overflow-y:auto, and the history
	 * inside it carries its own max-height + overflow-y:auto. That is two
	 * nested scrollers over the same content: dragging inside the chat moved
	 * the OUTER one, which carried the composer up and out of view, and the
	 * inner history then scrolled unpredictably against it. Anchoring the
	 * composer at the bottom is meaningless while the thing it is anchored to
	 * can itself scroll away.
	 *
	 * So the panel becomes a fixed frame and the history becomes the only
	 * scroller in it.
	 */
	overflow: hidden;
}
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai > .ka-imm__hero-head { order: 0; }
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai > .ka-imm__hero-context { order: 1; }
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai > .ka-imm__ask { order: 2; }
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai > .ka-imm__hero-form { order: 3; flex: 0 0 auto; }
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai > .ka-imm__hero-mic-note { order: 4; }
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai > .ka-imm__hero-chips { order: 5; }
.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai > .ka-imm__hero-hint { order: 6; }

/*
 * Docked history: the one scroller, bounded by the panel rather than by its
 * own max-height.
 *
 * max-height is dropped deliberately. Two competing bounds — the element's own
 * cap and the flex share of a fixed-height parent — meant the history could
 * stop short while the panel still had room, leaving dead space under the last
 * turn. `flex:1 1 auto` with `min-height:0` lets it take exactly the space the
 * header and composer do not, and `min-height:0` is what actually permits a
 * flex child to shrink below its content and scroll at all.
 *
 * The separator moves to the bottom edge, since the history now sits above the
 * composer rather than below it.
 */
.ka-imm[data-ai-place="docked"] .ka-imm__ask {
	flex: 1 1 auto;
	min-height: 0;
	max-height: none;
	overflow-y: auto;
	overscroll-behavior: contain;
	border-top: 0;
	border-bottom: 1px solid rgba(var(--imm-ink-rgb), 0.16);
	margin: 0 0 0.75rem;
	padding: 0 0 0.75rem;
}

/*
 * The manual minimize (owner review 2026-08-08): a small minus in the
 * panel head, offered only while the panel is docked — in the centred
 * hero there is nothing to minimize, and in the bubble it IS the
 * minimized state. margin-left:auto keeps it beside the AI mark without
 * disturbing the eyebrow.
 */
.ka-imm__hero-min {
	display: none;
	margin-left: auto;
	align-items: center;
	justify-content: center;
	width: 26px;
	height: 26px;
	padding: 0 0 2px;
	background: none;
	border: 1px solid rgba(67, 57, 45, 0.18);
	border-radius: var(--ka-radius-pill);
	color: var(--imm-stone);
	font-size: 15px;
	line-height: 1;
	cursor: pointer;
	transition: color 160ms ease, border-color 160ms ease;
}
.ka-imm__hero-min:hover { color: var(--imm-bg); border-color: rgba(67, 57, 45, 0.4); }
.ka-imm__hero-min:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }
.ka-imm[data-ai-place="docked"] .ka-imm__hero-min { display: inline-flex; }

/*
 * Owner review 2026-08-07: while a product is on display the search result is
 * exactly TWO functional panes — Ask Us must not remain as a third panel.
 * The bubble is the whole panel collapsed to a small pill in the corner: one
 * click (or Enter/Space) brings the docked panel back. It never occupies a
 * column of the product layout and never competes with the product.
 */
/* ------------------------------------------------------------ below the film
 * Ask Us in the page, not over it.
 *
 * Once a search has produced results the conversation stops being an overlay
 * on the film and becomes the header of those results: it is moved out of the
 * hero and placed immediately before the project section, sharing its width so
 * the answer and the products read as one column. Every fixed-position offset
 * from the docked and centred states has to be unwound explicitly, because
 * those rules set left/top/transform and would otherwise still apply.
 */
/*
 * Fixed to the screen, not to the products.
 *
 * The assistant is always-available: it floats at the bottom-right corner of
 * the viewport wherever the customer has scrolled to, so talking to it is
 * never something they have to go and find. It arrives collapsed so it does
 * not stand in front of the results it just produced — see .is-collapsed.
 */
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai {
	position: fixed;
	left: auto;
	top: auto;
	right: var(--imm-dock-gap, clamp(16px, 2vw, 28px));
	bottom: var(--imm-dock-gap, clamp(16px, 2vw, 28px));
	transform: none;
	width: min(22rem, calc(100vw - 2.5rem));
	margin: 0;
	max-height: 60vh;
	overflow-y: auto;
	cursor: auto;
	z-index: 30;
}

/* Minimising shuts the corner panel in place; it stays available. */
.ka-imm[data-ai-place="below"] .ka-imm__hero-min { display: inline-flex; }

/* ------------------------------------------------------------ collapsed
 * Arriving collapsed is the point: the customer has just been handed products
 * to look at, so the assistant shrinks to a corner marker that says it is
 * still there. Everything but the title is hidden, and one click restores it.
 */
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed {
	width: auto;
	max-height: none;
	overflow: hidden;
	padding: 10px 16px;
	border-radius: var(--ka-radius-pill);
	cursor: pointer;
}

/* Collapsed it is the only way back to the assistant, and it takes focus (see
 * askCollapse), so focus has to be visible on it like on any other control. */
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed:focus-visible {
	outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset);
}

.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-context,
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-form,
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-mic-note,
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-chips,
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-hint,
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__history,
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__ask,
.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-min {
	display: none;
}

.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-head {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
}

.ka-imm[data-ai-place="below"] .ka-imm__hero-ai.is-collapsed .ka-imm__hero-eyebrow {
	margin: 0;
	white-space: nowrap;
}

/* "Or scroll to step inside" invites the visitor into the film. Below it, the
   film is already behind them and the products are next. */
.ka-imm[data-ai-place="below"] .ka-imm__hero-hint { display: none; }

/* The transcript is the point of the panel here, so it is given room to be
   read rather than the short scroller a corner dock needs. */
.ka-imm[data-ai-place="below"] .ka-imm__ask {
	max-height: none;
}

.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai {
	left: 100%;
	top: 100%;
	transform: translate(calc(-100% - var(--imm-dock-gap)), calc(-100% - var(--imm-dock-gap)));
	/* width:max-content, not auto: shrink-to-fit on a fixed element anchored
	   at left:100% is ill-defined across engines and collapsed the bubble to
	   padding width on mobile. */
	width: max-content;
	/*
	 * A ceiling on max-content, so the bubble can never inflate again.
	 *
	 * width:max-content sizes to whatever is still visible inside. The hide
	 * list below is what keeps that small — and it is a list, so anything added
	 * to the panel later and not added to the list silently balloons this
	 * element into a page-wide 999px-radius lozenge. That is exactly what
	 * happened with .ka-imm__ask. The list is still the fix; this is the
	 * backstop that turns the next omission into a clipped bubble instead of a
	 * surface covering the storefront.
	 */
	max-width: 260px;
	max-height: none;
	overflow: hidden;
	padding: 10px 16px;
	border-radius: var(--ka-radius-pill);
	cursor: pointer;
	gap: 8px;
}
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__hero-context,
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__hero-form,
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__hero-mic-note,
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__hero-chips,
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__hero-hint,
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__history,
/*
 * The live conversation. Missing from this list, which is the whole bug: a
 * bubble is an affordance meaning "the assistant is here", not a place to read
 * a transcript. .ka-imm__history was hidden and .ka-imm__ask — the element the
 * turns are actually written into — was not, so after a single answer the
 * collapse-on-scroll produced a pill the width of the page with the chat
 * inside it, sitting on top of the film and the products.
 */
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__ask {
	display: none;
}
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__hero-head {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
}
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai .ka-imm__hero-eyebrow {
	margin: 0;
	font-size: 0.82rem;
	white-space: nowrap;
}
.ka-imm[data-ai-place="bubble"] .ka-imm__hero-ai:focus-visible {
	outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset);
}

/*
 * The AI Powered mark. One use, small, top right of the panel — the only place
 * it appears in the whole storefront.
 *
 * A mask, not an <img>. The three supplied files are flat RGB with the
 * transparency checkerboard baked in as pixels, so none of them could sit on
 * the glass as-is; the mark was keyed out of the gold variant into a single
 * alpha asset. Masking it means its colour comes from the palette, so it
 * follows the owner's accent instead of being a frozen gold that would clash
 * the moment the accent moves.
 */
.ka-imm__hero-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
}
.ka-imm__hero-mark {
	flex: 0 0 auto;
	/* 48px — 20px, then +50%, +25%, and +25% again across three reviews. */
	width: 48px;
	height: 48px;
	/* Owner review 2026-08-10: the mark goes BLACK — the supplied black logo
	   file has the transparency checkerboard baked in as pixels, so it cannot
	   sit on the glass as-is; the keyed alpha mask IS that same mark, and
	   colouring the mask black reproduces the black logo exactly. */
	background-color: #000;
	-webkit-mask: url('../img/ai-powered-mark.png') center / contain no-repeat;
	mask: url('../img/ai-powered-mark.png') center / contain no-repeat;
	/* Held just under full strength: it is a provenance mark, not a second
	   logo competing with the brand in the top bar. */
	opacity: 0.82;
	transform-origin: center;
}

/*
 * The mark beats while a search is running.
 *
 * The search reaches the supplier leg when the local catalog has nothing
 * excellent, so it can take a couple of seconds — long enough that a still
 * panel reads as a page that ignored you. The thing that is working is the
 * thing that says so.
 *
 * A real heartbeat, not a throb: two beats and a rest, in the 0.85s a resting
 * pulse actually takes. The second beat is smaller than the first, which is
 * what makes it read as a heart rather than a bouncing icon.
 */
@keyframes ka-imm-heartbeat {
	0%   { transform: scale(1);    opacity: 0.82; }
	14%  { transform: scale(1.16); opacity: 1; }
	28%  { transform: scale(1);    opacity: 0.9; }
	42%  { transform: scale(1.09); opacity: 1; }
	56%  { transform: scale(1);    opacity: 0.82; }
	100% { transform: scale(1);    opacity: 0.82; }
}
.ka-imm[data-searching="true"] .ka-imm__hero-mark {
	animation: ka-imm-heartbeat 850ms ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
	/* No scaling. It still has to be legible that something is happening, so
	   the mark breathes in brightness alone. */
	.ka-imm[data-searching="true"] .ka-imm__hero-mark {
		animation: ka-imm-mark-breathe 1.4s ease-in-out infinite;
	}
	@keyframes ka-imm-mark-breathe {
		0%, 100% { opacity: 0.5; }
		50%      { opacity: 1; }
	}
}

.ka-imm__hero-eyebrow {
	margin: 0;
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.28em;
	text-transform: uppercase;
	color: var(--imm-stone);
}

.ka-imm__hero-form {
	/* THE SAME FIELD AS THE RESEARCH SEARCH, at the prominent weight.
	   It was the one search control still square-cornered, Jost, 300-weight
	   and 10px on a site where every other field is a pill — the single
	   loudest inconsistency the audit turned up, and the one the customer
	   meets first. Ask Us still leads: it is wider, its type is larger and it
	   sits on glass. It is now the same species as the archive box. */
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin: 0;
	padding: var(--ka-field-pad-y) var(--ka-field-pad-right) var(--ka-field-pad-y) var(--ka-field-pad-left);
	border-radius: var(--ka-radius-pill);
	background: color-mix(in srgb, var(--imm-bg) 7%, transparent);
	border: 1px solid color-mix(in srgb, var(--imm-bg) 30%, transparent);
	transition: border-color var(--ka-dur) var(--ka-ease), background-color var(--ka-dur) var(--ka-ease);
}
.ka-imm__hero-form:focus-within {
	border-color: var(--imm-caramel);
	background: color-mix(in srgb, var(--imm-bg) 12%, transparent);
}
.ka-imm__hero-form input {
	flex: 1 1 auto;
	min-width: 0;
	box-sizing: border-box;
	padding: 0.5rem 0;
	background: transparent;
	border: 0;
	color: var(--imm-bg);
	font-family: var(--ka-font-body);
	font-size: var(--ka-type-body);
	font-weight: var(--ka-weight-body);
	letter-spacing: 0.02em;
	outline: none;
	text-overflow: ellipsis; /* graceful clip if a prompt ever outgrows the field */
}
.ka-imm__hero-form input::placeholder {
	color: color-mix(in srgb, var(--imm-bg) 55%, transparent);
	font-weight: 300;
}

/* Hairlined so it reads as part of the field, not a competing element. */
.ka-imm__hero-send {
	flex: 0 0 auto;
	/* The action moves INSIDE the field as a pill, the way the reference has
	   it, instead of being a bare glyph behind a divider rule. */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--ka-tap-min);
	min-width: var(--ka-tap-min);
	padding: 0 var(--ka-control-pad-x-sm);
	border: 0;
	border-radius: var(--ka-radius-pill);
	background: var(--imm-bg);
	color: var(--imm-high);
	font-family: var(--ka-font-body);
	font-size: var(--ka-type-control);
	line-height: 1;
	cursor: pointer;
	transition: opacity var(--ka-dur) var(--ka-ease);
}
.ka-imm__hero-send:hover { color: var(--imm-bg); background: color-mix(in srgb, var(--imm-bg) 8%, transparent); }
.ka-imm__hero-send:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }

/*
 * Voice search. Sits inside the field, before the send arrow, so the two read
 * as the field's own controls rather than as buttons parked beside it.
 *
 * Hidden in markup and revealed by the script only where the browser can
 * actually listen, so this never styles a control that does nothing.
 */
.ka-imm__hero-mic {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0 10px;
	background: none;
	border: 0;
	color: var(--imm-stone);
	cursor: pointer;
	transition: color 160ms ease;
}
.ka-imm__hero-mic[hidden] { display: none; }
.ka-imm__hero-mic:hover { color: var(--imm-bg); }
.ka-imm__hero-mic:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }

/*
 * Listening. The pulse is the whole signal that the microphone is live, so it
 * is a real animation rather than a static colour — and it is the one thing
 * here that must survive a reduced-motion preference, because switching it off
 * would leave no indication that a microphone is open. Reduced motion gets a
 * steady accent instead of a pulse: still unmistakable, no movement.
 */
.ka-imm__hero-mic.is-listening {
	color: var(--imm-caramel);
	animation: ka-imm-mic-pulse 1.4s ease-in-out infinite;
}
@keyframes ka-imm-mic-pulse {
	0%, 100% { opacity: 1; transform: scale(1); }
	50%      { opacity: 0.55; transform: scale(1.12); }
}
@media (prefers-reduced-motion: reduce) {
	.ka-imm__hero-mic.is-listening { animation: none; opacity: 1; }
}

/* The field itself acknowledges that it is being spoken into. */
.ka-imm__hero-form.is-listening {
	box-shadow: 0 0 0 1px color-mix(in srgb, var(--imm-caramel) 70%, transparent);
}

.ka-imm__hero-form.is-listening {
	box-shadow: 0 0 0 1px color-mix(in srgb, var(--imm-caramel) 70%, transparent);
}

/*
 * Voice-language selector. Small, quiet, lives inside the form near the mic.
 *
 * position:relative is load-bearing (owner defect 2026-08-07 — "the language
 * switcher does not work"): without it the menu's containing block was the
 * fixed .ka-imm__hero-ai panel, whose overflow-y:auto clipped the menu to
 * the panel's box, so the menu opened somewhere the customer could neither
 * see nor click. Anchored to the button, the menu escapes the clip.
 */
.ka-imm__lang-select {
	position: relative;
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0 8px;
	background: none;
	border: 0;
	color: var(--imm-stone);
	cursor: pointer;
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.08em;
	transition: color 160ms ease;
}
.ka-imm__lang-select[hidden] { display: none; }
.ka-imm__lang-select:hover { color: var(--imm-bg); }
.ka-imm__lang-menu {
	/*
	 * Portal (2026-08-08): the menu is appended to <body> and positioned by
	 * JS from the button's viewport rect.  position:fixed is what lets it
	 * escape the Ask Us panel's overflow-y:auto clipping and its transform —
	 * as an in-panel absolute child it was cut off at the panel's top edge.
	 */
	position: fixed;
	margin: 0;
	padding: 6px;
	list-style: none;
	/*
	 * Owner directive 2026-08-07 §6: the menu speaks the same warm-ivory
	 * language as Ask Us — never a cramped dark list. Wide enough for the
	 * full language names, comfortable row rhythm, clear hover and selected
	 * states, scrollable, and stacked above the Ask Us panel (z 30) and the
	 * product surfaces.
	 */
	background: rgba(248, 245, 239, 0.97);
	backdrop-filter: blur(12px);
	-webkit-backdrop-filter: blur(12px);
	border: 1px solid var(--kas-line, rgba(74, 58, 32, 0.16));
	border-radius: var(--ka-radius-sm);
	box-shadow: var(--kas-shadow, 0 14px 34px rgba(76, 58, 26, 0.10));
	min-width: 240px;
	max-height: 264px;
	overflow-y: auto;
	z-index: 130;
}
.ka-imm__lang-menu li {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 18px;
	padding: 9px 12px;
	border-radius: var(--ka-radius-sm);
	color: var(--kas-ink);
	font-size: 13px;
	line-height: 1.4;
	white-space: nowrap;
	cursor: pointer;
	transition: background-color 120ms ease;
}
.ka-imm__lang-menu li .ka-imm__lang-code {
	font-size: 11px;
	font-weight: 500;
	letter-spacing: 0.08em;
	color: var(--kas-muted);
}
.ka-imm__lang-menu li:hover,
.ka-imm__lang-menu li:focus-visible {
	background: var(--kas-accent-soft, var(--ka-gold-12));
	outline: none;
}
.ka-imm__lang-menu li[aria-selected="true"] {
	background: var(--kas-accent-soft, var(--ka-gold-12));
	font-weight: 500;
}
.ka-imm__lang-menu li[aria-selected="true"] .ka-imm__lang-code {
	color: var(--kas-accent-deep);
}

.ka-imm__hero-mic-note {
	margin: 0.45rem 0 0;
	font-size: 11px;
	letter-spacing: 0.06em;
	color: color-mix(in srgb, var(--imm-caramel) 56%, var(--imm-bg));
}
.ka-imm__hero-mic-note[hidden] { display: none; }

.ka-imm__hero-hint {
	margin: 0;
	font-size: 10px;
	font-weight: 400;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	/* The design specifies the raw accent here, which measured 2.54:1 on the
	   charcoal glass and missed AA for 10px text. Lightened toward ivory rather
	   than swapped to stone, so the caramel the design intended is still what
	   you read. Expressed as the mix it actually is, so it now tracks whatever
	   accent the owner sets instead of being a frozen hand-picked hex. */
	color: color-mix(in srgb, var(--imm-caramel) 56%, var(--imm-bg));
	animation: ka-imm-breathe 2.6s ease-in-out infinite;
}

/* Sound toggle — same glass and hairline as the Ask AI panel so it reads as
   part of the same chrome, parked clear of the panel's travelling path. */
.ka-imm__sound {
	position: absolute;
	right: 24px;
	bottom: 24px;
	z-index: 3;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 9px 13px;
	background: var(--imm-glass);
	backdrop-filter: blur(16px);
	border: 1px solid color-mix(in srgb, var(--imm-bg) 22%, transparent);
	color: var(--imm-stone);
	font-family: var(--ka-font-body);
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	cursor: pointer;
	transition: color 160ms ease, border-color 160ms ease;
}
.ka-imm__sound:hover { color: var(--imm-bg); border-color: color-mix(in srgb, var(--imm-bg) 42%, transparent); }
.ka-imm__sound:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }
.ka-imm__sound[aria-pressed="true"] { color: var(--imm-bg); border-color: var(--imm-caramel); }
.ka-imm__sound-icon { font-size: 12px; line-height: 1; }
@media (max-width: 760px) {
	.ka-imm__sound { right: 16px; bottom: 16px; padding: 8px 11px; }
	.ka-imm__sound-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
}

/* The design's own scrims: they anchor the panel and keep type off the floor. */
.ka-imm__flight-stick::before,
.ka-imm__flight-stick::after {
	content: '';
	position: absolute;
	left: 0; right: 0;
	pointer-events: none;
	z-index: 1;
}
.ka-imm__flight-stick::before {
	top: 0; height: 150px;
	background: linear-gradient(to bottom, color-mix(in srgb, var(--imm-ink) 50%, transparent), transparent);
}
.ka-imm__flight-stick::after {
	bottom: 0; height: 200px;
	background: linear-gradient(to top, color-mix(in srgb, var(--imm-ink) 62%, transparent), transparent);
}

/* iOS zooms the viewport on focus below 16px, so the design's 14px has to
   lift on touch. The panel widens to suit rather than the text shrinking. */
@media (max-width: 760px) {
	/* Same fixed centre on phones, widened to the screen. The box used to be
	   pinned to the bottom band purely because a travelling panel would have
	   crossed the whole display; it no longer travels, so it keeps the one
	   position the visitor learns once. */
	.ka-imm__hero-ai {
		/* Owner review 2026-08-10: 25% narrower on phones, still exactly
		   centred — so the centre translate on X returns and the explicit
		   width (set in the later phone block, which wins the cascade)
		   replaces the edge-to-edge gutters. */
		left: 50%; right: auto;
		top: 50%; bottom: auto;
		/* max-width must go too: the base cap (100% - 88px) is narrower than
		   the 20px gutters, so it silently pinned the pane to the LEFT edge —
		   reported 2026-08-10 as "centre the Ask Us pane on mobile". With the
		   cap lifted, the centred translate defines the box: centred. */
		max-width: none;
		transform: translate(-50%, -50%);
	}
	/*
	 * Docked on a phone is a bottom sheet, not a corner: at this width the panel
	 * is nearly full-bleed, so a corner is meaningless and the top of the screen
	 * is where the product needs to be.
	 *
	 * This override has to live INSIDE the narrow query. The desktop docked rule
	 * sits earlier in the file, so the plain mobile `transform: translateY(-50%)`
	 * below it would win and the panel would simply never dock on a phone —
	 * which is precisely where it sitting over the product hurts most.
	 */
	.ka-imm[data-ai-place="docked"] .ka-imm__hero-ai {
		left: 12px; right: 12px;
		top: auto; bottom: calc(12px + env(safe-area-inset-bottom));
		transform: none;
		/* The base rule caps the panel at 100% - 88px so it never fills a wide
		   screen. As a bottom sheet it SHOULD span the width, so the cap goes. */
		max-width: none;
		max-height: 44vh;
	}
	/* A sheet is a control surface, not a record. What the room has already
	   shown is worth a scroll on a phone, not a permanent third of the screen —
	   dropping it roughly halves the sheet and gives that height to the product. */
	.ka-imm[data-ai-place="docked"] .ka-imm__history,
	.ka-imm[data-ai-place="docked"] .ka-imm__hero-hint {
		display: none;
	}
	/* Let the product scroll clear of the sheet instead of ending underneath it. */
	.ka-imm[data-ai-place="docked"] .ka-imm__room {
		padding-bottom: calc(clamp(1.25rem, 4vw, 3rem) + env(safe-area-inset-bottom) + 46vh);
	}
	/*
	 * The project section needs the same clearance, for the same reason.
	 *
	 * It was added under the film without it, so on a narrow viewport the
	 * docked sheet — which spans the width here by design — came down on top of
	 * the product cards and their controls. The room had been given this
	 * padding precisely so the sheet could never do that; the new section
	 * simply was not covered by the rule.
	 *
	 * This is a narrow-viewport concern only. Above 760px the panel is a 400px
	 * corner dock and nothing below it is obscured.
	 */
	.ka-imm[data-ai-place="docked"] .ka-imm__project {
		padding-bottom: calc(clamp(1.25rem, 4vw, 3rem) + env(safe-area-inset-bottom) + 46vh);
	}
	.ka-imm__hero-form input { font-size: 16px; }

	/*
	 * Owner review 2026-08-10: the pane's own copy goes full black on phones.
	 * The warm stone eyebrow and the 72% charcoal context line were tuned for
	 * the desktop glass; on a phone over the film, in daylight, they washed
	 * out. Black reads at every brightness.
	 */
	.ka-imm__hero-eyebrow { color: #000; }
	.ka-imm__hero-context { color: #000; }
}

/*
 * WS17 visual closure: ONE navigation language across every route. The old
 * charcoal bar is gone — the immersive nav is the same canonical warm-ivory
 * bar as Shop/category/product, differing by OPACITY ALONE (slightly more
 * translucent over the film). Structure, typography, logo scale, controls,
 * label and hairline all come from the shared .ka-nav in experience.css.
 */
body:has(.ka-imm) .ka-nav {
	background: rgba(248, 245, 239, 0.92) !important;
	transition: background-color 300ms ease;
}
/* The site hides its nav once you are past the hero. Here the bar IS the page
   frame and the visitor may reach for Ask Us at any point, so it stays. */
body:has(.ka-imm).past-hero .ka-nav {
	opacity: 1;
	transform: none;
	pointer-events: auto;
}
body:has(.ka-imm) .ka-nav:hover { background: rgba(248, 245, 239, 0.97) !important; }
@keyframes ka-imm-breathe {
	0%, 100% { opacity: 0.45; transform: translateY(0); }
	50%      { opacity: 1;    transform: translateY(3px); }
}

/*
 * The rail is live from the first frame.
 *
 * It used to fade in only past 55% of the film — which meant that for most of
 * the take it was painted at opacity 0 with pointer-events:none: present enough
 * to look like a menu, entirely unclickable. Reported as "the menu to the right
 * is not functional", and it was. The switching logic was always fine; nothing
 * could reach it. Like Ask AI, it is now simply there.
 */
/* Live from the first frame — it is how you move the camera. */
/*
 * The rail is fixed at z-index 30 so it can sit over the film, which means it
 * also sits over everything after the film — three room names printed across
 * the product cards. It is the film's control and it goes when the film does.
 *
 * A `[data-film-visible="false"]` rule used to live here, and nothing in the
 * theme has ever written that attribute: no JavaScript, no PHP, no template.
 * The rule was unreachable from the day it was added, so the defect it
 * describes was never actually fixed, and "The Gallery" was still printed over
 * the first card's Remove requirement button. The live mechanism is the class
 * below, set by the observer in immersive-store.js, which watches BOTH regions
 * that come after the film — the project panel and the room.
 */
.ka-imm__rail {
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	transition: opacity 420ms ease, visibility 0s;
}
.ka-imm.is-at-results .ka-imm__rail {
	opacity: 0;
	/* Hidden only once it has finished fading, so the fade is still seen. */
	visibility: hidden;
	pointer-events: none;
	transition: opacity 420ms ease, visibility 0s linear 420ms;
}

@media (prefers-reduced-motion: reduce) {
	.ka-imm__flight-track { height: 100svh; }
	.ka-imm__hero-hint { animation: none; }
}
.ka-imm__flight.is-static .ka-imm__flight-track { height: 100svh; }

/* ------------------------------------------------------------ room switcher */

.ka-imm__rail {
	position: fixed;
	top: 50%;
	right: clamp(0.5rem, 2vw, 1.5rem);
	transform: translateY(-50%);
	z-index: 30;
}
.ka-imm__rail-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.65rem; }

.ka-imm__rail-btn {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	background: none;
	border: 0;
	padding: 0.3rem 0.4rem;
	color: var(--imm-muted);
	cursor: pointer;
	font: inherit;
	font-size: 0.82rem;
	letter-spacing: 0.04em;
	border-radius: var(--ka-radius-pill);
}
.ka-imm__rail-dot {
	width: 9px; height: 9px; border-radius: 50%;
	background: currentColor;
	opacity: 0.5;
	transition: opacity 180ms ease, transform 180ms ease, background-color 180ms ease;
}
.ka-imm__rail-btn.is-current { color: var(--imm-ink); }
.ka-imm__rail-btn.is-current .ka-imm__rail-dot {
	background: var(--room-accent);
	opacity: 1;
	transform: scale(1.5);
}
.ka-imm__rail-btn:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }

/*
 * Owner review 2026-08-10 (second pass): on phones the room menu leaves the
 * right edge and hangs beneath the centred Ask Us pane — but QUIETLY: the
 * original vertical layout, no pill, no box. It is a way to move the camera,
 * not a feature to promote, so nothing about it should compete with the
 * pane: muted ink, transparent background, the accent living only on the
 * current room's dot. The pane's height is content-driven, so JS measures
 * its live rect and writes the rail's `top`; the 62% here is only the
 * pre-JS fallback.
 */
@media (max-width: 760px) {
	.ka-imm__rail {
		right: auto;
		left: 50%;
		top: 62%;
		transform: translateX(-50%);
	}
	.ka-imm__rail-btn { color: rgba(40, 36, 31, 0.55); }
	.ka-imm__rail-name {
		position: static;
		width: auto;
		height: auto;
		overflow: visible;
		clip: auto;
		font-size: 0.76rem;
		white-space: nowrap;
	}
	.ka-imm__rail-btn.is-current { color: rgba(40, 36, 31, 0.85); }
}

/* ------------------------------------------------------------------- room */

.ka-imm__room {
	position: relative;
	display: grid;
	grid-template-rows: auto auto 1fr;
	min-height: 100svh;
	padding: clamp(1.25rem, 4vw, 3rem);
	/* The theme's .ka-nav is position:fixed at y=16 with a 62px pill, so the
	   room's first line has to clear ~94px or the title renders behind it. */
	padding-top: calc(clamp(1.25rem, 4vw, 3rem) + 5.5rem);
	padding-bottom: calc(clamp(1.25rem, 4vw, 3rem) + env(safe-area-inset-bottom));
}

.ka-imm__scene {
	position: absolute;
	inset: 0;
	margin: 0;
	overflow: hidden;
	z-index: 0;
}
.ka-imm__scene-img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0.30;
	filter: saturate(0.92);
}
.ka-imm__scene::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(180deg, color-mix(in srgb, var(--imm-bg) 42%, transparent) 0%, color-mix(in srgb, var(--imm-bg) 86%, transparent) 66%, var(--imm-bg) 100%);
}
.ka-imm__scene-note {
	position: absolute;
	left: 0.75rem;
	bottom: 0.75rem;
	z-index: 3;
	margin: 0;
	font-size: 0.7rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--imm-ink);
	background: color-mix(in srgb, var(--imm-high) 86%, transparent);
	padding: 0.3rem 0.55rem;
	border-radius: var(--ka-radius-xs);
}

.ka-imm__intro { position: relative; z-index: 2; max-width: 46ch; }
.ka-imm__eyebrow {
	margin: 0 0 0.4rem;
	font-size: 0.78rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	/* The raw room accent is a light caramel and measures 2.68:1 on ivory.
	   Mixing it toward charcoal keeps each room's hue identity while clearing
	   AA; the undiluted accent stays on the rail dot and focus rings, where it
	   is decoration rather than text. */
	color: color-mix(in srgb, var(--room-accent) 50%, var(--imm-ink));
}
.ka-imm__title {
	margin: 0 0 1.1rem;
	font-size: clamp(2rem, 6vw, 3.6rem);
	line-height: 1.04;
	font-weight: 500;
}

.ka-imm__action {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.85rem 1.6rem;
	border: 1px solid var(--room-accent);
	border-radius: var(--ka-radius-pill);
	background: var(--imm-high);
	color: var(--imm-ink);
	font: inherit;
	font-size: 0.95rem;
	cursor: pointer;
	transition: background-color 200ms ease, transform 200ms ease;
}
.ka-imm__action:hover { background: color-mix(in srgb, var(--room-accent) 30%, transparent); }
.ka-imm__action:active { transform: translateY(1px); }
.ka-imm__action:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }

/* --------------------------------------------------------- product scene */

/*
 * Owner review 2026-08-07 (supersedes the 2026-08-07 removal): the boutique
 * scene is NOT removed when a product is shown. It stays behind the two-pane
 * product unit as a subtle filigrane — boutique atmosphere visible through
 * warm ivory paper. Very low opacity, a warm-ivory veil, a gentle blur, no
 * pointer events, no visual competition with the product, and never a third
 * perceived pane. The product panes remain the foreground.
 *
 * Owner directive 2026-08-07 §4: this is ONE shared watermark system, not a
 * room-specific one — the opacity, blur, saturate and veil all come from the
 * --ka-watermark-* tokens defined in storefront.css, the exact same tokens
 * the Shop and Category product pages use for their .ka-watermark layer.
 */
.ka-imm__room.has-product .ka-imm__scene {
	pointer-events: none;
	transition: opacity 400ms ease;
}
.ka-imm__room.has-product .ka-imm__scene-img {
	opacity: var(--ka-watermark-opacity);
	filter: blur(var(--ka-watermark-blur)) saturate(var(--ka-watermark-saturate));
}
.ka-imm__room.has-product .ka-imm__scene::after {
	background: var(--ka-watermark-veil);
}
.ka-imm__room.has-product .ka-imm__scene-note {
	display: none;
}

/*
 * The intro (category heading) is also hidden when a product is shown —
 * the category lives only as the small ka-pd__kicker inside the card.
 */
.ka-imm__room.has-product .ka-imm__intro {
	display: none;
}

/*
 * The gallery stage is the canonical ka-pd stage EXACTLY as the Shop page
 * renders it — same 4:5 proportion, same column-filling width (owner
 * directive 2026-08-07 §1: the left pane width and the pane gap must be the
 * SAME as Shop, not approximate). The earlier max-height cap shrank the
 * stage inside its own grid column (416px wide in a 582px column), which
 * broke pane parity; the fixed aspect-ratio already bounds the height, so
 * no cap is needed and none is kept.
 */



.ka-imm__product {
	position: relative;
	z-index: 2;
	display: flex;
	align-items: center;
	/* Owner review 2026-08-07: the search product is centred horizontally as
	   one product unit, exactly like a product opened from Shop — no offset
	   reserved for Ask Us (which is a bubble while the product is shown). */
	justify-content: center;
	padding-top: clamp(1.5rem, 5vh, 3rem);
	/* A floor, never a ceiling: the section grows to whatever the product
	   needs rather than the product being cropped to fit the section. */
	min-height: 22rem;
	height: auto;
}


.ka-imm__product[data-state='idle'] .ka-imm__product-inner { opacity: 0; }
.ka-imm__product[data-state='seeking'] .ka-imm__product-inner { opacity: 0.35; }

.ka-imm__product-inner {
	width: 100%;
	display: flex;
	justify-content: center;
}

.ka-imm__card {
	/* The found product is the point of the second section, so it is allowed
	   to be the biggest thing in it. Width matches the Shop product page's
	   effective content column (1280px container less its horizontal
	   padding), so the two panes land on the same dimensions as the
	   canonical ka-pd layout. width:100% is load-bearing — with max-width
	   alone the flex item shrinks to its content and the panes come out
	   narrower than Shop. (Sheet padding removed 2026-08-08, so the width
	   no longer adds it back.) */
	width: 100%;
	max-width: min(calc(1280px - 2 * clamp(16px, 4vw, 56px)), 100%);
}

/*
 * The canonical product sheet (owner directive 2026-08-06): a product found
 * through Ask Us is presented exactly the way the Shop product page presents
 * it — the same ka-pd layout, typography and controls from storefront.css.
 * Behaviour (gallery page-turn, availability picker, cart contract, history
 * revisit) is untouched; only the presentation moved.
 *
 * Owner review 2026-08-08: the sheet no longer draws a box of its own
 * behind the panes — no background, border, radius, shadow or padding —
 * so the search display shows the same two rectangles on the open scene
 * that the Shop page shows on the open page.
 */
.ka-imm__card.ka-imm__pd {
	/*
	 * Owner review 2026-08-08: no rectangular box behind the two rectangles.
	 * The gallery stage and the purchase panel stand directly on the room
	 * scene — exactly as they do on the Shop product page, whose .ka-pd
	 * wrapper carries no enclosing card. The sheet's ivory background,
	 * hairline, radius, shadow and padding all go; each pane keeps its own.
	 */
	background: transparent;
	border: 0;
	border-radius: 0;
	box-shadow: none;
	padding: 0;
}

/* The purchase pane is the SAME panel the Shop product page renders — same
   warm panel background, hairline, radius, padding rhythm and shadow — only
   the sticky pinning is dropped, because the sheet already scrolls as one
   unit. */
.ka-imm__pd-panel {
	background: var(--kas-panel);
	border: 1px solid var(--kas-line);
	border-radius: var(--kas-radius);
	padding: clamp(22px, 3vw, 36px);
	box-shadow: var(--kas-shadow);
	position: static;
}
.ka-imm__pd-kicker { margin-bottom: 8px; }
.ka-imm__pd-title { margin-bottom: 8px; }
.ka-imm__pd-meta { margin-bottom: 12px; }
.ka-imm__pd-prices { margin-bottom: 16px; }
.ka-imm__pd-assurances a { color: var(--kas-accent-deep); }

/* The About block sits below the layout inside the same sheet, as on the
   Shop product page — and carries the same "About this product" heading
   (ka-pd__h2), matching the reference section the owner pointed at. */
.ka-imm__pd-details { margin-top: clamp(22px, 4vw, 40px); }
.ka-imm__pd-details .ka-imm__details { margin-top: 1.1rem; }

.ka-imm__card-media {
	position: relative;
	align-self: start;
}
/* The canonical stage (ka-pd__stage in storefront.css) owns frame layout:
   4/5 aspect, radius, hairline border, cover frames. */
.ka-imm__card-media img[hidden] { display: none; }

/*
 * The slow page-turn. While the gallery loops on its own, the incoming
 * frame fades and drifts in gently — a turned page, not a slide show.
 * The class only ever comes from the loop, never from manual navigation,
 * so deliberate steps stay instant. Reduced-motion kills it in CSS too,
 * belt-and-braces beside the loop never starting.
 */
.ka-imm__pd-gallery.is-turning img[data-frame]:not([hidden]) {
	animation: kaImmPageTurn 900ms ease both;
}
@keyframes kaImmPageTurn {
	from { opacity: 0.25; transform: translateX(0.5rem); }
	to   { opacity: 1;    transform: none; }
}
@media (prefers-reduced-motion: reduce) {
	.ka-imm__pd-gallery.is-turning img[data-frame]:not([hidden]) { animation: none; }
}

/*
 * The blocking clarification, answered by tap. The question reads as the
 * room speaking; the options reuse the chip language the refinements
 * already established, so answering feels like continuing the conversation
 * rather than filling a form.
 */
.ka-imm__clarify {
	max-width: min(38rem, 100%);
	margin: 0 auto;
	padding: 1.25rem 0 0.25rem;
	text-align: center;
}
.ka-imm__clarify-q {
	margin: 0 0 0.9rem;
	color: var(--imm-ink);
	font-size: 1.05rem;
	line-height: 1.5;
}
.ka-imm__clarify-opts {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.5rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
/* The shared chip language is tuned for the dark panel; the product scene
   is light, so the clarify chips speak ink-on-cream. */
.ka-imm__clarify-opts .ka-imm__chip {
	font-size: 11px;
	padding: 9px 14px;
	color: var(--imm-ink);
	border-color: color-mix(in srgb, var(--imm-ink) 28%, transparent);
}
.ka-imm__clarify-opts .ka-imm__chip:hover {
	color: var(--imm-ink);
	border-color: color-mix(in srgb, var(--imm-ink) 52%, transparent);
	background: color-mix(in srgb, var(--imm-ink) 5%, transparent);
}
.ka-imm__clarify-opts .ka-imm__chip--quiet {
	border-style: dashed;
	opacity: 0.75;
}
.ka-imm__clarify-opts .ka-imm__chip--quiet:hover { opacity: 1; }

/*
 * Gallery navigation is the canonical thumbnail rail (ka-pd__views /
 * ka-view in storefront.css) — the same data-goto contract the dots used,
 * with real photographs instead of chrome over the image. The page-turn,
 * keyboard and swipe behaviour lives in bindGallery and is unchanged.
 */

/*
 * What the product is. A description list, because the content is pairs —
 * "Material: Cowhide Leather" — and reading them as a table is the whole
 * advantage over the run-on paragraph the supplier ships.
 */
.ka-imm__details {
	margin: 1.1rem 0 0;
	padding-top: 0.9rem;
	border-top: 1px solid rgba(var(--imm-ink-rgb), 0.12);
}
/*
 * Owner review 2026-08-08: the About block reads like the Shop page's —
 * plain Name: Value lines, one per row, no boxed spec grid. The name sits
 * muted on the left of its value, exactly as the reference description
 * lists its facts.
 */
.ka-imm__specs {
	display: block;
	margin: 0.35rem 0 0;
}
.ka-imm__spec {
	padding: 2px 0;
	font-size: 0.95rem;
	line-height: 1.6;
}
.ka-imm__spec dt {
	display: inline;
	color: var(--kas-muted);
}
.ka-imm__spec dt::after { content: ': '; }
.ka-imm__spec dd {
	display: inline;
	margin: 0;
	color: var(--kas-ink);
}
.ka-imm__details-text {
	margin: 0.7rem 0 0;
	font-size: 0.86rem;
	line-height: 1.5;
	color: var(--imm-ink);
}
.ka-imm__details-text p { margin: 0 0 0.4rem; }
.ka-imm__details-text p:last-child { margin-bottom: 0; }

/*
 * The card's own summary, beside the buy box where the decision happens:
 * the supplier's prose in one breath. The About block below never repeats
 * it (owner review 2026-08-08), so a fact appears once.
 */
.ka-imm__card-summary {
	margin: 0.6rem 0 0;
	font-size: 0.9rem;
	line-height: 1.55;
	color: var(--imm-ink);
}

.ka-imm__card-rating {
	display: flex;
	align-items: center;
	gap: 0.45rem;
	margin: 0.35rem 0 0;
	font-size: 0.85rem;
	color: var(--imm-muted);
	letter-spacing: 0.02em;
}

/*
 * Five stars, filled proportionally.
 *
 * Two identical glyph runs stacked: the lower one is the empty track, the
 * upper one is clipped to --ka-star-fill, which the script sets from the
 * rating. One element per star with a half/full class would round the truth
 * to the nearest half; clipping does not, so 4.8 renders as 4.8.
 *
 * currentColor is not used for the track — the caramel at low opacity keeps
 * the empty portion recognisably part of the same object rather than looking
 * like disabled text.
 */
.ka-imm__stars {
	position: relative;
	display: inline-block;
	flex: none;
	font-size: 1rem;
	line-height: 1;
	letter-spacing: 0.08em;
	/* The script overrides this; the default is a full row rather than an
	   empty one, so a missing value never reads as a zero rating. */
	--ka-star-fill: 100%;
}
.ka-imm__stars-empty { color: rgba(var(--imm-ink-rgb), 0.20); }
.ka-imm__stars-full {
	position: absolute;
	inset: 0;
	width: var(--ka-star-fill);
	overflow: hidden;
	white-space: nowrap;
	color: var(--imm-caramel);
}
.ka-imm__rating-value { white-space: nowrap; }

.ka-imm__card-orders {
	margin: 0.2rem 0 0;
	font-size: 0.85rem;
	color: var(--imm-muted);
	letter-spacing: 0.02em;
}

.ka-imm__card-oos { margin: 0.4rem 0 0; font-size: 0.85rem; color: var(--imm-muted); }

/*
 * The former price sits beside the current one, quieter and struck through —
 * styled by the canonical ka-pd__was rule; this keeps only the spacing.
 * It never competes with the price the customer actually pays — the saving is
 * information, not the headline.
 */
.ka-imm__was { margin-left: 0.25rem; }

/* Visually hidden, still announced — the "Was" that gives the struck-through
   figure its meaning when the price is read aloud rather than seen. */
.ka-imm__sr {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/*
 * Confirmation of what the customer asked for. The quote rail and the
 * paragraph that went with it are gone: they framed a sentence of matcher
 * narration, and without it a lone chip behind a caramel bar looked like the
 * page had lost something.
 */
.ka-imm__why-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 0.3rem;
	margin: 0.55rem 0 0;
	padding: 0;
	list-style: none;
}
.ka-imm__why-chip {
	padding: 0.12rem 0.5rem;
	border: 1px solid rgba(var(--imm-ink-rgb), 0.18);
	border-radius: var(--ka-radius-pill);
	font-size: 0.74rem;
	letter-spacing: 0.03em;
	color: var(--imm-muted);
}

/* Certified A6 quality explanation — server-composed prose, rendered verbatim. */
.ka-imm__quality-explanation {
	margin: 0.55rem 0 0;
	font-size: 0.82rem;
	line-height: 1.5;
	letter-spacing: 0.02em;
	color: var(--imm-muted);
}

/* Scarcity. Warm rather than alarming, and only ever shown when true. */
.ka-imm__card-stock {
	margin: 0.4rem 0 0;
	font-size: 0.82rem;
	letter-spacing: 0.03em;
	color: var(--imm-caramel);
}
.ka-imm__card-stock[hidden] { display: none; }

/* The reveal is a single class the controller can drop at any moment. */
.ka-imm__product.is-revealing .ka-imm__card {
	animation: ka-imm-rise 520ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
@keyframes ka-imm-rise {
	from { opacity: 0; transform: translateY(14px) scale(0.985); }
	to   { opacity: 1; transform: none; }
}

.ka-imm__status {
	position: relative;
	z-index: 2;
	max-width: 48ch;
	margin: 0;
	color: var(--imm-muted);
	font-size: 0.95rem;
	line-height: 1.5;
}
.ka-imm__product[data-state='empty'] .ka-imm__status,
.ka-imm__product[data-state='error'] .ka-imm__status { padding-top: 1rem; }

/* ------------------------------------------------------------- Ask AI dock */



/* ---- inside the Ask AI panel ------------------------------------------------
 * These moved out of the light dock and into the charcoal glass, so every ink
 * value had to invert: ink-on-ivory here would simply not be there. Radius
 * stays at 0 with the rest of the design.
 */
.ka-imm__hero-context {
	margin: 0;
	font-size: 12px;
	font-weight: 300;
	line-height: 1.45;
	color: color-mix(in srgb, var(--imm-bg) 72%, transparent);
}

.ka-imm__hero-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* An author `display` beats the UA's [hidden] rule, so setting the attribute
   alone left the chips painted. Anything that declares display must restate
   this or `hidden` becomes decorative. */
.ka-imm__hero-chips[hidden] { display: none; }

.ka-imm__chip {
	padding: 7px 11px;
	background: none;
	border: 1px solid color-mix(in srgb, var(--imm-bg) 24%, transparent);
	color: var(--imm-stone);
	font: inherit;
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	cursor: pointer;
	transition: color 160ms ease, border-color 160ms ease, background-color 160ms ease;
}
.ka-imm__chip:hover {
	color: var(--imm-bg);
	border-color: color-mix(in srgb, var(--imm-bg) 44%, transparent);
	background: color-mix(in srgb, var(--imm-bg) 6%, transparent);
}
.ka-imm__chip:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }
.ka-imm__chip[disabled] { opacity: 0.38; cursor: not-allowed; }

/*
 * FINGERS, NOT POINTERS.
 *
 * 7px of padding around 10px type is a 29–32px control. That is comfortable
 * for a mouse and small for a thumb: measured on a 375px viewport, every chip
 * in the project bar — Name this project, Add selected products to cart, the
 * new-project "+" — came out under the 44px that a touch target wants.
 *
 * The type size and the letterspacing are the design and do not change; only
 * the box grows, and only where the input is coarse. On a desktop with a
 * mouse nothing here applies.
 */
@media (pointer: coarse) {
	.ka-imm__chip {
		min-height: 44px;
		padding-top: 10px;
		padding-bottom: 10px;
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}
	.ka-imm__chip--icon { min-width: 44px; }
}

/* ---------------------------------------------------------------- history */

.ka-imm__history {
	margin: 2px 0 0;
	border-top: 1px solid color-mix(in srgb, var(--imm-bg) 18%, transparent);
	padding-top: 11px;
}
.ka-imm__history-title {
	margin: 0 0 7px;
	font-size: 10px;
	font-weight: 500;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: color-mix(in srgb, var(--imm-bg) 62%, transparent);
}
.ka-imm__history-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; }

.ka-imm__hist {
	display: grid;
	grid-template-columns: 2rem minmax(0, 1fr) auto;
	gap: 9px;
	align-items: center;
	width: 100%;
	padding: 4px;
	border: 0;
	background: none;
	color: var(--imm-bg);
	font: inherit;
	font-size: 12px;
	font-weight: 300;
	text-align: left;
	cursor: pointer;
	transition: background-color 160ms ease;
}
.ka-imm__hist:hover { background: color-mix(in srgb, var(--imm-bg) 8%, transparent); }
.ka-imm__hist:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }
.ka-imm__hist img { width: 2rem; height: 2rem; object-fit: cover; }
.ka-imm__hist span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ka-imm__hist em { font-style: normal; color: color-mix(in srgb, var(--imm-bg) 62%, transparent); }

/*
 * The room used to keep to a left-hand column so nothing slid under the panel
 * holding the centre. The panel now retires to the corner the moment anything
 * is asked, so the room gets its full width back — which is the whole point of
 * a section that has to grow around whatever product comes back.
 */

/* ------------------------------------------------------------------ variants
 * The chooser sits in the room's ivory register, not the panel's charcoal, so
 * every value here is ink-on-ivory. Square, hairlined, no radius — the design
 * carries none anywhere, and a row of rounded chips would read as a different
 * product's UI dropped into this one.
 *
 * The swatch IS the photograph. A colour named "Blue C2" means nothing to
 * anyone; the supplier's own image of that colourway means everything, so the
 * image carries the choice and the name is there for people who cannot see it.
 */
.ka-imm__variants { margin-top: 1.4rem; }

.ka-imm__variants-head {
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
	margin: 0 0 0.6rem;
	font-size: 0.72rem;
	letter-spacing: 0.18em;
	text-transform: uppercase;
}
.ka-imm__variants-name { color: var(--imm-muted); }
.ka-imm__variants-choice {
	color: var(--imm-ink);
	letter-spacing: 0.06em;
	text-transform: none;
	font-size: 0.85rem;
}

.ka-imm__swatches {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.ka-imm__swatch {
	display: block;
	padding: 0;
	width: 56px;
	height: 56px;
	background: var(--imm-high);
	border: 1px solid color-mix(in srgb, var(--imm-ink) 16%, transparent);
	cursor: pointer;
	/* The selected ring is drawn with box-shadow rather than a thicker border,
	   so selecting never changes the element's size and the row cannot reflow
	   under the cursor. */
	transition: box-shadow 160ms ease, border-color 160ms ease, opacity 160ms ease;
}
.ka-imm__swatch img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Local variations are often text — a size, a shipping origin — with no image
   to show. Those become a legible chip rather than an empty square. */
.ka-imm__swatch.is-text {
	width: auto;
	min-width: 56px;
	height: auto;
	min-height: 40px;
	padding: 0.55rem 0.9rem;
	color: var(--imm-ink);
	font-family: var(--ka-font-body);
	font-size: 0.84rem;
	letter-spacing: 0.04em;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}
.ka-imm__swatch-blank { display: block; width: 100%; height: 100%; background: var(--imm-stone); }

.ka-imm__swatch:hover:not([disabled]) {
	border-color: color-mix(in srgb, var(--imm-ink) 38%, transparent);
}
.ka-imm__swatch.is-selected {
	border-color: var(--imm-caramel);
	box-shadow: 0 0 0 2px var(--imm-caramel), 0 0 0 4px var(--imm-bg);
}
.ka-imm__swatch:focus-visible {
	outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset);
}

/* Sold out is shown, not hidden: a colourway that exists but is gone is
   information. It is simply not choosable — greyed, struck through, and out of
   the tab order via [disabled]. */
.ka-imm__swatch[disabled] {
	cursor: not-allowed;
	opacity: 0.32;
	position: relative;
	filter: grayscale(1);
}
.ka-imm__swatch[disabled]::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(to top left,
		transparent calc(50% - 1px), var(--imm-ink) 50%, transparent calc(50% + 1px));
	opacity: 0.55;
}

/* ---- buy ---- */
.ka-imm__buy { margin-top: 1.5rem; }
/* The canonical gold purchase button — identical treatment to the Shop
   product page's Add to Cart (ka-btn--primary / ka-pd__buy). */
.ka-imm__buy-btn {
	width: 100%;
	padding: 15px 20px;
	border: none;
	border-radius: var(--ka-radius-pill);
	background: var(--ka-accent);
	color: var(--ka-accent-ink);
	box-shadow: 0 12px 34px var(--ka-gold-35);
	font-family: inherit;
	font-size: 1rem;
	font-weight: 500;
	letter-spacing: normal;
	text-transform: none;
	cursor: pointer;
	transition: transform 0.25s var(--ka-ease, ease), box-shadow 0.25s var(--ka-ease, ease);
}
.ka-imm__buy-btn:hover:not([disabled]) {
	transform: translateY(-2px);
	box-shadow: 0 18px 44px var(--ka-gold-35);
	background: var(--ka-accent);
	color: var(--ka-accent-ink);
}
.ka-imm__buy-btn:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }
.ka-imm__buy-btn[disabled] {
	background: var(--ka-surface);
	color: var(--ka-text-dim);
	box-shadow: none;
	cursor: not-allowed;
	opacity: 1;
}
.ka-imm__buy-note {
	margin: 0.6rem 0 0;
	font-size: 0.82rem;
	line-height: 1.5;
	color: var(--imm-muted);
	max-width: 42ch;
}
/* Each outcome reads as itself. Colour is never the only signal — the sentence
   already says which it is — so this only reinforces what the words state. */
.ka-imm__buy-note[data-state="ok"] {
	color: var(--imm-ink);
	border-left: 2px solid var(--imm-caramel);
	padding-left: 0.7rem;
}
/* No new hue. An error red would be a sixth colour the owner cannot change
   from wp-admin, and the palette contract exists to stop exactly that. The
   sentence already says what went wrong; this distinguishes it by weight and
   by a dashed rule, which also survives being read in greyscale. */
.ka-imm__buy-note[data-state="error"] {
	color: var(--imm-ink);
	font-weight: 500;
	border-left: 2px dashed color-mix(in srgb, var(--imm-ink) 55%, transparent);
	padding-left: 0.7rem;
}
.ka-imm__buy-note[data-state="working"] { font-style: italic; }
.ka-imm__buy-note[data-state="signin"] { color: var(--imm-ink); }
.ka-imm__buy-note a {
	color: var(--imm-ink);
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
}
.ka-imm__buy-note a:hover { color: var(--imm-caramel); }

@media (prefers-reduced-motion: reduce) {
	.ka-imm__swatch, .ka-imm__buy-btn { transition: none; }
}

/* --------------------------------------------------------------- a11y/motion */

@media (prefers-reduced-motion: reduce) {
	.ka-imm__product.is-revealing .ka-imm__card { animation: none; }
	.ka-imm__rail-dot,
	.ka-imm__action { transition: none; }
}

/* Phones: tighten the hero so the input reads whole. At 375px the headline
   ran three lines and the placeholder truncated mid-word. */
@media (max-width: 760px) {
	/*
	 * Owner review 2026-08-10: the centred pane shrinks by 25% on phones —
	 * (100vw - 40px gutters) × 0.75 = 75vw - 30px, with a floor so very
	 * narrow screens keep the field usable. Scoped to the centred state
	 * ONLY (:not([data-ai-place])): the docked bottom sheet and the bubble
	 * keep their own widths. This block wins the cascade, so the width
	 * lives here and not in the earlier phone block.
	 */
	.ka-imm:not([data-ai-place]) .ka-imm__hero-ai { width: calc(75vw - 30px); min-width: 240px; }
	.ka-imm__hero-line { font-size: clamp(1.25rem, 6.4vw, 1.7rem); margin-bottom: 1rem; }
	/*
	 * THE FIELD WRAPS ON A PHONE, and it has to.
	 *
	 * The row carries the input plus three fixed controls — microphone,
	 * language and send. Once those are given the 44px targets they were
	 * missing (they measured 18px and 12px), they take about 140px of a 213px
	 * field, and the input was left with TWENTY-FOUR PIXELS: an Ask Us box
	 * showing "T…" before anyone had typed. Measured at 375px.
	 *
	 * Wrapping gives the input the whole first line and puts the three
	 * controls on the second. The field gets taller; every control stays
	 * present and hittable, and the thing the panel is FOR is legible. Hiding
	 * the language selector would have been shorter and would have taken a
	 * control away from the customers most likely to need it.
	 */
	.ka-imm__hero-form {
		flex-wrap: wrap;
		gap: 0.25rem 0.5rem;
		padding: var(--ka-field-pad-y) 0.75rem;
		border-radius: var(--ka-radius-lg);
	}
	.ka-imm__hero-form input { flex: 1 1 100%; padding: 0.5rem 0.25rem; }
	.ka-imm__hero-send { margin-left: auto; padding: 0 var(--ka-control-pad-x-sm); }
	.ka-imm__hero-chips { gap: 0.35rem; }
	.ka-imm__hero-chip { font-size: 0.78rem; padding: 0.35rem 0.7rem; }
	/* Owner review 2026-08-10, black pane copy: the eyebrow rule lives in the
	   earlier phone block (its base rule is earlier still, so it wins there);
	   THIS base rule sits at ~1215, after that block, so the black override
	   has to live down here to be the one that applies. */
	.ka-imm__hero-context { color: #000; }
}

/* Phones: the scene is the native 9:16 render, and the copy clears the dock. */
@media (max-width: 760px) {
	.ka-imm__room { grid-template-rows: auto auto auto; }
	.ka-imm__card { grid-template-columns: 1fr; max-width: 22rem; }
	.ka-imm__product { padding-bottom: 6.5rem; }
	}

.screen-reader-text {
	position: absolute !important;
	width: 1px; height: 1px;
	overflow: hidden;
	clip: rect(1px, 1px, 1px, 1px);
	white-space: nowrap;
}

/* --------------------------------------------------------------- Ask project
   Phase 3F-B. The project conversation shares the hero panel with the search:
   one input, two possible answers, and only the server decides which. Styled
   as part of the panel rather than as a second surface floating over it.

   Scrollable and height-capped because a proposal is longer than a product
   line and must not push the field it was typed into off a phone screen. */
.ka-imm__ask {
	margin: 0.75rem 0 0;
	padding: 0.75rem 0 0;
	border-top: 1px solid rgba(var(--imm-ink-rgb), 0.16);
	max-height: min(46vh, 22rem);
	overflow-y: auto;
	overscroll-behavior: contain;
}

.ka-imm__ask-heading {
	margin: 0 0 0.4rem;
	font-size: 0.98rem;
	line-height: 1.35;
	color: var(--imm-ink);
}

.ka-imm__ask-line {
	margin: 0 0 0.35rem;
	font-size: 0.86rem;
	line-height: 1.45;
	color: var(--imm-muted);
	/* Supplier and customer text both land here: a long unbroken string must
	   wrap rather than widen the panel and force a horizontal scrollbar. */
	overflow-wrap: anywhere;
}

.ka-imm__ask-line--question { color: var(--imm-ink); }
.ka-imm__ask-line--error { color: var(--imm-caramel); }
.ka-imm__ask-line--note { font-style: italic; }
.ka-imm__ask-line--working { opacity: 0.75; }
.ka-imm__ask-line--user { color: var(--imm-ink); font-weight: 600; }

.ka-imm__ask-market,
.ka-imm__ask-reply {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	align-items: center;
	margin-top: 0.5rem;
}

.ka-imm__ask .ka-ask__market-select,
.ka-imm__ask-reply-input {
	flex: 1 1 10rem;
	/* A <select> is sized by its widest option, and the country vocabulary
	   contains names far wider than the panel. min-width alone does not stop
	   that — max-width does, and without it the control ran 100px past the
	   panel edge on a phone and had to be scrolled to horizontally. */
	min-width: 0;
	max-width: 100%;
	padding: 0.45rem 0.55rem;
	font: inherit;
	font-size: 0.86rem;
	color: var(--imm-ink);
	background: rgba(var(--imm-bg-rgb), 0.72);
	border: 1px solid rgba(var(--imm-ink-rgb), 0.28);
	border-radius: var(--ka-radius-xs);
}

.ka-imm__ask-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	margin-top: 0.6rem;
}

.ka-imm__ask-signin {
	display: inline-block;
	margin-top: 0.5rem;
	text-decoration: none;
}

/* The focus ring is the keyboard user's only cursor — never removed. */
.ka-imm__ask :focus-visible {
	outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset);
}

@media (max-width: 760px) {
	.ka-imm__ask { max-height: 40vh; }
	.ka-imm__ask-market,
	.ka-imm__ask-reply { flex-direction: column; align-items: stretch; }
	/* Stacked, so each control takes the panel width rather than its own
	   intrinsic content width. */
	.ka-imm__ask .ka-ask__market-select,
	.ka-imm__ask-reply-input { width: 100%; }
}

/* ---------------------------------------------------------------- outfit list
 * The chosen list for a multi-item project: one row per requirement, the
 * selected product, and its alternatives behind a Change control. Replaces a
 * flat dump of every candidate as chat text.
 */
.ka-imm__outfit {
	display: flex;
	flex-direction: column;
	gap: .5rem;
	margin: .75rem 0;
	border-top: 1px solid rgba(0, 0, 0, .08);
	padding-top: .75rem;
}

.ka-imm__outfit-row {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: .35rem .6rem;
}

.ka-imm__outfit-label {
	flex: 0 0 100%;
	margin: 0;
	font-size: .72rem;
	letter-spacing: .08em;
	text-transform: uppercase;
	opacity: .6;
}

.ka-imm__outfit-pick {
	flex: 1 1 auto;
	margin: 0;
	font-size: .95rem;
}

.ka-imm__outfit-options {
	flex: 0 0 100%;
	display: flex;
	flex-wrap: wrap;
	gap: .35rem;
	padding: .35rem 0 .1rem;
}

.ka-imm__outfit-option {
	font-size: .8rem;
	opacity: .85;
}

/* ------------------------------------------------------------- product cards
 * Each item in the project is presented the way a single product is: picture,
 * what it is, what it costs, and the controls that belong to THAT product.
 * Sized so several sit under the film together rather than one filling it.
 */
.ka-imm__pcard {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 0 1rem;
	align-items: start;
	padding: 1rem;
	border: 1px solid rgba(0, 0, 0, .08);
	border-radius: var(--ka-radius-sm);
	background: rgba(255, 255, 255, .55);
}

.ka-imm__pcard-media { flex: none; }

.ka-imm__pcard-media .ka-imm__outfit-thumb {
	width: 96px;
	height: 128px;
	object-fit: cover;
	border-radius: var(--ka-radius-xs);
}

.ka-imm__pcard-body { min-width: 0; }

.ka-imm__pcard-kicker {
	margin: 0 0 .25rem;
	font-size: .7rem;
	letter-spacing: .09em;
	text-transform: uppercase;
	opacity: .6;
}

.ka-imm__pcard-title {
	margin: 0 0 .3rem;
	font-size: 1rem;
	font-weight: 500;
	line-height: 1.3;
}

.ka-imm__pcard-price {
	margin: 0 0 .35rem;
	font-size: .95rem;
}

/* Why this product answers the requirement, in the fit gate's own words. */
.ka-imm__pcard-why {
	margin: 0 0 .7rem;
	font-size: .78rem;
	line-height: 1.4;
	opacity: .7;
}

.ka-imm__pcard-controls {
	display: flex;
	flex-wrap: wrap;
	gap: .35rem;
}

.ka-imm__pcard-controls .ka-imm__chip { font-size: .78rem; }

/* A requirement that found nothing still gets a card: it is still in the plan. */
.ka-imm__pcard.is-empty { opacity: .75; }

/*
 * A requirement waiting on the customer is not a requirement still searching,
 * and it must not be dimmed like one — it is the only thing on the page that
 * needs them to act. See the blocking-decision branch in projectRow().
 */
.ka-imm__pcard.is-empty.needs-answer {
	opacity: 1;
	border-left: 3px solid var(--room-accent, #8a6f4a);
}
.ka-imm__pcard.needs-answer .ka-imm__pcard-title { font-weight: 600; }
.ka-imm__pcard.needs-answer .ka-imm__pcard-why { opacity: .95; }

/*
 * NO PRODUCT, NO PRODUCT SLOT.
 *
 * The card keeps the media column it would use for a photograph, so a question
 * with no product behind it reserved a large empty grey rectangle beside its
 * own text — the customer read it as an image that had failed to load, which is
 * a different problem with a different response. A question is text; it takes
 * the width of the card.
 */
.ka-imm__pcard.needs-answer { grid-template-columns: 1fr; }
.ka-imm__pcard.needs-answer .ka-imm__pcard-media { display: none; }

/* ------------------------------------------------------ alternatives panel
 * Comparing is the task, so the options sit beside the recommendation rather
 * than replacing it. A compact column: enough to tell the products apart and
 * see what each one costs relative to the one on display.
 */
.ka-imm__pcard.has-alts.is-rich {
	grid-template-columns: minmax(14rem, 30%) 1fr minmax(13rem, 16rem);
}

/*
 * EXPLORATION IS A DIFFERENT SURFACE FROM THE DECISION.
 *
 * The rail used to be the card's own surface with a hairline down its left
 * side, so five alternatives and the product the customer had chosen read as
 * one continuous list. A warm beige one step toward the page ground separates
 * them without adding weight: the card stays the near-white, dominant thing;
 * the rail recedes. The border-left goes with it — the change of surface is
 * the separation, and keeping both would be twice the line.
 */
.ka-imm__pcard-alts {
	background: #F8F5EF;
	border-radius: var(--ka-radius-sm);
	padding: .85rem;
	display: flex;
	flex-direction: column;
	gap: .55rem;
	max-height: 30rem;
	overflow-y: auto;
}
/* The panel's own display:flex would otherwise beat the UA stylesheet's
   [hidden]{display:none}, so a "hidden" rail stayed visible below the main
   image. Re-assert the hidden state so the collapsed card has no rail. */
.ka-imm__pcard-alts[hidden] { display: none; }

.ka-imm__pcard-alts-head { margin: 0 0 .1rem; }

.ka-imm__pcard-alts-title {
	margin: 0;
	font-size: .68rem;
	letter-spacing: .09em;
	text-transform: uppercase;
	opacity: .55;
}

/*
 * The closing note at the foot of the rail.
 *
 * A soft tinted panel one shade warmer than the rail it sits in, with a small
 * outlined heart. It reads as a reassurance after the options rather than a
 * heading above them, so it is deliberately the quietest thing in the column —
 * no border, no shadow, and the text muted against a tint that only just
 * separates it from the beige behind.
 */
.ka-imm__pcard-alts-note {
	display: flex;
	align-items: flex-start;
	gap: .5rem;
	margin: .35rem 0 0;
	padding: .7rem .8rem;
	background: #F3ECE0;
	border-radius: var(--ka-radius-xs);
	font-size: .78rem;
	line-height: 1.45;
	letter-spacing: 0;
	text-transform: none;
	color: rgba(40, 36, 31, .72);
}
.ka-imm__pcard-alts-note .ka-imm__ico {
	flex: none;
	margin-top: .1rem;
	color: var(--imm-caramel);
	opacity: .95;
}
.ka-imm__pcard-alts-note-text { min-width: 0; }

/*
 * Each alternative is a near-white card sitting inside the beige. One surface
 * change and a small radius — no per-row rules, which on a tinted ground read
 * as a table rather than a set of things to choose between.
 */
.ka-imm__alt {
	display: grid;
	grid-template-columns: 48px 1fr;
	grid-template-areas: "thumb meta" "actions actions";
	gap: .4rem .55rem;
	align-items: start;
	padding: .55rem;
	background: rgba(255, 255, 255, .72);
	border-radius: var(--ka-radius-xs);
}

.ka-imm__alt-thumb { grid-area: thumb; width: 48px; height: 48px; }
.ka-imm__alt-meta { grid-area: meta; display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
/* Swap replaces what is on the card; Add keeps both. They sit together on
   one row so the choice between them reads as a choice. */
/* HOW MANY. Sits with the cart control because it is part of the same
   decision: which product, and how many of it. */
.ka-imm__pcard-qty {
	display: inline-flex;
	align-items: center;
	gap: .1rem;
	border: 1px solid rgba(40, 36, 31, .18);
	border-radius: var(--ka-radius-pill);
	padding: .1rem;
	background: rgba(255, 255, 255, .5);
}
.ka-imm__qty-step {
	min-width: 1.9rem;
	padding: .25rem .3rem;
	font-size: .9rem;
	line-height: 1;
	border: 0;
	background: transparent;
}
.ka-imm__qty-step:disabled { opacity: .3; cursor: default; }
.ka-imm__qty-value {
	min-width: 1.6rem;
	text-align: center;
	font-size: .8rem;
	font-variant-numeric: tabular-nums;
}

.ka-imm__alt-swap,
.ka-imm__alt-add { grid-area: actions; font-size: .72rem; }
.ka-imm__alt-swap { justify-self: start; }
.ka-imm__alt-add {
	justify-self: start;
	margin-left: 5.1rem;
	/* Add is the secondary of the two: swapping is the common answer, and a
	   second equally loud control would make the row ask a question it is not
	   asking. */
	opacity: .82;
}
.ka-imm__alt-add:hover,
.ka-imm__alt-add:focus-visible { opacity: 1; }

/* The label carries the reason this option is worth a look. Tones are
   deliberately quiet — this is guidance, not a sale. */
/* The same recommendation wording as the main card's badge, so it reads the
   same way in both places rather than shouting only in the rail. */
.ka-imm__alt-label {
	font-size: .66rem;
	letter-spacing: .02em;
	text-transform: none;
	opacity: .75;
}
.ka-imm__alt-label.is-best    { color: #2f6f4f; }
.ka-imm__alt-label.is-value   { color: #6f5115; }
.ka-imm__alt-label.is-premium { color: #4a4a6f; }

.ka-imm__alt-name {
	font-size: .8rem;
	line-height: 1.25;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.ka-imm__alt-price { font-size: .82rem; }
.ka-imm__alt-detail { font-size: .72rem; opacity: .65; }

/* The requirement this card answers, with its selection status beside it. */
.ka-imm__pcard-status {
	margin-left: .5rem;
	padding: .1rem .4rem;
	border-radius: var(--ka-radius-xs);
	background: rgba(40, 36, 31, .07);
	letter-spacing: .06em;
}

/* The catalog category is a detail about the product, set below the role. */
.ka-imm__pcard-cat {
	margin: 0 0 .2rem;
	font-size: .68rem;
	opacity: .5;
}

@media (max-width: 900px) {
	.ka-imm__pcard.has-alts.is-rich { grid-template-columns: minmax(14rem, 34%) 1fr; }
	/* Stacked below the product. The beige surface is the separation, so the
	 * old top rule goes with the old left one — see the panel's base rule. */
	.ka-imm__pcard-alts {
		grid-column: 1 / -1;
		margin-top: .8rem;
	}
}

/* A way out of a dead conversation, for signed-in and anonymous alike. */
.ka-imm__hero-reset {
	margin-left: auto;
	margin-right: .4rem;
	padding: 3px 9px;
	font: inherit;
	font-size: .68rem;
	letter-spacing: .07em;
	text-transform: uppercase;
	color: inherit;
	background: none;
	border: 1px solid currentColor;
	border-radius: var(--ka-radius-pill);
	opacity: .55;
	cursor: pointer;
}
.ka-imm__hero-reset:hover { opacity: 1; }
.ka-imm__hero-reset:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }

/* Collapsed, the panel is a marker; it carries no controls. */
.ka-imm__hero-ai.is-collapsed .ka-imm__hero-reset { display: none; }

/* --------------------------------------------------------------- budget
 * Going over a stated budget is the one number that must not slip past
 * quietly, so it is marked on the figure and stated above the products with
 * the single swap that would fix it.
 */
.ka-imm__project-budget.is-over { color: #8a2f2f; }

.ka-imm__project-overbudget {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: .6rem;
	margin: 0 0 1rem;
	padding: .7rem .9rem;
	border: 1px solid rgba(138, 47, 47, .3);
	border-radius: var(--ka-radius-xs);
	background: rgba(138, 47, 47, .06);
}

.ka-imm__project-overbudget-line { margin: 0; font-size: .85rem; }
.ka-imm__project-overbudget-none { margin: 0; font-size: .8rem; opacity: .75; }

.ka-imm__project .ka-imm__project-overbudget-fix {
	border-color: rgba(138, 47, 47, .45);
	color: #8a2f2f;
}
.ka-imm__project .ka-imm__project-overbudget-fix:hover {
	background: rgba(138, 47, 47, .1);
	border-color: #8a2f2f;
	color: #8a2f2f;
}

/* ------------------------------------------------------------- loading
 * The card already holds its final size; only the content is still coming.
 * A quiet placeholder is what stops the second and a half before the product
 * data lands from reading as a broken half-card.
 */
.ka-imm__pcard.is-loading .ka-imm__outfit-thumb {
	background: linear-gradient(90deg, rgba(40,36,31,.05) 25%, rgba(40,36,31,.09) 37%, rgba(40,36,31,.05) 63%);
	background-size: 400% 100%;
}

@media (prefers-reduced-motion: no-preference) {
	.ka-imm__pcard.is-loading .ka-imm__outfit-thumb {
		animation: ka-pcard-shimmer 1.4s ease-in-out infinite;
	}
}

@keyframes ka-pcard-shimmer {
	0%   { background-position: 100% 50%; }
	100% { background-position: 0 50%; }
}

/* Reserve the rows the variants and assurances will occupy, so their arrival
   does not push the controls down the page. */
.ka-imm__pcard.is-loading .ka-imm__pcard-detail { min-height: 5.5rem; }

/* A partner's product: it carries the partner's name and one outbound action.
   The KoolAllure controls are absent rather than disabled — see projectRow. */
.ka-imm__pcard-partner {
	text-decoration: none;
	border-color: rgba(40, 36, 31, .4);
}
.ka-imm__pcard.is-partner .ka-imm__pcard-why::after {
	content: ' · sold and shipped by the partner';
	opacity: .8;
}

@media (max-width: 480px) {
	.ka-imm__pcard { grid-template-columns: 1fr; gap: .75rem; }
	.ka-imm__pcard-media .ka-imm__outfit-thumb { width: 100%; height: 180px; }
}

/* ----------------------------------------------------------------- icons
 * These controls commit things — save the plan, buy it, drop an item — and as
 * identical text chips they all carried the same weight. The icon is what
 * makes each one findable without reading the row.
 */
.ka-imm__chip--icon {
	display: inline-flex;
	align-items: center;
	gap: .4rem;
}

.ka-imm__ico {
	display: inline-flex;
	flex: none;
}

.ka-imm__ico svg { display: block; }

/* Already in the cart: faded and inert, not gone. A control that disappears
   reads as a bug; one that fades reads as "handled". */
.ka-imm__pcard-cart.is-done {
	opacity: .45;
	cursor: default;
	border-color: rgba(40, 36, 31, .18);
	background: none;
}

.ka-imm__pcard-cart.is-done:hover {
	background: none;
	border-color: rgba(40, 36, 31, .18);
}

/* Add a product: always reachable at the section's upper right, and it opens
   the assistant rather than a second form. */
.ka-imm__project { position: relative; }

.ka-imm__project-add {
	position: absolute;
	top: 2.5rem;
	right: 1.25rem;
	width: 2.4rem;
	height: 2.4rem;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid var(--ka-gold-55);
	border-radius: var(--ka-radius-pill);
	background: var(--ka-gold-12);
	color: #6f5115;
	cursor: pointer;
	transition: background 160ms ease, border-color 160ms ease;
}

.ka-imm__project-add:hover {
	background: var(--ka-gold-20);
	border-color: var(--ka-accent);
}

.ka-imm__project-add:focus-visible {
	outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset);
}

/* The top bar sits with the title; the bottom one closes the section. */
.ka-imm__project-bar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: .5rem;
}

/* Which project you are looking at, stated plainly beside the controls. */
.ka-imm__project-saved {
	font-size: .8rem;
	opacity: .7;
}

.ka-imm__project-name {
	display: flex;
	gap: .4rem;
	flex: 0 0 100%;
	margin-top: .4rem;
}

.ka-imm__project-name-input {
	flex: 1 1 auto;
	max-width: 22rem;
	padding: .45rem .6rem;
	font: inherit;
	font-size: .85rem;
	border: 1px solid rgba(40, 36, 31, .28);
	border-radius: var(--ka-radius-xs);
	background: #fff;
	color: var(--ka-ink, #28241f);
}

.ka-imm__project-name-input:focus-visible {
	outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset);
}

.ka-imm__project-head .ka-imm__project-bar {
	flex: 0 0 100%;
	margin-top: .75rem;
}

@media (max-width: 560px) {
	.ka-imm__project-add { top: 1.5rem; right: 1rem; }
}

/* ------------------------------------------------------- decision card v2
 * The product display, at card scale. Same vocabulary as the full product
 * page — stage image, thumbnail rail, now/was price with the saving, colour
 * swatches, size chips, shipping and returns — so a customer comparing three
 * of these is reading the same things they would read on one product's page.
 */
/* The single product display's proportions: a large gallery column beside a
   detail column, not a thumbnail beside a paragraph. */
.ka-imm__pcard.is-rich {
	grid-template-columns: minmax(16rem, 38%) 1fr;
	gap: 0 2rem;
	padding: 1.5rem;
}

.ka-imm__pcard.is-rich .ka-imm__outfit-thumb {
	width: 100%;
	height: auto;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	border-radius: var(--ka-radius-xs);
}

.ka-imm__pcard.is-rich .ka-imm__pcard-title { font-size: 1.15rem; }
.ka-imm__pcard.is-rich .ka-imm__pcard-now { font-size: 1.25rem; }

/* Room to breathe at the size the card now has. */
.ka-imm__pcard.is-rich .ka-imm__pcard-thumb {
	width: 56px;
	height: 56px;
}

.ka-imm__pcard-thumbs {
	display: flex;
	gap: .35rem;
	margin-top: .45rem;
	flex-wrap: wrap;
}

.ka-imm__pcard-thumb {
	width: 40px;
	height: 40px;
	object-fit: cover;
	border-radius: var(--ka-radius-xs);
	cursor: pointer;
	border: 1px solid transparent;
	background: rgba(0, 0, 0, .05);
}

.ka-imm__pcard-thumb:hover { border-color: rgba(40, 36, 31, .35); }

/* Price: what you pay first and largest, what it was beside it. A discount is
   never shown without the figure it is a discount from. */
.ka-imm__pcard-price {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: .45rem;
}

.ka-imm__pcard-now { font-size: 1.05rem; }

.ka-imm__pcard-was {
	font-size: .85rem;
	opacity: .55;
}

.ka-imm__pcard-save {
	font-size: .72rem;
	letter-spacing: .06em;
	padding: .12rem .4rem;
	border-radius: var(--ka-radius-xs);
	background: var(--ka-gold-12);
	color: #6f5115;
}

.ka-imm__pcard-detail {
	display: flex;
	flex-direction: column;
	gap: .6rem;
	margin: .2rem 0 .8rem;
}

.ka-imm__pcard-group {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: .4rem;
}

.ka-imm__pcard-group-label {
	flex: 0 0 100%;
	font-size: .68rem;
	letter-spacing: .09em;
	text-transform: uppercase;
	opacity: .6;
}

.ka-imm__pcard-options {
	display: flex;
	flex-wrap: wrap;
	gap: .35rem;
}

.ka-imm__pcard-option {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 2rem;
	padding: .25rem .5rem;
	font: inherit;
	font-size: .78rem;
	background: none;
	border: 1px solid rgba(40, 36, 31, .22);
	border-radius: var(--ka-radius-xs);
	cursor: pointer;
}

.ka-imm__pcard-option.is-on {
	border-color: var(--ka-accent);
	box-shadow: 0 0 0 1px var(--ka-accent) inset;
}

.ka-imm__pcard-swatch {
	width: 34px;
	height: 34px;
	object-fit: cover;
	border-radius: var(--ka-radius-xs);
	display: block;
}

.ka-imm__pcard-option:has(.ka-imm__pcard-swatch) { padding: 2px; }

/* How many options were not shown. Stated, so the set does not look complete
   when it is not. */
.ka-imm__pcard-more {
	align-self: center;
	font-size: .72rem;
	opacity: .6;
}

.ka-imm__pcard-assurances {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: .25rem .9rem;
	font-size: .76rem;
	opacity: .75;
}

.ka-imm__pcard-assurances a { color: inherit; }

.ka-imm__pcard-oos {
	margin: 0;
	font-size: .8rem;
	color: #8a2f2f;
}

@media (max-width: 560px) {
	.ka-imm__pcard.is-rich { grid-template-columns: 1fr; gap: .75rem; }
	/* The alternatives panel must not re-impose the two-column split that the
	 * rich card drops on a phone: the main product stacks, then the options
	 * follow below it, full width. */
	.ka-imm__pcard.has-alts.is-rich { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------- thumbnails
 * The panel is a list of things being chosen between, so each one shows what
 * it looks like. The slot keeps its size whether or not an image arrived, so
 * rows stay aligned rather than shifting per supplier.
 */
.ka-imm__outfit-media {
	display: flex;
	align-items: center;
	gap: .7rem;
	min-width: 0;
}

.ka-imm__outfit-thumb {
	flex: 0 0 auto;
	width: 64px;
	height: 64px;
	object-fit: cover;
	border-radius: var(--ka-radius-xs);
	background: rgba(0, 0, 0, .05);
}

/* No image, or one that failed to load: an empty slot, never a stand-in. */
.ka-imm__outfit-thumb.is-empty {
	display: inline-block;
	background: rgba(0, 0, 0, .05);
}

.ka-imm__outfit-option {
	display: inline-flex;
	align-items: center;
	gap: .5rem;
	text-align: left;
}

.ka-imm__outfit-option .ka-imm__outfit-thumb {
	width: 40px;
	height: 40px;
}

@media (max-width: 640px) {
	.ka-imm__outfit-thumb { width: 52px; height: 52px; }
}

@media (prefers-reduced-motion: no-preference) {
	.ka-imm__outfit-row { transition: opacity .2s ease; }
}

/* --------------------------------------------------------------- project list
 * The plan, under the film. Not inside the Ask panel: a conversation scrolls
 * and a list the customer edits must not.
 */
.ka-imm__project {
	max-width: 62rem;
	margin: 0 auto;
	padding: 2.5rem 1.25rem 1.5rem;
}

/*
 * Chips, readable on THIS surface.
 *
 * .ka-imm__chip is drawn for the dark hero film: pale stone text on a
 * transparent ground. The project section is a light page, so those same chips
 * came out cream-on-cream — "Show me another", "Something cheaper", "Remove"
 * and "Add all to cart" were all present, all working, and all very nearly
 * invisible. These are the section's primary controls now, so they take the
 * page's own ink instead of the film's.
 */
.ka-imm__project .ka-imm__chip {
	color: var(--ka-ink, #28241f);
	border-color: rgba(40, 36, 31, .26);
}

.ka-imm__project .ka-imm__chip:hover {
	color: var(--ka-ink, #28241f);
	border-color: rgba(40, 36, 31, .5);
	background: rgba(40, 36, 31, .06);
}

/* The two that commit something read as the primary pair. */
.ka-imm__project .ka-imm__project-cart,
.ka-imm__project .ka-imm__project-save {
	border-color: var(--ka-gold-55);
	background: var(--ka-gold-12);
	color: #6f5115;
}

.ka-imm__project .ka-imm__project-cart:hover,
.ka-imm__project .ka-imm__project-save:hover {
	border-color: var(--ka-accent);
	background: var(--ka-gold-20);
	color: #6f5115;
}

.ka-imm__project-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: .5rem;
	border-bottom: 1px solid rgba(0, 0, 0, .1);
	padding-bottom: .6rem;
	margin-bottom: 1rem;
}

.ka-imm__project-title {
	margin: 0;
	font-size: 1.1rem;
	letter-spacing: .1em;
	text-transform: uppercase;
	font-weight: 400;
}

.ka-imm__project-budget {
	margin: 0;
	font-size: .85rem;
	opacity: .75;
}

/*
 * One product per row, at the size a single product is shown.
 *
 * Two-up made each card compete for half the width, which shrank the image to
 * a thumbnail and stacked the controls — the opposite of the product display
 * they are meant to read like. Full width lets each product keep the gallery
 * and the detail column it would have on its own page, and the customer reads
 * them one after the other rather than comparing postage stamps.
 */
.ka-imm__project-list {
	display: flex;
	flex-direction: column;
	gap: 1.5rem;
}

.ka-imm__project .ka-imm__outfit-row {
	display: grid;
	grid-template-columns: 1fr auto;
	gap: .25rem .75rem;
	align-items: center;
	padding-bottom: .75rem;
	border-bottom: 1px solid rgba(0, 0, 0, .06);
}

.ka-imm__project .ka-imm__outfit-label { grid-column: 1 / -1; }
.ka-imm__project .ka-imm__outfit-options { grid-column: 1 / -1; }

.ka-imm__outfit-controls {
	display: flex;
	gap: .35rem;
	justify-self: end;
}

.ka-imm__project-subtitle {
	margin: 1.25rem 0 .4rem;
	font-size: .75rem;
	letter-spacing: .1em;
	text-transform: uppercase;
	opacity: .6;
}

.ka-imm__project-gaps {
	margin: 0;
	padding-left: 1.1rem;
	font-size: .9rem;
}

.ka-imm__project-note {
	margin: .4rem 0 0;
	font-size: .8rem;
	opacity: .7;
}

/* Says the panel was restored, not freshly answered. Quiet on purpose — it is
   an explanation, not an announcement. */
.ka-imm__project-restored {
	margin: 0 0 .9rem;
	font-size: .85rem;
	opacity: .75;
}

.ka-imm__project-actions {
	display: flex;
	flex-wrap: wrap;
	gap: .5rem;
	margin-top: 1.25rem;
}

.ka-imm__project-prompt {
	display: flex;
	gap: .4rem;
	flex: 1 1 18rem;
}

.ka-imm__project-input {
	flex: 1 1 auto;
	padding: .45rem .7rem;
	font: inherit;
	font-size: .9rem;
	border: 1px solid rgba(0, 0, 0, .2);
	background: transparent;
}

.ka-imm__project-status {
	margin: .75rem 0 0;
	font-size: .85rem;
	min-height: 1.2em;
}

@media (max-width: 600px) {
	.ka-imm__project .ka-imm__outfit-row { grid-template-columns: 1fr; }
	.ka-imm__outfit-controls { justify-self: start; }
}

/*
 * The remove control on a "still looking" row.
 *
 * Quiet by default and only as loud as the card's own trash chip on hover —
 * this list is a set of things the customer is waiting for, not a list of
 * mistakes, so the way out should be findable rather than prominent.
 */
.ka-imm__project-gap { display: flex; align-items: center; gap: .5rem; }
.ka-imm__gap-remove {
	margin-left: auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 4px;
	border: 0;
	background: none;
	color: inherit;
	opacity: .45;
	cursor: pointer;
	transition: opacity 160ms ease;
}
.ka-imm__gap-remove:hover,
.ka-imm__gap-remove:focus-visible { opacity: 1; }
.ka-imm__gap-remove:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }
@media (pointer: coarse) {
	.ka-imm__gap-remove { min-width: 44px; min-height: 44px; }
}

/*
 * "Your projects" on a cold landing.
 *
 * A quiet line under the Ask Us field — the way back for a returning visitor
 * who has not signed in. It sits below the input rather than in the top bar,
 * which on a 360px phone has no room left for anything.
 */
.ka-imm__hero-projects { display: block; margin-top: .55rem; position: relative; }
.ka-imm__hero-projects-open {
	border: 0;
	background: none;
	padding: 6px 0;
	font: inherit;
	font-size: .74rem;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: inherit;
	opacity: .62;
	cursor: pointer;
	text-decoration: underline;
	text-underline-offset: 3px;
	transition: opacity 160ms ease;
}
.ka-imm__hero-projects-open:hover,
.ka-imm__hero-projects-open:focus-visible { opacity: 1; }
.ka-imm__hero-projects-open:focus-visible { outline: var(--ka-focus-width) solid hsl(var(--ka-focus)); outline-offset: var(--ka-focus-offset); }
.ka-imm__hero-projects .ka-account__menu { right: auto; left: 0; }
@media (pointer: coarse) {
	.ka-imm__hero-projects-open { min-height: 44px; }
}

/* ==========================================================================
   PRODUCT CARD — EDITORIAL PASS
   Hierarchy, not redesign. The order the eye should travel in is: product
   title, price, recommendation, category, then the supporting labels. Before
   this, six things were set in the same small uppercase letterspaced style
   and competed at the same volume.
   ========================================================================== */

/* 1 — the product. Given the room to lead. */
.ka-imm__pcard.is-rich .ka-imm__pcard-title {
	font-size: 1.2rem;
	line-height: 1.25;
	letter-spacing: -0.005em;
}

/* 2 — price stays exactly as prominent as it was. The customer is shopping to
   a budget; nothing here reduces it. */

/* 3 — the recommendation. A quiet badge, sentence case, not a shout. */
.ka-imm__pcard-status {
	text-transform: none;
	letter-spacing: .02em;
	font-size: .72rem;
	opacity: .8;
}

/* 4 — the requirement kicker. Still the card's identity, one step quieter. */
.ka-imm__pcard-kicker {
	letter-spacing: .07em;
	opacity: .5;
}

/* 5 — the catalog category: a detail, set plainly rather than in caps. */
.ka-imm__pcard-cat {
	text-transform: none;
	letter-spacing: .01em;
	opacity: .55;
}

/* 6 — group labels above the variant pickers. They name a control; they are
   not headings. */
.ka-imm__pcard-group-label {
	letter-spacing: .06em;
	opacity: .5;
}

/*
 * A QUIET, DESTRUCTIVE ACTION.
 *
 * Remove requirement sat in the same outlined uppercase chip as Add to cart,
 * so the two read as equals — one adds to a basket, the other deletes a need
 * from the project and cannot be undone. It becomes a text action: sentence
 * case, no border, and only as loud as it needs to be to be found. It is not
 * hidden, and the confirmation behind it is unchanged.
 */
/* Scoped the same way .ka-imm__project .ka-imm__chip is: that rule re-colours
 * every chip inside the project and would otherwise put the border back. */
.ka-imm__chip--quiet,
.ka-imm__project .ka-imm__chip--quiet {
	border-color: transparent;
	background: none;
	text-transform: none;
	letter-spacing: .01em;
	font-size: .78rem;
	opacity: .62;
	padding-inline: 6px;
}
.ka-imm__chip--quiet:hover,
.ka-imm__chip--quiet:focus-visible,
.ka-imm__project .ka-imm__chip--quiet:hover,
.ka-imm__project .ka-imm__chip--quiet:focus-visible {
	opacity: 1;
	background: none;
	border-color: transparent;
	color: #8a2f2f;
}
.ka-imm__chip--quiet .ka-imm__ico { opacity: .8; }

/* An option group the customer still has to answer, marked when they try to
 * add without it. Cleared the moment they choose. */
.ka-imm__pcard-group.is-missing .ka-imm__pcard-group-label { color: #8a2f2f; opacity: 1; }
.ka-imm__pcard-group.is-missing .ka-imm__pcard-option { border-color: rgba(138, 47, 47, .45); }

/* The chosen option, for anyone reading the page rather than looking at it,
 * is carried by aria-pressed; this is the same fact in colour. */
.ka-imm__pcard-option[aria-pressed="true"] { border-color: var(--imm-caramel, #A9825B); }

/* Which one of several identical-looking lines this is. */
.ka-drawer__variant {
	display: block;
	font-size: .78rem;
	opacity: .62;
}
