/*
 * VLINZZA — Global Classes
 * Layer 4 of 4. The page boundary and the reusable component classes the
 * project patterns are built from.
 * Depends on tokens.css, base.css and generatepress-bridge.css.
 *
 * Scope rule: every class defined here is used by the marketing page template
 * or by at least one of the three project patterns. This file is deliberately
 * not a general-purpose utility library.
 *
 * Direction: logical properties only. No class in this file needs an RTL
 * counterpart, and no direction-specific class exists.
 *
 * Responsive: this file contains NO media query. Every responsive difference
 * is expressed by consuming a token that tokens.css redefines per breakpoint
 * (--vlz-hero-columns, --vlz-grid-min, --vlz-grid-gap, --vlz-section-space,
 * --vlz-stack-space, --vlz-container-gutter, --vlz-control-height). That
 * keeps Desktop / Tablet / Mobile values in one central place.
 *
 * Naming: vlz-<block>, vlz-<block>--<variant>, vlz-<block>__<part>.
 *
 * ---------------------------------------------------------------------------
 * Layout host resolution
 *
 * Content saved before this theme had a theme.json carries the classic-theme
 * group wrapper:
 *
 *   <div class="wp-block-group vlz-grid">
 *     <div class="wp-block-group__inner-container"> …the real children… </div>
 *   </div>
 *
 * A class placed on the group is therefore NOT the parent of the content in
 * that older markup. Any class below that has to BE the flex/grid parent, or
 * that targets direct children, resolves its "layout host" instead: the inner
 * container when one is present, otherwise the element itself.
 *
 * Written with :has() rather than by assuming either shape, so the file is
 * correct both for content saved before theme.json existed and for content
 * saved after it, where WordPress no longer emits the wrapper.
 *
 * Purely visual properties (padding, border, background, max-width) stay on
 * the outer element; only layout ownership moves.
 *
 * Origin: LocalWP Foundation runtime validation, 2026-08-04, report 07 §F-1.
 * The parent-theme half of that correction now lives in
 * assets/css/generatepress-bridge.css as bridge rule B-3.
 * ---------------------------------------------------------------------------
 */

/* ===============================================================
 * Page boundary — project-owned content ownership
 *
 * Rendered by templates/marketing-page.php:
 *
 *   GeneratePress shell (header / footer)
 *   → .vlz-page                project-owned page boundary
 *     → .vlz-main              the document's single <main>
 *       → .vlz-content         the block canvas
 *         → Gutenberg / GenerateBlocks / Project Patterns
 *           → VLINZZA component classes
 *
 * The boundary owns alignment. `.vlz-content` constrains its own direct
 * children to the content measure, widens `.alignwide` to the wide measure,
 * and releases `.alignfull` completely.
 *
 * This deliberately replaces the parent theme's full-bleed mechanism
 *   .no-sidebar .entry-content .alignfull {
 *     margin-inline: calc(-100vw / 2 + 100% / 2); max-width: 100vw }
 * which resolves 100vw INCLUDING the classic scrollbar and therefore makes a
 * full-width section wider than the client area (report 07, risk R-D). The
 * rules below reach the same visual result by releasing a max-width instead
 * of by out-denting, so no element ever exceeds the client width and the
 * inline-start / inline-end sides stay symmetric in both RTL and LTR.
 *
 * Nothing here targets .site-content, .content-area, .inside-article,
 * .entry-content or .grid-container. The marketing template does not render
 * them, and no component may depend on them.
 * =============================================================== */
.vlz-page {
	inline-size: 100%;
}

.vlz-main {
	display: block;
	inline-size: 100%;
}

.vlz-content {
	inline-size: 100%;
}

/*
 * Default measure. The gutter is added to the max width and paid back as
 * padding, so the readable measure is exactly the content size while small
 * screens still keep an edge margin.
 */
