/**
 * `.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));
}
@media (max-width: 1023px) {
	.wh-single-panel__grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-5, var(--s-5)); }
}
@media (max-width: 640px) {
	.wh-single-panel__grid { grid-template-columns: 1fr; gap: var(--s-5, var(--s-5)); }
}

.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);
}

/* Skeleton state for any live-priced element. Applied to the top
   sticky card's total + the bottom Stap 4 "Totaal" while a price
   fetch is in flight. Shape follows the element's own box; the text
   is hidden to keep the layout from shifting when the real value
   arrives. Reuses the design system's neutral surface colours so
   the shimmer reads as part of the same family as cart/checkout
   loading states. */
@keyframes wh-sp-skeleton-shimmer {
	0%   { background-position: 200% 50%; }
	100% { background-position: -200% 50%; }
}
.wh-sp-pdp__price-card-total.is-skeleton,
.wh-sp-total__amount.is-skeleton {
	color: transparent;
	background-image: linear-gradient(
		90deg,
		var(--bg-alt) 0%,
		var(--surface-2) 50%,
		var(--bg-alt) 100%
	);
	background-size: 200% 100%;
	background-repeat: no-repeat;
	background-color: var(--bg-alt);
	animation: wh-sp-skeleton-shimmer 1.2s ease-in-out infinite;
	border-radius: 2px;
	user-select: none;
	display: inline-block;
	/* Skeleton box matches the typical rendered-price footprint —
	   ~4 em wide (representative of "€158,75" / "€42,85" prices in
	   the display font), 0.85 em tall (cap height of the digits).
	   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-skeleton {
	/* 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 skeleton 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-skeleton {
	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__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(--surface-2); }
.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);
}
.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);
	margin: 0 0 var(--s-5, var(--s-5));
}
.wh-sp-pdp__specs-table {
	width: 100%;
	max-width: 640px;
	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);
}
