/**
 * `.wh-single-panel` — `/foto-op-paneel/` archive + product PDP.
 *
 * Phase A ships archive styles. Phase B will add the configurator
 * (`.wh-sp-config__step`, `.wh-sp-config__cropper`) on top.
 *
 * Tokens-only — no raw hex / px / rem in CSS values, audit-clean per
 * `Vault/01 Brand & Design System/Design Tokens Policy.md`.
 */

/* ================================================================== */
/* Archive (`/foto-op-paneel/`)                                         */
/* ================================================================== */

.wh-single-panel {
	background: var(--bg);
}
.wh-single-panel__inner {
	max-width: var(--page-max-content, 1440px);
	margin-inline: auto;
	/* Top matches the canonical page-header so the breadcrumb lines up with the
	   other archive pages (was --s-9/96px, far lower). Page Consistency Standard. */
	padding: clamp(var(--s-6), 5vw, var(--s-56)) var(--page-pad) var(--s-9, var(--s-9));
}

/* ---------- Intro ---------- */
.wh-single-panel__intro {
	display: flex;
	flex-direction: column;
	gap: var(--s-3);
	/* Full-width bottom separator to match the /collecties/ archive intro
	   (.wccb-collections-intro: padding 32px 0 + 1px border-bottom). max-width
	   dropped so the rule spans full width; the lead keeps its own 56ch. */
	padding-bottom: var(--s-6, var(--s-6));
	margin-bottom: var(--s-5, var(--s-5));
	border-bottom: 1px solid var(--border);
}
/* `.wh-single-panel__eyebrow` rule removed 2026-05-07 — markup deleted
 * from template-parts/single-panel/archive.php per user rule: drop
 * page-header eyebrows where the breadcrumb trail already does the
 * wayfinding job. */
.wh-single-panel__title {
	font-family: var(--font-display);
	font-size: clamp(40px, 6vw, 72px);
	line-height: 1.0;
	letter-spacing: -0.030em;
	font-weight: 400;
	color: var(--ink);
	margin: 0;
}
.wh-single-panel__title em {
	font-style: italic;
	font-weight: 400;
}
.wh-single-panel__lead {
	font-family: var(--font-body);
	font-size: 16px;
	line-height: 1.55;
	color: var(--ink-2);
	margin: 0;
	max-width: 56ch;
}

/* ---------- Grid ---------- */
.wh-single-panel__grid {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--s-6, var(--s-6));
	/*
	 * Rij-gap in de MATRIX (2 of 3 kolommen): gelijk aan de kolom-gap.
	 *
	 * Zodra er kolommen naast elkaar staan, geeft de kolom-gap een tweede
	 * referentie en leest het raster als een raster. Een rij-gap die daar
	 * anderhalf keer overheen gaat maakt er losse stroken van. Op 1440 stond
	 * 48 px rij tegen 32 px kolom en dat zag je meteen.
	 *
	 * De extra lucht van het paneel-kaarttype (--s-7) is er voor het GESTAPELDE
	 * geval, waar witruimte het enige scheidingsteken is; zie de media query
	 * hieronder en design preview §06.
	 */
	row-gap: var(--s-6, var(--s-6));
}
@media (max-width: 1023px) {
	/*
	 * Nog steeds een matrix (2 kolommen), dus rij en kolom gelijk. De kolom-gap
	 * gaat hier van --s-5 naar --s-6: de rij KAN niet lager dan --s-6, want dan
	 * zakt de verhouding tot het grootste interne gat (16px) onder de canon,
	 * en een rij die de kolom voorbijloopt maakt er losse stroken van. Dus tilt
	 * de kolom mee in plaats van de rij te verlagen. Kaarten worden hierdoor
	 * 4px smaller op tablet, wat verwaarloosbaar is.
	 */
	.wh-single-panel__grid {
		grid-template-columns: repeat(2, 1fr);
		gap: var(--s-6, var(--s-6));
	}
}
@media (max-width: 640px) {
	/*
	 * GESTAPELD: er is geen kolom-gap meer, dus witruimte is het enige dat
	 * kaart N van kaart N+1 scheidt. Hier geldt de canonieke paneelwaarde
	 * --s-7 (48px). Een paneelkaart sluit af met één rechts uitgelijnd prijsje
	 * in een verder lege regel: visueel licht en asymmetrisch, dus er moet
	 * meer lucht onder dan bij de editoriale kaart met haar volle metaregel.
	 */
	.wh-single-panel__grid { grid-template-columns: 1fr; gap: var(--s-7, 48px); }
}

.wh-single-panel__item {
	min-width: 0;
}

/* ---------- Empty state ---------- */
.wh-single-panel__empty {
	font-family: var(--font-body);
	font-size: 14px;
	color: var(--ink-muted);
	padding: var(--s-7, var(--s-7)) 0;
	border-top: 1px solid var(--border);
}