.vlz-content > * {
	box-sizing: border-box;
	max-inline-size: calc(var(--vlz-content-max) + (var(--vlz-container-gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--vlz-container-gutter);
}

.vlz-content > .alignwide {
	max-inline-size: calc(var(--vlz-wide-max) + (var(--vlz-container-gutter) * 2));
}

.vlz-content > .alignfull {
	max-inline-size: none;
	padding-inline: 0;
}

/*
 * A section supplies its own vertical rhythm through --vlz-section-space, so
 * the boundary must not add the WordPress block gap between stacked sections
 * on top of it.
 */
.vlz-content > .vlz-section + .vlz-section {
	margin-block-start: 0;
}

/* ===============================================================
 * Section — the full-width band a pattern occupies
 * =============================================================== */
.vlz-section {
	padding-block: var(--vlz-section-space);
	background-color: var(--vlz-color-surface);
	color: var(--vlz-color-text);
}

/* Alternating muted band. */
.vlz-section--alt {
	background-color: var(--vlz-color-surface-alt);
}

/* Inverted band for high-contrast marketing moments. */
.vlz-section--invert {
	background-color: var(--vlz-color-surface-invert);
	color: var(--vlz-color-text-invert);
}

.vlz-section--invert h1,
.vlz-section--invert h2,
.vlz-section--invert h3,
.vlz-section--invert h4 {
	color: var(--vlz-color-text-invert);
}

.vlz-section--invert .vlz-lead {
	color: var(--vlz-color-text-invert-muted);
}

.vlz-section--invert a:not(.wp-block-button__link) {
	color: var(--vlz-color-accent);
}

.vlz-section--invert :focus-visible {
	outline-color: var(--vlz-color-focus-invert);
}

/* ===============================================================
 * Container — horizontal measure and gutters inside a full-width band
 * =============================================================== */
.vlz-container {
	box-sizing: border-box;
	inline-size: 100%;
	max-inline-size: var(--vlz-container-max);
	margin-inline: auto;
	padding-inline: var(--vlz-container-gutter);
}

/* ===============================================================
 * Block-gap neutralisation inside gap-based components
 *
 * Now that this theme has a theme.json, WordPress enables its layout system
 * and emits a flow block-gap margin for the children of every group:
 *
 *   :where(.is-layout-flow) > * { margin-block-start: var(--wp--style--block-gap) }
 *   :where(.is-layout-flow) > :first-child { margin-block-start: 0 }
 *
 * The components below already space their children with `gap`. Receiving the
 * flow margin as well offsets the FIRST child against its siblings — measured
 * on 2026-08-04 as a 24px vertical offset of the first Service Grid card
 * against the other two, at every viewport.
 *
 * WordPress writes those rules inside `:where()`, so they carry zero
 * specificity and a single class selector is enough to correct them. The
 * vertical rhythm of a `.vlz-stack` is deliberately NOT reset here: the stack
 * IS the rhythm component and sets its own value below.
 * =============================================================== */
.vlz-grid > *,
.vlz-grid > .wp-block-group__inner-container > *,
.vlz-cluster > *,
.vlz-cluster > .wp-block-group__inner-container > *,
.vlz-hero > *,
.vlz-hero > .wp-block-group__inner-container > *,
.vlz-card > *,
.vlz-card > .wp-block-group__inner-container > *,
.vlz-card__icon > *,
.vlz-card__icon > .wp-block-group__inner-container > * {
	margin-block: 0;
}

/* ===============================================================
 * Stack — consistent vertical rhythm between direct children
 * =============================================================== */
.vlz-stack:not(:has(> .wp-block-group__inner-container)) > * + *,
.vlz-stack > .wp-block-group__inner-container > * + * {
	margin-block-start: var(--vlz-stack-space);
}

/* ===============================================================
 * Cluster — a wrapping row of inline items (used for button groups)
 * =============================================================== */
.vlz-cluster:not(:has(> .wp-block-group__inner-container)),
.vlz-cluster > .wp-block-group__inner-container {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--vlz-space-sm);
}

/* ===============================================================
 * Grid — repeating items, column count driven by a central token
 *
 * auto-fit + minmax lets the number of columns follow the available width,
 * so the same rule serves 3, 4 or 5 repeated items and needs no change when
 * an editor adds or removes one. `--vlz-grid-min` is redefined per breakpoint
 * in tokens.css, which is where the Desktop / Tablet / Mobile column
 * behaviour is decided. Direction is handled by the grid algorithm.
 * =============================================================== */
.vlz-grid:not(:has(> .wp-block-group__inner-container)),
.vlz-grid > .wp-block-group__inner-container {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(var(--vlz-grid-min), 100%), 1fr));
	gap: var(--vlz-grid-gap);
	align-items: stretch;
}

