/*
Theme Name: Ivan Moshe Studio
Description: Custom WordPress block theme for the Ivan Moshe commercial photography, AI product imagery and high-end retouching studio. Presentation layer only; business logic lives in the ivan-moshe-site-core companion plugin.
Version: 5.19.1
Requires at least: 6.5
Requires PHP: 8.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: ivan-moshe-studio
*/

/* Base resets */
*,
*::before,
*::after {
	box-sizing: border-box;
}

/* Visually-hidden utility (screen-reader text, default skip-link state) */
.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(1px, 1px, 1px, 1px);
	white-space: nowrap;
}

/* Skip link becomes visible on keyboard focus */
.skip-link.screen-reader-text:focus {
	position: fixed;
	top: 1rem;
	left: 1rem;
	width: auto;
	height: auto;
	clip: auto;
	z-index: 100000;
	padding: 0.75rem 1.25rem;
	background: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--paper);
	text-decoration: none;
}

/*
 * Visible focus state for interactive elements, tuned to remain legible on
 * both light and dark sections: an inner outline plus an outer "halo" shadow
 * so at least one ring stays high-contrast against any background.
 */
:where(a, button, input, textarea, select, [tabindex]):focus-visible,
.wp-block-navigation-item__content:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 2px;
	box-shadow: 0 0 0 4px var(--wp--preset--color--paper);
}

/* Eyebrow / metadata text style: uppercase, wide tracking, small size */
.is-style-eyebrow {
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-size: var(--wp--preset--font-size--small);
}

/*
 * Breadcrumb trail ([rank_math_breadcrumb], added to page.html/
 * archive-work.html/single-work.html — Phase 8). Rank Math renders its
 * own <nav class="rank-math-breadcrumb"><p>...</p></nav>; styled here as
 * metadata (matching .is-style-eyebrow's treatment elsewhere) rather than
 * left at browser defaults.
 */
.ims-breadcrumbs {
	padding-top: var(--wp--preset--spacing--m);
}

.rank-math-breadcrumb {
	font-size: var(--wp--preset--font-size--small);
	/*
	 * A dedicated, darker value rather than --wp--preset--color--mid-neutral
	 * (#8C8578 on paper = 3.33:1, fails WCAG AA's 4.5:1 for normal text —
	 * Phase 9A contrast audit). #726B5C on paper (#F7F4EF) = 4.82:1,
	 * passes. Scoped to this selector only; the mid-neutral token itself
	 * and its border-color uses elsewhere are unchanged.
	 */
	color: #726B5C;
}

.rank-math-breadcrumb a {
	color: inherit;
	text-decoration: none;
}

.rank-math-breadcrumb a:hover {
	color: var(--wp--preset--color--ink);
	text-decoration: underline;
}

/* Project card (patterns/project-card.php): keep featured images consistently cropped */
/* Related projects (rendered by the [imsc_related_projects] shortcode) */
.imsc-related-projects__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--m);
}

.imsc-related-projects__item {
	flex: 1 1 200px;
}

.imsc-related-projects__item a {
	display: block;
	text-decoration: none;
}

.imsc-related-projects__item img {
	display: block;
	width: 100%;
	height: auto;
	object-fit: cover;
}

/* Homepage section rhythm: generous, consistent breathing space (templates/front-page.html) */
.ims-section {
	padding-block: var(--wp--preset--spacing--xl);
}

/* Mobile-only: the same generous desktop rhythm reads as excessive
   whitespace on a small screen (header-to-intro, intro-to-packages,
   section-to-section on the service pages). Desktop/tablet spacing above
   is untouched — this only applies below 768px. */
@media (max-width: 767px) {
	.ims-section {
		padding-block: var(--wp--preset--spacing--l);
	}
}

/* Removes the doubled top padding when a section follows directly after
   an intro section with no need for extra separation (e.g. /order-online/'s
   product grid, right under its intro). */
.ims-section--tight-top {
	padding-top: 0;
}

/* Same fix, applied by selector rather than a class, so it also reaches an
   /order-online/ page already seeded into the database before this change
   (page seeding is idempotent and won't overwrite existing content). */
.ims-section:has(.woocommerce) {
	padding-top: 0;
}

/*
 * Page gutters. .ims-section, the header and the footer only ever set
 * vertical padding — below desktop widths their content sits flush against
 * the viewport edges, which WooCommerce's grids/tables/forms make obvious
 * (2-column tablet cards and full-width buttons visually touching the
 * edges). Covers mobile AND the 768-1023px tablet range together, since the
 * gap is identical at both — a narrower cutoff previously left tablet
 * un-gutter'd even after mobile was fixed. Sitewide, not just WooCommerce
 * pages, since it's the same underlying gap; leaving other pages
 * edge-to-edge while only WooCommerce pages got gutters would be a worse
 * inconsistency. Desktop (1024px+) spacing is unaffected.
 */
@media (max-width: 1023px) {
	.ims-section,
	header.wp-block-group,
	footer.wp-block-group {
		padding-inline: 18px;
	}
}

/*
 * Header: site title, cart, nav toggle stay in one compact, non-wrapping
 * row (parts/header.html). WooCommerce automatically inserts a Mini Cart
 * and Customer Account icon into block-theme headers via its own Block
 * Hooks registration (triggered by add_theme_support('woocommerce')) —
 * confirmed by reading the actual rendered template: they were being
 * appended as extra flex siblings after the navigation block, and on
 * narrow screens flex-wrap caused the cart to wrap onto its own row.
 * Explicitly placing them in the template (as siblings of the
 * navigation, not nested inside it, so they stay visible even when the
 * nav collapses to a hamburger) gives full control over layout instead.
 */
.ims-header-row {
	flex-wrap: nowrap;
	gap: var(--wp--preset--spacing--s);
}

.ims-header-title {
	min-width: 0;
	flex-shrink: 1;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.ims-header-controls {
	flex-wrap: nowrap;
	align-items: center;
	gap: var(--wp--preset--spacing--xs);
	flex-shrink: 0;
}

.wp-block-woocommerce-customer-account,
.wp-block-woocommerce-mini-cart {
	display: flex;
	align-items: center;
	flex-shrink: 0;
}

.wc-block-mini-cart__button {
	display: flex;
	align-items: center;
	justify-content: center;
	background: none;
	border: none;
	padding: 0.5em;
	color: inherit;
	cursor: pointer;
}

.wc-block-customer-account__link {
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0.5em;
	color: inherit;
}

/* Account icon may be hidden while the header runs in its compact
   (hamburger) mode if space is tight; cart and the nav toggle always
   stay. Matches the widened hamburger breakpoint below (up to 1023px). */
@media (max-width: 1023px) {
	.ims-header-account {
		display: none;
	}
}

/*
 * Core's own navigation/style.min.css hard-codes its inline-nav /
 * hamburger-toggle breakpoint at exactly 600px (`@media (min-width:600px)`,
 * not exposed as a configurable block attribute) — confirmed by reading
 * wp-includes/blocks/navigation/style.min.css directly. At 768px that put
 * the FULL inline link list (Work/Services/About/Process/Contact/Request a
 * Quote) in the same row as the site title and cart, leaving so little
 * room that .ims-header-title's own text-overflow:ellipsis kicked in
 * ("Ivan M..."). Overriding core's breakpoint here so the compact
 * hamburger mode (already used below 600px) extends through the 1023px
 * tablet range too, matching this stylesheet's other tablet/mobile
 * breakpoints — the site title gets its own row's-worth of room back, and
 * the .is-menu-open overlay rules above/below already apply at any width
 * since they're keyed off the "menu is currently open" state, not a
 * viewport size.
 */
@media (min-width: 600px) and (max-width: 1023px) {
	.wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex !important;
	}

	.wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open) {
		display: none !important;
	}
}

/*
 * Mobile navigation overlay: full width, generous internal padding,
 * left-aligned readable links, nothing clipped. Core's default overlay
 * otherwise inherits this block's own "justifyContent":"right" (set for
 * the collapsed desktop row) into the open overlay too, pushing links
 * toward the right edge and clipping longer labels like "Request a Quote".
 *
 * Padding is set on the actual inner container
 * (.wp-block-navigation__responsive-container-content, where core renders
 * the scrollable menu body) rather than only the outer .is-menu-open
 * wrapper, since core's own container/item rules can otherwise win the
 * cascade on that inner element and leave links sitting closer to the
 * edge than the outer wrapper's padding alone would suggest. The outer
 * wrapper keeps box-sizing/width control but no horizontal padding of its
 * own, so there is exactly one source of truth for the inset.
 */
.wp-block-navigation__responsive-container.is-menu-open {
	width: 100vw;
	max-width: 100%;
	box-sizing: border-box;
}

.wp-block-navigation__responsive-container.is-menu-open * {
	box-sizing: border-box;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content {
	width: 100%;
	align-items: stretch;
	padding-inline: 30px;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container {
	width: 100%;
	align-items: stretch;
	padding-inline: 0;
	margin-inline: 0;
	/* Core's own navigation/style.min.css sets `gap: inherit` on this exact
	   element via a selector chain using :where() (which core relies on
	   for zero added specificity, but still out-classes this 3-class rule
	   on raw class count since :where()'s zero-specificity contents don't
	   subtract from the classes outside it) — confirmed by reading the
	   computed matched-rules list, not guessed. That inherited value
	   resolves to a real ~24px, which then stacks with the explicit
	   margin-top below (24px + 20px = 44px measured — well past the
	   18-22px target). !important is the only way to reliably win against
	   a core rule of higher effective specificity without copying its
	   full :where()-laden selector chain. */
	gap: 0 !important;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item {
	width: 100%;
	padding-inline: 0;
	margin-inline: 0;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item:not(:first-child) {
	margin-top: 20px;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
	display: block;
	width: 100%;
	text-align: left;
	padding: 0;
	font-size: 20px;
	line-height: 1.3;
}

/*
 * Services, packages, credibility stats: dividers and typography instead of
 * shadowed cards. Applies to any wide column row inside a section (services
 * list, package comparison rows, homepage credibility stats).
 */
.ims-section .wp-block-columns.alignwide > .wp-block-column {
	border-top: 1px solid var(--wp--preset--color--soft-neutral);
	padding-top: var(--wp--preset--spacing--m);
}

/*
 * Package comparison rows (AI/Product/Jewelry/Fashion "Choose a Package"
 * grids): equal-height flex-column cards so a longer title or price on one
 * card doesn't push its CTA lower than its siblings' in the same row.
 *
 * Matched two ways on purpose: the new .ims-packages class (added to
 * packages_row() going forward) AND a :has(.wp-block-buttons) structural
 * selector, which matches this same markup on the four canonical pages'
 * *already-stored* content without needing any migration or plugin
 * reactivation — Services/Retouching-overview/credibility rows use the same
 * .wp-block-columns.alignwide wrapper but never contain a button block, so
 * they're correctly excluded either way.
 *
 * No fixed card height anywhere here — .wp-block-column already stretches
 * to the tallest sibling by default (flexbox's own align-items: stretch),
 * this only makes each card's own content fill that stretched height in a
 * controlled way: title/price get a reserved minimum, the feature list
 * grows to absorb the remaining space, and the CTA is pinned to the
 * bottom with margin-top: auto.
 */
.ims-packages > .wp-block-column,
.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column {
	display: flex;
	flex-direction: column;
}

.ims-packages > .wp-block-column > .wp-block-heading,
.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .wp-block-heading {
	min-height: calc(1.3em * 2);
	line-height: 1.3;
}

.ims-packages > .wp-block-column > .is-style-eyebrow,
.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .is-style-eyebrow {
	min-height: 1.5em;
	margin-bottom: var(--wp--preset--spacing--s);
}

.ims-packages > .wp-block-column > .wp-block-list,
.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .wp-block-list {
	flex-grow: 1;
}

.ims-packages > .wp-block-column > .wp-block-buttons,
.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .wp-block-buttons {
	margin-top: auto;
}

/*
 * Mobile package cards (below 768px): the desktop/tablet rule above
 * (.ims-packages > .wp-block-column etc.) equal-heights each card via
 * flex-column + reserved title/price min-height + a flex-grow feature
 * list + margin-top: auto on the CTA — correct when cards sit side by
 * side, but on a stacked single-column mobile layout there are no
 * siblings to match, so those same reservations just show up as dead
 * air between title/price/list/CTA. Everything below resets that back to
 * natural content height and replaces it with an explicit, deliberate
 * mobile rhythm (title -> price -> list -> CTA), and separately bumps
 * mobile type sizes for readability. Desktop/tablet (768px+) rules above
 * this media query are untouched.
 */
@media (max-width: 767px) {
	.ims-packages > .wp-block-column,
	.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column {
		display: block;
	}

	.ims-packages > .wp-block-column > .wp-block-heading,
	.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .wp-block-heading {
		min-height: 0;
		font-size: 21px;
		line-height: 1.25;
		margin-bottom: 16px;
	}

	.ims-packages > .wp-block-column > .is-style-eyebrow,
	.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .is-style-eyebrow {
		min-height: 0;
		font-size: 19px;
		line-height: 1.25;
		font-weight: 600;
		margin-bottom: 20px;
	}

	.ims-packages > .wp-block-column > .wp-block-list,
	.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .wp-block-list {
		flex-grow: 0;
		font-size: 17px;
		line-height: 1.5;
		margin-bottom: 20px;
	}

	.ims-packages > .wp-block-column > .wp-block-buttons,
	.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .wp-block-buttons {
		margin-top: 0;
		width: 100%;
	}

	.ims-packages .wp-block-button,
	.wp-block-columns.alignwide:has(.wp-block-buttons) .wp-block-button {
		width: 100%;
	}

	.ims-packages .wp-block-button__link,
	.wp-block-columns.alignwide:has(.wp-block-buttons) .wp-block-button__link {
		width: 100%;
		box-sizing: border-box;
		white-space: nowrap;
		min-height: 48px;
		font-size: 16px;
		justify-content: center;
	}

	/*
	 * "Packages" / "Included" / "Scope" / "Priority Delivery" headings sit
	 * in the same .ims-section as the package row itself (see
	 * content_ai()/content_product_retouching()/etc. in class-imsc-pages.php,
	 * which build heading + packages_row + a following list in one
	 * self::section() call) — :has(.ims-packages) scopes this to only that
	 * section's own headings, not the page's H1 (a separate intro section)
	 * or unrelated FAQ/quote headings (their own separate sections).
	 */
	.ims-section:has(.ims-packages) > .wp-block-heading {
		font-size: 23px;
		line-height: 1.25;
	}
}

/*
 * Heading/section entrance reveal (assets/js/reveal.js adds "is-in-view" once
 * an .ims-reveal element scrolls into view). Reduced-motion / small-screen
 * visitors get "is-in-view" immediately with no observed delay, and the
 * transition itself collapses to near-zero under prefers-reduced-motion below.
 */
.ims-reveal {
	opacity: 0;
	transform: translateY(12px);
	transition: opacity var(--wp--custom--motion--duration-base) var(--wp--custom--motion--easing),
		transform var(--wp--custom--motion--duration-base) var(--wp--custom--motion--easing);
}

.ims-reveal.is-in-view {
	opacity: 1;
	transform: translateY(0);
}

/* Button arrow: decorative, CSS-generated (does not alter approved CTA copy) */
.wp-block-button__link {
	display: inline-flex;
	align-items: center;
	gap: 0.5em;
}

.wp-block-button__link::after {
	content: "\2192";
	transition: transform var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--easing);
}

.wp-block-button__link:hover::after,
.wp-block-button__link:focus-visible::after {
	transform: translateX(4px);
}

/* Quote/contact forms (rendered server-side by IMSC_Quote_Forms) */
.imsc-form {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--s);
	max-width: 640px;
}

.imsc-form__field {
	display: flex;
	flex-direction: column;
	gap: 0.35em;
	margin: 0;
}

.imsc-form__field label {
	font-weight: 600;
}

.imsc-form__field input:not([type="checkbox"]),
.imsc-form__field textarea,
.imsc-form__field select {
	width: 100%;
	padding: 0.6em 0.75em;
	border: 1px solid var(--wp--preset--color--mid-neutral);
	background: var(--wp--preset--color--white);
	color: var(--wp--preset--color--ink);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--medium);
}

.imsc-form__field--checkbox label {
	display: flex;
	align-items: flex-start;
	gap: 0.5em;
	font-weight: 400;
}

.imsc-form__errors {
	border-left: 3px solid var(--wp--preset--color--ink);
	background: var(--wp--preset--color--soft-neutral);
	padding: var(--wp--preset--spacing--s) var(--wp--preset--spacing--m);
}

.imsc-form__errors ul {
	margin: 0.5em 0 0;
	padding-inline-start: 1.25em;
}

.imsc-form--success {
	border-left: 3px solid var(--wp--preset--color--ink);
	padding: var(--wp--preset--spacing--s) var(--wp--preset--spacing--m);
}

/* Honeypot: off-screen (not display:none) and excluded from tab order/AT via markup */
.imsc-form__honeypot {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.imsc-form__submit {
	margin: var(--wp--preset--spacing--xs) 0 0;
}

.imsc-form--success > p {
	margin: 0;
}

/* FAQ accordions: native <details>/<summary>, no JS required */
.imsc-faq-item {
	border-top: 1px solid var(--wp--preset--color--soft-neutral);
	padding-block: var(--wp--preset--spacing--s);
}

.imsc-faq-item summary {
	cursor: pointer;
	font-weight: 600;
}

.imsc-faq-item p {
	margin-top: var(--wp--preset--spacing--xs);
}

/*
 * WooCommerce (shop archive, single product, cart, checkout, account):
 * CSS-only against WooCommerce's own documented, stable core classes.
 * Deliberately not shipping custom archive-product.html/single-product.html
 * block templates — WooCommerce supplies its own default block templates
 * for block themes, and authoring replacements without WooCommerce
 * installed in this environment to verify actual markup risked guessing
 * wrong. This can be revisited with real templates once WooCommerce is
 * installed and its default rendering has actually been seen.
 *
 * Two DIFFERENT product-grid markups exist and both are styled below,
 * confirmed by reading WooCommerce 10.9.4's own source in this Local site
 * (wp-content/plugins/woocommerce/templates and /src/Blocks/BlockTypes):
 *
 * 1. Classic `ul.products li.product` — used by the [products skus="..."]
 *    shortcode on /order-online/ (templates/loop/loop-start.php).
 * 2. WooCommerce Blocks `ul.wc-block-product-template > li` — used by the
 *    default Shop archive, because this theme has no custom
 *    archive-product.html and WooCommerce falls back to its own
 *    "blockified" block-theme template (templates/templates/blockified/
 *    archive-product.html), built from the woocommerce/product-collection
 *    block, not the classic loop at all.
 *
 * !important is used only on the grid-template-columns/width declarations
 * that WooCommerce's own bundled CSS was verified to otherwise win:
 * - assets/css/woocommerce-layout.css sets `li.product { float:left;
 *   width:22.05% }` at the SAME selector specificity as this stylesheet's
 *   equivalent rule, so the winner depends on enqueue order, not intent.
 * - assets/css/woocommerce-smallscreen.css (WooCommerce's own <768px
 *   stylesheet) sets `ul.products[class*=columns-] li.product { float:left;
 *   width:48% }` — the [class*=columns-] attribute selector gives it HIGHER
 *   specificity than a plain `.woocommerce ul.products li.product` rule, so
 *   it wins outright regardless of load order. This was the confirmed cause
 *   of "2 per row" on /order-online/ persisting at mobile widths.
 */

/* --- 1. Classic markup (/order-online/) --- */
.woocommerce ul.products,
.woocommerce-page ul.products {
	display: grid !important;
	grid-template-columns: repeat(4, 1fr) !important;
	gap: var(--wp--preset--spacing--l) var(--wp--preset--spacing--m);
	list-style: none;
	margin: 0;
	padding: 0;
}

/* 2 columns only in the tablet range (768-1023px), where each card still
   has enough width for its image/title/price/button to stay readable and
   the button text not to wrap. Below that, 1 column. */
@media (max-width: 1023px) {
	.woocommerce ul.products,
	.woocommerce-page ul.products {
		grid-template-columns: repeat(2, 1fr) !important;
	}
}

@media (max-width: 767px) {
	.woocommerce ul.products,
	.woocommerce-page ul.products {
		grid-template-columns: 1fr !important;
	}
}

.woocommerce ul.products li.product,
.woocommerce-page ul.products li.product {
	display: flex !important;
	flex-direction: column;
	float: none !important;
	clear: none !important;
	width: 100% !important;
	min-width: 0 !important;
	margin: 0 !important;
	height: 100%;
	border-top: 1px solid var(--wp--preset--color--soft-neutral);
	padding-top: var(--wp--preset--spacing--m);
}

.woocommerce ul.products li.product:nth-child(2n) {
	float: none !important;
}

/* WooCommerce wraps the image/title/price in one anchor, first child of the <li> */
.woocommerce ul.products li.product > a:first-child {
	display: block;
	color: inherit;
	text-decoration: none;
}

.woocommerce ul.products li.product img {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 5;
	object-fit: cover;
	background: var(--wp--preset--color--soft-neutral);
	margin-bottom: var(--wp--preset--spacing--s);
}

/*
 * WooCommerce's own placeholder (wc_placeholder_img(), used whenever a
 * product has no featured image — currently true for all 12 seeded
 * packages) renders with class="woocommerce-placeholder wp-post-image",
 * distinguishing it from a real featured image, which never carries
 * "woocommerce-placeholder". Hiding just that element turns the card
 * into a deliberate text-first layout (divider, title, price, button)
 * instead of a large empty gray box — consistent with this site's
 * "dividers and typography instead of shadowed/placeholder boxes"
 * language used for services and package cards elsewhere. A product that
 * later receives a real featured image renders it normally; this rule
 * simply never matches that image.
 */
.woocommerce ul.products li.product img.woocommerce-placeholder {
	display: none;
}

/*
 * Same placeholder, caught everywhere else it can appear (Cart/Checkout
 * line items, Checkout's order-summary sidebar) — those are rendered
 * client-side by WooCommerce Blocks' React components from the Store
 * API's own image data, not through the classic wc_placeholder_img() PHP
 * helper above, so they never carry the "woocommerce-placeholder" class
 * to select against. Every placeholder image WordPress/WooCommerce ever
 * serves ultimately points at the same underlying file regardless of
 * which component rendered the <img> tag, so matching on that filename
 * in the src is the one selector robust enough to cover both rendering
 * paths. A product that later receives a real featured image gets a
 * completely different src and is unaffected.
 */
img[src*="woocommerce-placeholder"] {
	display: none;
}

/*
 * Single product page (templates/single-product.html): when a product
 * has no real gallery image, WooCommerce's own product-image-gallery
 * block already marks its wrapper with the purpose-built
 * "woocommerce-product-gallery--without-images" class (confirmed by
 * reading the live rendered markup) — a more robust signal than matching
 * the placeholder image's src, since this class is present even before
 * any fallback <img> renders. Collapsing that column and letting the
 * product summary use the full content width satisfies "let the product
 * summary use the available content width" without needing a second,
 * per-product template. A product that later receives a real gallery
 * image never gets this class, so the two-column layout returns
 * automatically with no code changes.
 */
.wp-block-columns.alignwide:has(.woocommerce-product-gallery--without-images) > .wp-block-column:first-child {
	display: none;
}

.wp-block-columns.alignwide:has(.woocommerce-product-gallery--without-images) > .wp-block-column:last-child {
	flex-basis: 100% !important;
}

.woocommerce ul.products li.product .woocommerce-loop-product__title {
	margin: 0 0 0.5em;
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.3;
	/* Reserve room for up to 3 lines so titles of different lengths don't
	   shift the price/button position between cards in the same row. */
	min-height: calc(1.3em * 3);
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.woocommerce ul.products li.product .price {
	display: block;
	margin-bottom: var(--wp--preset--spacing--s);
}

.woocommerce ul.products li.product a.button,
.woocommerce ul.products li.product a.add_to_cart_button {
	display: block;
	margin-top: auto;
	width: 100% !important;
	box-sizing: border-box;
	text-align: center;
	white-space: nowrap !important;
	padding: 0.75em 1em;
}

.woocommerce span.price,
.woocommerce p.price {
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-size: var(--wp--preset--font-size--small);
}

.woocommerce div.product .woocommerce-tabs,
.woocommerce div.product .cart {
	margin-top: var(--wp--preset--spacing--m);
}

/* --- 2. WooCommerce Blocks markup (default /shop/ archive) ---
   Real classes confirmed from src/Blocks/BlockTypes/ProductImage.php,
   ProductPrice.php, and the compiled product-template-style.css: the
   product-collection block renders <ul class="wc-block-product-template">
   with plain <li> children, an image wrapped in
   .wc-block-components-product-image, a price wrapped in
   .wc-block-components-product-price, a core post-title block for the
   title, and a button wrapped in .wc-block-components-product-button
   (whose inner link WooCommerce explicitly sets white-space:normal on —
   overridden below with !important so it can't wrap). */
.wc-block-product-template {
	display: grid !important;
	grid-template-columns: repeat(4, 1fr) !important;
	gap: var(--wp--preset--spacing--l) var(--wp--preset--spacing--m) !important;
	list-style: none;
	margin: 0;
	padding: 0;
}

@media (max-width: 1023px) {
	.wc-block-product-template {
		grid-template-columns: repeat(2, 1fr) !important;
	}
}

@media (max-width: 767px) {
	.wc-block-product-template {
		grid-template-columns: 1fr !important;
	}
}

.wc-block-product-template > li {
	display: flex !important;
	flex-direction: column;
	width: 100% !important;
	min-width: 0 !important;
	max-width: 100% !important;
	flex-basis: auto !important;
	height: 100%;
	margin: 0 !important;
	border-top: 1px solid var(--wp--preset--color--soft-neutral);
	padding-top: var(--wp--preset--spacing--m);
}

.wc-block-components-product-image {
	margin-bottom: var(--wp--preset--spacing--s);
}

.wc-block-components-product-image img {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 5;
	object-fit: cover;
	background: var(--wp--preset--color--soft-neutral);
}

.wc-block-product-template .wp-block-post-title {
	margin: 0 0 0.5em;
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.3;
	min-height: calc(1.3em * 3);
	display: -webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.wc-block-components-product-price {
	margin-bottom: var(--wp--preset--spacing--s);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-size: var(--wp--preset--font-size--small);
}

.wc-block-components-product-button {
	margin-top: auto !important;
	width: 100%;
}

.wc-block-components-product-button .wp-block-button__link,
.wc-block-components-product-button__button {
	width: 100% !important;
	white-space: nowrap !important;
	text-align: center;
	box-sizing: border-box;
	padding: 0.75em 1em;
}

.woocommerce table.cart,
.woocommerce table.shop_table {
	width: 100%;
	border-collapse: collapse;
	/* If the table still can't fit narrow viewports, it scrolls within
	   itself rather than forcing the whole page to overflow horizontally. */
	display: block;
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
}

.woocommerce table.cart th,
.woocommerce table.cart td,
.woocommerce table.shop_table th,
.woocommerce table.shop_table td {
	border-top: 1px solid var(--wp--preset--color--soft-neutral);
	padding: var(--wp--preset--spacing--s);
	text-align: left;
}

.woocommerce form .form-row input.input-text,
.woocommerce form .form-row textarea,
.woocommerce form .form-row select {
	width: 100%;
	padding: 0.6em 0.75em;
	border: 1px solid var(--wp--preset--color--mid-neutral);
	background: var(--wp--preset--color--white);
	color: var(--wp--preset--color--ink);
	font-family: inherit;
}

.woocommerce-message,
.woocommerce-info,
.woocommerce-error,
.woocommerce-noreviews {
	/* Same treatment as the block notices above: white ground, hairline
	   box, and the heavy left edge kept as the status marker. */
	border: 1px solid var(--wp--preset--color--soft-neutral);
	border-left: 3px solid var(--wp--preset--color--ink);
	background: var(--wp--preset--color--white);
	padding: var(--wp--preset--spacing--s) var(--wp--preset--spacing--m);
	list-style: none;
}

.woocommerce a.button,
.woocommerce button.button,
.woocommerce input.button,
.woocommerce #respond input#submit {
	display: inline-flex;
	align-items: center;
	gap: 0.5em;
	background: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--paper);
	border: none;
	padding: 0.75em 1.5em;
	font-size: var(--wp--preset--font-size--small);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	cursor: pointer;
}

.woocommerce a.button:hover,
.woocommerce button.button:hover,
.woocommerce input.button:hover {
	/*
	 * Same dedicated darker value as .rank-math-breadcrumb above, for the
	 * same reason: mid-neutral (#8C8578) with the paper-colored button
	 * text on top is 3.33:1, fails WCAG AA. #726B5C keeps the paper text
	 * at 4.82:1. The base (non-hover) button background stays ink, so
	 * hover remains a clearly visible, distinct state.
	 */
	background: #726B5C;
}

.woocommerce-account .woocommerce-MyAccount-navigation ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--s);
	border-bottom: 1px solid var(--wp--preset--color--soft-neutral);
	padding-bottom: var(--wp--preset--spacing--s);
	margin-bottom: var(--wp--preset--spacing--m);
}

.woocommerce-result-count,
.woocommerce-ordering {
	margin: 0 0 var(--wp--preset--spacing--s);
}

.woocommerce-ordering select {
	max-width: 100%;
}

/*
 * WooCommerce mobile gutters. Two cases, gutter'd separately to avoid
 * doubling up where they'd otherwise nest inside each other:
 * - Shop/product archive and breadcrumb/result-count/ordering: these use
 *   WooCommerce's own default block template, not templates/page.html, so
 *   they never picked up the .ims-section gutter above.
 * - Cart/Checkout/My Account: these route through templates/page.html, so
 *   the page's own <main> is gutter'd instead of the nested .woocommerce div.
 * /order-online/ is unaffected either way — it's a plain Page (not a
 * WooCommerce archive/cart/checkout/account), already covered by the
 * sitewide .ims-section rule above.
 */
/* Gutters: mobile AND tablet (768-1023px), matching the .ims-section/header/
   footer rule above — this is what was missing at tablet widths. */
@media (max-width: 1023px) {
	.wc-block-breadcrumbs,
	.woocommerce-result-count,
	.woocommerce-ordering,
	.woocommerce-notices-wrapper,
	.wp-block-woocommerce-product-collection {
		padding-inline: 18px;
	}

	body.woocommerce-page:not(.woocommerce-cart):not(.woocommerce-checkout):not(.woocommerce-account) .woocommerce {
		padding-inline: 18px;
	}

	body.woocommerce-cart main.wp-block-group,
	body.woocommerce-checkout main.wp-block-group,
	body.woocommerce-account main.wp-block-group {
		padding-inline: 18px;
	}
}

/* Stacking the sort dropdown full-width only makes sense once there's no
   room beside the result count — mobile only, tablet has room for both. */