/* ================================================================== */
/* PDP configurator (`/foto-op-paneel/<material>/`)                     */
/* ================================================================== */

.wh-sp-pdp {
	background: var(--bg);
}
.wh-sp-pdp__inner {
	max-width: var(--page-max-content, 1440px);
	margin-inline: auto;
	padding: var(--s-8, var(--s-8)) var(--page-pad) var(--s-9, var(--s-9));
}

/* ---------- Header ---------- */
.wh-sp-pdp__head {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin-bottom: var(--s-7, var(--s-7));
	max-width: 60ch;
}
.wh-sp-pdp__title {
	font-family: var(--font-display);
	font-size: clamp(36px, 5vw, 56px);
	line-height: 1.05;
	letter-spacing: -0.02em;
	font-weight: 400;
	color: var(--ink);
	margin: 0;
}
.wh-sp-pdp__intro {
	font-family: var(--font-body);
	font-size: 16px;
	line-height: 1.55;
	color: var(--ink-2);
	margin: var(--s-1) 0 0;
}

/* ---------- 2-col grid ---------- */
.wh-sp-pdp__grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--s-8, var(--s-8));
	align-items: start;
}
@media (max-width: 1023px) {
	.wh-sp-pdp__grid {
		grid-template-columns: 1fr;
		gap: var(--s-6, var(--s-6));
	}
}