/* ===============================================================
 * Content helpers
 * =============================================================== */

/* Caps long-form copy at a readable line length. */
.vlz-measure {
	max-inline-size: var(--vlz-measure);
}

/* Centres a block and its text, including its measure. */
.vlz-text-center {
	text-align: center;
}

.vlz-text-center.vlz-measure,
.vlz-text-center .vlz-measure {
	margin-inline: auto;
}

.vlz-text-center .vlz-cluster {
	justify-content: center;
}

/* Introductory paragraph. */
.vlz-lead {
	font-size: var(--vlz-text-lead);
	color: var(--vlz-color-text-muted);
}

/*
 * Long-form legal/policy content column. Centres a `.vlz-measure`-width
 * block inside a wider `.vlz-container` without forcing `.vlz-text-center`
 * text alignment (RTL body copy stays right-aligned). First used by the
 * Privacy Policy rebuild (page 27478 replacement) — see
 * _project/docs/PRIVACY-POLICY-LOCAL-CLEAN-REBUILD-AND-FINAL-QA.md — and
 * reusable by any future plain long-form legal/content page.
 */
.vlz-legal-content {
	max-inline-size: var(--vlz-measure);
	margin-inline: auto;
}

/* ===============================================================
 * Buttons
 *
 * These classes are applied to the core/button block wrapper
 * (.wp-block-button), which is where the block editor stores an
 * "Additional CSS class". The generated inner link is styled from there,
 * so the classes survive normal editing and re-saving.
 *
 * The default, unclassed core button is owned by theme.json; bridge rule B-4
 * raises those values above the parent theme's selector. The variants below
 * sit at (0,3,0) and (0,4,0) and win over both.
 * =============================================================== */
.vlz-btn .wp-block-button__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--vlz-space-2xs);
	min-block-size: var(--vlz-control-height);
	padding-block: var(--vlz-space-2xs);
	padding-inline: var(--vlz-control-padding-inline);
	border: var(--vlz-border-width-strong) solid transparent;
	border-radius: var(--vlz-radius-pill);
	font-size: var(--vlz-control-font-size);
	font-weight: var(--vlz-weight-semibold);
	line-height: var(--vlz-leading-tight);
	text-decoration: none;
	transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}

/*
 * Colour rules carry :not(.has-background) for two reasons.
 *
 * 1. Specificity. GeneratePress colours every button with
 *    `a.wp-block-button__link:not(.has-background)` — (0,2,1) — which outranks
 *    a plain `.vlz-btn--primary .wp-block-button__link` — (0,2,0). Matching
 *    the parent's shape raises these to (0,3,0) and (0,4,0) on hover, which
 *    wins on specificity rather than on load order.
 * 2. Behaviour. When an editor picks an explicit background in the block
 *    editor, core adds .has-background and these rules stand aside — the same
 *    contract GeneratePress and bridge rule B-4 honour.
 */

/* Primary */
.vlz-btn--primary .wp-block-button__link:not(.has-background) {
	background-color: var(--vlz-color-primary);
	border-color: var(--vlz-color-primary);
	color: var(--vlz-color-text-invert);
}