@media (max-width: 767px) {
	.woocommerce-ordering {
		display: block;
		width: 100%;
	}

	.woocommerce-ordering select {
		width: 100%;
	}
}

/*
 * Cart cross-sell grid horizontal overflow (found on the real empty Cart
 * page, only visible once Store Live was turned on — Coming Soon mode had
 * been hiding it entirely). WooCommerce's own assets/client/blocks/
 * all-products.css sets `.wc-block-grid__products { margin: 0 -8px 16px }`
 * — a standard negative-margin gutter technique that relies on a
 * containing element with matching positive padding to absorb the bleed.
 * The mobile/tablet gutter rule above only covers up to 1023px; at 1024px+
 * nothing absorbs it, so the grid's own -8px bleed became real page-level
 * horizontal scroll (confirmed via getBoundingClientRect() — scrollWidth
 * was exactly 8px past clientWidth on both sides). Removing the bleed
 * entirely at all widths is simpler and safer than widening the gutter
 * rule sitewide for one WooCommerce-owned grid; the only visual cost is
 * the first/last product losing an 8px flush-edge alignment nicety.
 */
.wc-block-grid__products {
	margin-inline: 0;
}


/* =========================================================
   VISUAL SYSTEM V2
   =========================================================
   Additive layer. Everything above this banner is pre-V2 and
   load-bearing (WooCommerce specificity counters, core Navigation
   breakpoint overrides, gutters, focus treatment, form and FAQ
   rules, defect-specific fixes) and is deliberately left intact.
   V2 introduces surfaces, layout registers and a typographic
   hierarchy as new, clearly-scoped classes rather than by
   rewriting existing rules.

   Tokens come from theme.json settings.custom.v2.* and therefore
   resolve as --wp--custom--v2--*. They are used in preference to
   --wp--preset--color--* because a Site Editor style variation can
   replace the preset palette wholesale, whereas settings.custom is
   deep-merged and survives a variation switch.
   ========================================================= */

/* ---------------------------------------------------------
   1. Surfaces
   Each surface sets its own background, foreground and the
   foreground of everything conventionally coloured inside it
   (links, headings, buttons, hairlines), so a section can be
   dropped onto any surface without per-element overrides.
   Contrast, measured against WCAG 2.1 AA:
     ink on paper .............. 17.0:1
     paper on ink .............. 17.0:1
     quiet on paper ............ 4.82:1
     quiet on ink .............. ~8.1:1
     white on cobalt ........... 6.80:1
     ink on coral .............. 4.93:1   (white would be 3.83:1 — rejected)
     ink on acid ............... 10.97:1
     white on violet ........... 10.41:1
   --------------------------------------------------------- */
.ims-surface {
	background-color: var(--ims-surface-bg);
	color: var(--ims-surface-fg);
	--ims-hairline: var(--ims-surface-hairline);
	--ims-quiet: var(--ims-surface-quiet);
}

.ims-surface--paper {
	--ims-surface-bg: var(--wp--custom--v2--surface--paper);
	--ims-surface-fg: var(--wp--custom--v2--surface--on-paper);
	--ims-surface-hairline: var(--wp--custom--v2--surface--hairline);
	--ims-surface-quiet: var(--wp--custom--v2--surface--quiet);
}

.ims-surface--ink {
	--ims-surface-bg: var(--wp--custom--v2--surface--ink);
	--ims-surface-fg: var(--wp--custom--v2--surface--on-ink);
	--ims-surface-hairline: var(--wp--custom--v2--surface--hairline-on-ink);
	--ims-surface-quiet: var(--wp--custom--v2--surface--quiet-on-ink);
}

.ims-surface--cobalt {
	--ims-surface-bg: var(--wp--custom--v2--accent--cobalt);
	--ims-surface-fg: var(--wp--custom--v2--accent--on-cobalt);
	--ims-surface-hairline: rgba(255, 255, 255, 0.28);
	--ims-surface-quiet: rgba(255, 255, 255, 0.78);
}

.ims-surface--coral {
	--ims-surface-bg: var(--wp--custom--v2--accent--coral);
	--ims-surface-fg: var(--wp--custom--v2--accent--on-coral);
	--ims-surface-hairline: rgba(20, 20, 20, 0.28);
	--ims-surface-quiet: rgba(20, 20, 20, 0.78);
}

.ims-surface--acid {
	--ims-surface-bg: var(--wp--custom--v2--accent--acid);
	--ims-surface-fg: var(--wp--custom--v2--accent--on-acid);
	--ims-surface-hairline: rgba(20, 20, 20, 0.28);
	--ims-surface-quiet: rgba(20, 20, 20, 0.78);
}

.ims-surface--violet {
	--ims-surface-bg: var(--wp--custom--v2--accent--violet);
	--ims-surface-fg: var(--wp--custom--v2--accent--on-violet);
	--ims-surface-hairline: rgba(255, 255, 255, 0.28);
	--ims-surface-quiet: rgba(255, 255, 255, 0.78);
}

/* Inherit the surface foreground for the elements theme.json
   otherwise pins to ink. Scoped to .ims-surface so nothing
   outside a V2 surface is affected. */
.ims-surface :where(h1, h2, h3, h4, h5, h6),
.ims-surface :where(a):not(.wp-block-button__link):not(.wp-element-button) {
	color: inherit;
}

.ims-surface :where(hr, .wp-block-separator) {
	border-color: var(--ims-hairline);
	color: var(--ims-hairline);
}

/* On a non-paper surface the default ink-on-paper button would
   disappear or clash; invert it to the surface foreground. */
.ims-surface--ink :where(.wp-block-button__link, .wp-element-button),
.ims-surface--cobalt :where(.wp-block-button__link, .wp-element-button),
.ims-surface--coral :where(.wp-block-button__link, .wp-element-button),
.ims-surface--acid :where(.wp-block-button__link, .wp-element-button),
.ims-surface--violet :where(.wp-block-button__link, .wp-element-button) {
	background-color: var(--ims-surface-fg);
	color: var(--ims-surface-bg);
}

.ims-surface--ink :where(.wp-block-button__link, .wp-element-button):hover,
.ims-surface--cobalt :where(.wp-block-button__link, .wp-element-button):hover,
.ims-surface--coral :where(.wp-block-button__link, .wp-element-button):hover,
.ims-surface--acid :where(.wp-block-button__link, .wp-element-button):hover,
.ims-surface--violet :where(.wp-block-button__link, .wp-element-button):hover {
	background-color: transparent;
	color: var(--ims-surface-fg);
	box-shadow: inset 0 0 0 1px var(--ims-surface-fg);
}

/* The pre-V2 focus treatment is an ink outline plus a paper halo,
   tuned for a light page. On a dark or accent surface the halo is
   the wrong colour, so the same two-ring idea is re-expressed in
   surface terms: the outline takes the surface foreground and the
   halo takes the surface background. This preserves the existing
   "at least one ring is always high-contrast" guarantee rather
   than replacing it. */
.ims-surface :where(a, button, input, textarea, select, [tabindex]):focus-visible,
.ims-surface .wp-block-navigation-item__content:focus-visible {
	outline-color: var(--ims-surface-fg);
	box-shadow: 0 0 0 4px var(--ims-surface-bg);
}

/* ---------------------------------------------------------
   2. Layout registers
   EDGE  visual sections, close to viewport width
   WIDE  structured supporting content (1600px)
   READ  long-form copy, FAQs, forms, transactional (740px)
   theme.json's contentSize stays at 740px — the registers are
   opt-in classes, not a global width change, so database-seeded
   page content keeps its current measure untouched.
   --------------------------------------------------------- */
.ims-edge {
	width: 100%;
	max-width: none;
	margin-inline: 0;
	padding-inline: var(--wp--custom--v2--register--gutter);
}

.ims-edge--bleed {
	padding-inline: 0;
}

.ims-wide {
	width: 100%;
	max-width: var(--wp--custom--v2--register--wide);
	margin-inline: auto;
	padding-inline: var(--wp--custom--v2--register--gutter);
}

.ims-read {
	width: 100%;
	max-width: var(--wp--custom--v2--register--read);
	margin-inline: auto;
	padding-inline: var(--wp--custom--v2--register--gutter);
}

/*
 * Text gutter.
 *
 * The governing rule of the finished system is: photography can reach the
 * edge, text should breathe. .ims-edge is the photography inset — small on
 * purpose, so images sit close to the viewport edge. .ims-textblock is its
 * counterpart for anything a reader reads: a wider, protected inset that
 * keeps copy off the glass.
 *
 * Deliberately a sibling of .ims-edge rather than an addition to it. The two
 * are never combined on the same element, so the paddings can never stack
 * and every wrapper has exactly one source of horizontal inset.
 */
.ims-textblock {
	width: 100%;
	max-width: none;
	margin-inline: 0;
	padding-inline: var(--wp--custom--v2--register--text-gutter);
}

/* Vertical rhythm for V2 sections. Deliberately a separate class
   from the pre-V2 .ims-section, which database-seeded page content
   already uses and which must keep its current spacing. */
/* ---------------------------------------------------------
   3. Typography hierarchy
   Instrument Sans, variable: wght 400-700, wdth 75-100.
   The width axis is applied only where it is asked for
   (.ims-statement--condensed), never mechanically to every
   heading — see docs/VISUAL-SYSTEM-V2.md section 6.
   --------------------------------------------------------- */
.ims-statement {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--statement);
	font-weight: 600;
	line-height: var(--wp--custom--v2--type--statement-leading);
	letter-spacing: var(--wp--custom--v2--type--statement-tracking);
	margin-block: 0;
	text-wrap: balance;
	/* At statement scale a single long unbroken word can be wider than the
	   viewport. Breaking it is preferable to clipping or to forcing a
	   horizontal scrollbar on the whole page. */
	overflow-wrap: break-word;
}

/* All-caps settings have no descenders, so they can close up
   further than the mixed-case default without collision. */
.ims-statement--caps {
	text-transform: uppercase;
	line-height: 0.88;
}

/* Opt-in width-axis compression, for a statement that should fill
   its measure. Guarded by @supports so a browser without variable
   font support renders the normal width rather than a synthesised
   one. */
@supports (font-variation-settings: "wdth" 90) {
	.ims-statement--condensed {
		font-stretch: 88%;
	}
}

.ims-statement--heavy {
	font-weight: 700;
}

.ims-headline {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--headline);
	font-weight: 600;
	line-height: var(--wp--custom--v2--type--headline-leading);
	letter-spacing: var(--wp--custom--v2--type--headline-tracking);
	margin-block: 0;
	text-wrap: balance;	overflow-wrap: break-word;
}

.ims-headline--light {
	font-weight: 500;
}

.ims-body {
	font-size: clamp(1.0625rem, 0.4vw + 1rem, 1.1875rem);
	line-height: var(--wp--custom--v2--type--body-leading);
	max-width: 68ch;
}

/* Project titles are treated as graphic captions rather than as
   body-scale headings. */
/* V2 metadata. The pre-V2 .is-style-eyebrow block style is
   deliberately kept and reused for editor-authored content; this
   is its template-side equivalent, with a 12px floor because 11px
   is below comfortable reading size, especially at reduced
   opacity. */
.ims-meta {
	font-size: clamp(0.75rem, 0.2vw + 0.7rem, 0.8125rem);
	line-height: 1.4;
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--v2--type--meta-tracking);
	font-weight: 400;
	margin: 0;
}

.ims-meta--quiet {
	color: var(--ims-quiet, var(--wp--custom--v2--surface--quiet));
}

/* Negative tracking is a display-size device only. Never let it
   reach metadata, which needs positive tracking to stay legible. */
.ims-meta,
.ims-meta--quiet {
	letter-spacing: var(--wp--custom--v2--type--meta-tracking);
}

/*
 * Scale-jump spacing.
 *
 * A metadata line sitting immediately above a statement or headline is the
 * sharpest size change in the whole system — roughly 12px against up to
 * 128px. At the container's ordinary gap the two read as one clamped
 * block rather than as two deliberately contrasted levels. This adds air
 * on exactly that adjacency and nowhere else, so the contrast stays but
 * the transition is legible as intent.
 *
 * One rule rather than per-page margins: it covers the homepage opening,
 * each service panel and the Single Work intro, because all three use the
 * same metadata-then-statement pairing.
 */
.ims-meta:has(+ .ims-statement),
.ims-meta:has(+ .ims-headline) {
	margin-bottom: clamp(0.5rem, 1.4vw, 1.25rem);
}

/* A hairline that takes its colour from the current surface. */
.ims-rule {
	border: 0;
	border-top: 1px solid var(--ims-hairline, var(--wp--custom--v2--surface--hairline));
	margin: 0;
}


/* ---------------------------------------------------------
   4. Header
   Keeps the pre-V2 class names (.ims-header-row,
   .ims-header-title, .ims-header-controls, .ims-header-account,
   .ims-header-cart) so every existing header rule above —
   including the WooCommerce Block Hooks placement fix and the
   core Navigation breakpoint override — continues to apply
   unchanged.
   --------------------------------------------------------- */
.ims-v2-header {
	position: relative;
	z-index: 10;
	padding-block: clamp(14px, 1.4vw, 22px);
	border-bottom: 1px solid var(--ims-hairline);
}

.ims-v2-nav .wp-block-navigation-item__content {
	font-size: clamp(0.75rem, 0.25vw + 0.7rem, 0.8125rem);
	text-transform: uppercase;
	letter-spacing: 0.1em;
	text-decoration: none;
	position: relative;
	padding-block: 4px;
}

/* Underline drawn from the left on hover/focus rather than a colour
   change, so the label itself never loses contrast. */
.ims-v2-nav .wp-block-navigation-item__content::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 1px;
	background: currentColor;
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--easing);
}

.ims-v2-nav .wp-block-navigation-item__content:hover::after,
.ims-v2-nav .wp-block-navigation-item__content:focus-visible::after,
.ims-v2-nav .current-menu-item > .wp-block-navigation-item__content::after {
	transform: scaleX(1);
}

/* Info submenu. Core positions this itself; only its surface is set
   here, with no radius and no shadow, matching the sitewide rule. */
.ims-v2-nav .wp-block-navigation__submenu-container {
	background-color: var(--ims-surface-bg);
	color: var(--ims-surface-fg);
	border: 1px solid var(--ims-hairline);
	border-radius: 0;
	box-shadow: none;
	min-width: 12rem;
	padding-block: 0.5rem;
}

/*
 * WooCommerce cart and account, visually demoted.
 *
 * The cart is never hidden: /order-online/ is a plain Page carrying a
 * [products] shortcode, so WooCommerce does not add a woocommerce-page
 * body class there, and a body-class-driven hide would remove the cart
 * from the one catalogue page most likely to need it. It is demoted
 * instead — quiet colour, no chrome, smaller than the CTA beside it.
 *
 * The account icon was already hidden below 1023px by the pre-V2 rule
 * above. That is extended here to "hidden unless this is genuinely an
 * account/checkout context", using only WooCommerce's own documented
 * body classes. No PHP, no template change, so the Block Hooks
 * placement fix is untouched and the block still renders for the
 * contexts that need it.
 */
.ims-header-cart .wc-block-mini-cart__button {
	color: var(--ims-quiet);
	transition: color var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--easing);
}

.ims-header-cart .wc-block-mini-cart__button:hover,
.ims-header-cart .wc-block-mini-cart__button:focus-visible {
	color: var(--ims-surface-fg);
}

.ims-header-account {
	display: none;
}

body.woocommerce-cart .ims-header-account,
body.woocommerce-checkout .ims-header-account,
body.woocommerce-account .ims-header-account,
body.single-product .ims-header-account,
body.post-type-archive-product .ims-header-account {
	display: flex;
}

/* Keep the account icon suppressed on narrow screens even in those
   contexts, matching the pre-V2 behaviour that freed room for the
   site title. */
@media (max-width: 1023px) {
	body.woocommerce-cart .ims-header-account,
	body.woocommerce-checkout .ims-header-account,
	body.woocommerce-account .ims-header-account,
	body.single-product .ims-header-account,
	body.post-type-archive-product .ims-header-account {
		display: none;
	}
}

/* ---------------------------------------------------------
   12. Service, About, Process, Contact and Quote pages
   -----------------------------------------------------------
   Every one of these pages is database-seeded content that
   IMSC_Pages::upsert_page() deliberately never overwrites, so
   nothing here migrates or edits post_content. The whole surface
   is restyled through the classes that content already emits:
   .ims-section, .is-style-eyebrow, .ims-packages,
   .wp-block-columns.alignwide, .imsc-faq-item and .imsc-form.

   The pre-V2 package rules above (equal-height columns, reserved
   title/price heights, flex-grow lists, margin-top:auto CTAs and
   the whole mobile reset) are commerce-critical and are left
   completely untouched — only type, rule and colour are changed
   here.
   --------------------------------------------------------- */
/*
 * Database-seeded page sections take the text gutter.
 *
 * The pre-V2 rule only gave .ims-section horizontal padding below 1024px,
 * so from 1024px up its alignwide children — the package comparison rows,
 * the services row, the credibility row — ran the full width of the
 * viewport with nothing between them and the glass. The constrained 740px
 * children were centred and looked fine, which is why it went unnoticed.
 *
 * This is the same value the rest of V2 uses, and at mobile it resolves to
 * 18px, matching the pre-V2 rule it supersedes exactly, so nothing changes
 * at the width that rule was written for.
 */
.ims-page .ims-section {
	position: relative;
	/*
	 * The fallback matters here. This token is bridged onto body.ims-world
	 * only, so on a service, process or catalogue page the declaration was
	 * invalid at computed-value time and dropped — measured at 0px — and
	 * because this selector is (0,2,0) it also outranked the (0,1,0)
	 * sitewide 18px mobile gutter. The result was an H1 and body copy
	 * flush against the edge of a phone screen.
	 */
	padding-inline: var(--wp--custom--v2--register--text-gutter, clamp(18px, 3.2vw, 48px));
}

/* The alternating paper/soft-neutral banding that used to sit here is
   gone. It read as copy trapped inside large tinted panels, with the
   section's own inline padding looking like a cramped container edge.
   Structure on these pages now comes from typography, number hierarchy
   and white space. .ims-page only ever applies to the practical pages
   (services, process, quote, commerce), so removing the rule changes
   nothing on Home or in the portfolio. */

/* The opening section carries the page's H1. */
.ims-page .wp-block-post-content > .ims-section:first-of-type {
	padding-top: clamp(1.5rem, 3vw, 3rem);
	padding-bottom: var(--wp--custom--v2--rhythm--section);
}

.ims-page .ims-section > h1 {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(2.5rem, 7vw, 7rem);
	font-weight: 600;
	line-height: 0.96;
	letter-spacing: var(--wp--custom--v2--type--statement-tracking);
	margin-block: clamp(0.5rem, 1.5vw, 1.25rem);
	text-wrap: balance;
}

.ims-page .ims-section > h2 {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--headline);
	font-weight: 600;
	line-height: var(--wp--custom--v2--type--headline-leading);
	letter-spacing: var(--wp--custom--v2--type--headline-tracking);
	margin-top: 0;
	margin-bottom: clamp(1rem, 2vw, 1.75rem);
	text-wrap: balance;
}

.ims-page .ims-section > h3 {
	font-size: clamp(1.25rem, 1.6vw + 0.9rem, 1.75rem);
	line-height: 1.15;
	letter-spacing: -0.01em;
}

.ims-page .ims-section > p,
.ims-page .ims-section > ul,
.ims-page .ims-section > ol {
	font-size: clamp(1.0625rem, 0.4vw + 1rem, 1.1875rem);
	line-height: var(--wp--custom--v2--type--body-leading);
}

.ims-page .ims-section > p.is-style-eyebrow {
	font-size: clamp(0.75rem, 0.2vw + 0.7rem, 0.8125rem);
	letter-spacing: var(--wp--custom--v2--type--meta-tracking);
	color: var(--wp--custom--v2--surface--quiet);
	margin-bottom: 0;
}

/* Package cards: dividers and typography, never boxes. Layout mechanics
   are inherited from the pre-V2 rules and not restated. */
.ims-page .ims-packages > .wp-block-column,
.ims-page .wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column {
	border-top-width: 1px;
	border-top-color: var(--wp--custom--v2--surface--quiet);
}

.ims-page .ims-packages > .wp-block-column > .wp-block-heading {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	letter-spacing: -0.01em;
}

/* The price line, which the seeded content marks with .is-style-eyebrow.
   Given display treatment here because on a package card it is the single
   most consulted value. */
.ims-page .ims-packages > .wp-block-column > .is-style-eyebrow {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.5rem, 2vw + 1rem, 2.5rem);
	font-weight: 600;
	letter-spacing: -0.01em;
	text-transform: none;
	color: inherit;
	line-height: 1.1;
}

/* FAQ: hairline rows, larger question type, no chrome. */
.ims-page .imsc-faq-item {
	border-top-color: var(--wp--custom--v2--surface--quiet);
	padding-block: clamp(0.875rem, 1.6vw, 1.25rem);
}

.ims-page .imsc-faq-item summary {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.0625rem, 0.6vw + 0.95rem, 1.25rem);
	font-weight: 600;
	line-height: 1.3;
	letter-spacing: -0.005em;
}

.ims-page .imsc-faq-item p {
	font-size: clamp(1rem, 0.3vw + 0.95rem, 1.125rem);
	line-height: var(--wp--custom--v2--type--body-leading);
	max-width: 68ch;
}

/* ---------------------------------------------------------
   13. Forms
   Quieter than the portfolio, by design: neutral surface, no
   accent, strong typography, generous spacing. Every
   accessibility behaviour the form engine renders — labels,
   required markers, aria-invalid/aria-describedby wiring,
   role="alert" errors and role="status" success — is untouched;
   this changes appearance only.
   --------------------------------------------------------- */
.ims-page .imsc-form {
	gap: clamp(1rem, 2vw, 1.5rem);
	max-width: 680px;
}

.ims-page .imsc-form__field label {
	font-size: clamp(0.75rem, 0.2vw + 0.7rem, 0.8125rem);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-weight: 600;
	display: block;
	margin-bottom: 0.4em;
}

/* The required marker is decorative to a screen reader — the input's own
   required/aria-required attribute carries the meaning — but it must still
   be visible, and legible, to everyone else. */
.ims-page .imsc-form__field label [aria-hidden="true"] {
	color: var(--wp--custom--v2--surface--quiet);
	margin-left: 0.2em;
}

/* The consent control was the browser's 13px default — small to see on a
   white ground and small to hit on a phone. Sized up and given the ink
   accent so the checked state is unmistakable; the label, the link and the
   consent logic are untouched. */
.ims-page .imsc-form__field--checkbox input[type="checkbox"],
.ims-sales .imsc-form__field--checkbox input[type="checkbox"] {
	width: 1.15rem;
	height: 1.15rem;
	margin-top: 0.15em;
	flex: 0 0 auto;
	accent-color: var(--wp--preset--color--ink);
}

.ims-page .imsc-form__field--checkbox label {
	text-transform: none;
	letter-spacing: normal;
	font-size: clamp(0.9375rem, 0.3vw + 0.9rem, 1.0625rem);
	font-weight: 400;
}

/*
 * Fields read as fields.
 *
 * These were underline-only on a transparent background — consistent with
 * the sitewide "dividers and typography, not containers" language, and
 * genuinely elegant, but on a paper surface an empty input became almost
 * invisible: owner QA found people could not tell where to type. A quote
 * form is the one surface where affordance beats restraint, so each field
 * now has a real white ground and a full hairline. Still square, still no
 * shadow, still no accent — it is the same language, just legible.
 */