/* ---------- LEFT — Media (sticky) ---------- */
.wh-sp-pdp__media {
	position: sticky;
	top: var(--wh-topnav-h, 88px);
	min-height: 0;
}
@media (max-width: 1023px) {
	.wh-sp-pdp__media {
		position: relative;
		top: auto;
	}
}
.wh-sp-pdp__media-frame {
	position: relative;
	background: var(--surface-product-fallback, #C4C0B8);
	overflow: hidden;
	width: 100%;
	aspect-ratio: 4 / 5; /* JS overrides per chosen size */
	transition: aspect-ratio .25s var(--ease);
}
.wh-sp-pdp__media-img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
.wh-sp-pdp__cropper {
	position: absolute;
	inset: 0;
	background-color: var(--surface-product-fallback, #C4C0B8);
	overflow: hidden;
	cursor: grab;
	touch-action: none;
}
.wh-sp-pdp__cropper:active { cursor: grabbing; }
/* Konva injects an inline-block <canvas>; force it to fill the mount. */
.wh-sp-pdp__cropper > .konvajs-content,
.wh-sp-pdp__cropper > .konvajs-content > canvas {
	width: 100% !important;
	height: 100% !important;
	display: block;
}

/* Media-frame loading scrim — visible from upload start until the
   Konva stage has finished painting the uploaded photo. Subtle white
   scrim so the customer can still see the placeholder/dropzone state
   underneath, with a centered spinner using the canonical
   `wh-loader-spin` keyframe + tokens (matches tokens.css:794-803). */
.wh-sp-pdp__media-loader {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	background: rgba(255, 255, 255, 0.55);
	z-index: 3;
	pointer-events: none;
}
.wh-sp-pdp__media-loader[hidden] { display: none; }
.wh-sp-pdp__spinner {
	width: 36px;
	height: 36px;
	border: 3px solid rgba(14, 14, 12, .12);
	border-top-color: var(--accent);
	border-radius: 50%;
	animation: wh-loader-spin 700ms linear infinite;
	box-sizing: border-box;
}

/* Crop tools — chip bar anchored bottom-center inside the media frame.
   Mirrors the builder's reposition bar idiom (chip pills + mono zoom
   readout) but slimmer since the single-panel flow only needs three
   actions. Tokens: --surface fill · --border 1px outline · --shadow-2
   so it lifts off the photo without competing visually. */
.wh-sp-pdp__crop-tools {
	position: absolute;
	left: 50%;
	bottom: 12px;
	transform: translateX(-50%);
	display: inline-flex;
	align-items: center;
	gap: var(--s-1);
	padding: var(--s-1);
	background: var(--surface);
	border: 1px solid var(--border);
	box-shadow: var(--shadow-2);
	z-index: 2;
}
.wh-sp-pdp__crop-tools[hidden] { display: none; }
.wh-sp-pdp__crop-chip {
	/* Storefront's parent theme `button { ... }` rule applies a dark
	   `#43454b` background, white text and ~.6em / 1.4em padding to
	   every <button>. With box-sizing: border-box + width: 32px,
	   that padding eats the entire content area and the SVG icon
	   collapses to zero. Reset padding + the dark background here
	   so the chip renders as the design system's neutral icon
	   button (transparent ink-muted SVG, no chrome). */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	padding: 0;
	background: transparent;
	background-color: transparent;
	border: 0;
	border-radius: 0;
	color: var(--ink-muted);
	font-weight: 400;
	cursor: pointer;
	box-shadow: none;
	text-shadow: none;
	transition: background-color .12s var(--ease), color .12s var(--ease);
}
.wh-sp-pdp__crop-chip:hover,
.wh-sp-pdp__crop-chip:focus {
	background: var(--bg-alt);
	background-color: var(--bg-alt);
	color: var(--ink);
}
.wh-sp-pdp__crop-chip:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}
.wh-sp-pdp__crop-chip svg {
	display: block;
	width: 14px;
	height: 14px;
	fill: none;
	stroke: currentColor;
}
.wh-sp-pdp__crop-zoom {
	font-size: 10px;
	letter-spacing: 0.06em;
	color: var(--ink-muted);
	min-width: 38px;
	text-align: center;
	user-select: none;
}

/* Drop-zone overlay — only visible on step 2 with no photo yet.
   Sits over the WC product image, click-through closes the affordance.
   `.is-clickable` flips cursor + slight darkening over the product
   image so the customer feels invited to interact. `.is-dragover`
   amplifies on file-drag over. */
.wh-sp-pdp__media-frame.is-clickable {
	cursor: pointer;
}
.wh-sp-pdp__media-frame.is-clickable .wh-sp-pdp__media-img {
	transition: opacity .2s var(--ease);
	opacity: .55;
}
.wh-sp-pdp__media-frame.is-dragover {
	box-shadow: inset 0 0 0 3px var(--accent);
}
.wh-sp-pdp__media-frame.is-dragover .wh-sp-pdp__media-img {
	opacity: .35;
}
.wh-sp-pdp__media-frame.is-uploading {
	cursor: wait;
}
/* Subordinate to the §13 canonical zone in the right sidebar — same
   typography idiom (body 14/500 title, 12px sub) but no dashed
   border (the media frame already supplies its own affordance via
   `.wh-sp-pdp__media-frame.is-clickable` darken-on-hover). */
.wh-sp-pdp__dropzone {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 6px;
	padding: var(--s-5);
	color: var(--ink);
	text-align: center;
	pointer-events: none;
}
.wh-sp-pdp__dropzone[hidden] { display: none; }
.wh-sp-pdp__dropzone-line {
	font-size: 14px;
	font-weight: 500;
	line-height: 1.3;
}
.wh-sp-pdp__dropzone-sub {
	font-size: 12px;
	color: var(--ink-muted);
}

/* ---------- RIGHT — Step stack ---------- */
.wh-sp-pdp__form {
	display: flex;
	flex-direction: column;
	gap: 0;
}

/* Sticky price card — sits above the step stack on the right column.
   Live total + chosen size + optional accessory delta. Visible from
   Step 1 so the customer always knows what they're paying before
   reaching Step 4. */
.wh-sp-pdp__price-card {
	display: grid;
	grid-template-columns: auto 1fr;
	column-gap: var(--s-4);
	row-gap: var(--s-1);
	align-items: baseline;
	padding: var(--s-4) 18px;
	background: var(--surface);
	border: 1px solid var(--border);
	margin-bottom: var(--s-5, var(--s-5));
}
.wh-sp-pdp__price-card-eyebrow {
	grid-column: 1 / 2;
	grid-row: 1 / 2;
	font-size: 10px;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--ink-muted);
	align-self: center;
}
.wh-sp-pdp__price-card-total {
	grid-column: 2 / 3;
	grid-row: 1 / 3;
	font-family: var(--font-display);
	font-size: clamp(28px, 4vw, 40px);
	line-height: 1.05;
	letter-spacing: -0.02em;
	font-weight: 400;
	color: var(--ink);
	text-align: right;
	align-self: center;
	transition: color .15s var(--ease);
}

/* Loading state for any live-priced element (2026-08-10, verving het
   skeleton). Applied to the top sticky card's total + the bottom Stap 4
   "Totaal" while a price fetch is in flight: the text is hidden, the
   canonical 14 px spinner (`wh-loader-spin`, same recipe as the media-loader
   above) is centered in the element's own reserved footprint so the card
   doesn't shift when the real value arrives. The skeleton pulse that stood
   here measured 0.0% pixel delta over 350 ms — below perceptibility; a
   rotating ring cannot fail that way. */