.vlz-btn--primary .wp-block-button__link:not(.has-background):hover,
.vlz-btn--primary .wp-block-button__link:not(.has-background):focus,
.vlz-btn--primary .wp-block-button__link:not(.has-background):active {
	background-color: var(--vlz-color-primary-strong);
	border-color: var(--vlz-color-primary-strong);
	color: var(--vlz-color-text-invert);
}

/* Secondary */
.vlz-btn--secondary .wp-block-button__link:not(.has-background) {
	background-color: transparent;
	border-color: var(--vlz-color-border-strong);
	color: var(--vlz-color-heading);
}

/*
 * The secondary button must state its own background on every interaction
 * state, not only at rest. GeneratePress paints `background-color: #3f4047`
 * through `a.wp-block-button__link:not(.has-background):hover` — and the same
 * selector list for `:focus` and `:active` — at (0,3,1). A rest-only
 * declaration leaves those three states to the parent theme, which turned the
 * transparent secondary button solid grey on hover. Measured and corrected on
 * 2026-08-04; the rules below are (0,4,0) and win.
 */
.vlz-btn--secondary .wp-block-button__link:not(.has-background):hover,
.vlz-btn--secondary .wp-block-button__link:not(.has-background):focus,
.vlz-btn--secondary .wp-block-button__link:not(.has-background):active {
	background-color: transparent;
	border-color: var(--vlz-color-primary);
	color: var(--vlz-color-primary);
}

.vlz-section--invert .vlz-btn--secondary .wp-block-button__link {
	border-color: var(--vlz-color-border-invert);
	color: var(--vlz-color-text-invert);
}

.vlz-section--invert .vlz-btn--secondary .wp-block-button__link:hover,
.vlz-section--invert .vlz-btn--secondary .wp-block-button__link:focus,
.vlz-section--invert .vlz-btn--secondary .wp-block-button__link:active {
	background-color: transparent;
	border-color: var(--vlz-color-accent);
	color: var(--vlz-color-accent);
}

/*
 * Focus state. The ring is drawn outside the control and does not rely on a
 * colour change alone, so it stays perceivable on light and dark surfaces.
 */
.vlz-btn .wp-block-button__link:focus-visible {
	outline: var(--vlz-focus-width) solid var(--vlz-color-focus);
	outline-offset: var(--vlz-focus-offset);
}

.vlz-section--invert .vlz-btn .wp-block-button__link:focus-visible {
	outline-color: var(--vlz-color-focus-invert);
}

/* Disabled, for the rare case a button is rendered inert. */
.vlz-btn .wp-block-button__link[aria-disabled="true"] {
	background-color: var(--vlz-color-border);
	border-color: var(--vlz-color-border);
	color: var(--vlz-color-text-muted);
	cursor: not-allowed;
	pointer-events: none;
}

/* ===============================================================
 * Card — one repeated item inside .vlz-grid
 * =============================================================== */
/* Visual box — always the outer element, so it is the grid item that is styled. */
.vlz-card {
	box-sizing: border-box;
	block-size: 100%;
	padding: var(--vlz-space-md);
	border: var(--vlz-border-width) solid var(--vlz-color-border);
	border-radius: var(--vlz-radius-lg);
	background-color: var(--vlz-color-surface);
	text-align: center;
}

/* Layout — on the host, so the card's own children are the flex items. */
.vlz-card:not(:has(> .wp-block-group__inner-container)),
.vlz-card > .wp-block-group__inner-container {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--vlz-space-xs);
	block-size: 100%;
}

/*
 * Icon slot. Rendered as an empty circular badge so a pattern never depends
 * on a media file that may not exist. An editor inserts an Image (or any
 * icon-capable block) inside it; the badge sizing does not change.
 */
.vlz-card__icon {
	inline-size: 4rem;
	block-size: 4rem;
	flex: 0 0 auto;
	border: var(--vlz-border-width-strong) solid var(--vlz-color-primary-soft);
	border-radius: var(--vlz-radius-pill);
	background-color: var(--vlz-color-primary-soft);
	overflow: hidden;
}