.ims-page .imsc-form__field input:not([type="checkbox"]),
.ims-page .imsc-form__field textarea,
.ims-page .imsc-form__field select,
.ims-sales .imsc-form__field input:not([type="checkbox"]),
.ims-sales .imsc-form__field textarea,
.ims-sales .imsc-form__field select {
	/*
	 * The fallback is the fix. This border resolved to nothing on every
	 * page outside the portfolio, so the declaration was dropped and the
	 * computed border-width was 0: a white field on what is now a white
	 * canvas, with nothing to show where to type. Measured at 0px on all
	 * three service pages before this change.
	 *
	 * .ims-sales is included so /request-a-quote/ gets the same field as
	 * the three service forms rather than the weaker 1px #8C8578 base.
	 */
	border: 1px solid var(--wp--custom--v2--surface--quiet, #726B5C);
	border-radius: 0;
	background: var(--wp--preset--color--white);
	color: var(--wp--preset--color--ink);
	padding: 0.65em 0.75em;
	font-size: clamp(1rem, 0.3vw + 0.95rem, 1.125rem);
	width: 100%;
}

/* Focus is a second ring rather than a colour change, so it survives
   forced-colours mode and never depends on hue alone. */
.ims-page .imsc-form__field input:not([type="checkbox"]):focus-visible,
.ims-page .imsc-form__field textarea:focus-visible,
.ims-page .imsc-form__field select:focus-visible,
.ims-sales .imsc-form__field input:not([type="checkbox"]):focus-visible,
.ims-sales .imsc-form__field textarea:focus-visible,
.ims-sales .imsc-form__field select:focus-visible {
	border-color: var(--wp--preset--color--ink);
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 2px;
}

/* A field the server marked invalid must not rely on colour alone, so the
   border also thickens. */
.ims-page .imsc-form__field [aria-invalid="true"],
.ims-sales .imsc-form__field [aria-invalid="true"] {
	border-width: 2px;
	border-color: var(--wp--preset--color--ink);
}

.ims-page .imsc-form__errors,
.ims-page .imsc-form--success {
	border-left-width: 3px;
	font-size: clamp(1rem, 0.3vw + 0.95rem, 1.125rem);
}


/* ---------------------------------------------------------
   14. Motion
   -----------------------------------------------------------
   CORE effects only, all CSS transitions driven by a class the
   IntersectionObserver in assets/js/reveal.js adds. No animation
   library, no scroll listener, no parallax, no custom cursor, no
   auto-advancing gallery, no page transitions.

   The pre-V2 .ims-reveal rules above still define the base
   transition; this only tunes travel per context. The global
   prefers-reduced-motion block at the end of this file collapses
   every transition here to 0.001ms with !important, and reveal.js
   independently applies the final state without observing, so
   reduced motion is honoured twice over.
   --------------------------------------------------------- */
.ims-reveal {
	transform: translateY(16px);
}

/* Narrow screens keep the reveal but travel less far. */
.ims-motion-compact .ims-reveal {
	transform: translateY(8px);
}

.ims-reveal.is-in-view,
.ims-motion-compact .ims-reveal.is-in-view {
	transform: translateY(0);
}

/* A statement heading is the one place a slightly longer, softer
   settle reads as intent rather than decoration. */
.ims-statement.ims-reveal {
	transition-duration: 500ms;
}

/* Surface transitions: a section that changes state should ease rather
   than jump. Applies to the deliberate cases only, not to every element. */
.ims-surface,


/* =========================================================
   VISUAL SYSTEM V3 — presentation
   =========================================================
   Replaces every Home, portfolio, project-card, archive,
   single-project and footer layout the V2 milestones produced.
   Those sections were removed rather than overridden, so this
   file no longer carries several generations of layout arguing
   with each other.

   Retained above, deliberately: the pre-V2 defect fixes
   (WooCommerce specificity counters, navigation breakpoint
   override, contrast values, gutters, form and FAQ rules), and
   the V2 foundations that are working — surfaces, the
   EDGE/WIDE/READ registers and the text gutter, the type
   hierarchy, the header, the database-seeded page rules and
   the motion primitives.

   The governing rule is unchanged: photography reaches the
   edge, text keeps its gutter.
   ========================================================= */

/* ---------------------------------------------------------
   17. Utilities the V3 compositions share
   --------------------------------------------------------- */
:root {
	--ims-shot-gap: clamp(8px, 0.9vw, 16px);
	--ims-field-edge: clamp(8px, 1vw, 16px);
}

/* One short sentence, never a paragraph block. Sits between the
   metadata and statement scales without inventing a new token. */
.ims-lede {
	font-size: clamp(1rem, 0.35vw + 0.95rem, 1.1875rem);
	line-height: 1.5;
	max-width: 46ch;
	margin: 0;
}

/* Links that read as metadata: no underline until wanted, then an
   underline that grows rather than appears. */
.ims-link a {
	text-decoration: none;
	color: inherit;
	background-image: linear-gradient(currentColor, currentColor);
	background-repeat: no-repeat;
	background-position: 0 100%;
	background-size: 100% 1px;
	padding-bottom: 3px;
	transition: background-size var(--wp--custom--motion--duration-base) var(--wp--custom--motion--easing);
}

.ims-link--strong a {
	background-size: 100% 2px;
}

@media (hover: hover) and (pointer: fine) {
	.ims-link a {
		background-size: 0% 1px;
	}

	.ims-link a:hover,
	.ims-link a:focus-visible {
		background-size: 100% 1px;
	}

	.ims-link--strong a {
		background-size: 100% 2px;
	}
}

/* Breadcrumbs stay for orientation and SEO but are reduced to one quiet
   line of metadata. (The base .ims-breadcrumbs rule is pre-V2 and still
   above; this is the modifier the V3 templates apply.) */
.ims-breadcrumbs--quiet {
	padding-top: 0;
	margin-bottom: clamp(0.75rem, 1.5vw, 1.5rem);
}

.ims-breadcrumbs--quiet .rank-math-breadcrumb {
	font-size: 12px;
	text-transform: uppercase;
	letter-spacing: 0.08em;
}

/*
 * The photograph object.
 *
 * Every dynamic image on the site — homepage moments, the archive
 * field, related work — is one of these. It never sets a height, an
 * aspect-ratio or object-fit, so a photograph is shown at the
 * proportions it was made in. Only its width is composed.
 */
.ims-shot {
	position: relative;
	min-width: 0;
}

.ims-shot .wp-block-post-featured-image {
	margin: 0;
	overflow: hidden;
}

.ims-shot img {
	display: block;
	width: 100%;
	height: auto;
	transition: transform 600ms var(--wp--custom--motion--easing);
}

@media (hover: hover) and (pointer: fine) {
	.ims-shot:hover img,
	.ims-shot:focus-within img {
		transform: scale(1.03);
	}

}

/*
 * Empty dynamic sections collapse to nothing, without relying on
 * :has(). Core emits a post-template element only when a query
 * actually returned posts, so hanging the vertical rhythm on that
 * element means a moment with no photograph contributes no padding,
 * no colour band and no gap — in every browser, with no selector
 * doing the work.
 */

/* =========================================================
   VISUAL SYSTEM V5 — two layers
   =========================================================
   The site stops trying to be a portfolio, a service
   explanation, a price list, a process page and a conversion
   funnel in one visual register.

   LAYER 1, the portfolio: monochrome poster Home, then three
   full-colour worlds. Desire, impact, identity. Almost no copy.

   LAYER 2, the sales side: paper, quiet, readable, information
   rich. Clarity, trust, conversion.

   One masthead serves both, carrying no surface of its own so it
   inherits whichever register the page is in.

   Everything above this banner is retained deliberately: the
   pre-V2 defect fixes, and the foundations that work — surfaces,
   registers, the text gutter, the type hierarchy, the shot
   object, the seeded-page rules, forms and motion primitives.
   ========================================================= */

:root {
	--ims-gap: clamp(10px, 0.9vw, 18px);
	--ims-edge: clamp(12px, 1.1vw, 22px);
}

/* ---------------------------------------------------------
   33. Display type
   -----------------------------------------------------------
   The poster and every heading that has to carry weight use
   Instrument Sans at the far end of its own axes: weight 700 and
   width 75%. That is a genuinely heavy condensed grotesk — the
   Impact-adjacent character the direction asks for — obtained
   from the variable font already licensed and self-hosted here.
   No second typeface is added, nothing is downloaded, and there
   is no licensing question to answer.

   Guarded by @supports so a browser without variable-font width
   support renders the normal width rather than a synthesised
   one.
   --------------------------------------------------------- */
.ims-display {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	line-height: 0.86;
	letter-spacing: -0.02em;
	text-transform: uppercase;
	margin: 0;
	overflow-wrap: break-word;
}

@supports (font-variation-settings: "wdth" 75) {
	.ims-display {
		font-stretch: 75%;
	}
}

/* ---------------------------------------------------------
   34. Masthead
   Compact, strong, no surface of its own, no rule beneath it.
   --------------------------------------------------------- */
.ims-mast {
	padding-inline: var(--ims-edge);
	padding-block: clamp(0.875rem, 1.4vw, 1.5rem);
	position: relative;
	z-index: 20;
}

.ims-mast__row {
	flex-wrap: nowrap;
	gap: var(--wp--preset--spacing--s);
	align-items: center;
}

.ims-mast__brand,
.ims-mast__brand a {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(0.9375rem, 1.1vw, 1.375rem);
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	text-decoration: none;
	color: inherit;
	white-space: nowrap;
}

.ims-mast__controls {
	flex-wrap: nowrap;
	align-items: center;
	gap: clamp(0.5rem, 1vw, 1rem);
	flex-shrink: 0;
	min-width: 0;
}

.ims-nav .wp-block-navigation-item__content {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(0.8125rem, 0.6vw + 0.55rem, 1.0625rem);
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	text-decoration: none;
	padding-block: 2px;
}

.ims-nav .wp-block-navigation__container {
	gap: clamp(0.875rem, 1.5vw, 2rem);
}

/* Submenus: same surface as the page, hairline edge, no radius,
   no shadow. Core handles the button semantics and keyboard
   behaviour; only the surface is set here. */
.ims-nav .wp-block-navigation__submenu-container {
	background-color: var(--ims-submenu-bg, var(--wp--preset--color--paper));
	color: var(--ims-submenu-fg, var(--wp--preset--color--ink));
	border: 1px solid currentColor;
	border-radius: 0;
	box-shadow: none;
	min-width: 11rem;
	padding-block: 0.4rem;
}

.ims-nav__cta .wp-block-navigation-item__content,
.ims-nav .ims-nav__cta > a {
	box-shadow: inset 0 0 0 1px currentColor;
	padding: 0.5em 0.9em;
}

/* Commerce stays reachable and deliberately quiet: metadata scale
   beside a navigation that is now much heavier. */
.ims-header-cart .wc-block-mini-cart__button {
	font-size: clamp(0.6875rem, 0.55vw, 0.8125rem);
	opacity: 0.75;
	padding: 0.35em;
}

.ims-header-cart .wc-block-mini-cart__button:hover,
.ims-header-cart .wc-block-mini-cart__button:focus-visible {
	opacity: 1;
}

/* ---------------------------------------------------------
   35. Home — the poster
   -----------------------------------------------------------
   A standalone graphic page. front-page.html renders neither the
   header nor the footer template part, so nothing is hidden with
   global CSS and every other template keeps both untouched.

   True white here and only here: the owner's WebP artwork is drawn
   against #FFFFFF, and the sitewide warm Paper token would show as
   a halo around it. The token itself is unchanged.

   ONE gutter token frames the page. The headline, both links and
   all three graphics share its left guide; the mark mirrors it on
   the right. They are never tuned independently.

   The whole composition is built to sit in one desktop screen. The
   artwork's cap height is therefore the smallest of three budgets:
   a width budget, a height budget and an absolute ceiling. The
   height budget subtracts everything above and below the artwork —
   both gutters, two headline lines, the link row and the gaps —
   from 100svh and divides what is left between the three words,
   with a margin for the inter-word gaps. The width budget counts
   RETOUCH at the peak of its hover zoom plus the mark, which is
   now cap-tall too, so the animation cannot reach the right guide.
   --------------------------------------------------------- */
body.ims-poster {
	background-color: #FFFFFF;
	color: var(--wp--preset--color--ink);

	--ims-home-poster-gutter: clamp(16px, 2vw, 32px);
	--ims-poster-colgap: clamp(16px, 2vw, 40px);
	--ims-poster-rowgap: clamp(4px, 0.45vw, 10px);
	--ims-poster-headline: clamp(2rem, 5.2vw, 5.5rem);
	--ims-poster-linkgap: clamp(20px, 2.2vw, 36px);
	--ims-poster-headgap: clamp(8px, 1vw, 14px);
	/* Guaranteed white space below the final row: reserved in the height
	   budget rather than added as padding, so it can never be eaten. */
	--ims-poster-bottom: clamp(20px, 2.5vw, 40px);

	/*
	 * Optical top trim. A line box at line-height 0.98 still carries the
	 * font's own internal leading above the capitals, so the statement's
	 * first pixel sat about 0.117em below the box it starts in. Measured
	 * from the real Instrument Sans metrics at the rendered size:
	 * half-leading -9.77px plus (ascender 77 - cap 58) at font-size 79px.
	 * Removing it puts the cap height of "Images" on the gutter line
	 * itself, which is what "pinned to the top-left frame" means.
	 */
	--ims-poster-headtrim: calc(0.117 * var(--ims-poster-headline));

	/*
	 * Everything on the page that is not one of the three words, plus the
	 * breathing room reserved beneath them. 1.96 is two headline lines at
	 * line-height 0.98; 30px is the metadata link row.
	 */
	--ims-poster-chrome: calc(
		2 * var(--ims-home-poster-gutter)
		+ 1.96 * var(--ims-poster-headline)
		+ var(--ims-poster-headgap)
		+ 30px
		+ var(--ims-poster-linkgap)
		+ 2 * var(--ims-poster-rowgap)
		+ var(--ims-poster-bottom)
	);

	/*
	 * Divided by 3.2 rather than 3.15, and against a lower ceiling: 5.0.3
	 * sized the words to the largest that would technically fit, which left
	 * the stack tall and the page with no visible air under it. The extra
	 * 0.05 and the reserved breathing room together take the cap to roughly
	 * 161px at 1536x864 — AI ~155px, PHOTO ~496px, RETOUCH ~690px — which is
	 * the compact scale the reference asks for.
	 */
	--ims-poster-cap: min(
		clamp(56px, 14vw, 240px),
		calc((100svh - var(--ims-poster-chrome)) / 3.2),
		calc(
			(100vw - 2 * var(--ims-home-poster-gutter)
				- var(--ims-poster-colgap)) / 5.45
		)
	);
}

.ims-poster-main {
	display: flex;
	flex-direction: column;
	min-height: 100svh;
	background-color: #FFFFFF;
	padding: var(--ims-home-poster-gutter);
	/* The page's own gutter is the only space above the statement. This
	   holds whether or not the surrounding block styles contribute a
	   top margin to top-level blocks, which is what put the composition
	   below its frame once the skip link preceded it. */
	margin-block: 0;
}

/* The headline and the two links, with the treatment restored from
   theme 4.0.0 (commit 1ef86d5). Only the class names differ, because
   .ims-mast now belongs to the global header, and the links reuse the
   untouched .ims-meta and .ims-link rules. */
.ims-home-intro {
	display: grid;
	gap: var(--ims-poster-headgap);
	max-width: min(100%, 90rem);
	/* Pull the statement's internal leading off the top so its capitals,
	   not its invisible line box, sit on the gutter line. */
	margin-top: calc(-1 * var(--ims-poster-headtrim));
}

.ims-home-say {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--ims-poster-headline);
	font-weight: 600;
	line-height: 0.98;
	letter-spacing: -0.03em;
	margin: 0;
	max-width: 16ch;
	overflow-wrap: break-word;
}

.ims-home-links {
	gap: clamp(1rem, 2.4vw, 2rem);
	margin-top: 0;
}

/*
 * The poster grid. The list is display: contents so its three items
 * become grid items in their own right, which is what lets the mark
 * share RETOUCH's row rather than being anchored to the viewport
 * bottom. The <ul> keeps an explicit role="list" in the pattern,
 * because that display value drops implicit list semantics in some
 * browsers.
 */
/*
 * Column one is the words' own width; column two is 1fr, so the space
 * between RETOUCH and the mark flexes and the mark is carried to the
 * right edge of the padded box — the mirrored right guide — rather than
 * sitting a fixed gap away from RETOUCH. align-content: start keeps the
 * stack compact at the top, so the reserved breathing room shows as real
 * white space beneath the final row.
 *
 * Qualified by .ims-poster-main deliberately. The <body> carries the
 * class ims-poster as well, so an unqualified .ims-poster matched the
 * body too and gave the whole document a grid display and a
 * padding-top of one link gap — 33px of blank white above the
 * statement on every desktop width, and a vertical scrollbar on a page
 * built to fit one screen. The body keeps the class for its own
 * page-level rules; only this composition rule is scoped away from it.
 */
.ims-poster-main .ims-poster {
	flex: 1 1 auto;
	display: grid;
	grid-template-columns: minmax(0, auto) 1fr;
	align-items: end;
	align-content: start;
	column-gap: var(--ims-poster-colgap);
	row-gap: var(--ims-poster-rowgap);
	padding-top: var(--ims-poster-linkgap);
}

.ims-poster__list {
	display: contents;
	list-style: none;
	margin: 0;
	padding: 0;
}

/* justify-self: start makes each item as wide as its own word rather
   than as wide as the column, which is RETOUCH's width. The hover
   target is then the artwork itself: pointing at the empty space to
   the right of AI no longer starts AI's animation. The column is still
   sized by RETOUCH, so the mark does not move. */
.ims-poster__item {
	grid-column: 1;
	justify-self: start;
	margin: 0;
	max-width: 100%;
}

.ims-poster__item--ai {
	grid-row: 1;
}

.ims-poster__item--photo {
	grid-row: 2;
}

.ims-poster__item--retouch {
	grid-row: 3;
}

/* Per-word optical hooks. Each word's left sidebearing differs in the
   artwork, so a nudge is available for one without disturbing the
   other two. All three default to zero. */
.ims-poster__link--ai {
	margin-left: var(--ims-optical-ai, 0);
}

.ims-poster__link--photo {
	margin-left: var(--ims-optical-photo, 0);
}

.ims-poster__link--retouch {
	margin-left: var(--ims-optical-retouch, 0);
}

.ims-poster__link {
	display: block;
	max-width: 100%;
}

/* The artwork is never distorted: no opacity, no filter, no recolour by
   CSS, no outline, no scale. The only thing that changes on hover is
   which of the two supplied files is displayed. */
.ims-poster__art {
	display: block;
	height: var(--ims-poster-cap);
	width: auto;
	max-width: 100%;
}

/* Both files sit in the markup and are exchanged with display alone.
   No transition, no animation, no transform: the word is black, then it
   is coloured, with nothing in between and nothing moving. Because the
   two files share intrinsic dimensions, the box never changes.

   Held inside the fine-pointer query so a touch device never reaches the
   coloured state and never depends on hover to navigate; the black
   artwork is the whole control there. Keyboard focus is included, as the
   pointer-free equivalent of hover, and adds no motion of its own. */
.ims-poster__art--hover {
	display: none;
}

@media (hover: hover) and (pointer: fine) {
	.ims-poster__link:hover .ims-poster__art--rest,
	.ims-poster__link:focus-visible .ims-poster__art--rest {
		display: none;
	}

	.ims-poster__link:hover .ims-poster__art--hover,
	.ims-poster__link:focus-visible .ims-poster__art--hover {
		display: block;
	}
}

/* The mark's hover is the shutter closing, not a scale. See the shutter
   block further down; nothing here grows, turns or springs. */

/* Keyboard focus is an outline, not a pulse. */
.ims-poster__link:focus-visible,
.ims-poster__mark:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 6px;
}


/* The mark closes the frame on RETOUCH's own row, optically centred
   against it and mirroring the left guide. It is the About link. */
.ims-poster__mark {
	grid-column: 2;
	grid-row: 3;
	justify-self: end;
	align-self: center;
	display: block;
	line-height: 0;
}

/* ---------------------------------------------------------
   The shutter.
   Open at rest; closed under a fine pointer, under keyboard
   focus, and while the link is firing. A straight exchange of
   one file for the other — no fade, no scale, no rotation, no
   spring; the two drawings do the work. Both are in the markup
   and both are 650x649, so there is no fetch on first hover,
   no blank frame and nothing moves between the states.
   --------------------------------------------------------- */
/* Scoped to the link so this outranks `.ims-poster__mark img`, which is
   (0,1,1) and sets display: block on both frames; a bare class here is
   (0,1,0) and loses to it, which left the closed blades stacked under the
   open ones at rest. */
.ims-poster__mark .ims-poster__mark-art--shut {
	display: none;
}

@media (hover: hover) and (pointer: fine) {
	.ims-poster__mark:hover .ims-poster__mark-art--open {
		display: none;
	}

	.ims-poster__mark:hover .ims-poster__mark-art--shut {
		display: block;
	}
}

.ims-poster__mark:focus-visible .ims-poster__mark-art--open,
.ims-poster__mark.is-shut .ims-poster__mark-art--open {
	display: none;
}

.ims-poster__mark:focus-visible .ims-poster__mark-art--shut,
.ims-poster__mark.is-shut .ims-poster__mark-art--shut {
	display: block;
}

/* The reopen, for the 50ms after the camera fires.
   The pointer is still physically over the mark at that moment, so the
   hover rules above would hold the closed frame in place right up to
   navigation and the shutter would appear never to reopen. Those rules are
   (0,3,0); qualifying with the parent makes these (0,4,0), so they win on
   specificity alone rather than on where they happen to sit in the file.
   Same straight exchange of one drawing for the other: no fade, no
   transform, no transition. */
.ims-poster .ims-poster__mark.is-reopening .ims-poster__mark-art--open {
	display: block;
}

.ims-poster .ims-poster__mark.is-reopening .ims-poster__mark-art--shut {
	display: none;
}

/* The mark takes its height from the same token as the words, so it
   reads as an equal-weight element on RETOUCH's row rather than a small
   sign beside it. Width follows the intrinsic ratio; it is never
   stretched or cropped. */
.ims-poster__mark img {
	display: block;
	height: var(--ims-poster-cap);
	width: auto;
}

/* The composition is the same at every width. Narrow screens used to
   drop the mark to a fourth row of its own, on the assumption that
   RETOUCH and the mark could not share a row below 600px. They can:
   the cap's width budget already reserves the mark's width and the
   3% the hover zoom adds, so at 360px RETOUCH peaks at 249px, the
   mark is 56px and the column gap 16px — 321px inside a 328px frame.
   The old override put 26-30px of dead space between the final word
   and the mark and broke the frame the composition closes with. */

/* ---------------------------------------------------------
   36. Portfolio worlds
   -----------------------------------------------------------
   One canvas per world, so entering a category is a visible
   change of world after the monochrome Home. All three come from
   the approved palette and are close enough in weight to read as
   a family.

   Contrast, computed from the tokens:
     white on cobalt ... 6.80:1
     white on violet ... 10.41:1
     ink on coral ...... 4.80:1
     paper on ink ...... 16.79:1
   Every text pairing passes AA.
   --------------------------------------------------------- */
body.ims-world {
	background-color: var(--ims-world-bg);
	color: var(--ims-world-fg);
	--ims-hairline: var(--ims-world-rule);
	--ims-quiet: var(--ims-world-quiet);
	--ims-submenu-bg: var(--ims-world-bg);
	--ims-submenu-fg: var(--ims-world-fg);
}

/* The gallery surface is white, on all four Work URLs.

   The three accent canvases are gone. They were competing with the work:
   136 photographs, each with its own colour, read as one composition only
   when the surface behind them holds still. Colour on the portfolio side
   now comes entirely from the photography.

   Kept as one rule per world rather than collapsed into body.ims-world,
   because the per-world hook is what a future accent would attach to
   again, and because the tokens below are what the header, footer and
   focus rings read. */
body.ims-world--photo,
body.ims-world--ai,
body.ims-world--retouch,
body.ims-world--all {
	--ims-world-bg: #FFFFFF;
	--ims-world-fg: var(--wp--custom--v2--surface--ink);
	--ims-world-rule: var(--wp--custom--v2--surface--hairline);
	--ims-world-quiet: var(--wp--custom--v2--surface--quiet);
}

body.ims-world :where(h1, h2, h3, h4, h5, h6) {
	color: inherit;
}

body.ims-world :where(a):not(.wp-block-button__link):not(.wp-element-button) {
	color: inherit;
}

body.ims-world :where(a, button, input, textarea, select, [tabindex]):focus-visible,
body.ims-world .wp-block-navigation-item__content:focus-visible {
	outline-color: var(--ims-world-fg);
	box-shadow: 0 0 0 4px var(--ims-world-bg);
}

.ims-world-main {
	padding-bottom: clamp(2rem, 4vw, 4rem);
}

/* --- the bar: category name and the switch between worlds --- */
.ims-world-bar {
	padding-inline: var(--ims-edge);
	padding-block: clamp(0.25rem, 0.8vw, 0.75rem) clamp(0.75rem, 1.4vw, 1.5rem);
	align-items: baseline;
	gap: 0.5rem clamp(1rem, 2vw, 2rem);
}

.ims-world-bar__title {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(2rem, 4.6vw, 5rem);
	line-height: 0.86;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	margin: 0;
}

@supports (font-variation-settings: "wdth" 75) {
	.ims-world-bar__title {
		font-stretch: 75%;
	}
}

.ims-world-switch {
	gap: clamp(0.75rem, 1.6vw, 1.5rem);
}

/* --- the wall --- */
.ims-wall {
	padding-inline: var(--ims-edge);
}

.ims-wall .wp-block-post-template {
	columns: 1;
	column-gap: var(--ims-gap);
	list-style: none;
	margin: 0;
	padding: 0;
}

.ims-wall .wp-block-post-template > li {
	break-inside: avoid;
	-webkit-column-break-inside: avoid;
	margin: 0 0 var(--ims-gap);
	display: block;
}

@media (min-width: 480px) {
	.ims-wall .wp-block-post-template {
		columns: 2;
	}
}

@media (min-width: 768px) {
	.ims-wall .wp-block-post-template {
		columns: 3;
	}
}

@media (min-width: 1280px) {
	.ims-wall .wp-block-post-template {
		columns: 4;
	}
}

/* Square edges. Photography is not a UI component. */
.ims-shot .wp-block-post-featured-image {
	margin: 0;
	overflow: hidden;
	border-radius: 0;
	line-height: 0;
}

.ims-shot img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 0;
}

.ims-shot__name {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(1.0625rem, 0.95vw + 0.75rem, 1.625rem);
	line-height: 1.02;
	letter-spacing: -0.015em;
	text-transform: uppercase;
	margin: clamp(7px, 0.6vw, 11px) 0 0;
	overflow-wrap: break-word;
}

@supports (font-variation-settings: "wdth" 75) {
	.ims-shot__name {
		font-stretch: 82%;
	}
}

.ims-shot__name a {
	text-decoration: none;
	color: inherit;
}

.ims-shot__cat {
	margin: 2px 0 0;
	color: var(--ims-quiet, var(--wp--custom--v2--surface--quiet));
}

.ims-shot__cat a {
	text-decoration: none;
}

.ims-empty {
	padding-block: clamp(2rem, 6vw, 6rem);
	display: grid;
	gap: clamp(0.75rem, 1.5vw, 1.25rem);
	max-width: 46ch;
}

.ims-pagination {
	padding-inline: var(--ims-edge);
	margin-top: clamp(1.5rem, 3vw, 3rem);
	padding-top: clamp(0.875rem, 1.6vw, 1.5rem);
	border-top: 1px solid var(--ims-hairline);
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--s);
	font-size: clamp(0.75rem, 0.25vw + 0.7rem, 0.8125rem);
	text-transform: uppercase;
	letter-spacing: 0.08em;
}

/* ---------------------------------------------------------
   37. Single project
   Part of the portfolio side; inherits its world's canvas.
   --------------------------------------------------------- */
.ims-project__bar {
	padding-inline: var(--ims-edge);
	padding-block: clamp(0.25rem, 0.8vw, 0.75rem) clamp(1rem, 2vw, 2rem);
	display: grid;
	gap: clamp(0.5rem, 1vw, 1rem);
	max-width: min(100%, 90rem);
}

.ims-project__name {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(1.875rem, 4.4vw, 5rem);
	line-height: 0.9;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	margin: 0;
	overflow-wrap: break-word;
}

@supports (font-variation-settings: "wdth" 75) {
	.ims-project__name {
		font-stretch: 78%;
	}
}

.ims-project__meta {
	gap: 0.5rem clamp(1rem, 2.5vw, 2.5rem);
	align-items: baseline;
}

.ims-project__meta .wp-block-post-terms,
.ims-project__meta .wp-block-post-excerpt__excerpt {
	margin: 0;
}

.ims-project__meta .wp-block-post-terms a {
	text-decoration: none;
}

.ims-project__open {
	padding-inline: var(--ims-edge);
}

.ims-project__open .wp-block-post-featured-image {
	margin: 0;
	line-height: 0;
}

.ims-project__open img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 0;
}

.ims-project__body {
	padding-inline: var(--wp--custom--v2--register--text-gutter);
	padding-block: clamp(1.5rem, 3.5vw, 3.5rem);
}

.ims-project__body .wp-block-post-content > * {
	margin-block: clamp(1rem, 2.4vw, 2rem);
}

.ims-project__body img {
	height: auto;
	border-radius: 0;
}

.ims-project__body .wp-block-post-content > .alignfull,
.ims-project__body .wp-block-image.is-style-ims-full {
	margin-inline: calc(-1 * var(--wp--custom--v2--register--text-gutter));
	width: auto;
	max-width: none;
}

.wp-block-image.is-style-ims-full {
	width: 100%;
	max-width: none;
	margin-inline: 0;
}

.wp-block-image.is-style-ims-full img,
.wp-block-image.is-style-ims-inset img {
	width: 100%;
	height: auto;
	border-radius: 0;
}

.wp-block-image.is-style-ims-inset {
	width: min(100%, 62%);
	margin-inline-start: 0;
	margin-inline-end: auto;
}

.wp-block-gallery.is-style-ims-pair {
	gap: var(--ims-gap);
}

.wp-block-gallery.is-style-ims-pair .wp-block-image {
	flex-basis: calc(50% - (var(--ims-gap) / 2)) !important;
}

.wp-block-gallery.is-style-ims-pair .wp-block-image img {
	width: 100%;
	height: auto;
	border-radius: 0;
	object-fit: fill;
}

@media (max-width: 599px) {
	.wp-block-gallery.is-style-ims-pair .wp-block-image {
		flex-basis: 100% !important;
	}

	.wp-block-image.is-style-ims-inset {
		width: 100%;
	}
}

.ims-project__related {
	padding-inline: var(--ims-edge);
	padding-bottom: clamp(2rem, 4vw, 4rem);
	max-width: min(100%, 80rem);
}

.ims-project__related .imsc-related-projects__item img {
	height: auto;
	border-radius: 0;
}

.ims-nextprev {
	padding-inline: var(--ims-edge);
	padding-block: clamp(1.5rem, 3vw, 3rem);
	border-top: 1px solid var(--ims-hairline);
}

.ims-nextprev__inner {
	gap: var(--wp--preset--spacing--m);
	align-items: baseline;
	max-width: min(100%, 90rem);
}

.ims-nextprev .wp-block-post-navigation-link {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(1.0625rem, 1.9vw, 1.875rem);
	line-height: 1.05;
	letter-spacing: -0.02em;
	text-transform: uppercase;
	max-width: 34ch;
	overflow-wrap: break-word;
}

.ims-nextprev .wp-block-post-navigation-link a {
	text-decoration: none;
	color: inherit;
}

.ims-nextprev__link--next {
	text-align: right;
	margin-inline-start: auto;
}


/* ---------------------------------------------------------
   38. The sales side
   -----------------------------------------------------------
   The opposite register to the portfolio, on purpose: paper,
   quiet, generous, readable. Headings are strong but the body is
   not — long commercial copy set in a heavy condensed face would
   be a worse read, not a bolder one. No portfolio wall here.
   --------------------------------------------------------- */
.ims-sales {
	/*
	 * The fallback is doing the work. --wp--custom--v2--* is bridged onto
	 * body.ims-world only, so on a commerce or quote page this token
	 * resolves to nothing, the declaration is invalid at computed-value
	 * time, and the gutter became 0 — which is why the checkout sat flush
	 * against the left edge of the viewport.
	 */
	padding-inline: var(--wp--custom--v2--register--text-gutter, clamp(18px, 3.2vw, 48px));
	padding-bottom: clamp(2rem, 5vw, 5rem);
}

.ims-sales__head {
	max-width: min(100%, 60rem);
	padding-block: clamp(0.5rem, 1.5vw, 1.5rem) clamp(2rem, 4vw, 4rem);
	display: grid;
	gap: clamp(0.75rem, 1.5vw, 1.25rem);
}

.ims-sales__title {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(2.25rem, 5vw, 5rem);
	line-height: 0.92;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	margin: 0;
	overflow-wrap: break-word;
}

@supports (font-variation-settings: "wdth" 75) {
	.ims-sales__title {
		font-stretch: 80%;
	}
}

.ims-sales__intro {
	max-width: 52ch;
}

.ims-offers {
	display: grid;
	gap: clamp(2.5rem, 5vw, 4.5rem);
	max-width: min(100%, 76rem);
}

/* Dividers and typography, never cards: no border box, no radius,
   no shadow — the same language the rest of the site uses. */
.ims-offer {
	border-top: 1px solid var(--wp--custom--v2--surface--hairline);
	padding-top: clamp(1.25rem, 2.5vw, 2rem);
	display: grid;
	gap: clamp(0.625rem, 1.2vw, 1rem);
}

.ims-offer__name {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(1.5rem, 2.6vw, 2.75rem);
	line-height: 1.02;
	letter-spacing: -0.02em;
	margin: 0;
	max-width: 24ch;
	overflow-wrap: break-word;
}

.ims-offer__body {
	font-size: clamp(1rem, 0.35vw + 0.95rem, 1.125rem);
	line-height: 1.55;
	max-width: 62ch;
	margin: 0;
}

.ims-offer__facts {
	margin: clamp(0.25rem, 0.8vw, 0.75rem) 0 0;
	padding-inline-start: 1.1em;
	max-width: 62ch;
	font-size: clamp(0.9375rem, 0.3vw + 0.9rem, 1.0625rem);
	line-height: 1.5;
}

.ims-offer__facts li {
	margin-block: 0.35em;
}

.ims-offer__links {
	gap: clamp(1rem, 2.4vw, 2rem);
	margin-top: clamp(0.5rem, 1.2vw, 1rem);
}

.ims-sales__form {
	margin-top: clamp(3rem, 6vw, 6rem);
	padding-top: clamp(1.5rem, 3vw, 2.5rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	max-width: min(100%, 60rem);
}

/* The seeded page content inside the gateway sits in the same
   quiet register; its own .ims-section wrapper keeps the padding
   the seeded pages use elsewhere, so it is neutralised here to
   avoid doubling with the gateway's own gutter. */
.ims-sales__form .ims-section {
	padding-inline: 0;
}

/* ---------------------------------------------------------
   39. Commerce
   -----------------------------------------------------------
   Cart, checkout, the package order page, the catalogue and the
   order confirmation — the rest of the journey the gateway above
   starts, in the same quiet register. This is one sales
   experience, not a separate store.

   Deliberately restrained. WooCommerce owns the behaviour of
   these surfaces and owns every price; what follows is type,
   colour, hairline and measure. No field is added, removed,
   reordered or required here, and no layout of a working
   checkout is rebuilt.
   --------------------------------------------------------- */

/*
 * The commerce surfaces reuse .ims-sales for the gutter and the
 * paper register; this is the measure. Wider than the 60rem
 * reading measure, because a cart line and a checkout column are
 * tabular rather than prose, and never the full width of a large
 * display.
 */
.ims-commerce > .wp-block-post-content,
.ims-commerce > .wp-block-columns,
.ims-commerce > .wp-block-woocommerce-store-notices,
.ims-commerce > .ims-next,
.ims-commerce > [class*="wc-block-order-confirmation"],
.ims-commerce > .wp-block-woocommerce-order-confirmation-additional-information {
	width: 100%;
	max-width: min(100%, 76rem);
	/*
	 * Centred, not flush left. At 1440 this block is 1216px inside a
	 * 1424px viewport, so left-aligning it left a 208px void down one
	 * side and pinned the checkout — form column and order summary
	 * together — against the opposite edge.
	 */
	margin-inline: auto;
}

/*
 * The customer is paying for that cart; a second control over it
 * beside the payment form is a distraction, not a convenience.
 * Cart, account and product pages keep theirs.
 */
body.woocommerce-checkout .ims-header-cart {
	display: none;
}

/* --- the package ladder ---------------------------------
   Rows, not pricing cards: a hairline between them, no box, no
   radius, no shadow. Rendered by ims_render_package_group() from
   live WooCommerce data, so nothing here assumes a package count
   or a price. */
.ims-pkgs {
	margin-top: clamp(1rem, 2vw, 1.75rem);
	max-width: 62ch;
}

.ims-pkgs + .ims-pkgs {
	margin-top: clamp(1.5rem, 3vw, 2.5rem);
}

.ims-pkgs__label {
	margin: 0 0 0.5em;
}

.ims-pkgs__list {
	list-style: none;
	margin: 0;
	padding: 0;
}

.ims-pkg {
	display: grid;
	/* The price column has a floor so the amounts line up as a money
	   column down the ladder instead of drifting with their own
	   width. */
	grid-template-columns: minmax(0, 1fr) minmax(5rem, auto) auto;
	grid-template-areas:
		"name  price cta"
		"scope price cta";
	column-gap: clamp(0.75rem, 2vw, 1.75rem);
	align-items: baseline;
	border-top: 1px solid var(--wp--custom--v2--surface--hairline);
	padding-block: clamp(0.625rem, 1.2vw, 0.9rem);
}

.ims-pkg__name {
	grid-area: name;
	font-weight: 600;
	font-size: clamp(1rem, 0.3vw + 0.95rem, 1.125rem);
	line-height: 1.25;
}

.ims-pkg__scope {
	grid-area: scope;
	font-size: clamp(0.875rem, 0.25vw + 0.85rem, 0.9375rem);
	line-height: 1.4;
	color: var(--wp--custom--v2--surface--quiet);
}

.ims-pkg__price {
	grid-area: price;
	align-self: center;
	text-align: right;
	font-weight: 600;
	font-size: clamp(1rem, 0.3vw + 0.95rem, 1.125rem);
	white-space: nowrap;
}

/* WooCommerce wraps every formatted amount in .woocommerce-Price-amount;
   the ladder sets its own weight and colour so a price reads as a
   figure in this composition rather than as WooCommerce's. */
.ims-pkg__price .woocommerce-Price-amount {
	color: inherit;
	font-weight: inherit;
}

.ims-pkg__cta {
	grid-area: cta;
	align-self: center;
	white-space: nowrap;
	font-size: clamp(0.75rem, 0.2vw + 0.7rem, 0.8125rem);
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--v2--type--meta-tracking);
	text-decoration: none;
	color: inherit;
	border-bottom: 2px solid currentColor;
	padding-bottom: 2px;
}