.wh-sp-pdp__price-card-total.is-loading,
.wh-sp-total__amount.is-loading {
	position: relative;
	color: transparent;
	user-select: none;
	display: inline-block;
	/* Footprint matches the typical rendered price ("€158,75" / "€42,85"
	   in the display font). No vertical-align push since the parent has
	   `align-self: center`. */
	height: 0.85em;
	vertical-align: -0.1em;
}
.wh-sp-pdp__price-card-total.is-loading::after,
.wh-sp-total__amount.is-loading::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 14px;
	height: 14px;
	margin: -7px 0 0 -7px;
	border: 2px solid rgba(26, 31, 34, .12);
	border-top-color: var(--accent);
	border-radius: 50%;
	animation: wh-loader-spin 700ms linear infinite;
	box-sizing: border-box;
}
.wh-sp-pdp__price-card-total.is-loading {
	/* The span is a grid item; `text-align: right` on its content
	   doesn't right-align the BOX itself within the grid cell.
	   `justify-self: end` pushes the reserved box to the cell's right
	   edge so it lands where the rendered "€43,10" lands. */
	justify-self: end;
	width: 4.5em; /* clamp(28px, 4vw, 40px) display-font → ~125-180px */
}
.wh-sp-total__amount.is-loading {
	width: 3.5em; /* 26px display-font → ~90px */
}
.wh-sp-pdp__price-card-meta {
	/* Under the "Jouw paneel" eyebrow, left-aligned, in the slot the
	   (removed) "btw vrijgesteld" sub used to occupy. Size · materiaal. */
	grid-column: 1 / 2;
	grid-row: 2 / 3;
	align-self: center;
	font-size: 12px;
	color: var(--ink-2);
	margin-top: 0;
}
.wh-sp-pdp__price-card-row {
	grid-column: 1 / -1;
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	padding-top: var(--s-2);
	margin-top: var(--s-2);
	border-top: 1px solid var(--border);
	font-size: 12px;
	color: var(--ink-2);
}
.wh-sp-pdp__price-card-row[hidden] { display: none; }

/* ---------- Step ---------- */
.wh-sp-step {
	border-bottom: 1px solid var(--border);
	padding: var(--s-5, var(--s-5)) 0;
	display: grid;
	grid-template-columns: 1fr auto;
	gap: 0 var(--s-4);
	align-items: start;
}
.wh-sp-step:first-child { padding-top: 0; }

.wh-sp-step__head {
	grid-column: 1 / 2;
	display: flex;
	flex-direction: column;
	gap: var(--s-1);
}
.wh-sp-step__num {
	font-size: 10px;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	color: var(--ink-muted);
}
.wh-sp-step__title {
	font-family: var(--font-display);
	font-size: 22px;
	line-height: 1.2;
	letter-spacing: -0.015em;
	font-weight: 400;
	color: var(--ink);
	margin: 0;
}

.wh-sp-step__body {
	grid-column: 1 / -1;
	display: none;
	flex-direction: column;
	gap: var(--s-4);
	margin-top: var(--s-4, var(--s-4));
}
.wh-sp-step.is-active .wh-sp-step__body { display: flex; }

.wh-sp-step.is-locked .wh-sp-step__title { color: var(--ink-faint); }
.wh-sp-step.is-locked .wh-sp-step__num   { color: var(--ink-faint); }
.wh-sp-step.is-complete .wh-sp-step__title { color: var(--ink-muted); }

/* Ghost CTA — transparent fill at all states. Storefront's parent-
   theme `button:hover { background-color: #2b2d33 }` would otherwise
   bleed a dark fill through; explicit `background-color: transparent`
   at every state beats it. */
.wh-sp-step__edit {
	grid-column: 2 / 3;
	background: transparent;
	background-color: transparent;
	border: 0;
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ink-muted);
	cursor: pointer;
	padding: 6px 0;
	transition: color .15s var(--ease);
	box-shadow: none;
	text-shadow: none;
}
.wh-sp-step__edit:hover,
.wh-sp-step__edit:focus,
.wh-sp-step__edit:focus-visible {
	background: transparent;
	background-color: transparent;
	color: var(--ink);
	outline: none;
}

/* ---------- Step CTA (Volgende / In winkelmandje) ---------- */
.wh-sp-step__cta {
	display: inline-flex;
	align-items: center;
	gap: var(--s-2);
	background: transparent;
	color: var(--ink);
	border: 1px solid var(--ink);
	border-radius: 999px;
	padding: 10px var(--s-20);
	font: 500 13px/1 var(--font-body);
	letter-spacing: 0.01em;
	cursor: pointer;
	width: max-content;
	transition: background .15s var(--ease), color .15s var(--ease);
}
.wh-sp-step__cta:hover {
	background: var(--ink);
	color: var(--bg);
}
.wh-sp-step__cta:disabled {
	opacity: .35;
	cursor: not-allowed;
}
.wh-sp-step__cta--primary {
	background: var(--accent);
	color: var(--on-ink);
	border-color: var(--accent);
	/* Bigger than the Volgende secondary CTAs — this is the hero
	   action of the entire flow. */
	padding: 18px 36px;
	font-size: 16px;
	gap: var(--s-3);
	letter-spacing: 0.02em;
	/* Storefront's parent-theme `button` rule applies an inset
	   `box-shadow` that renders as a darker line at the bottom of
	   the button (subtle ridge / "bevel" effect). Kill it so the
	   pill reads as flat — matches the design-system convention. */
	box-shadow: none;
	text-shadow: none;
}
.wh-sp-step__cta--primary:hover {
	background: var(--accent-ink);
	border-color: var(--accent-ink);
	color: var(--on-ink);
	box-shadow: none;
}
.wh-sp-step__cta--primary:focus,
.wh-sp-step__cta--primary:focus-visible,
.wh-sp-step__cta--primary:active {
	box-shadow: none;
	outline: none;
}
.wh-sp-step__cta--primary svg {
	width: 18px;
	height: 18px;
}