.vlz-card__icon:not(:has(> .wp-block-group__inner-container)),
.vlz-card__icon > .wp-block-group__inner-container {
	display: flex;
	align-items: center;
	justify-content: center;
	block-size: 100%;
}

.vlz-card__icon img {
	inline-size: 2rem;
	block-size: 2rem;
	object-fit: contain;
}

/*
 * The three rules below are written as `.vlz-card .vlz-card__*` rather than
 * `.vlz-card__*` on purpose. Three sources now set a heading colour and size —
 * theme.json (element selector, 0,0,1), the GeneratePress dynamic CSS
 * (element selector, 0,0,1) and base.css (element selector, 0,0,1) — and which
 * of those three the browser applies is decided purely by print order. A
 * single class at (0,1,0) would win today, but the descendant form at (0,2,0)
 * leaves headroom so a later card variant can still override the card title
 * without reaching for !important.
 */
.vlz-card .vlz-card__title {
	margin-block: 0;
	color: var(--vlz-color-primary);
}

.vlz-card .vlz-card__text {
	margin-block: 0;
	font-size: var(--vlz-text-sm);
	color: var(--vlz-color-text-muted);
}

/*
 * The optional card link is pushed to the end of the card so that cards of
 * unequal text length still line their links up. This relies on the flex
 * column above, not on a fixed height.
 */
.vlz-card .vlz-card__link {
	margin-block-start: auto;
	padding-block-start: var(--vlz-space-2xs);
	font-size: var(--vlz-text-sm);
	font-weight: var(--vlz-weight-semibold);
}

/* ===============================================================
 * Hero
 *
 * The column definition comes from --vlz-hero-columns, which tokens.css
 * redefines per breakpoint. This component therefore has no media query of
 * its own: changing where the hero collapses to one column is a token edit.
 * =============================================================== */
.vlz-hero:not(:has(> .wp-block-group__inner-container)),
.vlz-hero > .wp-block-group__inner-container {
	display: grid;
	grid-template-columns: var(--vlz-hero-columns);
	gap: var(--vlz-grid-gap);
	align-items: center;
}

/*
 * Media slot. Reserves its space with an aspect ratio so nothing shifts when
 * an editor drops an image in, and stays empty-safe when they do not.
 */
.vlz-hero__media {
	aspect-ratio: 4 / 3;
	border: var(--vlz-border-width) solid var(--vlz-color-border-invert);
	border-radius: var(--vlz-radius-lg);
	overflow: hidden;
}

.vlz-hero__media img {
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

/* ===============================================================
 * CTA — a self-contained call-to-action panel inside a section
 * =============================================================== */
.vlz-cta {
	padding-block: var(--vlz-space-xl);
	padding-inline: var(--vlz-container-gutter);
	border-radius: var(--vlz-radius-lg);
	background-color: var(--vlz-color-accent);
	color: var(--vlz-color-accent-contrast);
	box-shadow: var(--vlz-shadow-md);
}

.vlz-cta h2 {
	color: var(--vlz-color-accent-contrast);
}

.vlz-cta .vlz-lead {
	color: var(--vlz-color-accent-contrast);
}

/*
 * Inside the accent panel the primary button uses the dark ink colour: the
 * brand blue does not hold enough contrast against turquoise.
 */
.vlz-cta .vlz-btn--primary .wp-block-button__link:not(.has-background) {
	background-color: var(--vlz-color-ink);
	border-color: var(--vlz-color-ink);
	color: var(--vlz-color-text-invert);
}

.vlz-cta .vlz-btn--primary .wp-block-button__link:not(.has-background):hover,
.vlz-cta .vlz-btn--primary .wp-block-button__link:not(.has-background):focus,
.vlz-cta .vlz-btn--primary .wp-block-button__link:not(.has-background):active {
	background-color: var(--vlz-color-ink-soft);
	border-color: var(--vlz-color-ink-soft);
}