@media (hover: hover) and (pointer: fine) {
	.ims-pkg__cta:hover {
		border-bottom-color: transparent;
	}
}

/* Below the reading width the three columns stop being readable
   as a row; the price and the action sit under the package. */
@media (max-width: 599px) {
	.ims-pkg {
		grid-template-columns: minmax(0, 1fr) auto;
		grid-template-areas:
			"name  price"
			"scope scope"
			"cta   cta";
		row-gap: 0.4em;
	}

	.ims-pkg__cta {
		justify-self: start;
		margin-top: 0.3em;
	}
}

/* --- what happens next ----------------------------------
   Used on the package order page and on the order confirmation.
   The sequence itself is the workflow in docs/CONTENT-SPEC.md. */
.ims-next {
	margin-top: clamp(2.5rem, 5vw, 4.5rem);
	padding-top: clamp(1.25rem, 2.5vw, 2rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline);
	max-width: min(100%, 60rem);
}

.ims-next__title {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: clamp(1.25rem, 2vw, 1.875rem);
	line-height: 1.05;
	letter-spacing: -0.02em;
	margin: 0 0 clamp(0.75rem, 1.5vw, 1.25rem);
}

.ims-next__steps {
	margin: 0;
	padding-inline-start: 1.3em;
	max-width: 62ch;
	font-size: clamp(0.9375rem, 0.3vw + 0.9rem, 1.0625rem);
	line-height: 1.5;
}

.ims-next__steps li {
	margin-block: 0.4em;
}

.ims-next .ims-offer__links {
	margin-top: clamp(1rem, 2vw, 1.5rem);
}

/* --- the package order page ------------------------------ */
.ims-product {
	margin-top: clamp(1rem, 2vw, 2rem);
}

.ims-product__scope {
	font-size: clamp(1rem, 0.35vw + 0.95rem, 1.125rem);
	line-height: 1.55;
	max-width: 52ch;
	color: var(--wp--custom--v2--surface--quiet);
}

.ims-product__price {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: clamp(1.5rem, 2.4vw, 2.25rem);
	line-height: 1.1;
	letter-spacing: -0.02em;
	margin-block: clamp(0.5rem, 1.2vw, 1rem);
}

/* Quantity is commercially meaningful here — two hero images is a
   real order — so it stays, but it reads as a field rather than as
   a retail stepper. */
.ims-commerce--product .quantity input.qty {
	width: 4.5em;
	padding: 0.55em 0.5em;
	border: 1px solid var(--wp--preset--color--mid-neutral);
	border-radius: 0;
	background: var(--wp--preset--color--white);
	color: var(--wp--preset--color--ink);
	font-family: inherit;
	text-align: center;
}

.ims-commerce--product form.cart {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: clamp(0.5rem, 1.5vw, 1rem);
	margin-top: clamp(0.75rem, 1.5vw, 1.25rem);
}

/* --- the catalogue --------------------------------------- */
/*
 * The [products] grid is a bare shortcode inside the seeded
 * section, so the constrained-layout rule held it to the 740px
 * reading measure — a four-column grid inside a column meant for
 * prose. page-order-online.html therefore renders post-content
 * unconstrained and the two measures are set explicitly here: the
 * grid gets room, and the seeded copy keeps the reading measure it
 * has on every other seeded page. Nothing else on the page moves.
 */
.ims-commerce--catalogue .ims-section > :not(.woocommerce) {
	max-width: var(--wp--custom--v2--register--read);
	margin-inline: auto;
}

.ims-commerce--catalogue .ims-section > .woocommerce {
	max-width: min(100%, 76rem);
	margin-inline: auto;
}

/* The custom-quote note ran the full width of the viewport while the grid
   above it sat centred in 76rem — the one row that read as shifted. It now
   shares the catalogue's container exactly. */
.ims-catalogue-note {
	padding-top: clamp(1.25rem, 2.5vw, 2rem);
	max-width: min(100%, 76rem);
	margin-inline: auto;
}

.ims-catalogue-note > p:first-child {
	max-width: 62ch;
}

/* --- cart and checkout ------------------------------------
   Both are WooCommerce Blocks. Their structure, fields, totals
   and validation are WooCommerce's; these rules change how they
   read, not what they do. */
.ims-commerce .wc-block-components-title,
.ims-commerce .wc-block-components-checkout-step__title,
.ims-commerce .wp-block-woocommerce-cart-order-summary-heading-block {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	letter-spacing: -0.01em;
	color: var(--wp--preset--color--ink);
}

.ims-commerce .wc-block-components-checkout-step__description {
	color: var(--wp--custom--v2--surface--quiet);
}

/* Square, ink, uppercase — the same button language as the rest of
   the site, which theme.json already sets for core buttons and the
   pre-V2 rules set for classic WooCommerce buttons. */
.ims-commerce .wc-block-components-button:not(.is-link) {
	border-radius: 0;
	background-color: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--paper);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	font-size: var(--wp--preset--font-size--small);
}

@media (hover: hover) and (pointer: fine) {
	.ims-commerce .wc-block-components-button:not(.is-link):hover {
		/* The same corrected value the classic WooCommerce buttons and
		   the breadcrumb use: mid-neutral under paper text is 3.33:1
		   and fails AA; #726B5C keeps it at 4.82:1. */
		background-color: #726B5C;
	}
}

.ims-commerce .wc-block-components-button.is-link {
	color: var(--wp--preset--color--ink);
}

/* Fields: square, hairline, white, never a rounded pill. The two
   component classes are confirmed present in the installed
   WooCommerce build's own stylesheets; the country and state
   controls are plain <select> elements inside the block wrappers,
   so they are reached by element rather than by a class name that
   changes between WooCommerce releases. */
.ims-commerce .wc-block-components-text-input input,
.ims-commerce .wc-block-components-textarea,
.ims-commerce .wp-block-woocommerce-checkout select,
.ims-commerce .wp-block-woocommerce-cart select {
	border-radius: 0;
	border-color: var(--wp--preset--color--mid-neutral);
	background-color: var(--wp--preset--color--white);
	color: var(--wp--preset--color--ink);
}

.ims-commerce .wc-block-components-totals-item {
	font-size: clamp(0.9375rem, 0.3vw + 0.9rem, 1.0625rem);
}

.ims-commerce .wc-block-components-totals-footer-item {
	font-weight: 600;
}

.ims-commerce .wc-block-components-product-name {
	font-weight: 600;
	color: var(--wp--preset--color--ink);
	text-decoration: none;
}

.ims-commerce .wc-block-components-product-metadata,
.ims-commerce .wc-block-components-order-summary-item__description {
	color: var(--wp--custom--v2--surface--quiet);
}

/*
 * The cart's cross-sell collection ("New in store") is page content
 * the owner can remove in the editor, so it is quieted rather than
 * hidden: a hairline above it, a metadata-scale heading, and no
 * competition with the order the customer is already placing.
 */
.ims-commerce--cart .wp-block-woocommerce-product-collection {
	margin-top: clamp(2.5rem, 5vw, 4.5rem);
	padding-top: clamp(1.25rem, 2.5vw, 2rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline);
}

.ims-commerce--cart .wp-block-woocommerce-product-collection h2 {
	font-size: clamp(0.75rem, 0.2vw + 0.7rem, 0.8125rem);
	font-weight: 400;
	line-height: 1.4;
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--v2--type--meta-tracking);
	color: var(--wp--custom--v2--surface--quiet);
	text-align: left;
}

/*
 * The checkout's order summary sits in WooCommerce's own sidebar
 * column. It still needs to read as "what you are buying" rather
 * than "what you are filling in", but on the white canvas a filled
 * soft-neutral panel was one of the large beige blocks this pass
 * removes. The separation is now a hairline, which does the same
 * job at a fraction of the visual weight. The two-column layout
 * WooCommerce builds is untouched.
 */
.ims-commerce--checkout .wc-block-components-sidebar {
	border: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	padding: clamp(1rem, 2.5vw, 1.75rem);
}

/* --- the order confirmation ------------------------------ */
.ims-commerce--received .wc-block-order-confirmation-status {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	letter-spacing: -0.01em;
}

.ims-commerce--received .wc-block-order-confirmation-summary {
	border-top: 1px solid var(--wp--custom--v2--surface--hairline);
	border-bottom: 1px solid var(--wp--custom--v2--surface--hairline);
	padding-block: clamp(0.75rem, 1.5vw, 1.25rem);
}

.ims-commerce--received h2 {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: clamp(1.25rem, 2vw, 1.875rem);
	line-height: 1.05;
	letter-spacing: -0.02em;
}

/* Site Core prints the per-service Dropbox upload instructions
   through woocommerce_thankyou, which the additional-information
   block fires. It is the customer's next action, so it is given a
   surface of its own rather than being left as loose text. */
/* White, with a hairline. This block carries the upload step and the
   brief, which are the two things the customer came here to do; a beige
   field around them was the last decorative panel left in the post-order
   flow. */
.ims-commerce--received .wp-block-woocommerce-order-confirmation-additional-information {
	margin-top: clamp(1.5rem, 3vw, 2.5rem);
	padding: clamp(1rem, 2.5vw, 2rem) 0 0;
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	background-color: transparent;
}

.ims-commerce--received .wp-block-woocommerce-order-confirmation-additional-information:empty {
	display: none;
}


/* --- PASS 1: one commercial experience -------------------
   Added after owner QA of the live sales flow. The findings
   were consistent: the transactional pages read as lightly
   styled WooCommerce rather than as this site, prices were
   set at metadata scale on the very cards meant to sell them,
   and short pages left the footer stranded mid-viewport. */

/*
 * Short transactional pages put the footer wherever the content
 * happened to end, leaving it floating with empty background
 * beneath it. svh rather than vh on purpose: vh ignores mobile
 * browser chrome and would overshoot the screen on exactly the
 * devices this matters most on. Only commercial surfaces are
 * affected; Home already manages its own height, and the
 * portfolio pages are long enough that this never engages.
 */
body.ims-commerce-page .wp-site-blocks {
	display: flex;
	flex-direction: column;
	min-height: 100svh;
}

body.ims-commerce-page .wp-site-blocks > main {
	flex: 1 0 auto;
}

body.ims-commerce-page .wp-site-blocks > footer {
	flex: 0 0 auto;
}

/*
 * A package card's price was set in the eyebrow style: 13px,
 * uppercase, wide-tracked, quiet — metadata scale on the one
 * number the card exists to communicate. It becomes a figure.
 * Below 768px the existing mobile rules already give it their
 * own treatment and are left alone.
 */
@media (min-width: 768px) {
	.ims-packages > .wp-block-column > .is-style-eyebrow,
	.wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .is-style-eyebrow {
		font-family: var(--wp--preset--font-family--display);
		font-size: clamp(1.5rem, 2vw, 2.125rem);
		font-weight: 600;
		line-height: 1.05;
		letter-spacing: -0.02em;
		text-transform: none;
		color: var(--wp--preset--color--ink);
		min-height: 0;
	}

	/* Every card's CTA is the same width as its card, so three
	   packages read as three equal choices rather than three
	   different-sized buttons. */
	.ims-packages > .wp-block-column > .wp-block-buttons .wp-block-button,
	.ims-packages > .wp-block-column > .wp-block-buttons .wp-block-button__link {
		width: 100%;
		box-sizing: border-box;
		justify-content: center;
	}
}

/*
 * Scope / Included / Priority Delivery sit in the same section as
 * the package row and were reading at the same weight as the
 * packages themselves. They are supporting information about what
 * is on offer — including Priority Delivery, which is quoted on
 * request and has no product behind it — so they are set as
 * supporting information, clearly outside the purchasable cards.
 */
.ims-section:has(.ims-packages) > .wp-block-list {
	max-width: 62ch;
	color: var(--wp--custom--v2--surface--quiet);
}

.ims-section:has(.ims-packages) > .wp-block-list li {
	margin-block: 0.3em;
}

/*
 * WooCommerce's own store notice is a full-width banner with a
 * heavy tinted ground. It is the loudest element on a cart whose
 * job is to show an order. Reduced to a hairline-ruled line of
 * text: same words, same role, same colours available to assistive
 * technology, a quarter of the visual weight.
 */
.ims-commerce .wc-block-components-notice-banner {
	border-radius: 0;
	/*
	 * White ground, outlined in WooCommerce's own per-type colour: a
	 * thick left edge as before, now closed by a hairline on the other
	 * three sides so the notice still reads as a distinct object on the
	 * white canvas instead of a leftover beige block. The colour is not
	 * set here, so success stays green and error stays red, and the icon
	 * is untouched — status is never carried by the background alone.
	 */
	border-style: solid;
	border-width: 1px 1px 1px 3px;
	background: var(--wp--preset--color--white);
	color: var(--wp--preset--color--ink);
	padding: clamp(0.6rem, 1.4vw, 0.9rem) clamp(0.75rem, 1.8vw, 1.25rem);
	font-size: clamp(0.9375rem, 0.3vw + 0.9rem, 1.0625rem);
}

/* The icon stays. It and the left border keep WooCommerce's own
   per-type colour, so success and error are still distinguishable
   by more than the wording — it is only made proportionate to a
   line of text rather than to a banner. */
.ims-commerce .wc-block-components-notice-banner > svg {
	width: 1.25em;
	height: 1.25em;
	padding: 0;
	margin-inline-end: 0.5em;
}

/* --- cart --------------------------------------------------- */
/*
 * Compact rows. WooCommerce's default cart line is built around a
 * product thumbnail these packages do not have, so the row was
 * mostly empty space with a name floating in it.
 */
.ims-commerce--cart .wc-block-cart-items {
	border-collapse: collapse;
}

.ims-commerce--cart .wc-block-cart-item__product {
	padding-block: clamp(0.75rem, 1.6vw, 1.25rem);
}

.ims-commerce--cart .wc-block-components-product-name {
	font-size: clamp(1rem, 0.35vw + 0.95rem, 1.1875rem);
	line-height: 1.25;
}

.ims-commerce--cart .wc-block-cart-item__remove-link {
	color: var(--wp--custom--v2--surface--quiet);
	text-underline-offset: 0.2em;
}

.ims-commerce--cart .wc-block-cart-item__remove-link:hover,
.ims-commerce--cart .wc-block-cart-item__remove-link:focus-visible {
	color: var(--wp--preset--color--ink);
}

/* The one number the customer is looking for. */
.ims-commerce .wc-block-components-totals-footer-item {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.125rem, 1.2vw, 1.5rem);
	line-height: 1.2;
	letter-spacing: -0.01em;
}

.ims-commerce--cart .wc-block-cart__submit-button {
	width: 100%;
}

/* --- checkout ------------------------------------------------ */
/*
 * Numbered steps are WooCommerce's own; this only gives them the
 * hierarchy the rest of the site uses, so Contact / Billing /
 * Payment read as three stages rather than three paragraphs.
 */
.ims-commerce--checkout .wc-block-components-checkout-step {
	padding-block: clamp(0.5rem, 1.2vw, 1rem);
}

.ims-commerce--checkout .wc-block-components-checkout-step__title {
	font-size: clamp(1.0625rem, 0.6vw + 0.95rem, 1.375rem);
	line-height: 1.2;
}

/*
 * The payment method list is deliberately styled as a neutral list
 * of options rather than around any one gateway's branding, so a
 * second or third method can be added later without this needing
 * to be rewritten.
 */
.ims-commerce--checkout .wc-block-components-radio-control__option {
	padding-block: clamp(0.5rem, 1.2vw, 0.9rem);
}

.ims-commerce--checkout .wc-block-components-checkout-place-order-button {
	width: 100%;
	min-height: 52px;
	font-size: var(--wp--preset--font-size--small);
}

.ims-commerce--checkout .wc-block-components-order-summary-item {
	padding-block: clamp(0.5rem, 1.2vw, 0.9rem);
}

/* --- the header cart indicator ------------------------------- */
/*
 * The count is the only part of the control that changes, so it
 * carries the weight; the button itself stays as quiet as the rest
 * of the masthead and never competes with the navigation beside it.
 */
.ims-header-cart .wc-block-mini-cart__badge {
	font-weight: 600;
	letter-spacing: 0;
}

.ims-header-cart .wc-block-mini-cart__button:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 4px;
}


/* --- PASS 2: the project brief ---------------------------
   The post-purchase intake Site Core renders on the
   order-received page, on the brief page and in the customer's
   account. Same form language as the quote forms above — the
   .imsc-form rules already carry the fields, the errors and the
   success state — plus the few things a brief needs that an
   inquiry does not: a heading that names the order, grouped
   sections, and help text under a field. */
.imsc-brief {
	margin-top: clamp(1.5rem, 3vw, 2.5rem);
	padding-top: clamp(1.25rem, 2.5vw, 2rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline);
	max-width: min(100%, 60rem);
}

.imsc-brief__title {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: clamp(1.25rem, 2vw, 1.875rem);
	line-height: 1.05;
	letter-spacing: -0.02em;
	margin: 0 0 0.35em;
}

.imsc-brief__order {
	font-size: clamp(0.75rem, 0.2vw + 0.7rem, 0.8125rem);
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--v2--type--meta-tracking);
	color: var(--wp--custom--v2--surface--quiet);
	margin: 0 0 0.75em;
}

/* What happens next, stated from the order's real state. Never a
   promise that production has started. */
.imsc-brief__next {
	font-size: clamp(1rem, 0.3vw + 0.95rem, 1.125rem);
	line-height: 1.55;
	max-width: 62ch;
	margin: 0 0 clamp(1rem, 2vw, 1.75rem);
}

.imsc-brief__account {
	margin-block: 1em;
}

/* The received state is a short statement rather than a form, so it
   does not need the form's measure and reads better with air under it. */
.imsc-brief--received {
	padding-bottom: clamp(0.5rem, 1.5vw, 1.25rem);
}

.imsc-brief--received .imsc-form--success {
	margin-block: 0.75em;
}

/* A production brief is long enough to need sections; without them
   it is one undifferentiated column of inputs. */
.imsc-form__group {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: clamp(1rem, 0.8vw + 0.85rem, 1.25rem);
	line-height: 1.2;
	letter-spacing: -0.01em;
	margin: clamp(1.25rem, 2.5vw, 2rem) 0 0;
	padding-top: clamp(0.75rem, 1.5vw, 1.25rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline);
}

.imsc-form__group:first-child {
	margin-top: 0;
	padding-top: 0;
	border-top: 0;
}

/* Help text belongs between the label and the control, and is wired
   to the input with aria-describedby by the renderer. */
.imsc-form__help {
	display: block;
	font-size: clamp(0.8125rem, 0.2vw + 0.78rem, 0.875rem);
	line-height: 1.45;
	color: var(--wp--custom--v2--surface--quiet);
	margin-bottom: 0.45em;
	max-width: 62ch;
}

/* Arriving at the brief page without an order link. */
.imsc-form--notice {
	border-left: 3px solid var(--wp--custom--v2--surface--quiet);
	background: var(--wp--preset--color--soft-neutral);
	padding: clamp(0.75rem, 1.8vw, 1.25rem);
	max-width: 62ch;
}

.imsc-form--notice p {
	margin: 0;
}

/* The brief sits inside WooCommerce's order-confirmation block on the
   thank-you page; it keeps the measure the rest of that page uses. */
.ims-commerce--received .imsc-brief,
.ims-commerce--brief .imsc-brief {
	max-width: min(100%, 60rem);
}

/* --- PASS 2: payment method selection --------------------
   Gateway-agnostic on purpose. Direct bank transfer is the only
   method enabled today; a card gateway added later drops into the
   same list without any of this needing to change. */
.ims-commerce--checkout .wc-block-components-radio-control__option {
	border-radius: 0;
}

.ims-commerce--checkout .wc-block-components-radio-control__label {
	font-weight: 600;
}

.ims-commerce--checkout .wc-block-components-radio-control__description {
	color: var(--wp--custom--v2--surface--quiet);
	font-size: clamp(0.875rem, 0.25vw + 0.85rem, 0.9375rem);
	line-height: 1.45;
}

/* The selected method is marked by more than a tint, so the choice is
   not conveyed by colour alone. */
.ims-commerce--checkout .wc-block-components-radio-control__option--checked-option-highlighted {
	box-shadow: inset 3px 0 0 0 var(--wp--preset--color--ink);
}

/* ---------------------------------------------------------
   40. Footer
   Minimal on every register: three short columns and a line.
   --------------------------------------------------------- */
.ims-foot {
	padding-inline: var(--ims-edge);
	padding-block: clamp(2rem, 4vw, 3.5rem) clamp(1.25rem, 2vw, 1.75rem);
	border-top: 1px solid var(--ims-hairline, var(--wp--custom--v2--surface--hairline));
	display: grid;
	gap: clamp(1.5rem, 3vw, 2.5rem);
}

/* The navigation is three labelled groups; its layout lives in the V6
   layer at the end of this file. The single-row rules that used to sit
   here - a flex row of nine with hairlines between them and a
   deterministic 8 / 4 / 2 column wrap - were removed with the row: they
   were written for nine paragraphs as direct children, and the footer no
   longer has any.

   Hairlines between the legal links, so that row reads as a list of
   places rather than one long sentence. Decorative, so hidden from
   assistive technology and never read out between destinations. */
.ims-foot__base > p + p::before {
	content: "";
	display: inline-block;
	width: 1px;
	height: 0.85em;
	margin-right: clamp(0.55rem, 1.6vw, 1.35rem);
	vertical-align: -0.05em;
	background-color: var(--ims-hairline, var(--wp--custom--v2--surface--hairline, #E7E2D9));
}

/* The copyright is a statement, not a destination, so no rule follows it
   into the legal links. */
.ims-foot__base > p:nth-child(2)::before {
	display: none;
}

.ims-foot p {
	margin: 0;
}

/* Terms, Privacy and Refunds, with the copyright. Deliberately the
   quietest row on the page: always reachable, never competing with the
   navigation above it. */
.ims-foot__base {
	color: var(--ims-quiet, var(--wp--custom--v2--surface--quiet));
	gap: clamp(0.55rem, 1.6vw, 1.35rem);
	align-items: baseline;
	max-width: min(100%, 76rem);
	margin-inline: auto;
	text-align: center;
}

/* /work/ is a service gateway: its whole purpose is the three choices in
   the middle of the page, and a footer repeating them is noise. Removed
   rather than hidden — display:none also takes it out of the
   accessibility tree, so nothing is announced twice. */
body.ims-world--all footer.ims-foot {
	display: none;
}

/* The How We Work gateway has the same duplication in miniature: its own
   content is the choice between the three categories. Only those three
   links go; Info and Enquiries stay. */
body.ims-page-process .ims-foot__work {
	display: none;
}

/* ---------------------------------------------------------
   41. Interaction
   Restrained everywhere; fine pointers only; nothing that
   carries information.
   --------------------------------------------------------- */
.ims-shot img {
	transition: opacity var(--wp--custom--motion--duration-base) var(--wp--custom--motion--easing);
}

@media (hover: hover) and (pointer: fine) {
	.ims-shot:hover img,
	.ims-shot:focus-within img {
		opacity: 0.86;
	}

	.ims-shot:hover .ims-shot__name,
	.ims-shot:focus-within .ims-shot__name {
		text-decoration: underline;
		text-underline-offset: 0.16em;
		text-decoration-thickness: 2px;
	}
}

@media (hover: none), (pointer: coarse) {
	.ims-shot img {
		transition: none;
	}
}

/* Reduced-motion foundation: later phases' entrance/hover motion must respect this */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}
}

/* ---------------------------------------------------------
   42. The gallery wall
   Three destinations, one object. /work/ and each of the three
   worlds render the same wall; the only difference is which
   photographs are in it.

   ONE SPACING TOKEN. The outer page gutter and the gap between
   images are the same value, by construction rather than by
   coincidence: the wall is padded by --ims-wall-gap and its
   grid gap is --ims-wall-gap, so the rhythm at the edge of the
   screen matches the rhythm between the pictures at every
   width. Change the token and both move together.

   Equal cells, because every supplied photograph is portrait
   at 4:5. Column counts and their breakpoints match the sizes
   hint emitted with each image exactly.
   --------------------------------------------------------- */
:root {
	--ims-wall-gap: clamp(6px, 0.6vw, 12px);
}

.ims-gal-main {
	background-color: #FFFFFF;
}

.ims-gal {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--ims-wall-gap);
	padding: var(--ims-wall-gap);
	margin: 0;
	list-style: none;
}

/* One image per row on every phone. The second column used to arrive at
   480px, which put two small pictures side by side on the larger handsets
   and made the photography the one thing on the page you had to squint
   at. It now waits for 600px, where the viewport is a small tablet and a
   pair still reads at a decent size. The sizes hint emitted with each
   image uses the same breakpoint. */
@media (min-width: 600px) {
	.ims-gal {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (min-width: 768px) {
	.ims-gal {
		grid-template-columns: repeat(3, 1fr);
	}
}

@media (min-width: 1280px) {
	.ims-gal {
		grid-template-columns: repeat(4, 1fr);
	}
}

.ims-gal__item {
	margin: 0;
	min-width: 0;
}

/* The seam between categories on the combined wall. A hairline across the
   full width of the grid with a little air either side — perceptible when
   looked for, close to invisible when not. It spans every column so the
   grid resumes cleanly on the next row whatever the column count. */
.ims-gal__rule {
	grid-column: 1 / -1;
	margin: 0;
	padding-block: clamp(1.25rem, 3vw, 2.75rem);
}

.ims-gal__rule::before {
	content: "";
	display: block;
	height: 1px;
	background-color: var(--wp--custom--v2--surface--hairline, #E7E2D9);
}

/* The photograph is the control. Square edges, no card, no frame,
   no caption, and nothing that moves: no scale, no zoom, no
   translate, no opacity shift on hover. The only thing a pointer
   changes anywhere on this wall is which state of a retouching
   pair is shown (section 43). */
.ims-gal__open,
.ims-gal__still {
	display: block;
	position: relative;
	line-height: 0;
	text-decoration: none;
}

.ims-gal__open img,
.ims-gal__still img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 0;
	transform: none;
	transition: none;
}

.ims-gal__open:focus-visible {
	outline: 2px solid var(--ims-world-fg, var(--wp--custom--v2--surface--ink));
	outline-offset: 3px;
}

/* A core/gallery inside a Portfolio Project — the owner's editing
   surface — is rendered on the same terms as the wall it feeds. */
.ims-gallery {
	margin-block: 0;
	padding: 0;
}

.ims-gallery.wp-block-gallery.has-nested-images {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--ims-wall-gap);
	align-items: start;
}

@media (min-width: 600px) {
	.ims-gallery.wp-block-gallery.has-nested-images {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (min-width: 768px) {
	.ims-gallery.wp-block-gallery.has-nested-images {
		grid-template-columns: repeat(3, 1fr);
	}
}

@media (min-width: 1280px) {
	.ims-gallery.wp-block-gallery.has-nested-images {
		grid-template-columns: repeat(4, 1fr);
	}
}

/* Core sizes a gallery item with :not(#individual-image); the ID inside
   :not() gives that rule ID-level specificity while matching nothing by
   ID, so a class-only selector cannot outrank it. Carrying the same
   :not() wins on the same terms, with no !important. */
.ims-gallery.wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image) {
	display: block;
	width: auto;
	max-width: none;
	flex: none;
	margin: 0;
}

.ims-gallery.wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image) img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 0;
}

.ims-gallery.wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image) figcaption {
	position: static;
	max-height: none;
	padding: 0;
	background: none;
	color: var(--ims-quiet, var(--wp--custom--v2--surface--quiet));
	text-align: left;
}

.ims-project .wp-block-post-content {
	padding: var(--ims-wall-gap);
}

/* ---------------------------------------------------------
   43. Retouching Before/After
   AFTER is the portfolio image and the default state. BEFORE
   is stacked in the identical frame — same attachment
   dimensions, same box — so revealing it changes opacity and
   nothing else. No scale, no slide, no wipe, no pan, and no
   layout shift in either direction.

   There is no control. The comparison is a fine-pointer hover
   and nothing more, which is a deliberate trade: on a phone a
   tap can only ever mean "open this image", so the lightbox is
   never ambiguous, and the AFTER — the finished work, and the
   thing the portfolio is actually showing — is what a touch
   visitor sees. A button would have had to sit on top of the
   photograph on every retouching tile to serve a comparison
   that is supporting evidence, not the work itself.
   --------------------------------------------------------- */
.ims-ba .ims-gal__before,
.ims-ba .ims-ba__before {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0;
	pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
	.ims-ba:hover .ims-gal__before,
	.ims-ba:hover .ims-ba__before {
		opacity: 1;
	}
}

/* ---------------------------------------------------------
   45. Gallery navigation
   The only navigation a gallery carries. The categories were
   named by the owner's wordmarks until the launch polish; they
   are set in type now (see the V6 layer), and the artwork
   rules that used to sit here went with the artwork.

   Centred as one balanced unit. It used to sit hard against
   the left edge, which read as an orphaned list rather than
   as a way out of the gallery.
   --------------------------------------------------------- */
.ims-galnav-wrap-top {
	padding: clamp(1.5rem, 4vw, 3rem) var(--ims-wall-gap) clamp(1.25rem, 3vw, 2.25rem);
}

.ims-galnav-wrap-bottom {
	padding: clamp(2.5rem, 6vw, 5rem) var(--ims-wall-gap) clamp(2.5rem, 6vw, 5rem);
}