/* Inline spinner used inside the "In winkelwagen" CTA during the
   async submit. Mirrors the builder's `wccb-btn__spinner` (see
   `assets/builder/builder.bundle.css` line 995-1008) so the loading
   visual reads as part of the same family across surfaces. White-on-
   accent context — translucent-white track + solid-white head.
   `wh-loader-spin` keyframe is shipped globally in tokens.css. */
.wh-sp-step__cta.is-busy {
	cursor: progress;
	opacity: .85;
}
.wh-sp-step__cta.is-busy:disabled {
	opacity: .85; /* override the .35 dim from `:disabled` */
}
.wh-sp-step__cta-spinner {
	display: inline-block;
	width: 14px;
	height: 14px;
	margin-right: var(--s-2);
	border: 2px solid rgba(255, 255, 255, .35);
	border-top-color: #fff;
	border-radius: 50%;
	vertical-align: -2px;
	animation: wh-loader-spin 700ms linear infinite;
	flex-shrink: 0;
}

/* ---------- Step 1 — Orientation tabs (multi-axis matrix) ----------
 * Renders only for materials whose Probo grid carries an axis
 * discriminator (canvas: staand / liggend / vierkant). Underlined-
 * tab pattern from design system §11.
 */
.wh-sp-axis {
	display: flex;
	align-items: center;
	gap: var(--s-6, var(--s-5));
	border-bottom: 1px solid var(--border);
	padding: 0;
	margin: 0 0 var(--s-3, var(--s-3));
}
.wh-sp-axis__tab {
	appearance: none;
	background: transparent;
	border: 0;
	border-bottom: 2px solid transparent;
	padding: var(--s-2, var(--s-2)) 0;
	margin-bottom: -1px; /* overlap container border so the active bar sits flush */
	font-family: var(--font-mono);
	font-size: var(--text-xs, 12px);
	letter-spacing: .14em;
	text-transform: uppercase;
	color: var(--ink-muted);
	cursor: pointer;
	transition: color .15s var(--ease), border-color .15s var(--ease);
}
.wh-sp-axis__tab:hover { color: var(--ink); }
.wh-sp-axis__tab.is-active {
	color: var(--ink);
	border-bottom-color: var(--ink);
}
.wh-sp-axis__tab:focus-visible {
	outline: 2px solid var(--ink);
	outline-offset: 2px;
}

/* Preset list filter — paired with the orientation tabs above. The
 * partial pre-stamps non-default-axis items with both `hidden` and
 * `.is-axis-hidden`; JS toggles both on tab change. The `display:none`
 * here belts the attribute against any css/grid reflow weirdness. */
.wh-sp-presets > li.is-axis-hidden { display: none; }

/* ---------- Step 1 — Preset tile picker ---------- */
.wh-sp-presets {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 10px;
}
.wh-sp-preset {
	display: block;
	cursor: pointer;
}
.wh-sp-preset input { position: absolute; opacity: 0; pointer-events: none; }
.wh-sp-preset__face {
	display: flex;
	flex-direction: column;
	gap: var(--s-1);
	padding: 14px var(--s-4);
	border: 1px solid var(--border);
	background: var(--surface);
	transition: border-color .15s var(--ease), background .15s var(--ease);
}
.wh-sp-preset:hover .wh-sp-preset__face { border-color: var(--ink-muted); }
.wh-sp-preset input:checked + .wh-sp-preset__face {
	border-color: var(--ink);
	background: var(--surface-2);
}
.wh-sp-preset__dim {
	font-family: var(--font-display);
	font-size: 16px;
	color: var(--ink);
	letter-spacing: -0.01em;
}
/* Per-tile price suffix injected by JS once the bulk-prices endpoint
   resolves. Quieter than the dimension itself so the eye still leads
   on size; price reads as supporting metadata. */
.wh-sp-preset__price {
	font-family: var(--font-mono);
	font-size: 11px;
	font-weight: 400;
	letter-spacing: 0.04em;
	color: var(--ink-muted);
	margin-left: var(--s-1);
}
.wh-sp-preset input:checked + .wh-sp-preset__face .wh-sp-preset__price {
	color: var(--ink);
}

/* "Toon alle X maten" overflow toggle. Hidden presets ride a
   `[hidden]` attribute on `<li class="is-overflow">` — JS swaps
   the attribute on click. The button itself is a borderless
   ghost link styled like the design system's tertiary CTA. */
.wh-sp-presets__more {
	align-self: start;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	margin-top: var(--s-1);
	background: transparent;
	background-color: transparent;
	border: 0;
	padding: var(--s-2) 0;
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ink-muted);
	cursor: pointer;
	transition: color .15s var(--ease);
	box-shadow: none;
	text-shadow: none;
}
.wh-sp-presets__more:hover,
.wh-sp-presets__more:focus,
.wh-sp-presets__more:focus-visible {
	background: transparent;
	background-color: transparent;
	color: var(--ink);
	outline: none;
}
.wh-sp-presets__more-icon {
	transition: transform .2s var(--ease);
}
.wh-sp-presets__more.is-expanded .wh-sp-presets__more-icon {
	transform: rotate(180deg);
}

/* ---------- Step 1 — Free-dim numeric inputs ---------- */
.wh-sp-freedim {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.wh-sp-freedim__field {
	display: grid;
	grid-template-columns: 110px 1fr auto;
	align-items: center;
	gap: var(--s-3);
	padding: 10px 14px;
	border: 1px solid var(--border);
	background: var(--surface);
	transition: border-color .15s var(--ease);
}
.wh-sp-freedim__field:focus-within {
	border-color: var(--ink);
}
.wh-sp-freedim__label {
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ink-muted);
}
.wh-sp-freedim__field input {
	width: 100%;
	border: 0;
	background: transparent;
	font-family: var(--font-display);
	font-size: 18px;
	color: var(--ink);
	outline: 0;
	padding: 0;
	letter-spacing: -0.01em;
}
.wh-sp-freedim__unit {
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	color: var(--ink-muted);
	text-transform: uppercase;
}
.wh-sp-freedim__error {
	margin: var(--s-2) 0 0;
	font-family: var(--font-body);
	font-size: 13px;
	font-weight: 500;
	color: var(--err);
}
.wh-sp-freedim__range {
	font-family: var(--font-body);
	font-size: 12px;
	color: var(--ink-muted);
	margin: var(--s-1) 0 0;
}

/* ---------- Step 2 — Photo upload ----------
   Follows the canonical §13 Dropzone tokens from the design system
   (design-preview/Wandhaus Builder.html, section 13). Idle: 2px dashed
   `--border-strong` on `--bg-alt` with body-weight title and an accent-
   underlined "selecteer een bestand" link in the sub. Hover lifts to
   `--ink` border + ink copy. Drag-over flips border + bg to `--accent` /
   `--accent-soft` and title text swaps to "Laat los om te uploaden"
   (handled in JS). The post-upload state mirrors §13's file-list-item
   pattern: image icon · filename · OK badge · replace control. */

.wh-sp-photo__zone {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 10px;
	padding: var(--s-6) var(--s-5);
	background: var(--bg-alt);
	border: 2px dashed var(--border-strong);
	color: var(--ink-muted);
	cursor: pointer;
	text-align: center;
	transition: border-color .14s var(--ease), background .14s var(--ease), color .14s var(--ease);
	font: inherit;
}
.wh-sp-photo__zone:hover,
.wh-sp-photo__zone:focus-visible {
	border-color: var(--ink);
	color: var(--ink);
	outline: none;
}
.wh-sp-photo__zone.is-dragover {
	border-color: var(--accent);
	background: var(--accent-soft);
	color: var(--accent);
}
.wh-sp-photo__zone.is-dragover .wh-sp-photo__zone-icon { color: var(--accent); }
.wh-sp-photo__zone.is-uploading {
	cursor: wait;
	opacity: .7;
}
.wh-sp-photo__zone-icon {
	color: var(--ink-muted);
	flex-shrink: 0;
}
.wh-sp-photo__zone-line {
	font-size: 14px;
	font-weight: 500;
	line-height: 1.3;
	color: var(--ink);
}
.wh-sp-photo__zone-sub {
	font-size: 12px;
	color: var(--ink-muted);
}
.wh-sp-photo__zone-link {
	color: var(--accent);
	border-bottom: 1px solid var(--accent);
}
.wh-sp-photo__zone.is-dragover .wh-sp-photo__zone-line { color: var(--accent); }