/* Two halves and a line down the middle.
   Flex with a gap centred the pair, not the divider: the two wordmarks
   are different widths, so "AI | RETOUCH" put the rule wherever the
   letterforms happened to end. A two-column grid of equal tracks fixes
   the centre by construction, and each mark is centred inside its own
   half. The arrows are taken out of that calculation entirely - absolutely
   positioned on the outer edge - so a one-character glyph on one side
   cannot pull its wordmark off centre. */
.ims-galnav {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: center;
	margin: 0 auto;
	padding: 0;
	max-width: min(100%, 56rem);
	list-style: none;
}

.ims-galnav__item {
	display: flex;
	justify-content: center;
	margin: 0;
	padding-inline: clamp(2rem, 6vw, 4rem);
}

/* The divider belongs to the boundary, so it is drawn on the second
   half's leading edge - exactly the grid's midpoint, whatever the marks
   inside the halves happen to measure. */
.ims-galnav__item + .ims-galnav__item {
	border-left: 1px solid var(--ims-hairline, var(--wp--custom--v2--surface--hairline, #E7E2D9));
}

.ims-galnav__link {
	position: relative;
	display: block;
}

/* Outside the mark, out of the flow, and so out of the centring. */
.ims-galnav__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	font-size: clamp(0.95rem, 2vw, 1.25rem);
	line-height: 1;
	color: var(--ims-quiet, var(--wp--custom--v2--surface--quiet, #726B5C));
}

.ims-galnav__arrow-before {
	right: calc(100% + clamp(0.5rem, 1.6vw, 0.9rem));
}

.ims-galnav__arrow-after {
	left: calc(100% + clamp(0.5rem, 1.6vw, 0.9rem));
}

.ims-galnav__item {
	margin: 0;
	display: flex;
}

.ims-galnav__link {
	display: block;
	text-decoration: none;
}

.ims-galnav__link:focus-visible {
	outline: 2px solid var(--ims-world-fg, var(--wp--custom--v2--surface--ink));
	outline-offset: 6px;
}

/* ---------------------------------------------------------
   46. Lightbox
   Built by assets/js/portfolio.js and never present without
   it. The picture is shown whole — contained, never cropped —
   and the controls stay out of its way.
   --------------------------------------------------------- */
.ims-lb {
	position: fixed;
	inset: 0;
	z-index: 1000;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: clamp(12px, 3vw, 40px);
	background-color: rgba(20, 20, 20, 0.94);
}

.ims-lb[hidden] {
	display: none;
}

.ims-lb__figure {
	margin: 0;
	max-width: 100%;
	max-height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* The whole photograph, always: bounded by both axes of the viewport
   and contained, so nothing is ever cropped to fit. */
.ims-lb__img {
	display: block;
	max-width: 100%;
	max-height: calc(100vh - clamp(24px, 6vw, 80px));
	width: auto;
	height: auto;
	object-fit: contain;
}

.ims-lb__btn {
	position: absolute;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 48px;
	height: 48px;
	padding: 0;
	border: 0;
	background-color: transparent;
	color: #FFFFFF;
	cursor: pointer;
}

.ims-lb__btn svg {
	width: 24px;
	height: 24px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: square;
}

.ims-lb__btn:focus-visible {
	outline: 2px solid #FFFFFF;
	outline-offset: 2px;
}

.ims-lb__close {
	top: clamp(6px, 1.5vw, 18px);
	right: clamp(6px, 1.5vw, 18px);
}

.ims-lb__prev {
	left: clamp(2px, 1vw, 14px);
	top: 50%;
	margin-top: -24px;
}

.ims-lb__next {
	right: clamp(2px, 1vw, 14px);
	top: 50%;
	margin-top: -24px;
}

.ims-lb__btn[hidden] {
	display: none;
}

/* Scroll lock. position: fixed rather than overflow: hidden, because
   iOS ignores the latter; the offset is reapplied on close by the
   script, so the wall returns to the same place. */
body.ims-lb-open {
	position: fixed;
	inset-inline: 0;
	width: 100%;
	overflow: hidden;
}

/* ---------------------------------------------------------
   47. Back to top
   A long wall earns a way back. Quiet, small, out of the
   reading path, and absent until the scroll has actually
   been long enough to need it.
   --------------------------------------------------------- */
.ims-totop {
	position: fixed;
	right: clamp(10px, 1.6vw, 22px);
	bottom: clamp(10px, 1.6vw, 22px);
	z-index: 900;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 42px;
	height: 42px;
	padding: 0;
	border: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	background-color: rgba(255, 255, 255, 0.92);
	color: var(--wp--custom--v2--surface--ink, #141414);
	cursor: pointer;
}

.ims-totop[hidden] {
	display: none;
}

.ims-totop svg {
	width: 18px;
	height: 18px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: square;
}

.ims-totop:focus-visible {
	outline: 2px solid var(--wp--custom--v2--surface--ink, #141414);
	outline-offset: 3px;
}

/* The sentinel sits in the flow at the top of the wall and is observed
   with default margins, so the control appears once that point has been
   scrolled a full screen out of view. It occupies no space of its own. */
.ims-totop-mark {
	width: 100%;
	height: 1px;
	margin-bottom: -1px;
	pointer-events: none;
}

/* ---------------------------------------------------------
   44. V2 token bridge  (defect fix)

   Every var(--wp--custom--v2--*) reference in this file — 87
   of them, none with a fallback — was resolving to nothing,
   which is why the three portfolio worlds rendered on the
   default paper instead of their own accent canvas.

   The cause is in the name, not the values. theme.json holds
   these tokens under settings.custom.v2, and WordPress runs
   every key through _wp_to_kebab_case() when it builds the
   custom properties. That function separates a letter from a
   following digit, so the key "v2" is emitted as "v-2":

     theme.json   settings.custom.v2.surface.paper
     emitted      --wp--custom--v-2--surface--paper
     referenced   --wp--custom--v2--surface--paper   (never set)

   An invalid var() does not fall back to the previous
   declaration — the property computes to its inherited or
   initial value — so body.ims-world's background-color landed
   on transparent and the paper behind it showed through.

   Fixed by aliasing rather than renaming: theme.json keeps its
   authored shape, all 87 existing references keep working
   unchanged, and the whole defect stays visible in one place
   instead of being spread across the file. Only the tokens
   this stylesheet actually references are bridged.

   SCOPE. The bridge is deliberately attached to body.ims-world
   rather than :root, so it reaches the Work archive, the three
   worlds and every project page — the portfolio surface this
   phase is responsible for — and nothing else.

   That restraint is measured, not assumed. With the bridge on
   :root the homepage rendered differently at 390px, 768px and
   1440px: shared header and footer metadata picked up the
   0.1em meta tracking the dead token had been withholding.
   Correct, but a visible change to a page that is frozen, and
   to service and commerce pages this phase was not asked to
   restyle. Scoped to body.ims-world, all three homepage widths
   are byte-identical to the pre-change capture.

   The same defect therefore still affects every non-portfolio
   page. It is recorded for the owner rather than fixed here:
   promoting this block to :root is the whole fix, but it is a
   site-wide visual change and belongs to a phase that can
   review its results page by page.
   --------------------------------------------------------- */
body.ims-world {
	--wp--custom--v2--surface--paper: var(--wp--custom--v-2--surface--paper);
	--wp--custom--v2--surface--on-paper: var(--wp--custom--v-2--surface--on-paper);
	--wp--custom--v2--surface--ink: var(--wp--custom--v-2--surface--ink);
	--wp--custom--v2--surface--on-ink: var(--wp--custom--v-2--surface--on-ink);
	--wp--custom--v2--surface--hairline: var(--wp--custom--v-2--surface--hairline);
	--wp--custom--v2--surface--hairline-on-ink: var(--wp--custom--v-2--surface--hairline-on-ink);
	--wp--custom--v2--surface--quiet: var(--wp--custom--v-2--surface--quiet);
	--wp--custom--v2--surface--quiet-on-ink: var(--wp--custom--v-2--surface--quiet-on-ink);

	--wp--custom--v2--accent--cobalt: var(--wp--custom--v-2--accent--cobalt);
	--wp--custom--v2--accent--on-cobalt: var(--wp--custom--v-2--accent--on-cobalt);
	--wp--custom--v2--accent--coral: var(--wp--custom--v-2--accent--coral);
	--wp--custom--v2--accent--on-coral: var(--wp--custom--v-2--accent--on-coral);
	--wp--custom--v2--accent--acid: var(--wp--custom--v-2--accent--acid);
	--wp--custom--v2--accent--on-acid: var(--wp--custom--v-2--accent--on-acid);
	--wp--custom--v2--accent--violet: var(--wp--custom--v-2--accent--violet);
	--wp--custom--v2--accent--on-violet: var(--wp--custom--v-2--accent--on-violet);

	--wp--custom--v2--register--read: var(--wp--custom--v-2--register--read);
	--wp--custom--v2--register--wide: var(--wp--custom--v-2--register--wide);
	--wp--custom--v2--register--gutter: var(--wp--custom--v-2--register--gutter);
	--wp--custom--v2--register--text-gutter: var(--wp--custom--v-2--register--text-gutter);

	--wp--custom--v2--rhythm--section: var(--wp--custom--v-2--rhythm--section);

	--wp--custom--v2--type--statement-leading: var(--wp--custom--v-2--type--statement-leading);
	--wp--custom--v2--type--statement-tracking: var(--wp--custom--v-2--type--statement-tracking);
	--wp--custom--v2--type--headline-leading: var(--wp--custom--v-2--type--headline-leading);
	--wp--custom--v2--type--headline-tracking: var(--wp--custom--v-2--type--headline-tracking);
	--wp--custom--v2--type--body-leading: var(--wp--custom--v-2--type--body-leading);
	--wp--custom--v2--type--meta-tracking: var(--wp--custom--v-2--type--meta-tracking);
}

/* ---------------------------------------------------------
   48. Masthead wordmark
   The site name in the header is now the owner's artwork
   rather than letter-spaced type. Sized by height so the
   ratio is fixed by the markup and the mark can neither
   distort nor crop, and tuned to the cap height of the text
   it replaces so the masthead does not change height.
   --------------------------------------------------------- */
.ims-brand {
	margin: 0;
	line-height: 0;
	min-width: 0;
}

.ims-brand__link {
	display: inline-block;
	line-height: 0;
}

/* Raised from clamp(14px, 1.05vw, 18px). The wordmark is the brand on
   every page that is not Home, and at fourteen pixels it read as a
   caption rather than a masthead. About 60% larger, which is presence
   without crowding the navigation beside it. */
.ims-brand__mark {
	display: block;
	height: clamp(23px, 1.7vw, 29px);
	width: auto;
	max-width: 100%;
}

/*
 * The same height again, at a weight that can hold it.
 *
 * woocommerce-layout.css carries `.woocommerce img, .woocommerce-page img
 * { height: auto }` at (0,1,1), which outranks .ims-brand__mark at (0,1,0)
 * — so on every body.woocommerce-page the masthead wordmark ignored its
 * clamp and fell back to its natural ratio: measured 767x134 on the cart
 * against 84x15 everywhere else. This selector is (0,2,0) and restores the
 * intended size. Nothing about the header changes; it stops changing.
 */
body.woocommerce-page .ims-brand__mark {
	height: clamp(23px, 1.7vw, 29px);
}

.ims-brand__link:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 5px;
}

/* ---------------------------------------------------------
   49. The two menus
   Below 1024px the compact menu carries the owner's mobile
   hierarchy — AI, Photo, Retouch, Process, About, Request a
   Quote — and the desktop menu is not rendered at all. At and
   above 1024px the reverse. Each is a real navigation block,
   so the hidden one is out of the tab order and out of the
   accessibility tree rather than merely invisible.
   --------------------------------------------------------- */
@media (max-width: 1023px) {
	.ims-nav-desktop {
		display: none !important;
	}
}

@media (min-width: 1024px) {
	.ims-nav-mobile {
		display: none !important;
	}
}

/* --- the compact menu, opened ---

   The overlay was inheriting a full-width outlined box for Request a
   Quote from the desktop CTA, which at 100% width became the loudest
   thing on the screen and sat oddly against a plain list. The emphasis
   is now typographic: the CTA is separated from the destinations above
   it by the same hairline used elsewhere, and underlined. It still reads
   first without being a slab.

   Items get real padding so each tap target clears 44px, and the
   inherited 20px stack margin is dropped in favour of that padding, so
   the rhythm comes from one place. */
.ims-nav-mobile .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item:not(:first-child) {
	margin-top: 0;
}

.ims-nav-mobile .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
	font-size: clamp(1.25rem, 5vw, 1.75rem);
	font-weight: 700;
	letter-spacing: 0.02em;
	line-height: 1.15;
	padding-block: 0.42em;
	min-height: 44px;
	display: flex;
	align-items: center;
}

/* Carries the same :not(:first-child) as the rule above it so the two match
   on specificity and this one wins on order; without it the separator got
   its air below the hairline but none above. */
.ims-nav-mobile .wp-block-navigation__responsive-container.is-menu-open .ims-nav__cta:not(:first-child) {
	margin-top: clamp(1.1rem, 4vw, 1.75rem);
	padding-top: clamp(1.1rem, 4vw, 1.75rem);
	border-top: 1px solid var(--ims-hairline, var(--wp--custom--v2--surface--hairline));
}

/* The desktop CTA's outline has no business in the overlay. */
.ims-nav-mobile .wp-block-navigation__responsive-container.is-menu-open .ims-nav__cta .wp-block-navigation-item__content,
.ims-nav-mobile .wp-block-navigation__responsive-container.is-menu-open .ims-nav__cta > a {
	box-shadow: none;
	padding-inline: 0;
	text-decoration: underline;
	text-underline-offset: 0.18em;
	text-decoration-thickness: 2px;
}

/* ---------------------------------------------------------
   50. The intent prompt
   One question, once, after the work has been seen. It is
   part of the studio's own language — white, typographic,
   generous — and not a marketing overlay: no banner, no
   countdown, no discount, no bouncing, and a dismissal that
   is as easy to find as the acceptance.
   --------------------------------------------------------- */

/* The trigger marker: sixty per cent of the wall's own height.
   Absolutely positioned, so although it is a list item inside the
   grid it is not a grid item and occupies no cell. */
.ims-gal {
	position: relative;
}

.ims-intent-mark {
	position: absolute;
	top: 60%;
	left: 0;
	width: 1px;
	height: 1px;
	margin: 0;
	pointer-events: none;
}

.ims-intent {
	width: min(34rem, calc(100vw - 2 * clamp(16px, 5vw, 48px)));
	max-height: calc(100vh - 2 * clamp(16px, 5vw, 48px));
	padding: 0;
	border: 0;
	background-color: #FFFFFF;
	color: var(--wp--custom--v2--surface--ink, #141414);
	overflow: auto;
}

.ims-intent::backdrop {
	background-color: rgba(20, 20, 20, 0.72);
}

/* --- arriving and leaving ---

   At 220ms of pure opacity the prompt still read as a collision: you were
   scrolling, and then it was simply there. The fade is now longer, and the
   panel travels a little as it arrives — eight pixels, enough to read as
   arriving rather than appearing, small enough that it is never a slide.
   No scale, no zoom, no bounce, no spring. The site's own easing curve,
   cubic-bezier(0.4, 0, 0.2, 1), so this belongs to the same system as
   everything else.

   Both the panel and the backdrop start at zero and are raised by
   .is-open, which the script adds immediately after showModal() — the
   modal, and therefore the block on everything behind it, is in place from
   the first millisecond. Only the paint is gradual; the interaction lock
   is not, and the gallery is never clickable during the fade.

   Leaving is quicker than arriving, which is the way round that feels
   deliberate rather than sluggish, and it settles back down by five
   pixels rather than rising. */
.ims-intent,
.ims-intent::backdrop {
	opacity: 0;
	transition: opacity 300ms var(--wp--custom--motion--easing);
}

.ims-intent {
	transform: translateY(8px);
	transition: opacity 300ms var(--wp--custom--motion--easing),
		transform 300ms var(--wp--custom--motion--easing);
}

.ims-intent.is-open,
.ims-intent.is-open::backdrop {
	opacity: 1;
}

.ims-intent.is-open {
	transform: translateY(0);
}

.ims-intent.is-closing,
.ims-intent.is-closing::backdrop {
	opacity: 0;
	transition-duration: 200ms;
}

.ims-intent.is-closing {
	transform: translateY(5px);
}

/* The global reduced-motion rule reaches *, *::before and *::after — it
   does not reach ::backdrop, so the backdrop is collapsed explicitly.
   The dialog itself is already covered by the * in that rule, which
   flattens the entrance travel along with the fade; the transform is
   zeroed here as well so the panel is never offset even for the one frame
   before the class lands. */
@media (prefers-reduced-motion: reduce) {
	.ims-intent::backdrop,
	.ims-intent.is-closing::backdrop {
		transition-duration: 0.001ms;
	}

	.ims-intent,
	.ims-intent.is-open,
	.ims-intent.is-closing {
		transform: none;
	}
}

.ims-intent__panel {
	position: relative;
	padding: clamp(2rem, 6vw, 3.5rem) clamp(1.5rem, 5vw, 3rem) clamp(1.75rem, 5vw, 3rem);
	text-align: left;
}

.ims-intent__title {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(1.5rem, 4.4vw, 2.25rem);
	line-height: 1.06;
	letter-spacing: -0.02em;
	margin: 0;
	max-width: 18ch;
}

.ims-intent__support {
	font-size: clamp(0.95rem, 2.6vw, 1.0625rem);
	line-height: 1.5;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	margin: clamp(0.6rem, 2vw, 0.9rem) 0 0;
	max-width: 32ch;
}

/* The primary decision dominates: it is the only filled surface in the
   panel, and the only thing at button scale. */
/* Weighted to (0,3,1) for the same reason as the Process CTA: the prompt
   is printed inside <body class="ims-world">, and section 40's
   `body.ims-world :where(a):not(...):not(...)` pins links there to
   `color: inherit`, which painted this label ink-on-ink. */
.ims-intent a.ims-intent__yes:not(.wp-element-button) {
	/* Flex with a floor rather than padding alone: at the mobile type
	   size the padded box came out at 42px, under the 44px a thumb needs. */
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	margin-top: clamp(1.5rem, 5vw, 2.25rem);
	padding: 0.9em 1.25em;
	background-color: var(--wp--custom--v2--surface--ink, #141414);
	color: #FFFFFF;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(0.8125rem, 2.6vw, 0.9375rem);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	text-align: center;
	text-decoration: none;
}

/* Quieter, but a real control at a real size — not a grey whisper.
   Declining has to be as easy as accepting. */
.ims-intent__no {
	/* Declining is a real control at a real size, not a grey whisper —
	   it gets the same 44px floor as everything else. */
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	min-height: 44px;
	margin-top: clamp(0.75rem, 2.5vw, 1rem);
	padding: 0.6em 0;
	border: 0;
	background: none;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font-family: inherit;
	font-size: clamp(0.75rem, 2.4vw, 0.8125rem);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	cursor: pointer;
}

.ims-intent__close {
	position: absolute;
	top: 0;
	right: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	cursor: pointer;
}

.ims-intent__close svg {
	width: 18px;
	height: 18px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: square;
}

.ims-intent a.ims-intent__yes:focus-visible,
.ims-intent__no:focus-visible,
.ims-intent__close:focus-visible {
	outline: 2px solid var(--wp--custom--v2--surface--ink, #141414);
	outline-offset: 3px;
}

@media (hover: hover) and (pointer: fine) {
	.ims-intent__no:hover {
		color: var(--wp--custom--v2--surface--ink, #141414);
	}
}

/* Background scroll lock. overflow rather than position: fixed, so the
   scroll offset is never lost and closing returns the visitor to the
   photograph they were looking at. */
html.ims-intent-open,
html.ims-intent-open body {
	overflow: hidden;
}

/* ---------------------------------------------------------
   51. Process — five steps
   Confident and short. The number carries the sequence so
   the headings do not have to, and each step is one line of
   explanation rather than a paragraph of reassurance.
   --------------------------------------------------------- */
.ims-steps {
	display: grid;
	gap: clamp(1.75rem, 4vw, 2.75rem);
	margin-block: clamp(1.75rem, 5vw, 3rem) 0;
}

.ims-step {
	display: grid;
	grid-template-columns: auto 1fr;
	column-gap: clamp(0.9rem, 2.5vw, 1.5rem);
	row-gap: 0.35rem;
	align-items: baseline;
}

.ims-step__no {
	grid-row: 1 / span 2;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font-variant-numeric: tabular-nums;
}

.ims-step__title {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(1.0625rem, 1.5vw, 1.375rem);
	line-height: 1.15;
	letter-spacing: -0.01em;
}

.ims-step__body {
	margin: 0;
	max-width: 56ch;
}

/* ---------------------------------------------------------
   52. Process — the next action
   The end of the page is a decision, not a paragraph.
   --------------------------------------------------------- */
.ims-next {
	margin-block: clamp(2.5rem, 7vw, 4.5rem) 0;
	padding-top: clamp(2rem, 5vw, 3rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}

.ims-next__eyebrow {
	margin: 0 0 clamp(0.75rem, 2vw, 1.1rem);
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* The selector is heavier than it looks because it has to be. Section
   26's `.ims-surface :where(a):not(.wp-block-button__link):not(.wp-element-button)`
   pins every link inside a V2 surface to `color: inherit`, and the two
   :not() arguments give that rule a specificity of (0,3,1) — enough to
   beat a plain class and paint this button's label ink-on-ink, which is
   exactly what it did. Matching the shape ties the specificity and wins
   on order, without !important and without borrowing .wp-element-button,
   which would drag in WordPress's own button styling as well. */
.ims-next a.ims-next__cta:not(.wp-element-button) {
	/* Inline-flex with a 48px floor: padding alone left this at 42px on a
	   360px screen, under the 44px a thumb needs. */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	padding: 0.9em 1.5em;
	background-color: var(--wp--custom--v2--surface--ink, #141414);
	color: #FFFFFF;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(0.8125rem, 1.1vw, 0.9375rem);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	text-decoration: none;
}

.ims-next__alt {
	margin: clamp(0.9rem, 2.5vw, 1.25rem) 0 0;
	font-size: 0.9375rem;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/*
 * The secondary custom-quote link sits inline inside a sentence, so it is
 * exempt from WCAG 2.5.8, but it is a real funnel action on touch screens.
 * Padding on an inline-block grows the hit area to >=44px; the matching
 * negative margins pull the margin box back to the text height so the line
 * box, the sentence and the spacing below the primary CTA are unchanged.
 */
.ims-next__alt a {
	display: inline-block;
	padding: 0.8rem 0.25rem;
	margin: -0.8rem -0.25rem;
}

.ims-next__choices {
	display: flex;
	flex-wrap: wrap;
	gap: clamp(0.75rem, 2vw, 1.25rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-next__choice {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	padding: 0.6em 1.35em;
	box-shadow: inset 0 0 0 1px currentColor;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: clamp(0.8125rem, 1.1vw, 0.9375rem);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	text-decoration: none;
	color: inherit;
}

.ims-next a.ims-next__cta:focus-visible,
.ims-next__choice:focus-visible,
.ims-next__alt a:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}


/* =========================================================
   White canvas and the /work/ gateway
   Added together: the gateway is the first page that has to
   sit on the practical canvas rather than a portfolio one,
   and both exist to make the masthead and the page body read
   as a single system.
   ========================================================= */

/* ---------------------------------------------------------
   Practical pages move from warm paper to white.
   Home (.ims-poster) and the portfolio canvases (.ims-canvas)
   are deliberately excluded — there the colour is the
   identity, and both are approved as they stand. Everything
   else — services, process, quote, cart, checkout, catalogue,
   account, product — becomes one white field, so moving
   between a gallery and a service page is no longer a change
   of paper. Ink on white is 18.1:1, above the 17.0:1 these
   pages carried on paper, so no contrast pairing regresses.
   --------------------------------------------------------- */
body:not(.ims-canvas):not(.ims-poster) {
	background-color: var(--wp--preset--color--white);
}

body:not(.ims-canvas):not(.ims-poster) .ims-surface--paper {
	--ims-surface-bg: var(--wp--preset--color--white);
}

/* An intro paragraph and the meta link that follows it carried no space
   between them at all — 0px, measured — so the link read as a stray last
   line of the paragraph. Invisible enough on paper; obvious on white. The
   copy is untouched, this is the gap that was missing. */
.ims-page .ims-section > p + p.ims-meta.ims-link {
	margin-top: clamp(1rem, 2vw, 1.5rem);
}

/* ---------------------------------------------------------
   The /work/ gateway.
   Centred, white, and mostly air: one question and three
   wordmarks, with enough distance between them that the
   question reads as a question rather than as a label for the
   marks underneath.
   --------------------------------------------------------- */
.ims-gate {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: clamp(2.5rem, 7vw, 6rem);
	padding: clamp(3rem, 10vw, 8rem) var(--wp--custom--v2--register--text-gutter);
	min-height: min(60vh, 34rem);
	text-align: center;
}

.ims-gate__question {
	margin: 0;
	max-width: 22ch;
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.75rem, 4.4vw, 3.5rem);
	font-weight: 600;
	line-height: 1.05;
	letter-spacing: var(--wp--custom--v2--type--headline-tracking);
	text-wrap: balance;
}

.ims-gate__choices {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: clamp(1.25rem, 4vw, 3.5rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-gate__item {
	margin: 0;
	display: flex;
}

/* The padding is the tap target. The marks are sized by their
   letterforms, which leaves AI only ~31px tall and ~31px wide on a
   360px screen — comfortably under 44px, and these three links are the
   only controls the page has. The padding lifts every one of them past
   44px in both directions without changing how the artwork is sized. */
.ims-gate__link {
	display: block;
	padding: clamp(0.5rem, 1.5vw, 0.75rem) clamp(1rem, 3vw, 1.5rem);
	line-height: 0;
	text-decoration: none;
}

/* Height rather than width, for the same reason as the gallery
   navigation: AI is a short mark and PHOTO a long one, and matching them
   on width would make AI enormous beside the other two. */
.ims-gate__art {
	display: block;
	width: auto;
	max-width: 100%;
	height: clamp(2.25rem, 5vw, 4rem);
}

.ims-gate__art--hover {
	display: none;
}

/* Fine pointers and keyboard focus only, and a straight exchange of one
   file for the other: no fade, no scale, no movement. Both files are in
   the markup, so there is no fetch on first hover and no blank moment. */
@media (hover: hover) and (pointer: fine) {
	.ims-gate__link:hover .ims-gate__art--rest,
	.ims-gate__link:focus-visible .ims-gate__art--rest {
		display: none;
	}

	.ims-gate__link:hover .ims-gate__art--hover,
	.ims-gate__link:focus-visible .ims-gate__art--hover {
		display: block;
	}
}

.ims-gate__link:focus-visible {
	outline: 2px solid var(--wp--custom--v2--surface--ink);
	outline-offset: 8px;
}

/* One mark per row below the wrap point, rather than two on one line and
   a widow underneath — and everything larger while it is there. A phone
   has nothing else on this page: the question and the three choices are
   the whole of it, so they can afford the scale that makes them read as
   the decision they are. The question goes up about a sixth, the marks
   about a third, and the air between them with it. */
@media (max-width: 599px) {
	.ims-gate {
		gap: clamp(3rem, 9vw, 6rem);
		padding-block: clamp(3.5rem, 12vw, 8rem);
	}

	.ims-gate__question {
		font-size: clamp(2.05rem, 5.2vw, 3.5rem);
	}

	.ims-gate__choices {
		flex-direction: column;
		gap: clamp(2.25rem, 8vw, 3.25rem);
	}

	.ims-gate__art {
		height: clamp(3rem, 6.6vw, 4rem);
	}
}


/* =========================================================
   The catalogue, in two groups
   Six packages used to be one undifferentiated run in a
   four-column grid: 4 + 2 at desktop, 2/2/2 ragged at tablet,
   and ordered alphabetically, so Brand Campaign Collection at
   $1,500 sat between the $150 and the $600 AI package. They
   are now two named groups of three, each ordered by price —
   which is the commercial progression in both families — with
   one shared geometry.
   ========================================================= */
.ims-catgroup {
	padding-block: clamp(2rem, 4vw, 3.5rem);
}

/* The second group follows the first directly; only the page's
   own rhythm separates them, not a doubled section pad. */
.ims-catgroup + .ims-catgroup {
	padding-top: 0;
}

/*
 * The group heading labels the three packages under it; it does not
 * compete with them. `.ims-page .ims-section > h2` is (0,2,1) and sets the
 * page's display headline size, which put "AI PRODUCT ADVERTISING IMAGERY"
 * across two lines at the scale of an H1 — so this answers at (0,3,1) and
 * puts the heading back into the metadata register the eyebrows use.
 */
.ims-page .ims-catgroup > h2.ims-catgroup__title {
	max-width: min(100%, 76rem);
	margin-inline: auto;
	margin-bottom: clamp(1rem, 2vw, 1.75rem);
	padding-bottom: clamp(0.5rem, 1vw, 0.75rem);
	border-bottom: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.3;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	text-wrap: wrap;
}

/* Three across the group, not four across the page. The
   !important matches the rule it overrides: WooCommerce's own
   woocommerce-layout.css sets li.product widths at the same
   specificity, so this has to answer in kind. */
.ims-catgroup .woocommerce ul.products {
	grid-template-columns: repeat(3, 1fr) !important;
}

/* Every card in a row ends on the same line, and the buttons
   with them. The card is already a column flex box; this makes
   the price-and-button block take the slack instead of the
   card being sized by its longest description. */
/* Four to nine characters; it never needs two lines. The margin below it
   is the gap to the button when the card has no slack left to give. */
.ims-catgroup .woocommerce ul.products li.product .price {
	white-space: nowrap;
	margin-bottom: clamp(0.75rem, 1.5vw, 1rem);
}

/*
 * The button carries the auto margin, not the price.
 *
 * Anchoring the price instead only aligns the buttons while every card's
 * title and scope wrap to the same number of lines: in the tablet range
 * "Consistent Product Series" and "Brand Campaign Collection" take a line
 * more than "AI Hero Image" does, and the CTA row broke into two. Pinning
 * the button to the bottom of a stretched card aligns it whatever happens
 * above it.
 */
.ims-catgroup .woocommerce ul.products li.product a.button {
	margin-top: auto;
	align-self: start;
}

/* The scope line: what the package actually includes, printed
   from the product's own description. Quiet, above the price,
   and never taller than the card needs. */
.ims-catgroup .ims-pkg-scope {
	margin: 0 0 clamp(0.75rem, 1.5vw, 1.15rem);
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font-size: clamp(0.875rem, 0.25vw + 0.84rem, 0.9375rem);
	line-height: 1.5;
}

/*
 * Three all the way down to the phone, then one.
 *
 * The sitewide catalogue grid drops to two in the tablet range, which was
 * right for a six-package run. A group of exactly three cannot use two
 * columns without leaving an orphan on its own row, and the whole point of
 * the group is that it reads as three. The cards are text — name, scope,
 * price, button — so they survive the narrower track; below 768 they
 * become a single column, where a 2-up would be too tight to read.
 */
@media (max-width: 767px) {
	.ims-catgroup .woocommerce ul.products {
		grid-template-columns: 1fr !important;
	}
}

/*
 * WooCommerce's clearfix is not free in a grid.
 *
 * woocommerce-layout.css gives ul.products a ::before and ::after of
 * { content: " "; display: table } — inert in the float layout they were
 * written for. This stylesheet makes ul.products a grid, and in a grid
 * container each pseudo-element becomes a grid item and takes a cell.
 * The empty first cell is why three packages laid out as two-then-one,
 * and why the old six-package run rendered three per row inside a
 * four-column grid.
 */
.woocommerce ul.products::before,
.woocommerce ul.products::after,
.woocommerce-page ul.products::before,
.woocommerce-page ul.products::after {
	display: none;
}


/* =========================================================
   About
   A studio profile: white, black type, editorial spacing.
   No panels, no cards, no shadows — the structure is the
   measure, the hairlines and the air between sections.
   ========================================================= */

/*
 * About runs on a wider measure than the 740px reading column the other
 * seeded pages use — it carries a two-column profile, a film and a picture
 * grid, none of which fit in a prose measure. Every direct child of its
 * sections takes the same width, so the eyebrow, the name, the section
 * headings, the grids and the closing links all share one left edge. The
 * bio keeps a readable measure of its own inside that.
 */
body.ims-page-about .wp-block-post-content > .ims-section {
	max-width: min(100%, 76rem);
	margin-inline: auto;
}

body.ims-page-about .ims-section > * {
	max-width: 100%;
	margin-inline: auto;
}

/*
 * The section headings label their sections; they are not statements.
 * `.ims-page .ims-section > h2` is (0,2,1) and sets the page's display
 * headline size, which set "SELECTED RECOGNITION" across two lines at H1
 * scale. This answers at (0,3,2) and returns them to the metadata register
 * the eyebrows use.
 */
body.ims-page-about .ims-section > h2 {
	font-family: inherit;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.3;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	margin-bottom: clamp(1rem, 2vw, 1.75rem);
	text-wrap: wrap;
}

/* The profile: bio left, portrait right, and the portrait
   deliberately the smaller half. It is a professional
   profile picture, not a hero. */
.ims-about-profile {
	display: grid;
	grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
	gap: clamp(1.75rem, 4vw, 4rem);
	align-items: start;
}

/* Scoped to the page so it outranks the section-child rule above; a bare
   class lost to it and the bio ran the full width of its column. */
body.ims-page-about .ims-about-profile__text > p {
	max-width: 62ch;
}

.ims-about-portrait {
	margin: 0;
}

.ims-about-portrait__img {
	display: block;
	width: 100%;
	height: auto;
}

@media (max-width: 781px) {
	.ims-about-profile {
		grid-template-columns: 1fr;
		gap: clamp(1.5rem, 6vw, 2.5rem);
	}

	/* On a phone the portrait comes straight after the name, before the
	   words: the columns are gone, so the face is what introduces the
	   person and the biography reads underneath it. It takes the full
	   measure it is given rather than sitting in a narrow box off to one
	   side, and the crop is untouched. */
	.ims-about-portrait {
		order: -1;
		max-width: none;
		width: 100%;
	}

	.ims-about-portrait__img {
		width: 100%;
	}
}

/* And the two closing links sit with the film, not adrift below it. */
body.ims-page-about .ims-section-next {
	padding-top: 0;
}

/* The credential closes the biography.
   It belongs to the text column, not to the page, so it is
   centred against that column and sized from it — about three
   quarters of its width, enough for "200 BEST" and the Lürzer's
   stamp to read at a glance while staying subordinate to the
   name, the portrait and the words above it. The artwork carries
   its own wording, so nothing is set around it, and its
   intrinsic ratio is untouched: never stretched, never cropped.
   The space above is a pause, not a join. */
.ims-credit {
	margin: clamp(2rem, 4vw, 2.75rem) 0 0;
	display: flex;
	justify-content: center;
}

.ims-credit__mark {
	display: block;
	width: 75%;
	height: auto;
}

/* Once the columns stack there is no text column to sit inside, so the
   credential takes more of the width it is given — 75% of a phone screen
   would leave the stamp too small to read. */
@media (max-width: 781px) {
	.ims-credit {
		margin-top: clamp(1.75rem, 6vw, 2.5rem);
	}

	.ims-credit__mark {
		width: 86%;
		max-width: 26rem;
	}
}

/* The profile list: four hairline rows, label and line. */
.ims-about-facts {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	max-width: 52rem;
}

.ims-about-facts__item {
	display: grid;
	grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
	gap: 0.35rem clamp(1rem, 3vw, 2.5rem);
	padding-block: clamp(0.9rem, 2vw, 1.4rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	margin: 0;
}

.ims-about-facts__item:last-child {
	border-bottom: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}

.ims-about-facts__term {
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	margin: 0;
}

.ims-about-facts__desc {
	margin: 0;
	max-width: 46ch;
}

@media (max-width: 599px) {
	.ims-about-facts__item {
		grid-template-columns: 1fr;
	}
}

/* The two closing actions, on one hairline. */
.ims-about-next {
	display: flex;
	flex-wrap: wrap;
	gap: clamp(1.25rem, 3vw, 2.5rem);
	padding-top: clamp(0.9rem, 2vw, 1.4rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}


/* ---------------------------------------------------------
   The end of a wall.
   A visitor who has reached the bottom of a gallery has spent
   real attention on it; this is the offer that meets them
   there, above the two remaining categories. White, centred,
   typography and space - no card, no panel, no colour field.
   --------------------------------------------------------- */
.ims-endcta {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	gap: clamp(1rem, 2.4vw, 1.5rem);
	padding: clamp(3.5rem, 9vw, 7rem) var(--ims-edge) clamp(1.5rem, 4vw, 3rem);
	background-color: #FFFFFF;
}

.ims-endcta__ask {
	margin: 0;
	max-width: 24ch;
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.5rem, 3.6vw, 2.5rem);
	font-weight: 600;
	line-height: 1.1;
	letter-spacing: -0.02em;
	text-wrap: balance;
}

/* The same button the Process pages close with, so a visitor meets one
   control and not two designs for the same act. */
.ims-endcta a.ims-endcta__cta:not(.wp-element-button) {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	padding: 0.75rem 1.5rem;
	background-color: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--white);
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	text-decoration: none;
}

/* The "Need a custom project? Request a quote" line under the button was
   retired in 5.17.3 with the process-first ending; its rules went with it. */

.ims-endcta a:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 3px;
}


/* =========================================================
   Consolidated UX pass
   Spacing, hierarchy and mobile reading across the pages
   that carry long copy or a decision at the end.
   ========================================================= */

/* --- How We Work: the gateway ---------------------------
   The divider sat above "Choose your service", cutting the
   choice off from the sentence that sets it up; the block
   was left-aligned in a 740px column while the page runs to
   76rem. The rule moves below the choices, where it
   separates the page from the footer, and the whole block
   is centred on the page's own measure. */
body.ims-page-process .ims-next {
	margin-top: clamp(1.5rem, 3vw, 2.25rem);
	padding-top: 0;
	padding-bottom: clamp(2rem, 5vw, 3.5rem);
	border-top: 0;
	border-bottom: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	max-width: min(100%, 76rem);
	margin-inline: auto;
	text-align: center;
}

body.ims-page-process .ims-next__choices {
	justify-content: center;
	gap: clamp(1.5rem, 5vw, 4rem);
}

body.ims-page-process .ims-next__choice {
	flex: 0 0 auto;
}

/* The selector belongs to the sentence above it, not to a section of
   its own. */
body.ims-page-process .wp-block-post-content > .ims-section:last-child {
	padding-top: 0;
}

/* --- Process detail pages: mobile reading ---------------
   The step copy was set at the desktop body size on a
   360-430px screen, which is small for a page whose whole
   job is to be read. One step up, with the line-height to
   match, and the step titles strengthened so the hierarchy
   survives the increase. The numbers stay secondary. */
@media (max-width: 430px) {
	.ims-step__body,
	.ims-page .ims-section > p {
		font-size: 1.0625rem;
		line-height: 1.62;
	}

	.ims-step__title {
		font-size: 1.125rem;
		line-height: 1.3;
	}

	.ims-step__no {
		font-size: 0.8125rem;
	}
}

/* --- Process detail pages: the closing order ------------
   The divider used to sit between the last step and the
   call to action, which read as the CTA belonging to the
   footer rather than to the page. It now closes the page
   underneath the CTA instead. */
body.ims-page-photography .ims-next,
body.ims-page-ai-product-imagery .ims-next,
body.ims-page-retouching .ims-next {
	border-top: 0;
	padding-top: 0;
	padding-bottom: clamp(2rem, 5vw, 3.25rem);
	border-bottom: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}

/* --- Request a Quote: three routes, not one column ------
   The three service blocks ran together as continuous
   text. A hairline between families, a stronger category
   label, and less air inside each block than between them,
   so each one reads as a separate way in. */
.ims-sales__head {
	padding-block: clamp(0.5rem, 1.5vw, 1.25rem) clamp(1.25rem, 2.5vw, 2rem);
}

.ims-offers {
	gap: 0;
}

.ims-offer {
	padding-block: clamp(1.75rem, 4vw, 2.75rem);
	gap: clamp(0.5rem, 1.2vw, 0.9rem);
}

.ims-offer + .ims-offer {
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}

/* The category label is the thing a scanning eye lands on, so it is
   given weight; the service name stays the larger of the two. */
.ims-offer > .ims-meta--quiet:first-child {
	font-weight: 600;
	color: var(--wp--preset--color--ink);
	margin-bottom: 0.15rem;
}

/* --- Order Online: one title, not three ----------------
   The page said "Order Online" three times before any
   content: a breadcrumb, an eyebrow and the heading. Only
   the heading survives, and the top of the page closes up
   behind it. */
body.ims-page-order-online .ims-breadcrumbs,
body.ims-page-order-online .ims-section:first-of-type > .is-style-eyebrow {
	display: none;
}

body.ims-page-order-online .wp-block-post-content > .ims-section:first-of-type {
	padding-top: clamp(1rem, 2.5vw, 2rem);
}

/* --- Product pages: closing the gaps -------------------- */
.ims-commerce--product .ims-section,
.ims-commerce--product .summary {
	padding-block: clamp(1rem, 2.5vw, 1.75rem);
}

body.single-product .ims-breadcrumbs {
	padding-bottom: 0;
}


/* ---------------------------------------------------------
   The post-order flow: upload, then brief, then send.
   White canvas, hairlines and spacing - the only colour left
   in here belongs to status notices, which mean something.
   --------------------------------------------------------- */
.imsc-upload {
	margin: clamp(1.5rem, 3vw, 2.25rem) 0 clamp(2rem, 4vw, 3rem);
	padding-bottom: clamp(1.25rem, 2.5vw, 2rem);
	border-bottom: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}

.imsc-upload__title,
.imsc-brief__step {
	margin: 0 0 0.5rem;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.imsc-brief__step {
	margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
}

.imsc-upload__help {
	margin: 0 0 clamp(0.9rem, 2vw, 1.25rem);
	max-width: 56ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.imsc-upload__action {
	margin: 0 0 0.6rem;
}

/* The same control the rest of the site uses to mean "do this now". */
.imsc-upload a.imsc-upload__cta {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	padding: 0.75rem 1.5rem;
	background-color: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--white);
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	text-decoration: none;
}

.imsc-upload a.imsc-upload__cta:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 3px;
}

/* A family whose File Request is not configured yet. */
.imsc-upload__pending {
	margin: 0 0 0.6rem;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* Bank details are information, not a decorative panel. */
.ims-commerce--received .woocommerce-bacs-bank-details,
.ims-commerce--received ul.wc-bacs-bank-details {
	background: transparent;
	padding: 0;
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	padding-top: clamp(0.75rem, 1.5vw, 1.15rem);
	list-style: none;
}

/* Order received: close the gaps. */
body.woocommerce-order-received .ims-sales,
body.woocommerce-order-received .ims-commerce--received {
	padding-block: clamp(1rem, 2.5vw, 2rem) clamp(2rem, 4vw, 3.5rem);
}


/* =========================================================
   UX V2: spacing after the breadcrumbs, and the quote page
   ========================================================= */

/* The visible breadcrumb is gone from every template, so the first
   section no longer needs to clear it. Pages start under the header
   instead of a band of nothing. */
.ims-page .wp-block-post-content > .ims-section:first-of-type,
body.ims-page-about .wp-block-post-content > .ims-section:first-of-type,
body.ims-page-order-online .wp-block-post-content > .ims-section:first-of-type {
	padding-top: clamp(1.25rem, 3vw, 2.5rem);
}

.ims-sales__head {
	padding-block: clamp(1.25rem, 3vw, 2.5rem) clamp(1.25rem, 2.5vw, 2rem);
}

/* Request a Quote: the rule between service families ran only as far as
   the reading measure, so it stopped short of the page and read as a
   broken line. It now spans the container - gutter to gutter - while the
   copy inside keeps its own narrower measure. */
.ims-offers {
	max-width: min(100%, 76rem);
}

.ims-offer + .ims-offer {
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}

.ims-offer > * {
	max-width: min(100%, 60rem);
}

/* --- Every footer sits under the same line -------------
   Templates were each deciding their own separation, so the rule above
   the footer changed width and spacing from page to page. One treatment,
   set on the footer itself, so it cannot drift. */
.ims-foot {
	border-top: 1px solid var(--ims-hairline, var(--wp--custom--v2--surface--hairline, #E7E2D9));
	margin-top: clamp(2.5rem, 6vw, 5rem);
}

/* The gallery pages close with the CTA, the category navigation and then
   the footer; the rhythm between them is set here so the three do not
   pile up. */
.ims-galnav-wrap-bottom {
	padding-block: clamp(2rem, 5vw, 3.5rem) clamp(2.5rem, 6vw, 4.5rem);
}


/* =========================================================
   Stay in touch
   The quiet way out. Everything else on this site closes
   with an ask; this closes with an invitation, and is
   deliberately lighter than the CTA above it.
   ========================================================= */
.ims-stay {
	padding: clamp(2.5rem, 6vw, 4rem) var(--ims-edge) clamp(1rem, 2.5vw, 2rem);
	text-align: center;
	background-color: #FFFFFF;
}

.ims-stay__title {
	margin: 0 0 clamp(0.5rem, 1.4vw, 0.85rem);
	font-size: clamp(1.0625rem, 1.6vw, 1.375rem);
	font-weight: 600;
	line-height: 1.25;
	letter-spacing: -0.01em;
	text-wrap: balance;
}

.ims-stay__body {
	margin: 0 auto clamp(1.25rem, 3vw, 1.75rem);
	max-width: 56ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* Words, not brand badges: five text links in the site's own metadata
   register, with no third-party colour anywhere near them. */
.ims-social {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: baseline;
	gap: clamp(0.55rem, 1.6vw, 1.35rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-social__item {
	margin: 0;
}

.ims-social__item + .ims-social__item::before {
	content: "";
	display: inline-block;
	width: 1px;
	height: 0.85em;
	margin-right: clamp(0.55rem, 1.6vw, 1.35rem);
	vertical-align: -0.05em;
	background-color: var(--ims-hairline, var(--wp--custom--v2--surface--hairline, #E7E2D9));
}

.ims-social a.ims-social__link {
	display: inline-block;
	padding: 0.45rem 0.15rem;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	text-decoration: none;
	border-bottom: 1px solid transparent;
	white-space: nowrap;
}

/*
 * In the exit prompt the five sit on one line, tightened so they fit
 * without wrapping on a desktop panel. Same typography as the footer, no
 * boxes, no brand colour - five words and four hairlines.
 */
.ims-exit .ims-social {
	gap: clamp(0.4rem, 1vw, 0.8rem);
}

.ims-exit .ims-social__item + .ims-social__item::before {
	margin-right: clamp(0.4rem, 1vw, 0.8rem);
}

.ims-exit .ims-social a.ims-social__link {
	font-size: 0.75rem;
	letter-spacing: 0.05em;
	padding-inline: 0;
}

/* If the five cannot fit, they break to a second centred line - and the
   separator of whichever link starts that line is switched off, so no
   rule is ever left hanging at a line edge. */
@media (max-width: 519px) {
	.ims-exit .ims-social {
		display: grid;
		grid-template-columns: repeat(3, auto);
		justify-content: center;
		row-gap: 0.4rem;
	}

	.ims-exit .ims-social__item:nth-child(3n + 1)::before {
		display: none;
	}
}

@media (hover: hover) and (pointer: fine) {
	.ims-social a.ims-social__link:hover {
		border-bottom-color: currentColor;
	}
}

.ims-social a.ims-social__link:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

/* On About and the Process pages the block sits inside a section that
   already has its own rhythm, so it only needs a line above it. */
body.ims-page-about .ims-section-stay,
.ims-section-stay {
	padding-top: clamp(1.5rem, 3vw, 2.25rem);
}

.ims-section-stay .ims-stay {
	padding-inline: 0;
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	padding-top: clamp(1.75rem, 4vw, 2.75rem);
}

/* ---------------------------------------------------------
   The exit prompt.
   Same language as the 60% prompt - white panel, dark
   backdrop, no marketing chrome - but it does not lock the
   page. It is an offer, not a question that routes the
   visit, and an offer that seizes the scrollbar on the way
   out is the thing everyone hates.
   --------------------------------------------------------- */
.ims-exit {
	position: fixed;
	inset: 0;
	z-index: 1200;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: clamp(16px, 5vw, 48px);
	background-color: rgba(20, 20, 20, 0.72);
	opacity: 0;
	transition: opacity 260ms var(--wp--custom--motion--easing);
}

.ims-exit.is-open {
	opacity: 1;
}

.ims-exit[hidden] {
	display: none;
}

.ims-exit__panel {
	/* Wide enough that the five destinations stay on one line, which is
	   the whole point of the row; still a panel and not a page. */
	width: min(40rem, 100%);
	max-height: calc(100vh - 2 * clamp(16px, 5vw, 48px));
	overflow: auto;
	padding: clamp(2rem, 5vw, 3rem) clamp(1.5rem, 4vw, 2.5rem);
	background-color: #FFFFFF;
	color: var(--wp--custom--v2--surface--ink, #141414);
	text-align: center;
	transform: translateY(8px);
	transition: transform 260ms var(--wp--custom--motion--easing);
}

.ims-exit.is-open .ims-exit__panel {
	transform: translateY(0);
}

.ims-exit__eyebrow {
	margin: 0 0 0.5rem;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.ims-exit__title {
	margin: 0 0 clamp(0.6rem, 1.6vw, 1rem);
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.5rem, 3.4vw, 2rem);
	font-weight: 600;
	line-height: 1.1;
	letter-spacing: -0.02em;
}

.ims-exit__body {
	margin: 0 auto clamp(1.25rem, 3vw, 1.75rem);
	max-width: 46ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.ims-exit__no {
	margin-top: clamp(1.25rem, 3vw, 1.75rem);
	padding: 0.6rem 0.25rem;
	min-height: 44px;
	border: 0;
	background: none;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	cursor: pointer;
}

.ims-exit__no:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

/* The global reduced-motion rule collapses every transition here; the
   transform is zeroed as well so the panel is never offset. */
@media (prefers-reduced-motion: reduce) {
	.ims-exit__panel,
	.ims-exit.is-open .ims-exit__panel {
		transform: none;
	}
}


/* The footer navigation's tablet and phone wrapping rules were removed
   with the single-row footer (see the V6 layer). The legal row keeps its
   own phone treatment. */
@media (max-width: 599px) {
	/* The copyright takes its own line and the three legal links share the
	   next, so that row cannot break mid-way either. */
	.ims-foot .ims-foot__base.wp-block-group {
		display: grid;
		grid-template-columns: repeat(2, auto);
		justify-content: center;
		row-gap: clamp(0.4rem, 1.2vw, 0.7rem);
	}

	.ims-foot .ims-foot__base > p:first-child {
		grid-column: 1 / -1;
	}

	/* Four legal links in two rows of two (Cookie settings joined them in
	   5.18.0): no separator at the start of a row. */
	.ims-foot .ims-foot__base > p:nth-child(2)::before,
	.ims-foot .ims-foot__base > p:nth-child(4)::before {
		display: none;
	}
}


/* =========================================================
   Trust
   Proof placed where a decision is actually being made, and
   different evidence at each stage: a line on Home, client
   relationships on About, one relevant voice on a service
   page, a rating cue at the end of a wall, reassurance at
   the quote form, and the full account on /reviews/.
   Typography and hairlines - no badges, no panels, no green.
   ========================================================= */
.ims-trust {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: center;
	gap: clamp(0.4rem, 1.2vw, 0.9rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-trust__fact {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.ims-trust__fact:first-child {
	color: var(--wp--preset--color--ink);
	font-weight: 600;
}

.ims-trust__fact + .ims-trust__fact::before {
	content: "";
	display: inline-block;
	width: 1px;
	height: 0.8em;
	margin-right: clamp(0.4rem, 1.2vw, 0.9rem);
	vertical-align: -0.05em;
	background-color: var(--ims-hairline, var(--wp--custom--v2--surface--hairline, #E7E2D9));
}

/* The line that carries the proof, wherever it appears. */
.ims-trustline {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: clamp(0.5rem, 1.4vw, 0.85rem);
	text-align: center;
}

.ims-trustline__link {
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* --- the reviews themselves --------------------------- */
.ims-reviews {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: clamp(1.75rem, 4vw, 3rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-review {
	display: grid;
	gap: clamp(0.5rem, 1.2vw, 0.75rem);
	align-content: start;
	padding-top: clamp(1rem, 2.4vw, 1.5rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	margin: 0;
}

/* Neutral, not gold: the rating is typography like everything else, and
   the reader who cannot see the marks gets the same fact in words. */
.ims-review__stars {
	margin: 0;
	font-size: 0.9375rem;
	letter-spacing: 0.18em;
	color: var(--wp--preset--color--ink);
}

.ims-review__quote {
	margin: 0;
	font-size: clamp(1rem, 0.3vw + 0.95rem, 1.0625rem);
	line-height: 1.55;
}

.ims-review__who {
	margin: 0;
	font-weight: 600;
}

.ims-review__meta {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.ims-review__source {
	margin: 0;
	font-size: 0.8125rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

@media (max-width: 781px) {
	.ims-reviews {
		grid-template-columns: 1fr;
	}
}

/* Client names, set as type. No logos: none are licensed, and a wall of
   mismatched brand marks would look worse than the names do. */
.ims-clients {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: clamp(0.75rem, 2.5vw, 2rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-clients__item {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* Where a trust cue joins an existing closing block. */
.ims-endcta .ims-trustline {
	margin-top: clamp(0.75rem, 2vw, 1.15rem);
}

.ims-section-trust {
	padding-top: 0;
}

.ims-section-trust .ims-trustline,
.ims-section-trust .ims-clients {
	padding-top: clamp(1.25rem, 3vw, 2rem);
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
}


/* =========================================================
   V4 - Trust, reviews and lower-page composition
   An additive layer, per docs/VISUAL-SYSTEM-V2.md section 9.
   Three problems it exists to fix: proof was set as fine
   print and therefore was not proof; the lower half of every
   long page mixed centred and left-aligned blocks with a
   hairline between each of them; and the mobile footer
   wrapped rather than aligned.
   ========================================================= */

/* One spacing step either side of every section divider, so the rhythm at
   the end of a page is a scale rather than four independent decisions. */
:root {
	--ims-v4-gap: clamp(2rem, 5vw, 3.25rem);
	--ims-v4-measure: min(100%, 76rem);
	--ims-v4-line: var(--ims-hairline, var(--wp--custom--v2--surface--hairline, #E7E2D9));
}

/* An eyebrow stays an eyebrow inside a section.
   `.ims-page .ims-section > h2` is (0,2,1) and sets the page's display
   headline size, so "SELECTED CLIENT RELATIONSHIPS" was set at H1 scale and
   dominated a page whose subject is the reviews beneath it. About already
   had a page-scoped answer to this; this is the general one, at (0,3,1), so
   any section heading marked as an eyebrow is one. */
.ims-page .ims-section > h2.is-style-eyebrow {
	font-family: inherit;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.3;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	margin-bottom: clamp(1rem, 2vw, 1.75rem);
	text-wrap: wrap;
}

/* --- The proof, composed ------------------------------------------------
   Rating and count carry the weight; the marks support them at metadata
   size. Rendering all four facts identically is what made the old line
   read as a disclaimer. */
.ims-proof {
	display: grid;
	justify-items: center;
	gap: clamp(0.35rem, 1vw, 0.6rem);
	text-align: center;
}

.ims-proof__rating {
	margin: 0;
	display: flex;
	align-items: baseline;
	gap: clamp(0.4rem, 1.2vw, 0.75rem);
	flex-wrap: wrap;
	justify-content: center;
}

.ims-proof__score {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(3rem, 8vw, 5.5rem);
	font-weight: 600;
	line-height: 0.92;
	letter-spacing: -0.03em;
	color: var(--wp--preset--color--ink);
}

.ims-proof__scale {
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.ims-proof__count {
	margin: 0;
	font-size: clamp(1rem, 1.4vw + 0.7rem, 1.375rem);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink);
}

.ims-proof__marks {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: center;
	gap: clamp(0.4rem, 1.2vw, 0.9rem);
	margin: clamp(0.25rem, 0.8vw, 0.5rem) 0 0;
	padding: 0;
	list-style: none;
}

.ims-proof__mark {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* Decorative, so it is never read out between the marks. */
.ims-proof__mark + .ims-proof__mark::before {
	content: "";
	display: inline-block;
	width: 1px;
	height: 0.8em;
	margin-right: clamp(0.4rem, 1.2vw, 0.9rem);
	vertical-align: -0.05em;
	background-color: var(--ims-v4-line);
}

.ims-proof__cta {
	margin-top: clamp(0.75rem, 2vw, 1.25rem);
}

/* --- Home: the trust section, below the hero ---------------------------
   The hero keeps its full screen. This is the section beneath it, and it
   is a section: a rating at display size, the marks, and one way through
   to the reviews. */
.ims-home-main {
	margin-block: 0;
	background-color: #FFFFFF;
}

.ims-hometrust {
	display: grid;
	justify-items: center;
	gap: clamp(0.5rem, 1.5vw, 1rem);
	padding: clamp(2.5rem, 6vw, 4.5rem) var(--ims-home-poster-gutter, 24px) clamp(3rem, 7vw, 5.5rem);
	border-top: 1px solid var(--ims-v4-line);
	margin-inline: var(--ims-home-poster-gutter, 24px);
	background-color: #FFFFFF;
}

.ims-hometrust__title {
	margin: 0 0 clamp(0.75rem, 2vw, 1.5rem);
	font-weight: 600;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* The featured slot, when one approved testimonial exists. Single column
   and no top rule: it is one voice inside a section, not a grid of them. */
.ims-reviews--featured {
	grid-template-columns: 1fr;
	max-width: 44rem;
	margin-top: clamp(1.25rem, 3vw, 2rem);
}

.ims-reviews--featured .ims-review {
	border-top: 0;
	padding-top: 0;
	justify-items: center;
	text-align: center;
}

/* --- /reviews/: the trust summary --------------------------------------
   Left, not centred. It sits under a left-aligned H1 and a left-aligned
   paragraph, and a centred block between them is the mixed system that made
   the lower half of these pages look assembled rather than composed. The
   Home section keeps its centred composition because that page is centred
   throughout. */
.ims-trustsummary {
	display: grid;
	justify-items: start;
	gap: 0;
	padding-block: clamp(1.5rem, 4vw, 2.5rem) clamp(0.5rem, 1.5vw, 1rem);
	text-align: left;
}

.ims-trustsummary .ims-proof {
	justify-items: start;
	text-align: left;
}

.ims-trustsummary .ims-proof__rating,
.ims-trustsummary .ims-proof__marks {
	justify-content: flex-start;
}

/* Client names, given the weight the section heading used to take, and the
   same left edge as the heading above them. */
.ims-clients {
	justify-content: flex-start;
}

.ims-clients__item {
	font-size: clamp(0.9375rem, 0.5vw + 0.8rem, 1.0625rem);
	letter-spacing: 0.06em;
}

/* The trust line on About follows the same edge. */
.ims-section-trust .ims-trustline {
	align-items: flex-start;
	text-align: left;
}

.ims-section-trust .ims-trust {
	justify-content: flex-start;
}

/* --- The end of a gallery ---------------------------------------------
   Conversion, then Stay in Touch, then the other two categories. The three
   used to run together as one continuous block. One hairline between each,
   the same width, colour and spacing on all three galleries, drawn to the
   content measure rather than to the viewport. */
body.ims-world .ims-endcta {
	padding-bottom: var(--ims-v4-gap);
}

/* Both containers take the same inline padding, so "the content measure"
   means the same number for both rules. Left as they were, the two lines
   were drawn 366px and 378px wide on the same phone screen: the same
   declaration resolving 100% against two different content boxes. */
body.ims-world .ims-stay,
body.ims-world .ims-galnav-wrap-bottom {
	padding-top: 0;
	padding-inline: var(--ims-edge);
}

body.ims-world .ims-stay::before,
body.ims-world .ims-galnav-wrap-bottom::before {
	content: "";
	display: block;
	width: var(--ims-v4-measure);
	height: 1px;
	margin: 0 auto var(--ims-v4-gap);
	background-color: var(--ims-v4-line);
}

body.ims-world .ims-stay {
	padding-bottom: var(--ims-v4-gap);
}

/* --- About: one lower-page composition --------------------------------
   Each major section gets one rule above it and no rules inside it. The
   trust section used to draw a hairline above the client names and another
   above the Fiverr line, which made one section look like two and put four
   lines on a phone screen that needed one. */
.ims-section-trust {
	padding-top: 0;
}

.ims-section-trust .ims-trustline,
.ims-section-trust .ims-clients {
	padding-top: 0;
	border-top: 0;
}

.ims-section-trust > *:first-child,
.ims-section-talk > *:first-child {
	padding-top: clamp(1.75rem, 4vw, 2.75rem);
	border-top: 1px solid var(--ims-v4-line);
}

.ims-section-trust .ims-trustline {
	margin-top: clamp(1.25rem, 3vw, 2rem);
}

/* Let's talk: the relationship path, not the pricing one. */
.ims-talk__body {
	margin: 0 0 clamp(1rem, 2.5vw, 1.5rem);
	max-width: 52ch;
}

@media (max-width: 767px) {
	/*
	 * One alignment for the whole lower page. Trust, client names, the
	 * Fiverr line and the social row each centred themselves while the
	 * copy beside them was left aligned, so a phone screen showed three
	 * different left edges. Everything below the profile now shares the
	 * page's own edge.
	 */
	body.ims-page-about .ims-stay,
	body.ims-page-about .ims-trustline,
	body.ims-page-about .ims-proof,
	body.ims-page-about .ims-trust,
	body.ims-page-about .ims-clients,
	body.ims-page-about .ims-stay .ims-social,
	body.ims-page-reviews .ims-stay,
	body.ims-page-reviews .ims-trustline,
	body.ims-page-reviews .ims-proof,
	body.ims-page-reviews .ims-trust,
	body.ims-page-reviews .ims-clients,
	body.ims-page-reviews .ims-stay .ims-social {
		text-align: left;
		justify-items: start;
		justify-content: start;
		align-items: start;
	}

	body.ims-page-about .ims-stay__body,
	body.ims-page-reviews .ims-stay__body {
		margin-inline: 0;
	}

	/* Two columns, so four names read as a set rather than as an
	   uneven three-then-one wrap. */
	body.ims-page-about .ims-clients,
	body.ims-page-about .ims-stay .ims-social,
	body.ims-page-reviews .ims-clients,
	body.ims-page-reviews .ims-stay .ims-social {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: clamp(0.5rem, 2vw, 0.9rem) clamp(0.75rem, 3vw, 1.5rem);
	}

	/* Separators belong to a row, not to a grid cell. */
	body.ims-page-about .ims-trust__fact + .ims-trust__fact::before,
	body.ims-page-about .ims-stay .ims-social__item + .ims-social__item::before,
	body.ims-page-reviews .ims-trust__fact + .ims-trust__fact::before,
	body.ims-page-reviews .ims-stay .ims-social__item + .ims-social__item::before {
		display: none;
	}

	body.ims-page-about .ims-trust,
	body.ims-page-reviews .ims-trust {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: clamp(0.35rem, 1.5vw, 0.6rem) clamp(0.75rem, 3vw, 1.5rem);
	}
}

/* The two-column phone footer that used to be here went with the
   single-row footer; the grouped footer's phone layout is in the V6
   layer. */

/* --- Mobile menu: know, like, trust, act ------------------------------
   The groups are spacing only. About; the three galleries; Reviews; then
   the two ways to act. */
@media (max-width: 1023px) {
	.ims-nav-mobile .wp-block-navigation__container > li:nth-child(1),
	.ims-nav-mobile .wp-block-navigation__container > li:nth-child(4),
	.ims-nav-mobile .wp-block-navigation__container > li:nth-child(5) {
		margin-bottom: clamp(1rem, 4vw, 1.75rem);
	}
}

/* [ims_reviews] renders nothing until real quotes exist. An empty section
   still carries its padding, which would put a screen of dead white between
   the trust summary and the clients, so it collapses instead. */
.ims-section-reviews:not(:has(.ims-reviews)) {
	padding-block: 0;
}

/* The two closing actions belong to the invitation above them, not to a
   section of their own. About's lower half was Client trust, Let's talk,
   Stay in touch and this, each under its own rule: four short blocks and
   four lines, which is a ladder rather than a hierarchy. The rule here is
   the one that was doing the least work. */
.ims-about-next {
	border-top: 0;
	padding-top: 0;
}

/* --- The reviews grid, once it has reviews in it ---------------------
   Two columns inside the 740px measure gave each quote a 300px line, which
   is about forty characters: short enough that a three-line sentence
   becomes six. This section alone takes a wider register - the WIDE case
   in docs/VISUAL-SYSTEM-V2.md section 3 - which brings the measure to
   roughly fifty characters without touching contentSize or reflowing any
   other stored content. */
.ims-section-reviews {
	max-width: min(100%, 56rem);
}

/* Natural heights. A grid stretches its items to the row by default, and
   although these blocks have no box to reveal it, forcing two quotes of
   different lengths to the same height is exactly what the brief rules
   out. */
.ims-reviews {
	align-items: start;
}

/* One voice at a decision point reads as a note, not as a wall: the quote
   at reading size, the attribution quiet beneath it. */
.ims-section-trust .ims-reviews {
	gap: clamp(1.25rem, 3vw, 2rem);
	margin-bottom: clamp(1.5rem, 3.5vw, 2.25rem);
}


/* =========================================================
   V5 - The commercial journey
   Home became a sequence rather than a poster with a trust
   strip bolted underneath, and the service pages explain
   before they price. One spacing scale runs through all of
   it, because the previous complaint was not that sections
   were too big or too small but that every one of them had
   decided for itself.
   ========================================================= */

:root {
	/* Three steps, and nothing else. Between editorial chapters, between
	   blocks inside a chapter, and between a heading and the copy under it. */
	--ims-space-section: clamp(3.25rem, 7vw, 5.5rem);
	--ims-space-block: clamp(1.5rem, 3.5vw, 2.5rem);
	--ims-space-tight: clamp(0.65rem, 1.6vw, 1rem);
}

/* --- Home sections ----------------------------------------------------
   Every one of them takes the poster's own gutter, so the sequence below
   the hero sits on the same left edge as the statement above it. One
   hairline per chapter and none inside them. */
.ims-homesection {
	padding-block: var(--ims-space-section);
	padding-inline: var(--ims-home-poster-gutter, 24px);
	border-top: 1px solid var(--ims-v4-line);
	background-color: #FFFFFF;
}

.ims-homesection__title {
	margin: 0 0 var(--ims-space-block);
	font-weight: 600;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* The trust section joins the sequence rather than sitting outside it: it
   was centred and separately gutter-margined when it was the only thing
   under the poster, which now reads as the one section that does not line
   up with the others. */
.ims-hometrust {
	padding-block: var(--ims-space-section);
	padding-inline: var(--ims-home-poster-gutter, 24px);
	margin-inline: 0;
	justify-items: start;
	text-align: left;
}

.ims-hometrust .ims-proof {
	justify-items: start;
	text-align: left;
}

.ims-hometrust .ims-proof__rating,
.ims-hometrust .ims-proof__marks {
	justify-content: flex-start;
}

.ims-hometrust .ims-reviews--featured {
	margin-inline: 0;
}

.ims-hometrust .ims-reviews--featured .ims-review {
	justify-items: start;
	text-align: left;
}

/* The "work" section that used to sit here - three rows repeating the
   poster's choices - was removed in the launch polish, and its rules with
   it. */

/* --- What I can create ------------------------------------------------
   The question at statement size on the left, the answer at reading size
   on the right. A single column below 900px, because a two-column split of
   a heading and its own answer is unreadable on a phone. */
.ims-cap {
	display: grid;
	gap: var(--ims-space-block);
}

.ims-cap__title {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.875rem, 4.4vw, 3.25rem);
	font-weight: 600;
	line-height: 1.02;
	letter-spacing: -0.03em;
	text-wrap: balance;
}

.ims-cap__body > p {
	margin: 0 0 var(--ims-space-tight);
	max-width: 60ch;
}

.ims-cap__body > p:last-child {
	margin-bottom: 0;
}

@media (min-width: 900px) {
	.ims-cap {
		grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
		gap: clamp(2rem, 5vw, 4.5rem);
		align-items: start;
	}
}

/* --- Services on Home -------------------------------------------------
   Prose, not wordmarks: this section asks which of the three a project is,
   which the artwork above cannot answer. No prices - this is where someone
   chooses a page to read, not something to buy. */
.ims-homeserv__list {
	display: grid;
	gap: var(--ims-space-block);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-homeserv__item {
	display: grid;
	gap: var(--ims-space-tight);
	align-content: start;
	margin: 0;
	padding-top: clamp(1rem, 2.4vw, 1.5rem);
	border-top: 1px solid var(--ims-v4-line);
}

.ims-homeserv__name {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.375rem, 2.4vw, 1.875rem);
	font-weight: 600;
	line-height: 1.1;
	letter-spacing: -0.02em;
}

.ims-homeserv__when {
	margin: 0;
	font-weight: 600;
}

.ims-homeserv__what {
	margin: 0;
	max-width: 42ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.ims-homeserv__more {
	margin: var(--ims-space-tight) 0 0;
}

@media (min-width: 782px) {
	.ims-homeserv__list {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: clamp(1.5rem, 3.5vw, 3rem);
	}
}

/* --- Say hello --------------------------------------------------------
   The one closing move on this site that is not an ask. */
.ims-hello {
	display: grid;
	gap: var(--ims-space-tight);
	justify-items: start;
}

.ims-hello__title {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.875rem, 4.4vw, 3.25rem);
	font-weight: 600;
	line-height: 1.02;
	letter-spacing: -0.03em;
}

.ims-hello__body {
	margin: 0;
	max-width: 48ch;
}

.ims-hello__cta {
	margin-top: var(--ims-space-tight);
}

/* Home's Stay in Touch belongs to the same sequence, on the same edge. */
.ims-home-main .ims-stay {
	padding: var(--ims-space-section) var(--ims-home-poster-gutter, 24px);
	border-top: 1px solid var(--ims-v4-line);
	text-align: left;
}

.ims-home-main .ims-stay__title,
.ims-home-main .ims-stay__body {
	margin-inline: 0;
}

.ims-home-main .ims-social {
	justify-content: flex-start;
}

/* Home carries a footer now, so it needs the page gutter the other
   templates get from their own layout. */
.ims-home-main + .wp-block-template-part .ims-foot,
.ims-home-main ~ .ims-foot {
	margin-top: 0;
}

/* --- Service page components -----------------------------------------
   The same four shapes carry every service page: a split for the
   argument, labelled benefits, a numbered sequence, and the fit
   statement. Built once here so the three pages cannot drift into three
   different designs for the same idea. */

/* The argument: statement left, answer right. One column below 900px,
   because a heading beside its own answer is unreadable on a phone. */
.ims-split {
	display: grid;
	gap: var(--ims-space-block);
}

.ims-split__title {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.625rem, 3.4vw, 2.5rem);
	font-weight: 600;
	line-height: 1.04;
	letter-spacing: -0.025em;
	text-wrap: balance;
}

.ims-split__body > p {
	margin: 0 0 var(--ims-space-tight);
	max-width: 62ch;
}

.ims-split__body > p:last-child {
	margin-bottom: 0;
}

@media (min-width: 900px) {
	.ims-split {
		grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
		gap: clamp(2rem, 5vw, 4rem);
		align-items: start;
	}
}

/* Label plus one line. Never a four-column grid on a phone: the label is
   the scannable part and shrinking it defeats the point. */
.ims-benefits {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
}

.ims-benefit {
	display: grid;
	gap: 0.3rem;
	margin: 0;
	padding-block: clamp(0.9rem, 2vw, 1.25rem);
	border-top: 1px solid var(--ims-v4-line);
}

.ims-benefit:last-child {
	border-bottom: 1px solid var(--ims-v4-line);
}

.ims-benefit__label {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.ims-benefit__text {
	margin: 0;
	max-width: 60ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

@media (min-width: 900px) {
	.ims-benefit {
		grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
		gap: 0.35rem clamp(1rem, 3vw, 2.5rem);
		align-items: baseline;
	}
}

/* The sequence. The number is decorative: an ordered list already says
   these happen in order, so it is hidden from assistive technology in the
   markup and is here only to be read at a glance. */
.ims-steps {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: none;
}

.ims-step {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: clamp(0.75rem, 2.5vw, 1.75rem);
	align-items: baseline;
	margin: 0;
	padding-block: clamp(0.9rem, 2vw, 1.35rem);
	border-top: 1px solid var(--ims-v4-line);
}

.ims-step:last-child {
	border-bottom: 1px solid var(--ims-v4-line);
}

.ims-step__n {
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1rem, 1.6vw, 1.25rem);
	font-weight: 600;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font-variant-numeric: tabular-nums;
}

.ims-step__body {
	display: grid;
	gap: 0.25rem;
}

.ims-step__label {
	font-weight: 600;
}

.ims-step__text {
	max-width: 58ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* Who it is for, and when it is not the right route. The second half is
   what makes the first half believable. */
.ims-fit {
	display: grid;
	gap: var(--ims-space-tight);
	padding-top: clamp(1.1rem, 2.5vw, 1.6rem);
	border-top: 2px solid var(--wp--preset--color--ink);
}

.ims-fit__label {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.ims-fit__text {
	margin: 0;
	max-width: 56ch;
	font-size: clamp(1.0625rem, 0.6vw + 0.95rem, 1.25rem);
	line-height: 1.4;
}

.ims-fit__else {
	margin: 0;
	max-width: 56ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* Two ways to act, on one line where there is room. */
.ims-actions {
	display: flex;
	flex-wrap: wrap;
	gap: clamp(1rem, 3vw, 2.25rem);
	margin-top: var(--ims-space-block);
}

.ims-actions p {
	margin: 0;
}

.ims-section-close {
	border-top: 1px solid var(--ims-v4-line);
}

/* The service sections carry their own rhythm now, so the generic section
   padding is tightened where a section is a single component rather than a
   chapter of prose. */
.ims-section:has(> .ims-split),
.ims-section:has(> .ims-fit) {
	padding-block: var(--ims-space-section);
}

/* Home's sections carry their own vertical rhythm, so the block layout's
   own margins are removed rather than added to it. Left in, they put an
   uncontrolled 24px between every section and the hairline above it, which
   is exactly the kind of per-section drift the spacing scale exists to
   stop. */
.ims-home-main > * {
	margin-block: 0;
}

.ims-home-main > .ims-foot,
.ims-home-main + .ims-foot {
	margin-top: 0;
}

/* --- The poster, on a screen it cannot fill --------------------------
   --ims-poster-cap is the smaller of a height budget and a width budget,
   and on anything narrower than it is tall the width budget always wins.
   The three words are then sized by the viewport's width while the section
   still reserves its full height, so the composition finishes near the top
   and the rest of the screen is white. Measured before this rule: 316px of
   blank at 360x640, 520px at 390x844, 596px at 430x932 and 529px at
   768x1024 - between a half and two thirds of the hero.

   The predicate is the aspect ratio rather than a width, because the width
   is not what causes it. A 1024x768 window fills the poster with 129px to
   spare and a 1440x900 one with 88px, which is the breathing room the
   composition was designed around; a 768x1024 tablet held upright cannot
   fill it at any width. So the hero keeps its full-screen intent wherever
   that intent is achievable, and takes its content height wherever it is
   not.

   The words, the mark and their relationship are untouched: the mark still
   shares RETOUCH's row and still ends on the same baseline. */
@media (max-aspect-ratio: 5/4) {
	.ims-poster-main {
		min-height: 0;
		padding-bottom: var(--ims-space-block);
	}

	/* Without this the grid still grows to fill a flex parent that is no
	   longer taller than its contents, which would reintroduce the gap it
	   was given a content height to remove. */
	.ims-poster-main .ims-poster {
		flex: 0 0 auto;
	}
}

/* --- Paying by bank transfer -----------------------------------------
   WooCommerce prints the account details itself; this is the reference
   and the amount, which are the two things a customer has to type
   correctly and the two things that were missing. Quiet, transactional,
   no colour field: a payment page is not a place for marketing. */
.imsc-pay {
	margin: var(--ims-space-block) 0;
	padding-top: clamp(1.1rem, 2.5vw, 1.6rem);
	border-top: 1px solid var(--ims-v4-line);
}

.imsc-pay__title {
	margin: 0 0 var(--ims-space-tight);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.imsc-pay__help {
	margin: 0 0 var(--ims-space-block);
	max-width: 56ch;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.imsc-pay__row {
	display: grid;
	grid-template-columns: minmax(0, 12rem) minmax(0, 1fr) auto;
	gap: 0.35rem clamp(0.75rem, 2.5vw, 1.5rem);
	align-items: baseline;
	padding-block: clamp(0.6rem, 1.6vw, 0.9rem);
	border-top: 1px solid var(--ims-v4-line);
}

.imsc-pay__row:last-child {
	border-bottom: 1px solid var(--ims-v4-line);
}

.imsc-pay__label {
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.imsc-pay__value {
	font-weight: 600;
	overflow-wrap: anywhere;
}

/* A real target on a phone without becoming a button-shaped thing on a
   page that already has one primary action. */
.imsc-pay__copy {
	min-height: 44px;
	padding: 0.5rem 0.9rem;
	border: 1px solid var(--ims-v4-line);
	background: transparent;
	color: inherit;
	font: inherit;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	cursor: pointer;
}

.imsc-pay__copy:hover {
	border-color: var(--wp--preset--color--ink);
}

.imsc-pay__copy:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 2px;
}

/* Label above value above button on a phone: three columns of that at
   360px gives a two-character value column. */
@media (max-width: 599px) {
	.imsc-pay__row {
		grid-template-columns: minmax(0, 1fr) auto;
	}

	.imsc-pay__label {
		grid-column: 1 / -1;
	}
}

/* WooCommerce's own bank-details table, in the site's register rather
   than the plugin's default. */
.woocommerce-order .wc-bacs-bank-details,
.woocommerce-order ul.wc-bacs-bank-details {
	margin: var(--ims-space-block) 0 0;
	padding: 0;
	list-style: none;
}

.woocommerce-order .wc-bacs-bank-details li {
	padding-block: clamp(0.5rem, 1.4vw, 0.8rem);
	border-top: 1px solid var(--ims-v4-line);
	overflow-wrap: anywhere;
}


/* =========================================================
   V6 - Launch polish
   The last pass before launch. One quiet ground for the
   things that are components rather than prose - service
   cards, package cards, proof, the demo offer - so they read
   as deliberate objects on the page and not as paragraphs
   that happen to have a button. One top-spacing token for
   the internal pages, so they all begin the way the
   galleries do. Everything here is additive; the few legacy
   rules it overrides are named where it happens.
   ========================================================= */

:root {
	/* Where an internal page begins under the masthead. The galleries
	   start their photographs a wall-gap below the header; the text pages
	   started a chapter break below it. This is the gallery's rhythm with
	   just enough air for a display-size heading. */
	--ims-page-top: clamp(0.5rem, 1.2vw, 1rem);

	/* The one ground shared by every component below. A shade warmer and
	   lighter than Soft Neutral, so it sits under type without greying it
	   and a hairline still shows on it. */
	--ims-field: #F3F1EC;
	--ims-field-radius: 2px;
}

/* --- Page top ---------------------------------------------------------
   Overrides the opening-section padding in the V2 page rules and the
   Request a Quote head. Cart, Checkout and Order Received are commerce
   surfaces and keep their own rhythm. */
.ims-page .wp-block-post-content > .ims-section:first-of-type {
	padding-top: var(--ims-page-top);
}

.ims-sales:not(.ims-commerce) .ims-sales__head {
	padding-top: var(--ims-page-top);
}

/* The block layout puts its own 24px between the masthead and <main>,
   on top of the padding above. Measured: 48px of flow margin before a
   page's first section began, against 48px plus a wall gap on a
   gallery - so a text page's heading sat a full line lower than a
   gallery's first photograph. The main's share of that goes. */
.wp-site-blocks > .ims-page,
.wp-site-blocks > .ims-sales:not(.ims-commerce) {
	margin-block-start: 0;
}

/* --- Home: the services as cards --------------------------------------
   The question is asked by the section above; these are its three
   answers, so the hairline that separated them from it goes and the
   cards follow directly. Each card is a quiet field with the full
   service name at display size - not a shadowed box, not a button. */
.ims-cap + .ims-homeserv {
	border-top: 0;
	padding-top: 0;
}

.ims-homeserv__list {
	gap: clamp(0.75rem, 1.6vw, 1.25rem);
}

.ims-homeserv__item {
	/* Name, when, what, link: the third row takes whatever height the
	   tallest card needs, so the link sits at the foot of every card. */
	grid-template-rows: auto auto 1fr auto;
	align-content: stretch;
	padding: clamp(1.5rem, 3.2vw, 2.25rem);
	border-top: 0;
	border-radius: var(--ims-field-radius);
	background-color: var(--ims-field);
}

.ims-homeserv__name {
	font-size: clamp(1.25rem, 2vw, 1.625rem);
	letter-spacing: -0.015em;
	text-wrap: balance;
}

.ims-homeserv__more {
	margin-top: 0;
	padding-top: var(--ims-space-tight);
}

@media (min-width: 782px) {
	.ims-homeserv__list {
		gap: clamp(0.75rem, 1.6vw, 1.25rem);
	}
}

/* --- Home: the proof, centred -----------------------------------------
   Overrides the V5 rules that pulled this section to the left edge with
   the others: it is the one section on Home that is a moment rather than
   a chapter, and it reads as one when it is centred on its own ground.
   No heading; the rating is the first thing on it. */
.ims-hometrust {
	justify-items: stretch;
	text-align: center;
}

.ims-hometrust__inner {
	display: grid;
	justify-items: center;
	gap: clamp(0.5rem, 1.5vw, 1rem);
	max-width: min(100%, 48rem);
	margin-inline: auto;
	padding: clamp(2.25rem, 5vw, 4rem) clamp(1.25rem, 4vw, 3rem);
	border-radius: var(--ims-field-radius);
	background-color: var(--ims-field);
}

.ims-hometrust .ims-proof,
.ims-hometrust .ims-reviews--featured .ims-review {
	justify-items: center;
	text-align: center;
}

.ims-hometrust .ims-proof__rating,
.ims-hometrust .ims-proof__marks {
	justify-content: center;
}

.ims-hometrust .ims-reviews--featured {
	margin-inline: auto;
	max-width: 40rem;
}

.ims-hometrust .ims-proof__cta {
	margin-top: var(--ims-space-tight);
}

/* --- Stay in touch, centred everywhere --------------------------------
   Overrides the V5 rule that set Home's copy of this on the left edge:
   the invitation is the same on every page and so is its composition. */
.ims-home-main .ims-stay {
	text-align: center;
}

.ims-home-main .ims-stay__title,
.ims-home-main .ims-stay__body {
	margin-inline: auto;
}

.ims-home-main .ims-social {
	justify-content: center;
}

/* --- /work/: the three services, in type ------------------------------
   Set in the display face, bold and uppercase, with the arrow doing what
   the colour swap used to do. The padding is still the tap target. */
.ims-gate__choices {
	flex-direction: column;
	gap: clamp(0.25rem, 1vw, 0.5rem);
}

.ims-gate__link {
	display: inline-flex;
	align-items: baseline;
	gap: 0.35em;
	padding: clamp(0.5rem, 1.5vw, 0.75rem) clamp(1rem, 3vw, 1.5rem);
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.5rem, 4.2vw, 3rem);
	font-weight: 700;
	line-height: 1.05;
	letter-spacing: -0.01em;
	text-transform: uppercase;
	color: inherit;
	text-decoration: none;
	transition: color 180ms var(--wp--custom--motion--easing);
}

.ims-gate__go {
	display: inline-block;
	transition: transform 220ms var(--wp--custom--motion--easing);
}

@media (hover: hover) and (pointer: fine) {
	.ims-gate__link:hover {
		text-decoration: underline;
		text-underline-offset: 0.14em;
		text-decoration-thickness: 2px;
	}

	.ims-gate__link:hover .ims-gate__go {
		transform: translateX(0.3em);
	}
}

/* --- The end of a gallery ---------------------------------------------
   One rule closes the wall; proof, then the offer, then Stay in Touch,
   then the other two categories, each drawn under the same hairline at
   the same measure. The trust block takes the shared ground so it reads
   as one component: the quote, its attribution, the two facts, the way
   to the rest. */
body.ims-world .ims-galtrust,
body.ims-world .ims-endcta {
	padding-top: 0;
	padding-inline: var(--ims-edge);
}

body.ims-world .ims-galtrust::before,
body.ims-world .ims-endcta::before {
	content: "";
	display: block;
	width: var(--ims-v4-measure);
	height: 1px;
	margin: 0 auto var(--ims-v4-gap);
	background-color: var(--ims-v4-line);
}

/* The wall ends and the words begin: a full chapter gap above the rule so
   the last row of photographs is not touching it. */
body.ims-world .ims-galtrust {
	margin-top: var(--ims-v4-gap);
	padding-bottom: var(--ims-v4-gap);
}

.ims-galtrust__inner,
body.ims-world .ims-galtrust > .ims-reviews--gallery,
body.ims-world .ims-galtrust > .ims-trustline {
	max-width: min(100%, 44rem);
	margin-inline: auto;
}

body.ims-world .ims-galtrust > .ims-reviews--gallery {
	grid-template-columns: 1fr;
	padding: clamp(1.75rem, 4vw, 2.75rem) clamp(1.25rem, 4vw, 2.5rem) clamp(1.25rem, 3vw, 2rem);
	border-radius: var(--ims-field-radius) var(--ims-field-radius) 0 0;
	background-color: var(--ims-field);
}

body.ims-world .ims-galtrust > .ims-reviews--gallery .ims-review {
	border-top: 0;
	padding-top: 0;
	justify-items: center;
	text-align: center;
}

body.ims-world .ims-galtrust > .ims-trustline {
	padding: 0 clamp(1.25rem, 4vw, 2.5rem) clamp(1.75rem, 4vw, 2.75rem);
	border-radius: 0 0 var(--ims-field-radius) var(--ims-field-radius);
	background-color: var(--ims-field);
}

/* --- Service pages: the components on one ground ---------------------- */

/* Package cards. The V2 rule gave each column a rule on top and nothing
   else; each is now a field with balanced padding, and the button - or,
   on the quote-only page, the last line - sits at the foot with room
   above it. The card ground is the only difference between a package
   that can be bought and one that is quoted: both are the same object.

   The section that holds them takes the WIDE register (docs/
   VISUAL-SYSTEM-V2.md section 3), the way the reviews grid does: three
   cards with their own padding inside the 740px reading measure left
   each one about 220px wide, and "1 final image" broke across two
   lines. The copy around the cards keeps its reading measure. */
.ims-page .wp-block-post-content > .ims-section:has(> .ims-packages) {
	max-width: min(100%, 60rem);
}

.ims-page .ims-section:has(> .ims-packages) > p {
	max-width: 62ch;
}

.ims-page .ims-packages {
	gap: clamp(0.75rem, 1.6vw, 1.25rem);
}

.ims-page .ims-packages > .wp-block-column,
.ims-page .wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column {
	border-top: 0;
	padding: clamp(1.5rem, 3vw, 2.25rem);
	border-radius: var(--ims-field-radius);
	background-color: var(--ims-field);
}

.ims-page .ims-packages > .wp-block-column > .wp-block-list {
	margin-bottom: 0;
}

/* Breathing room between the specification and the button: the two used
   to touch, so "2 revisions" read as the button's caption. */
.ims-page .ims-packages > .wp-block-column > .wp-block-buttons,
.ims-page .wp-block-columns.alignwide:has(.wp-block-buttons) > .wp-block-column > .wp-block-buttons {
	padding-top: clamp(1.5rem, 3.2vw, 2.25rem);
}

.ims-page .ims-packages > .wp-block-column > .wp-block-buttons .wp-block-button,
.ims-page .ims-packages > .wp-block-column > .wp-block-buttons .wp-block-button__link {
	width: 100%;
	text-align: center;
}

@media (max-width: 767px) {
	.ims-page .ims-packages > .wp-block-column > .wp-block-buttons {
		padding-top: clamp(1.25rem, 4vw, 1.75rem);
	}
}

/* Package columns on a phone: core's columns block puts a 0.5em gap
   between stacked columns, which on cards with a ground of their own
   reads as an accident. Same gap as the desktop row. */
@media (max-width: 781px) {
	.ims-page .ims-packages {
		gap: clamp(0.75rem, 1.6vw, 1.25rem) !important;
	}

	.ims-page .ims-packages > .wp-block-column + .wp-block-column {
		margin-top: 0;
	}
}

/* What clients say. One component: the heading, the voices, the facts
   and the way to the rest, on one ground. Overrides the About-era rule
   that drew a hairline above the section's first child. */
.ims-page .ims-section-trust {
	padding-top: var(--ims-space-section);
}

.ims-page .ims-section-trust > *:first-child {
	padding-top: 0;
	border-top: 0;
}

.ims-page .ims-section-trust > h2 {
	text-align: center;
	margin-bottom: var(--ims-space-block);
}

.ims-page .ims-section-trust .ims-reviews {
	/* One voice or two: a single review takes the whole measure rather
	   than the left half of a two-column grid. */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
	gap: clamp(0.75rem, 1.6vw, 1.25rem);
	margin-bottom: clamp(0.75rem, 1.6vw, 1.25rem);
}

.ims-page .ims-section-trust .ims-review {
	padding: clamp(1.5rem, 3vw, 2.25rem);
	border-top: 0;
	border-radius: var(--ims-field-radius);
	background-color: var(--ims-field);
}

.ims-page .ims-section-trust .ims-trustline {
	align-items: center;
	text-align: center;
	margin-top: 0;
	padding: clamp(1.25rem, 2.5vw, 1.75rem) clamp(1.25rem, 3vw, 2rem);
	border-radius: var(--ims-field-radius);
	background-color: var(--ims-field);
}

.ims-page .ims-section-trust .ims-trust {
	justify-content: center;
}

/* The free demo. A standalone offer, centred on its own ground, with the
   form inside it: heading, one sentence, then the four fields and the
   button that names what is being asked for. */
.ims-page .ims-section-demo {
	padding: clamp(2.25rem, 5vw, 4rem) clamp(1.25rem, 4vw, 3rem);
	margin-block: var(--ims-space-section);
	border-radius: var(--ims-field-radius);
	background-color: var(--ims-field);
	text-align: center;
	scroll-margin-top: clamp(4rem, 10vh, 6rem);
}

.ims-page .ims-section-demo > h2 {
	max-width: 22ch;
	margin-inline: auto;
	text-wrap: balance;
}

.ims-page .ims-section-demo > p {
	max-width: 52ch;
	margin-inline: auto;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.ims-page .ims-section-demo .imsc-form {
	max-width: 32rem;
	margin: var(--ims-space-block) auto 0;
	text-align: left;
}

.ims-page .ims-section-demo .imsc-form__submit {
	text-align: center;
}

/* The file input, in the same register as the text fields. The native
   control is left as it is - restyling it hides its state from assistive
   technology - and is given the field's width and hairline. */
.ims-page .imsc-form__field input[type="file"],
.ims-sales .imsc-form__field input[type="file"] {
	display: block;
	width: 100%;
	padding: 0.65rem 0;
	font: inherit;
	font-size: var(--wp--preset--font-size--small);
	border: 0;
	border-bottom: 1px solid var(--wp--custom--v2--surface--quiet, #726B5C);
	background: transparent;
	color: inherit;
}

.ims-page .imsc-form__field input[type="file"]:focus-visible,
.ims-sales .imsc-form__field input[type="file"]:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 4px;
}

.ims-page .imsc-form__field input[type="file"]::file-selector-button,
.ims-sales .imsc-form__field input[type="file"]::file-selector-button {
	margin-right: 0.85rem;
	padding: 0.45rem 0.9rem;
	border: 1px solid var(--wp--preset--color--ink);
	border-radius: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	cursor: pointer;
}

/* The trust component on the About page shares the service-page treatment
   through .ims-page; the About-specific rules below handle what is only
   on About. */

/* --- About: one lower-page system -------------------------------------
   Expertise closes on its last row without a second rule beneath it, so
   the section that follows draws the only divider. Then Selected clients,
   Let's talk, Stay in touch and the two closing links are each centred
   and balanced, the same way, so the lower half of the page is one system
   rather than four boxes that happen to be stacked. */
body.ims-page-about .ims-about-facts__item:last-child {
	border-bottom: 0;
}

body.ims-page-about .ims-section-trust {
	padding-top: 0;
}

body.ims-page-about .ims-section-trust > *:first-child {
	padding-top: clamp(1.75rem, 4vw, 2.75rem);
	border-top: 1px solid var(--ims-v4-line);
	margin-bottom: var(--ims-space-block);
}

body.ims-page-about .ims-section-trust .ims-trustline {
	margin-top: 0;
	padding-top: clamp(1.25rem, 2.5vw, 1.75rem);
	border-radius: 0 0 var(--ims-field-radius) var(--ims-field-radius);
}

body.ims-page-about .ims-section-trust .ims-clients {
	justify-content: center;
	padding: clamp(1.75rem, 4vw, 2.75rem) clamp(1.25rem, 3vw, 2rem) clamp(1rem, 2.5vw, 1.5rem);
	border-radius: var(--ims-field-radius) var(--ims-field-radius) 0 0;
	background-color: var(--ims-field);
}

body.ims-page-about .ims-section-talk,
body.ims-page-about .ims-section-stay,
body.ims-page-about .ims-section-next {
	text-align: center;
}

body.ims-page-about .ims-section-talk {
	padding-top: 0;
}

body.ims-page-about .ims-talk__body {
	margin-inline: auto;
}

body.ims-page-about .ims-section-stay .ims-stay {
	padding-inline: 0;
}

body.ims-page-about .ims-section-next {
	padding-top: 0;
}

body.ims-page-about .ims-about-next {
	justify-content: center;
}

/* --- Footer: three groups -----------------------------------------------
   Replaces the single-row footer's wrapping rules. Each group is a label
   and its three destinations; on a wide screen the three groups share a
   row, on a tablet they still do, and on a phone they stack, each group
   keeping its links on one line. The vertical rules are gone from the
   navigation: they were the thing that broke when a label wrapped. */
.ims-foot__nav.wp-block-group {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, auto));
	justify-content: center;
	column-gap: clamp(2.5rem, 7vw, 6rem);
	row-gap: clamp(1.5rem, 3vw, 2rem);
	max-width: min(100%, 76rem);
	margin-inline: auto;
	text-align: left;
}

.ims-foot__group.wp-block-group {
	display: grid;
	gap: clamp(0.4rem, 1vw, 0.6rem);
	align-content: start;
	/* Core's flow layout puts a block margin between sibling groups; in
	   a grid row that dropped the second and third groups 24px below the
	   first. */
	margin-block: 0;
}

.ims-foot__group > p {
	margin: 0;
}

.ims-foot__label {
	margin-bottom: clamp(0.15rem, 0.6vw, 0.35rem);
}

@media (max-width: 599px) {
	.ims-foot__nav.wp-block-group {
		grid-template-columns: minmax(0, 1fr);
		justify-items: center;
		row-gap: clamp(1.25rem, 4vw, 1.75rem);
		text-align: center;
	}

	.ims-foot__group.wp-block-group {
		justify-items: center;
	}
}


/* =========================================================
   V6.2 - Final visual corrections
   The last human-acceptance notes: the gallery's way out set
   in type on a compact band, the wide-desktop footer back on
   one row, a quotation mark on every quote, and the /work/
   gateway as a selector rather than a hero.
   ========================================================= */

/* --- Gallery navigation: type, on a compact band ----------------------
   Photography / AI imagery / High-end retouching in the display face,
   bold and uppercase, with the arrows kept. The band used to reserve a
   hero's worth of space above and below two wordmarks; it is a
   navigation now and takes a navigation's height. */
.ims-galnav__name {
	display: inline-block;
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.125rem, 2.2vw, 1.625rem);
	font-weight: 700;
	line-height: 1.1;
	letter-spacing: -0.005em;
	text-transform: uppercase;
	color: inherit;
	text-decoration: none;
	text-underline-offset: 0.16em;
	text-decoration-thickness: 2px;
	transition: color 180ms var(--wp--custom--motion--easing);
}

.ims-galnav__item {
	padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.ims-galnav__link {
	padding-block: 0.35rem;
	line-height: 1.1;
}

.ims-galnav__arrow {
	color: var(--ims-quiet, var(--wp--custom--v2--surface--quiet, #726B5C));
	transition: transform 220ms var(--wp--custom--motion--easing);
}

@media (hover: hover) and (pointer: fine) {
	.ims-galnav__link:hover .ims-galnav__name,
	.ims-galnav__link:focus-visible .ims-galnav__name {
		text-decoration: underline;
	}

	.ims-galnav__link:hover .ims-galnav__arrow-after {
		transform: translate(0.25em, -50%);
	}

	.ims-galnav__link:hover .ims-galnav__arrow-before {
		transform: translate(-0.25em, -50%);
	}
}

/* The band's height. Overrides the two earlier paddings and the rule's
   margin, so the sequence at the foot of a wall is Stay in touch, one
   rule, the two links, the footer - with a navigation's spacing between
   each, not a section's. */
body.ims-world .ims-stay {
	padding-bottom: clamp(1.5rem, 3.5vw, 2.5rem);
}

body.ims-world .ims-galnav-wrap-bottom {
	padding-bottom: clamp(1.25rem, 3vw, 2rem);
}

body.ims-world .ims-galnav-wrap-bottom::before {
	margin-bottom: clamp(1.25rem, 3vw, 2rem);
}

body.ims-world .ims-foot {
	margin-top: clamp(1.25rem, 3vw, 2rem);
}

/* --- Footer: one row on a wide desktop --------------------------------
   The three groups stay in the markup - they are the phone and tablet
   layouts - but from 1200px they dissolve into one row of nine, the
   group labels are dropped and a hairline separates the links, which is
   the treatment the footer had before the groups and the one the owner
   asked to have back on a wide screen. */
@media (min-width: 1200px) {
	.ims-foot__nav.wp-block-group {
		display: flex;
		flex-wrap: nowrap;
		justify-content: center;
		align-items: baseline;
		gap: clamp(0.75rem, 1.4vw, 1.35rem);
		text-align: center;
	}

	.ims-foot__group.wp-block-group {
		display: contents;
	}

	.ims-foot__label {
		display: none;
	}

	.ims-foot__group > p {
		white-space: nowrap;
	}

	.ims-foot__group > p + p + p::before,
	.ims-foot__group + .ims-foot__group > p:nth-child(2)::before {
		content: "";
		display: inline-block;
		width: 1px;
		height: 0.85em;
		margin-right: clamp(0.75rem, 1.4vw, 1.35rem);
		vertical-align: -0.05em;
		background-color: var(--ims-hairline, var(--wp--custom--v2--surface--hairline, #E7E2D9));
	}
}

/* --- Reviews: the quotation marks ----------------------------------------
   Punctuation, not an icon. The opening mark precedes the first word and
   the closing mark follows the last, inside the blockquote's own
   paragraph, in the text's own size and colour - the way a quotation is
   set in print. (5.17.2 drew one large mark on a row of its own above the
   text; it read as decoration rather than as the client's words.) Both
   marks are aria-hidden spans, so nothing is announced twice. */
.ims-review__open,
.ims-review__close {
	font-family: inherit;
	font-weight: inherit;
	color: inherit;
}

/* A left-set quote hangs its opening mark into the margin, so the first
   letters of every line align; a centred quote leaves it in the line. */
.ims-review__quote p {
	margin: 0;
}

.ims-reviews:not(.ims-reviews--featured):not(.ims-reviews--gallery) .ims-review__quote p {
	text-indent: -0.42em;
}

.ims-hometrust .ims-review__quote p,
.ims-galtrust .ims-review__quote p,
.ims-section-trust .ims-review__quote p {
	text-indent: 0;
}

/* The rating sits on the quote it rates, not a row above it. */
.ims-review__stars + .ims-review__quote {
	margin-top: -0.15rem;
}

/* --- /work/: a selector, not a hero --------------------------------------
   Overrides the gateway's near-full-viewport centring: it takes the
   internal pages' top rhythm, keeps its three choices centred and stacked
   under the question, and stops when they end. */
.ims-gate {
	min-height: 0;
	justify-content: flex-start;
	gap: clamp(1.5rem, 3.5vw, 2.75rem);
	padding-top: clamp(1.5rem, 3vw, 2.5rem);
	padding-bottom: clamp(2.5rem, 6vw, 4.5rem);
}

/* The gateway alone; a wall keeps the top rhythm it was approved with. */
body.ims-world--all .wp-site-blocks > .ims-gal-main {
	margin-block-start: 0;
}


/* --- The free demo at the end of a wall ---------------------------------
   Second to "See how it works": a heading, one sentence and a link on the
   shared quiet ground, centred under its own rule at the same measure as
   everything else at the foot of the wall. It says what the demo is for
   and asks for nothing.

   The rule and the padding match the other blocks in the sequence, so the
   order reads trust, invitation, process, demo, stay in touch, the other
   two categories - one hairline between each. */
body.ims-world .ims-galdemo {
	padding: 0 var(--ims-edge) var(--ims-v4-gap);
	text-align: center;
}

body.ims-world .ims-galdemo::before {
	content: "";
	display: block;
	width: var(--ims-v4-measure);
	height: 1px;
	margin: 0 auto var(--ims-v4-gap);
	background-color: var(--ims-v4-line);
}

body.ims-world .ims-galdemo > * {
	max-width: min(100%, 44rem);
	margin-inline: auto;
}

/* The button is a flex box, which swallows the space before its arrow. */
.ims-endcta a.ims-endcta__cta:not(.wp-element-button) > span[aria-hidden] {
	margin-left: 0.45em;
}

body.ims-world .ims-galdemo > .ims-galdemo__title {
	margin: 0 auto;
	padding: clamp(1.75rem, 4vw, 2.75rem) clamp(1.25rem, 4vw, 2.5rem) 0;
	border-radius: var(--ims-field-radius) var(--ims-field-radius) 0 0;
	background-color: var(--ims-field);
	font-family: var(--wp--preset--font-family--display);
	font-size: clamp(1.25rem, 2.2vw, 1.75rem);
	font-weight: 600;
	line-height: 1.15;
	letter-spacing: -0.015em;
	text-wrap: balance;
}

body.ims-world .ims-galdemo > .ims-galdemo__body {
	margin: 0 auto;
	padding: clamp(0.6rem, 1.4vw, 0.9rem) clamp(2rem, 8vw, 5rem) 0;
	background-color: var(--ims-field);
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	text-wrap: pretty;
}

body.ims-world .ims-galdemo > .ims-galdemo__cta {
	margin: 0 auto;
	padding: clamp(1rem, 2.4vw, 1.5rem) clamp(1.25rem, 4vw, 2.5rem) clamp(1.75rem, 4vw, 2.75rem);
	border-radius: 0 0 var(--ims-field-radius) var(--ims-field-radius);
	background-color: var(--ims-field);
}


/* =========================================================
   V7 - Consent (Milestone 2, refined in 2.1.2)
   The privacy-options card Site Core's consent manager draws,
   in the site's own register: a quiet white card anchored at
   the foot of the viewport, a small editorial label, one short
   paragraph, three actions of equal weight on one row, and the
   Privacy link. Compact enough to sit under the poster without
   competing with it. It never blocks the page. Markup and
   behaviour live in ivan-moshe-site-core/assets/js/
   imsc-consent.js; only the presentation is here.
   ========================================================= */
:root {
	--imsc-consent-width: 35rem;              /* 560px: three actions on one row, 60ch of body */
	--imsc-consent-gutter: clamp(1rem, 2.4vw, 1.75rem);
	--imsc-consent-bottom: clamp(1.25rem, 2.2vw, 2rem);
	--imsc-consent-pad: clamp(1.25rem, 2vw, 1.625rem);
}

.imsc-consent {
	position: fixed;
	inset-inline: 0;
	bottom: 0;
	z-index: 900;
	padding: 0 var(--imsc-consent-gutter) max(var(--imsc-consent-bottom), env(safe-area-inset-bottom, 0px));
	pointer-events: none;
}

.imsc-consent[hidden] {
	display: none;
}

.imsc-consent__inner {
	position: relative;
	box-sizing: border-box;
	width: 100%;
	max-width: var(--imsc-consent-width);
	margin-inline: auto;
	padding: var(--imsc-consent-pad);
	border: 1px solid var(--ims-v4-line, #E7E2D9);
	border-radius: var(--ims-field-radius, 2px);
	background-color: #FFFFFF;
	box-shadow: 0 1px 2px rgba(20, 20, 20, 0.04);
	color: var(--wp--preset--color--ink, #141414);
	pointer-events: auto;
	/* On a short screen the preferences view scrolls inside the card;
	   the page behind is never trapped. */
	max-height: calc(100vh - 2.5rem);
	max-height: calc(100dvh - 2.5rem);
	overflow-y: auto;
	overscroll-behavior: contain;
}

/* The label: the site's META register, not a heading. */
.imsc-consent__title {
	margin: 0 0 0.45rem;
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.08em;
	line-height: 1.2;
	text-transform: uppercase;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.imsc-consent__title:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink, #141414);
	outline-offset: 4px;
}

.imsc-consent__body {
	margin: 0 0 1rem;
	max-width: 62ch;
	font-size: 0.875rem;
	line-height: 1.5;
	color: var(--wp--preset--color--ink, #141414);
}

.imsc-consent__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
}

/* Three actions, one register, one row where it fits: no colour or size
   says "this is the right one". */
.imsc-consent__button {
	min-height: 2.5rem;
	padding: 0.5rem 0.8rem;
	border: 1px solid var(--wp--preset--color--ink, #141414);
	border-radius: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	line-height: 1.2;
	text-transform: uppercase;
	white-space: nowrap;
	cursor: pointer;
	transition: background-color 160ms var(--wp--custom--motion--easing, ease);
}

.imsc-consent__button:hover {
	background-color: var(--ims-field, #F3F1EC);
}

.imsc-consent__button:focus-visible,
.imsc-consent__close:focus-visible,
.imsc-consent__check:focus-visible,
.imsc-consent__reopen-link:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink, #141414);
	outline-offset: 3px;
}

/* Manage preferences: the same card, one hairline and three short rows. */
.imsc-consent__prefs {
	margin-top: 0.9rem;
	padding-top: 0.25rem;
	border-top: 1px solid var(--ims-v4-line, #E7E2D9);
}

.imsc-consent__prefs[hidden] {
	display: none;
}

.imsc-consent__category {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: 0.65rem;
	align-items: start;
	padding-block: 0.6rem;
	cursor: pointer;
}

.imsc-consent__category + .imsc-consent__category {
	border-top: 1px solid var(--ims-v4-line, #E7E2D9);
}

.imsc-consent__check {
	width: 1rem;
	height: 1rem;
	margin: 0.15rem 0 0;
	accent-color: var(--wp--preset--color--ink, #141414);
}

.imsc-consent__label {
	display: grid;
	gap: 0.1rem;
	font-size: 0.875rem;
	line-height: 1.4;
}

.imsc-consent__name {
	font-weight: 600;
}

.imsc-consent__help {
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.imsc-consent__prefs .imsc-consent__actions {
	margin-top: 0.75rem;
}

/* The Privacy link: a quiet text link in the META register, not a button. */
.imsc-consent__foot {
	margin: 0.9rem 0 0;
	font-size: 0.75rem;
	letter-spacing: 0.08em;
	line-height: 1.2;
	text-transform: uppercase;
}

.imsc-consent__foot a {
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	text-underline-offset: 0.16em;
}

.imsc-consent__foot a:hover {
	color: var(--wp--preset--color--ink, #141414);
}

.imsc-consent__close {
	position: absolute;
	top: 0.35rem;
	right: 0.35rem;
	width: 2.5rem;
	height: 2.5rem;
	border: 0;
	background: transparent;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font: inherit;
	font-size: 1.25rem;
	line-height: 1;
	cursor: pointer;
}

.imsc-consent__close:hover {
	color: var(--wp--preset--color--ink, #141414);
}

.imsc-consent__close[hidden] {
	display: none;
}

/* The reopen link the consent manager adds where a page has no rendered
   footer link (the /work/ gateway): the legal row's register, at the foot
   of the content. */
.imsc-consent__reopen--fallback {
	margin: clamp(1.5rem, 4vw, 3rem) 0 0;
	text-align: center;
}

.imsc-consent__reopen-link {
	padding: 0.5rem 0.25rem;
	border: 0;
	background: transparent;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font: inherit;
	font-size: var(--wp--preset--font-size--small, 0.8125rem);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	text-decoration: underline;
	text-underline-offset: 0.16em;
	cursor: pointer;
}

/* A phone: a compact bottom sheet. Three equal 44px actions share one row;
	the labels may wrap instead of turning the first viewport into a menu. */
@media (max-width: 599px) {
	.imsc-consent {
		--imsc-consent-bottom: clamp(0.5rem, 2dvh, 0.875rem);
		padding-inline: clamp(0.625rem, 3vw, 1rem);
	}

	.imsc-consent__inner {
		max-width: none;
		padding: 0.75rem;
		max-height: calc(100dvh - 1rem - env(safe-area-inset-bottom, 0px));
	}

	.imsc-consent__title {
		margin-bottom: 0.25rem;
	}

	.imsc-consent__body {
		margin-bottom: 0.625rem;
		font-size: 0.8125rem;
		line-height: 1.4;
	}

	.imsc-consent__actions {
		display: grid;
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: 0.375rem;
	}

	.imsc-consent__button {
		min-height: 2.75rem;
		padding: 0.375rem 0.25rem;
		font-size: 0.6875rem;
		line-height: 1.15;
		white-space: normal;
		text-align: center;
	}

	.imsc-consent__foot {
		margin-top: 0.5rem;
	}

	/* Preferences contain explanatory rows and may use more height for
	   accessibility. Its save action does not inherit the three-column grid. */
	.imsc-consent__prefs .imsc-consent__actions {
		display: block;
	}

	.imsc-consent__prefs .imsc-consent__button--save {
		width: 100%;
	}
}

/* Tablet and short landscape viewports keep the desktop one-row actions,
	but trim the card enough to preserve the first viewport above it. */
@media (min-width: 600px) and (max-width: 1199px) {
	.imsc-consent {
		--imsc-consent-bottom: 0.75rem;
	}

	.imsc-consent__inner {
		padding: 1rem;
	}

	.imsc-consent__body {
		margin-bottom: 0.75rem;
	}

	.imsc-consent__foot {
		margin-top: 0.625rem;
	}
}

/* =========================================================
   M2.8 - mobile and tablet launch polish
   ========================================================= */
@media (max-width: 1199px) {
	/* Keep the footer's three semantic groups while removing visual labels. */
	.ims-foot__label {
		position: absolute;
		width: 1px;
		height: 1px;
		padding: 0;
		margin: -1px;
		overflow: hidden;
		clip: rect(0, 0, 0, 0);
		white-space: nowrap;
		border: 0;
	}

	/* The Retouching process closes as one compact decision unit. */
	body.ims-page-retouching .ims-next {
		margin-top: clamp(1.125rem, 3vw, 1.5rem);
		padding-bottom: clamp(1.125rem, 3vw, 1.5rem);
	}

	body.ims-page-retouching .ims-next__eyebrow {
		margin-bottom: 0.5rem;
	}

	body.ims-page-retouching .ims-next__alt {
		margin-top: 0.5rem;
	}

	/* A form that closes its section should not be followed by a second
	   chapter-sized blank area. Form mechanics and result markup are unchanged. */
	.ims-page .ims-section:has(> .imsc-form:last-child) {
		padding-bottom: clamp(1.5rem, 4vw, 2.5rem);
	}
}

/* --- Legal pages (Milestone 2.1) -----------------------------------------
   The Privacy Policy is seeded inside the page section now, so it takes
   the reading gutter; but a seventeen-section legal text set with the
   service pages' headline scale is a wall of display type. Its headings
   step down to a reading register and keep a chapter's space above them. */
.ims-page .ims-section-legal > h2 {
	font-size: clamp(1.375rem, 2.2vw, 1.875rem);
	line-height: 1.15;
	letter-spacing: -0.01em;
	margin-top: var(--ims-space-block);
	margin-bottom: clamp(0.5rem, 1.2vw, 0.85rem);
}

.ims-page .ims-section-legal > h3 {
	font-size: clamp(1.0625rem, 1.4vw, 1.25rem);
	margin-top: clamp(1rem, 2.2vw, 1.5rem);
	margin-bottom: 0.35rem;
}

.ims-page .ims-section-legal > p,
.ims-page .ims-section-legal > ul {
	max-width: 66ch;
}

/* --- Promotions and the AI lines (Milestone 2.3, theme 5.18.3) -----------
   Presentation for two Site Core additions. The campaign price: while a
   campaign is live the plugin prints every eligible price as WooCommerce's
   own del/ins pair followed by a label, in the catalogue, on the product
   page and in the package cards; the browser's default underline on <ins>
   is the one thing that has to go. And the three AI lines on Order Online
   (stills, stills + motion, motion add-ons), which sit under one family
   heading as sub-labels with a one-line lead each, in the grid's measure.
   Nothing here touches a total: the cart and checkout blocks print
   WooCommerce's own discount line. */
.imsc-campaign-price del {
	opacity: 0.55;
	font-weight: 400;
	text-decoration: line-through;
}

.imsc-campaign-price ins {
	font-weight: inherit;
	text-decoration: none;
}

.imsc-campaign-label {
	display: inline-block;
	margin-inline-start: 0.5em;
	font-size: 0.75em;
	font-weight: 600;
	letter-spacing: var(--wp--custom--v2--type--meta-tracking, 0.08em);
	text-transform: uppercase;
	vertical-align: middle;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

/* The package cards set their price line in the eyebrow register; the
   campaign pair inside it keeps that register and only adds the strike. */
.ims-packages > .wp-block-column > .imsc-package-price .imsc-campaign-label {
	display: block;
	margin: 0.35em 0 0;
}

.ims-page .ims-catgroup > h3.ims-catgroup__line {
	max-width: min(100%, 76rem);
	margin: clamp(1.75rem, 3.5vw, 3rem) auto 0.35rem;
	font-size: clamp(1.125rem, 0.4vw + 1rem, 1.375rem);
	font-weight: 600;
	line-height: 1.2;
	letter-spacing: -0.01em;
	text-transform: none;
}

.ims-page .ims-catgroup > h2.ims-catgroup__title + h3.ims-catgroup__line {
	margin-top: clamp(0.5rem, 1vw, 0.75rem);
}

.ims-page .ims-catgroup > p.ims-catgroup__lead {
	max-width: min(100%, 76rem);
	margin: 0 auto clamp(0.75rem, 1.5vw, 1.15rem);
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
	font-size: clamp(0.9375rem, 0.25vw + 0.9rem, 1rem);
	line-height: 1.5;
}

/* =========================================================
   Milestone 2.3.1 - commerce presentation polish (theme 5.18.4)
   Every rule below answers a defect the owner saw during the
   M2.3 visual QA. Additive; nothing above is rewritten.
   ========================================================= */

/* --- 1. No decorative arrows on the commerce and demo CTAs ---------------
   The plugin no longer prints an inline arrow on Add to cart or Request a
   free demo; the stylesheet's decorative ::after must not put one back. */
.imsc-package-button::after,
.imsc-form__submit .wp-block-button__link::after {
	content: none;
}

/* --- 2. Package cards: title, facts, price, button -----------------------
   Descriptions wrap to different depths, so a price under the title sat
   on a different line from card to card. The card is a column flex box
   already; the facts list takes the slack and the price and button are
   anchored to the foot, so every price in a row - and every button - is
   on the same line. On a phone the cards stack and return to natural
   flow (the existing mobile reset below 768px, plus the margin here). */
.ims-page .ims-packages > .wp-block-column > .wp-block-heading {
	order: 1;
}

.ims-page .ims-packages > .wp-block-column > .wp-block-list {
	order: 2;
	flex-grow: 1;
}

.ims-page .ims-packages > .wp-block-column > .is-style-eyebrow {
	order: 3;
	margin-top: auto;
	margin-bottom: 0;
	min-height: 0;
	padding-top: clamp(1rem, 2.4vw, 1.5rem);
}

.ims-page .ims-packages > .wp-block-column > .wp-block-buttons {
	order: 4;
	margin-top: 0;
	padding-top: clamp(0.85rem, 1.8vw, 1.15rem);
}

@media (max-width: 767px) {
	.ims-page .ims-packages > .wp-block-column > .is-style-eyebrow {
		margin-top: 0;
		padding-top: clamp(0.75rem, 2vw, 1rem);
	}
}

/* The catalogue's WooCommerce cards: the title, the scope line and the
   price all sit inside WooCommerce's product link, so that link is the
   column that grows and the scope line absorbs the slack inside it. The
   price of every card in a row then sits on the same line, and the button
   below it. */
.ims-catgroup .woocommerce ul.products li.product > a.woocommerce-LoopProduct-link {
	display: flex;
	flex-direction: column;
	flex-grow: 1;
}

.ims-catgroup .woocommerce ul.products li.product .ims-pkg-scope {
	flex-grow: 1;
}

/* --- 3. The campaign price pair inside a package card -------------------
   The label sits under the pair, so a long owner label never pushes the
   price off the card's price line. */
.ims-page .ims-packages > .wp-block-column > .imsc-package-price .imsc-campaign-price {
	display: inline-block;
}

/* --- 4. Add to cart -> View cart, in the same slot ----------------------
   WooCommerce's separate link never shows in the catalogue; the button
   keeps its width (pinned by assets/js/cart-button.js) and centres its
   new label. */
.ims-catgroup .woocommerce ul.products li.product a.added_to_cart {
	display: none;
}

.ims-catgroup .woocommerce ul.products li.product a.button.ims-cart-button--view {
	text-align: center;
	justify-content: center;
}

/* --- 5. The campaign in the block cart, checkout and drawer -------------
   The script marks the campaign chip and hides its remove control; this
   keeps it hidden through any re-render. The drawer lines are the
   discount and the estimated total, in the drawer's own register. */
.imsc-campaign-chip .wc-block-components-chip__remove {
	display: none;
}

.imsc-drawer-totals {
	padding-inline: 16px;
	padding-bottom: 12px;
	font-size: 0.9375rem;
}

.imsc-drawer-totals__row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 1rem;
	padding-block: 4px;
	color: var(--wp--custom--v2--surface--quiet, #726B5C);
}

.imsc-drawer-totals__row--total {
	margin-top: 4px;
	padding-top: 8px;
	border-top: 1px solid var(--wp--custom--v2--surface--hairline, #E7E2D9);
	color: inherit;
	font-weight: 600;
}

.imsc-drawer-totals__label {
	min-width: 0;
}

.imsc-drawer-totals__value {
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
}

/* --- 6. The open menu is authoritative over express payment -------------
   The header creates its own stacking context at z-index 10, so the core
   navigation overlay, at 100000 inside it, still sat under the PayPal
   iframe's container out in the page. While the menu is open the header
   is lifted above everything; and because a third-party iframe can bring
   its own layering, the express-payment surface is also made invisible
   for exactly as long as the menu is open. Both are pure CSS state: the
   moment the menu closes, both revert. */
.ims-v2-header:has(.wp-block-navigation__responsive-container.is-menu-open) {
	z-index: 100001;
}

body:has(.wp-block-navigation__responsive-container.is-menu-open) .wc-block-components-express-payment,
body:has(.wp-block-navigation__responsive-container.is-menu-open) .wc-block-components-express-payment-continue-rule,
body:has(.wp-block-navigation__responsive-container.is-menu-open) .ppc-button-wrapper,
body:has(.wp-block-navigation__responsive-container.is-menu-open) .paypal-buttons {
	visibility: hidden;
}

/* --- 7. The service process pages end centred, without the dead air ----
   The closing CTA block (eyebrow, button, custom-quote line) is one unit,
   centred. The CTA section and the Stay in touch section each stacked
   their own padding on top of the block's margin; the block now sets the
   rhythm on its own: last step, one breath, CTA, one breath, Stay in
   touch. */
body.ims-page-photography .ims-next,
body.ims-page-ai-product-imagery .ims-next,
body.ims-page-retouching .ims-next {
	text-align: center;
	margin-top: clamp(1.5rem, 3vw, 2.25rem);
	padding-bottom: clamp(1.5rem, 3.5vw, 2.25rem);
	margin-inline: auto;
}

body.ims-page-photography .ims-next__alt,
body.ims-page-ai-product-imagery .ims-next__alt,
body.ims-page-retouching .ims-next__alt {
	margin-inline: auto;
}

body.ims-page-photography .wp-block-post-content > .ims-section:has(> .ims-next),
body.ims-page-ai-product-imagery .wp-block-post-content > .ims-section:has(> .ims-next),
body.ims-page-retouching .wp-block-post-content > .ims-section:has(> .ims-next) {
	padding-top: 0;
	padding-bottom: clamp(0.5rem, 1.5vw, 1rem);
}

body.ims-page-photography .ims-section-stay,
body.ims-page-ai-product-imagery .ims-section-stay,
body.ims-page-retouching .ims-section-stay {
	padding-top: 0;
}

body.ims-page-photography .ims-section-stay .ims-stay,
body.ims-page-ai-product-imagery .ims-section-stay .ims-stay,
body.ims-page-retouching .ims-section-stay .ims-stay {
	border-top: 0;
	padding-top: clamp(1.5rem, 3vw, 2.25rem);
}

/* =========================================================
   Milestone 2.4 - portfolio media protection (theme 5.19.0)
   Site Core marks every rendered portfolio image with
   data-imsc-protected-media; these rules act on that marker and
   on nothing else. Casual paths only - a screenshot still works,
   and the picture stays a real, described image.
   ========================================================= */

/* --- 1. No drag, no selection, no long-press callout on the bitmap ------- */
[data-imsc-protected-media] {
	-webkit-user-drag: none;
	user-select: none;
	-webkit-user-select: none;
	-webkit-touch-callout: none;
}

/* --- 2. The wall trigger is a button now, not a link to the file --------
   It looks exactly like the image it wraps: no chrome, the image's own
   box, the zoom cursor, and the wall's focus ring on the keyboard. */
button.ims-gal__open {
	appearance: none;
	-webkit-appearance: none;
	display: block;
	width: 100%;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: inherit;
	cursor: zoom-in;
	-webkit-tap-highlight-color: transparent;
}

button.ims-gal__open > img {
	display: block;
	width: 100%;
	height: auto;
}