.wh-sp-photo__status {
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ink-muted);
	margin: 0;
}
.wh-sp-photo__status.is-error { color: var(--err); }

/* Active row — §13 file-list-item. Two visual states:
   · `.is-uploading` → filename · 3px progress track · percent label.
   · `.is-done`      → filename · OK badge · replace icon. */
.wh-sp-photo__actions {
	display: flex;
	align-items: center;
	gap: var(--s-3);
	padding: var(--s-2) var(--s-3);
	background: var(--surface);
	border: 1px solid var(--border);
}
.wh-sp-photo__actions-icon {
	color: var(--ink-muted);
	flex-shrink: 0;
}
.wh-sp-photo__meta {
	flex: 1 1 auto;
	min-width: 0;
}
.wh-sp-photo__filename {
	display: block;
	font-size: 12px;
	color: var(--ink);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.wh-sp-photo__progress {
	height: 3px;
	background: var(--bg-alt);
	margin-top: var(--s-1);
	overflow: hidden;
}
.wh-sp-photo__progress[hidden] { display: none; }
.wh-sp-photo__progress-bar {
	display: block;
	height: 100%;
	width: 0%;
	background: var(--accent);
	transition: width .12s linear;
}
.wh-sp-photo__percent {
	font-size: 10px;
	letter-spacing: 0.06em;
	color: var(--ink-muted);
	flex-shrink: 0;
	min-width: 32px;
	text-align: right;
}
.wh-sp-photo__percent[hidden] { display: none; }
.wh-sp-photo__badge {
	font-family: var(--font-mono);
	font-size: 10px;
	letter-spacing: 0.06em;
	color: var(--ok);
	flex-shrink: 0;
}
.wh-sp-photo__badge[hidden] { display: none; }
/* Amber variant for low-DPI warn state. Mirrors the builder's
   `wccb-warning--amber` chip. Non-blocking — purely informational. */
.wh-sp-photo__badge--warn {
	color: var(--warn);
}

/* Persistent notice bar below the actions row. Holds the full DPI
   message ("Lage resolutie: ~120 DPI. Print kan wazig zijn op
   100×150 cm.") so the customer sees concrete numbers, not just a
   badge. Lives in the design system's --warn family. */
.wh-sp-photo__notice {
	display: flex;
	align-items: flex-start;
	gap: var(--s-2);
	margin: 0;
	padding: var(--s-2) var(--s-3);
	background: var(--warn-soft);
	color: var(--warn-soft-ink);
	border: 1px solid color-mix(in srgb, var(--warn) 28%, transparent);
	font-size: 12px;
	line-height: 1.4;
}
.wh-sp-photo__notice[hidden] { display: none; }
.wh-sp-photo__notice-icon {
	flex-shrink: 0;
	color: var(--warn);
	margin-top: 1px;
}
.wh-sp-photo__replace {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: transparent;
	border: 0;
	color: var(--ink-muted);
	cursor: pointer;
	padding: var(--s-1);
	transition: color .15s var(--ease);
	flex-shrink: 0;
}
.wh-sp-photo__replace:hover { color: var(--ink); }
.wh-sp-photo__replace[hidden] { display: none; }

/* ---------- Print border (Step 2 sub-section) ----------
   Mirrors the multi-frame builder's per-frame border picker.
   Same data model (`borderWidthMm` + palette key), same preset
   widths (0/5/10/25/50 mm), same 8-color print-safe palette. */
.wh-sp-photo__border {
	border: 0;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.wh-sp-photo__border[hidden] { display: none; }
.wh-sp-photo__border-legend {
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ink-muted);
	padding: 0;
	margin: 0;
}
.wh-sp-photo__border-widths {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}
.wh-sp-photo__border-width {
	padding: 6px var(--s-3);
	background: var(--surface);
	background-color: var(--surface);
	border: 1px solid var(--border);
	border-radius: 0;
	color: var(--ink-muted);
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.04em;
	font-weight: 400;
	cursor: pointer;
	transition: background-color .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
	box-shadow: none;
	text-shadow: none;
	height: auto;
}
.wh-sp-photo__border-width:hover {
	background-color: var(--bg-alt);
	color: var(--ink);
}
.wh-sp-photo__border-width.is-active {
	background-color: var(--ink);
	border-color: var(--ink);
	color: var(--bg);
}

.wh-sp-photo__border-colors {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}
.wh-sp-photo__border-colors[hidden] { display: none; }
.wh-sp-photo__border-color {
	width: 28px;
	height: 28px;
	padding: 2px;
	background: var(--surface);
	background-color: var(--surface);
	border: 1px solid var(--border);
	border-radius: 50%;
	cursor: pointer;
	transition: border-color .12s var(--ease), transform .12s var(--ease);
	box-shadow: none;
	text-shadow: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: inherit;
	font-weight: 400;
}
.wh-sp-photo__border-color:hover {
	transform: scale(1.08);
}
.wh-sp-photo__border-color.is-active {
	border-color: var(--ink);
	border-width: 2px;
	padding: 1px;
}
.wh-sp-photo__border-color-dot {
	display: block;
	width: 100%;
	height: 100%;
	border-radius: 50%;
	background: var(--swatch, #FFFFFF);
	box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
}

/* ---------- Step 3 — Accessories ---------- */
.wh-sp-acc {
	display: grid;
	grid-template-columns: 18px 1fr;
	gap: var(--s-3);
	align-items: start;
	padding: 14px var(--s-4);
	border: 1px solid var(--border);
	background: var(--surface);
	cursor: pointer;
	transition: border-color .15s var(--ease), background .15s var(--ease);
}
.wh-sp-acc:hover { border-color: var(--ink-muted); background: var(--accent-soft); }
.wh-sp-acc:has(input:checked) { border-color: var(--ink); }
.wh-sp-acc input { margin-top: 2px; }
.wh-sp-acc__face {
	display: flex;
	flex-direction: column;
	gap: 2px;
}
.wh-sp-acc__title {
	font-family: var(--font-body);
	font-size: 14px;
	color: var(--ink);
	font-weight: 500;
}
.wh-sp-acc__sub {
	font-family: var(--font-mono);
	font-size: 11px;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--ink-muted);
}

/* ---------- Step 4 — Recap + total ---------- */
.wh-sp-recap {
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--s-2);
}
.wh-sp-recap__row {
	display: grid;
	grid-template-columns: 110px 1fr;
	gap: var(--s-3);
	align-items: baseline;
}
.wh-sp-recap__row dt {
	font-size: 10px;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--ink-muted);
	font-weight: 500;
	margin: 0;
}
.wh-sp-recap__row dd {
	font-family: var(--font-body);
	font-size: 14px;
	color: var(--ink);
	margin: 0;
}

.wh-sp-total {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--s-4);
	padding: var(--s-4) 0;
	border-top: 1px solid var(--border);
	border-bottom: 1px solid var(--border);
}
/* Shipping indication under the total row. Muted footnote register, right
   aligned under the amount it qualifies, so it reads as a note ON the price and
   never as part of it — shipping is charged at checkout against the real
   address and is deliberately NOT in the total. Mirrors the collage builder's
   `.wccb-bottombar__shipping`. */
.wh-sp-total__shipping {
	margin: var(--s-2) 0 0;
	text-align: right;
	font-size: 11px;
	color: var(--ink-muted);
}
.wh-sp-total__label {
	font-size: 11px;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--ink-muted);
}
.wh-sp-total__amount {
	font-family: var(--font-display);
	font-size: 26px;
	color: var(--ink);
	letter-spacing: -0.01em;
}

.wh-sp-error {
	font-family: var(--font-body);
	font-size: 13px;
	color: var(--err, #B23121);
	margin: 0;
}

/* ── Specificaties — key/value spec list below the configurator ──────
   Mirrors the collection PDP spec section (.wh-pdp__spectable-*) but as a
   2-col key/value table since a single panel has no S/M/L variants. */
.wh-sp-pdp__specs {
	margin-top: var(--s-9, var(--s-9));
	padding-top: var(--s-7, var(--s-7));
	border-top: 1px solid var(--border);
}
.wh-sp-pdp__specs-title {
	font-family: var(--font-display);
	font-size: var(--type-xl);
	line-height: 1.2;
	letter-spacing: -0.015em;
	font-weight: 400;
	color: var(--ink);
	/* Centered spec column (owner request 2026-08-01): title + table share one
	   640px column centered in the section; title text stays left-aligned
	   WITHIN that column per the site's heading convention. */
	max-width: 640px;
	margin: 0 auto var(--s-5, var(--s-5));
}
.wh-sp-pdp__specs-table {
	width: 100%;
	max-width: 640px;
	margin-left: auto;
	margin-right: auto;
	border-collapse: collapse;
	font-family: var(--font-body);
}
.wh-sp-pdp__specs-table tr {
	border-bottom: 1px solid var(--border);
}
.wh-sp-pdp__specs-table tr:last-child {
	border-bottom: 0;
}
.wh-sp-pdp__specs-table th,
.wh-sp-pdp__specs-table td {
	text-align: left;
	padding: 14px 0;
	font-size: 14px;
	vertical-align: top;
}
.wh-sp-pdp__specs-table th {
	font-weight: 500;
	color: var(--ink-muted);
	width: 40%;
	padding-right: var(--s-4, var(--s-4));
}
.wh-sp-pdp__specs-table td {
	color: var(--ink);
}
