/*
Theme Name: Solara Energy
Update URI: false
Author: Chantal van Nuland
Description: A project theme for the Solara Energy demo website, built on the Executive Property design foundation as a direct child of Twenty Twenty-Five. The project default is the Geometric Technical direction with architectural detailing; all fourteen Executive style directions remain available.
Tags: block-patterns, full-site-editing, style-variations, translation-ready
Requires at least: 6.9
Requires PHP: 8.2
Version: 1.0.0
License: GNU General Public License v3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Text Domain: solara-energy
Template: twentytwentyfive

Solara Energy WordPress Theme, (C) 2026 Chantal van Nuland.
Solara Energy is distributed under the terms of the GNU GPL.
Based on the Executive Property design foundation.

*/

/* CSS Reset
---------------------------------------------------------------------------- */

*,
*::before,
*::after {
	box-sizing: inherit;
}

html {
	box-sizing: border-box;
	-moz-osx-font-smoothing: grayscale;
	-webkit-font-smoothing: antialiased;
}

b,
strong {
	font-weight: var(--wp--custom--font-weight--semi-bold);
}

ol,
ul {
	padding: 0;
}

ol:where(:not([class*="wp-block"])),
ol:where(.wp-block-list),
ul:where(:not([class*="wp-block"])),
ul:where(.wp-block-list) {
	padding-inline-start: var(--wp--preset--spacing--medium);
}

div[class*="wp-block-"] figcaption {
	color: var(--wp--preset--color--ink-muted);
	font-size: var(--wp--preset--font-size--x-small);
	margin-bottom: 0;
	margin-top: 20px;
	text-align: center;
}

img,
figure {
	max-width: 100%;
	height: auto;
}

/* Temporary fix for text-decoration-thickness in Firefox */
@-moz-document url-prefix() {
	a {
		text-decoration-thickness: .1rem !important;
	}
}

/* Standardize form styling
--------------------------------------------- */

/*
 * A button does not inherit colour. The UA stylesheet gives it `buttontext`,
 * which is its own grey and ignores everything the theme says. Any block that
 * renders a real <button> — the Accordion heading above all — therefore came
 * out grey on white no matter what the palette held.
 *
 * Inheriting colour as well as font is what makes those blocks obey the theme.
 */
input,
button,
textarea,
select {
	font: inherit;
	color: inherit;
}

/*
 * The Accordion toggle is a full-width button. It has to look like the heading
 * it is, not like a control.
 */
.wp-block-accordion-heading__toggle {
	background: none;
	border: 0;
	color: inherit;
	cursor: pointer;
	font: inherit;
	text-align: start;
	width: 100%;
}

input[type="button"],
input[type="email"],
input[type="search"],
input[type="submit"],
input[type="text"],
textarea {
	-webkit-appearance: none;
	appearance: none;
}

input:not([type="submit"]):not([type="radio"]),
select,
textarea,
.wp-block-post-comments-form input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
.wp-block-post-comments-form textarea {
	color: var(--wp--preset--color--ink);
	border-radius: var(--executive-radius-control);
	border: solid 1px color-mix(in srgb, var(--wp--preset--color--surface) 100%, var(--wp--preset--color--ink) 20%);
	padding: .5em 1em;
	font-size: var(--wp--preset--font-size--small);
	background-color: var(--wp--preset--color--canvas);
	box-shadow: var(--executive-control-shadow);
}

input:focus-visible,
textarea:focus-visible,
select:focus-visible {
	outline-color: var(--wp--preset--color--ink);
	outline-offset: 1px;
}

input[type="checkbox"],
input[type="image"],
input[type="radio"] {
	width: auto;
}

/*
 * An SVG used as a background-image is a separate document, so it cannot read
 * currentColor. The arrow therefore lives in a custom property that a dark
 * style variation overrides with a light-stroked version.
 */
:root {
	--executive-select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23191C20' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}

select {
	-webkit-appearance: none;
	appearance: none;
	background: var(--wp--preset--color--canvas) var(--executive-select-arrow) no-repeat;
	background-size: 16px;
	background-position: calc(100% - 12px) 50%;
	padding-right: 2.2em;
}

label {
	width: 100%;
	display: block;
}

::placeholder {
	color: var(--wp--preset--color--ink-muted);
	opacity: 0.75;
}

/* Section styles
---------------------------------------------------------------------------- */

/*
 * Section: Dark, Light and Accent are defined in styles/blocks/section/*.json.
 * They were registered, they showed up in the sidebar, and picking one changed
 * nothing on a pattern. This is why.
 *
 * WordPress writes a block style variation as a plain declaration:
 *
 *     :root :where(.wp-block-group.is-style-section-dark){background-color:…}
 *
 * but a colour picked from the palette is written as a preset class, and those
 * are always marked important:
 *
 *     .has-canvas-background-color{background-color:… !important}
 *
 * Seventy-five of the patterns in this theme state their own background on the
 * outermost block that way, because that is what a section is. So the variation
 * lost to the pattern on exactly the block it was made for, every time, and no
 * amount of editing the JSON could change that — theme.json has no way to write
 * !important.
 *
 * The two properties that collide are restated here with the weight to win: one
 * extra class of specificity and the same !important. The values stay palette
 * slugs, so every colour variation keeps its own reading of them.
 *
 * Those files used to repaint headings, links and buttons for the whole subtree
 * as well. They no longer do, and the section below is why: a section says what
 * it is, and everything inside it works out its own colour from the surface it
 * actually stands on.
 */
:is(.wp-block-group, .wp-block-cover).is-style-section-dark {
	background-color: var(--wp--preset--color--canvas-inverse) !important;
	color: var(--wp--preset--color--ink-inverse) !important;
}

:is(.wp-block-group, .wp-block-cover).is-style-section-light {
	background-color: var(--wp--preset--color--surface) !important;
	color: var(--wp--preset--color--ink) !important;
}

:is(.wp-block-group, .wp-block-cover).is-style-section-accent {
	background-color: var(--wp--preset--color--brand-light) !important;
	color: var(--wp--preset--color--ink) !important;
}

/*
 * A cover keeps its image: the background above sits behind the media and the
 * overlay, where it cannot be seen. On a cover the section style is therefore a
 * text treatment, which is the useful half of it anyway — the colour it repaints
 * beats the fixed white that core puts on every cover.
 */

/* Colour context
---------------------------------------------------------------------------- */

/*
 * Everything above only settles what colour the section itself is. The harder
 * question is what happens to the blocks inside it, and that is where this theme
 * kept producing white text on a white card.
 *
 * The old arrangement pointed every text colour at a fixed palette slug: the
 * section style repainted headings, links and buttons for the whole subtree, the
 * Muted role carried a colour chosen for a light page, and a handful of rules
 * further down tried to undo the damage per combination. Each of those rules
 * named one surface, so a card nested one level deeper than the author imagined
 * fell straight through, and the two halves fought on specificity instead of on
 * meaning. Switching a section to Dark was enough to make a grid of white cards
 * unreadable, which is exactly what happened.
 *
 * So a colour is no longer stated per block. Every surface declares what reads
 * on it, once, in these four custom properties:
 *
 *     --executive-text          body text
 *     --executive-text-muted    the Muted and Eyebrow roles
 *     --executive-link          a link that is not a button
 *     --executive-button-*      a solid button
 *
 * Custom properties inherit, and a value set on a card always beats the value
 * coming down from the section around it — no !important, no specificity race,
 * and it holds at any nesting depth. A white card inside a dark section inside a
 * darker page reads correctly because the card is the nearest surface, full
 * stop. That is the whole mechanism.
 *
 * Three contexts, written against the palette so all nine colour variations get
 * the same guarantee.
 */

/*
 * Light surfaces: dark text. Also the page default.
 *
 * A cover is in this list twice, once each way. Its overlay colour sits on a
 * child element, so the cover itself carries no background class to read — but
 * WordPress already tells us which way it went, because core needs the same
 * answer for its own black-or-white rule. is-light means a light overlay.
 */
:root,
.has-canvas-background-color,
.has-surface-background-color,
.has-brand-light-background-color,
.has-brand-alt-background-color,
.has-border-background-color,
.wp-block-cover.is-light {
	--executive-text: var(--wp--preset--color--ink);
	--executive-text-muted: var(--wp--preset--color--ink-muted);
	--executive-link: var(--wp--preset--color--ink);
	--executive-button-background: var(--wp--preset--color--ink);
	--executive-button-text: var(--wp--preset--color--canvas);
	--executive-accent: var(--wp--preset--color--brand);
}

/* Dark surfaces: light text. */
.has-canvas-inverse-background-color,
.has-ink-background-color,
.has-border-inverse-background-color,
.has-brand-alt-dark-background-color,
.wp-block-cover:not(.is-light),
.executive-scrim-soft,
.executive-scrim-medium,
.executive-scrim-strong {
	--executive-text: var(--wp--preset--color--ink-inverse);
	--executive-text-muted: var(--wp--preset--color--ink-inverse-muted);
	--executive-link: var(--wp--preset--color--ink-inverse);
	--executive-button-background: var(--wp--preset--color--canvas);
	--executive-button-text: var(--wp--preset--color--ink);
	--executive-accent: var(--wp--preset--color--brand-alt);
}

/*
 * Three surfaces sit too close to the muted tone meant for them, so on those the
 * muted roles drop back to full-strength text. This is measured, not guessed —
 * docs/check-contrast.py runs the table below against all nine colour variations
 * and these were the only combinations that failed. Muted on Brand Alt lands
 * between 3.1:1 and 4.5:1 depending on the variation, and muted on Dark Section
 * Border between 3.0:1 and 4.4:1. A label you can read beats a decorative one.
 */
.has-brand-alt-background-color {
	--executive-text-muted: var(--wp--preset--color--ink);
}

.has-border-inverse-background-color {
	--executive-text-muted: var(--wp--preset--color--ink-inverse);
}

/* The brand colour as a surface, for the same reason. */
.has-brand-background-color {
	--executive-text: var(--wp--preset--color--canvas);
	--executive-text-muted: var(--wp--preset--color--canvas);
	--executive-link: var(--wp--preset--color--canvas);
	--executive-button-background: var(--wp--preset--color--canvas);
	--executive-button-text: var(--wp--preset--color--brand);
}

/*
 * The accent, and the three surfaces it cannot be used on.
 *
 * An eyebrow is the brand colour rather than a grey — that one change is most of
 * what makes a heading look considered instead of plain, because it gives the
 * heading an approach rather than a cold start.
 *
 * The brand colour is the accent on light ground and the lighter Brand Alt on
 * dark ground, which covers every surface except three: the brand colours
 * themselves, and Dark Section Border. On those, an accent would be a tint of
 * the surface it stands on, so it falls back to plain text — same reasoning, and
 * the same measurement, as the muted rules above.
 */
.has-brand-alt-background-color {
	--executive-accent: var(--wp--preset--color--ink);
}

.has-brand-background-color {
	--executive-accent: var(--wp--preset--color--canvas);
}

.has-brand-alt-dark-background-color,
.has-border-inverse-background-color {
	--executive-accent: var(--wp--preset--color--ink-inverse);
}

/*
 * A scrim: a translucent panel over a photo, used for a price tag or an add to
 * cart bar. Always dark, whatever is underneath, so it belongs with the dark
 * surfaces above. The three strengths are the ones the patterns were already
 * using as raw hex values.
 */
.executive-scrim-soft {
	background-color: color-mix(in srgb, var(--wp--preset--color--canvas-inverse) 19%, transparent);
}

.executive-scrim-medium {
	background-color: color-mix(in srgb, var(--wp--preset--color--canvas-inverse) 35%, transparent);
}

.executive-scrim-strong {
	background-color: color-mix(in srgb, var(--wp--preset--color--canvas-inverse) 77%, transparent);
}

/*
 * A section style is a surface too, and it outranks anything else sitting on the
 * same block — that is the point of picking one. Hence last, and matched on the
 * block class as well so it carries the same weight as the cover rules above.
 */
:is(.wp-block-group, .wp-block-cover).is-style-section-light,
:is(.wp-block-group, .wp-block-cover).is-style-section-accent {
	--executive-text: var(--wp--preset--color--ink);
	--executive-text-muted: var(--wp--preset--color--ink-muted);
	--executive-link: var(--wp--preset--color--ink);
	--executive-button-background: var(--wp--preset--color--ink);
	--executive-button-text: var(--wp--preset--color--canvas);
}

:is(.wp-block-group, .wp-block-cover).is-style-section-dark {
	--executive-text: var(--wp--preset--color--ink-inverse);
	--executive-text-muted: var(--wp--preset--color--ink-inverse-muted);
	--executive-link: var(--wp--preset--color--ink-inverse);
	--executive-button-background: var(--wp--preset--color--canvas);
	--executive-button-text: var(--wp--preset--color--ink);
}

/* What reads the context
---------------------------------------------------------------------------- */

/*
 * A block that states its own background but no text colour. Headings and plain
 * paragraphs below it need no rule at all: nothing declares a colour on them, so
 * they inherit this one.
 *
 * has-text-color means the author picked a colour by hand, and that always wins.
 */
:is(
	.has-canvas-background-color,
	.has-surface-background-color,
	.has-brand-light-background-color,
	.has-brand-alt-background-color,
	.has-border-background-color,
	.has-canvas-inverse-background-color,
	.has-ink-background-color,
	.has-border-inverse-background-color,
	.has-brand-alt-dark-background-color,
	.has-brand-background-color,
	.executive-scrim-soft,
	.executive-scrim-medium,
	.executive-scrim-strong
):not(.has-text-color) {
	color: var(--executive-text);
}

/*
 * A cover, for the same reason. Core already writes a colour here, but it writes
 * a literal black or white; this replaces it with the palette, so a cover reads
 * the same as every other surface in all nine colour variations. Set on the
 * inner container rather than on the cover, which is where the content actually
 * is and saves arguing with core over a tie in specificity.
 */
.wp-block-cover:not(.has-text-color) > .wp-block-cover__inner-container {
	color: var(--executive-text);
}

/*
 * Links and the named roles do need one, because something already declares a
 * colour on them — theme.json for links, styles/blocks/text/*.json for the
 * roles. Those files stay as they are: they are what the block is worth on a
 * default page, and on a default page these rules resolve to the same value.
 * They only diverge once the block is standing on a surface that says otherwise.
 *
 * The extra :not() is doing two jobs — it protects a hand-picked colour, and it
 * carries the weight needed to outrank the declaration being replaced.
 */
a:not(.wp-element-button):not(.has-text-color) {
	color: var(--executive-link);
}

:is(
	.is-style-muted,
	.is-style-muted-inverse
):not(.has-text-color) {
	color: var(--executive-text-muted);
}

/*
 * An eyebrow carries the accent, not the muted grey. It is a label of four or
 * five words above a heading, so it can afford to be the brand colour where a
 * paragraph could not — and that is exactly where the colour earns its keep.
 */
:is(
	.is-style-eyebrow,
	.is-style-eyebrow-inverse
):not(.has-text-color) {
	color: var(--executive-accent);
}

/*
 * A plain button, which is what nearly every pattern uses. Left alone: a button
 * carrying one of the styles from styles/blocks/button/, because Dark, Light and
 * Tint are a choice and not a default, and Outline, because it draws itself in
 * currentColor and so already follows the surface it stands on. That is what the
 * is-style- test excludes — the class sits on the wrapper, one level up.
 */
:not([class*="is-style-"]) > .wp-element-button:not(.has-background) {
	background-color: var(--executive-button-background);
}

:not([class*="is-style-"]) > .wp-element-button:not(.has-text-color) {
	color: var(--executive-button-text);
}

/* Helper styles
---------------------------------------------------------------------------- */

a.more-link {
	display: block;
}

/* Inline code */
*:not(.wp-block-code) > code {
	background-color: var(--wp--preset--color--surface);
	padding: 3px 5px;
	position: relative;
	border-radius: var(--executive-radius-control);
}

.wp-block-categories {
	position: relative;
}

/* Remove margin on term description on archive pages */
.wp-block-term-description p:last-child {
	margin-bottom: 0;
}

/* Remove last separator on post list */
.remove-border-and-padding .wp-block-post-template li:last-child .wp-block-separator {
	display: none;
}

/* Hide post meta div if no tags assigned */
.single .wp-block-group:has(> .post-meta:empty) {
	display: none;
}

.wp-block-group:empty:has(+ .comment-respond) {
	display: none;
}

.row-logos > figure {
	flex-shrink: 1 !important;
}

/*
 * A full width cover that keeps the container.
 *
 * Core shrink-wraps the inner container of a cover the moment a content
 * position is picked — `margin:0;width:auto` — and from that point the
 * constrained layout has nothing left to constrain: the content hugs whichever
 * corner was chosen, hard against the edge of the screen, no matter how the
 * content width toggle is set. That is harmless on a card, which is only as
 * wide as its column anyway, and wrong on a hero, where the copy has to start
 * on the same line as every other section on the page.
 *
 * Handing the inner container its width back is the whole fix: the constrained
 * layout takes the horizontal placement over again, and the cover's own flexbox
 * still does the vertical placement the content position was chosen for. Scoped
 * to full width covers, so cards keep the shrink-wrap that suits them.
 *
 * The repeated classes are counting, not decoration. Core writes this rule with
 * .wp-block-cover twice and the position class as well, which is five classes,
 * and its block stylesheet is printed after the theme's — so anything that only
 * matches that weight ties and loses. Six beats it whatever the order.
 */
.wp-block-cover.alignfull.alignfull.has-custom-content-position.has-custom-content-position > .wp-block-cover__inner-container {
	width: 100%;
}

/*
 * Floating header.
 *
 * Taken out of the flow, exactly like the transparent header below, so the
 * first section starts at the top of the page and the header floats over it.
 * Fixed rather than absolute so it stays in view while scrolling — that is what
 * makes the blur variant meaningful in the first place.
 *
 * Only use this on templates whose first section can carry a header on top of
 * it. Add .executive-unstick-mobile to opt out on small screens.
 */
body:not(.wp-admin) header:has(>.is-position-sticky) {
	position: fixed;
	left: 0;
	right: 0;
	top: var( --wp-admin--admin-bar--height, 0px );
	z-index: 100;
}

/*
 * Account for admin bar on mobile.
 *
 * The selector has to carry the same weight as the rule above, otherwise it
 * never lands: a media query changes nothing about specificity, and
 * `header:has(…)` on its own loses to `body:not(.wp-admin) header:has(…)`.
 */

@media (max-width: 600px) {
	body:not(.wp-admin) header:has(>.is-position-sticky) {
		top: 0;
	}
}

/*
 * Transparent header. Taken out of the flow so the section below starts at the
 * top of the page and the header sits on top of it. Only use this on templates
 * whose first section is dark enough to carry light navigation.
 *
 * The :not() is not decoration. This selector and the sticky one above have the
 * same specificity, so without it the later rule simply wins and a transparent
 * header that the editor has been told to make sticky scrolls away with the
 * page: absolute beats fixed purely because it is written second. Excluding the
 * sticky case here lets the two combine, which is the pairing anyone reaches
 * for — transparent over the hero, solid and pinned once past it.
 */
body:not(.wp-admin) header:has(> .executive-header-transparent):not(:has(> .is-position-sticky)) {
	position: absolute;
	left: 0;
	right: 0;
	top: var(--wp-admin--admin-bar--height, 0px);
	z-index: 100;
}

/* Mobile helper classes
---------------------------------------------------------------------------- */

/*
 * Opting a header out of sticking on a phone. Same weight as the sticky rule
 * for the same reason as the admin-bar correction above — without `body` in
 * front of it this rule loses and the header stays pinned.
 */

@media (max-width: 781px) {
	body:not(.wp-admin) header:has(>.executive-unstick-mobile) {
		position: static;
	}
}

/* Tabs
---------------------------------------------------------------------------- */

/*
 * A segmented control rather than a row of folder tabs: one rounded track with
 * the chosen segment raised out of it. It reads as a control at a glance, it
 * survives any number of labels, and it does not need the hairline-and-notch
 * trickery that makes classic tabs look dated the moment the palette changes.
 *
 * The track scrolls sideways when the labels no longer fit, which is what saves
 * this on a phone. Snapping makes that feel deliberate instead of loose, and the
 * scrollbar itself is hidden because the raised segment already says there is
 * more to the right.
 */
.executive-tabs__list {
	counter-reset: executive-tab;
	display: flex;
	gap: 4px;
	width: max-content;
	max-width: 100%;
	margin-inline: auto;
	padding: 5px;
	border: 1px solid var(--wp--preset--color--border);
	border-radius: 999px;
	background-color: var(--wp--preset--color--surface);
	overflow-x: auto;
	scrollbar-width: none;
	scroll-snap-type: x proximity;
	-webkit-overflow-scrolling: touch;
}

.executive-tabs__list::-webkit-scrollbar {
	display: none;
}

.executive-tabs__list p {
	margin: 0;
	counter-increment: executive-tab;
}

/*
 * The number in front of the label. It is drawn rather than typed, so nobody
 * has to renumber anything after adding or moving a tab, and the sequence
 * cannot end up wrong in a translation.
 */
.executive-tabs__list :is(a, button)::before {
	content: counter(executive-tab, decimal-leading-zero);
	margin-inline-end: .6em;
	opacity: .55;
	font-size: .8em;
	font-variant-numeric: tabular-nums;
}

.executive-tabs__list :is(a, button) {
	display: block;
	padding: .7em 1.5em;
	border: 0;
	border-radius: 999px;
	background: none;
	color: var(--wp--preset--color--ink-muted);
	cursor: pointer;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 500;
	scroll-snap-align: center;
	text-decoration: none;
	white-space: nowrap;
	transition: color .25s ease, background-color .25s ease, box-shadow .25s ease;
}

.executive-tabs__list :is(a, button):hover {
	color: var(--wp--preset--color--ink);
}

.executive-tabs__list :is(a, button).is-active {
	background-color: var(--wp--preset--color--canvas);
	box-shadow: var(--wp--preset--shadow--medium-light);
	color: var(--wp--preset--color--brand);
}

/* The number on the chosen tab carries the accent at full strength. */
.executive-tabs__list :is(a, button).is-active::before {
	opacity: 1;
}

/*
 * The tab row on a phone.
 *
 * Three pills side by side need more width than a phone has, and the row then
 * scrolls sideways: everything is still reachable, but the tabs past the fold
 * are reachable only by a swipe nobody is told about. Below 600px the script's
 * button takes over and the row itself drops down underneath it.
 *
 * The wrapper is display: contents on a wide screen, so it is there in the
 * markup and absent from the layout: the row goes on centring itself in the
 * section exactly as it did before, and only below the breakpoint does the
 * wrapper become a box for the panel to be positioned against.
 *
 * The button is hidden by default rather than shown by default, so a page where
 * the script did not run never shows a control that opens nothing.
 */
.executive-tabs__mobile {
	display: contents;
}

.executive-tabs__toggle {
	display: none;
}

@media (max-width: 600px) {

	.executive-tabs.is-enhanced .executive-tabs__mobile {
		display: block;
		position: relative;
		z-index: 1;
		max-width: 26rem;
		margin-inline: auto;
	}

	.executive-tabs.is-enhanced .executive-tabs__toggle {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 1em;
		width: 100%;
		padding: .8em 1.5em;
		border: 1px solid var(--wp--preset--color--border);
		border-radius: 999px;
		background-color: var(--wp--preset--color--surface);
		color: var(--wp--preset--color--ink);
		cursor: pointer;
		font-size: var(--wp--preset--font-size--small);
		font-weight: 500;
		text-align: start;
	}

	/*
	 * The arrow is drawn from two borders rather than set as an image, so it
	 * takes its colour from the palette like everything else and can turn over
	 * when the panel opens.
	 */
	.executive-tabs.is-enhanced .executive-tabs__toggle::after {
		content: "";
		flex: none;
		width: .45em;
		height: .45em;
		margin-top: -.2em;
		border-right: 2px solid var(--wp--preset--color--brand);
		border-bottom: 2px solid var(--wp--preset--color--brand);
		transform: rotate(45deg);
	}

	.executive-tabs.is-enhanced.is-open .executive-tabs__toggle::after {
		margin-top: .2em;
		transform: rotate(-135deg);
	}

	/*
	 * The panel hangs off the button instead of pushing the section down, so
	 * opening it moves nothing that was already on the screen. Closed it is
	 * hidden rather than transparent, which keeps it out of the tab order and
	 * out of what a screen reader walks through.
	 */
	.executive-tabs.is-enhanced .executive-tabs__list {
		position: absolute;
		inset-inline: 0;
		top: calc(100% + 6px);
		flex-direction: column;
		/* The row layout centres its items; stacked they have to fill the panel. */
		align-items: stretch;
		width: auto;
		max-width: none;
		margin-inline: 0;
		padding: 6px;
		border-radius: var(--executive-radius-control);
		box-shadow: var(--wp--preset--shadow--large-light);
		overflow: hidden;
		opacity: 0;
		visibility: hidden;
		transform: translateY(-.4em);
		transform-origin: top center;
	}

	.executive-tabs.is-enhanced.is-open .executive-tabs__list {
		opacity: 1;
		visibility: visible;
		transform: none;
	}

	.executive-tabs.is-enhanced .executive-tabs__list :is(a, button) {
		width: 100%;
		text-align: start;
		white-space: normal;
	}
}

/*
 * The opening itself, behind the usual guard. Visibility is switched at the end
 * of the closing move and at the start of the opening one, so the panel is
 * never both invisible and in the way.
 */
@media (max-width: 600px) and (prefers-reduced-motion: no-preference) {

	.executive-tabs.is-enhanced .executive-tabs__toggle::after {
		transition: transform .25s cubic-bezier(.22, .61, .36, 1), margin-top .25s cubic-bezier(.22, .61, .36, 1);
	}

	.executive-tabs.is-enhanced .executive-tabs__list {
		transition:
			opacity .2s ease,
			transform .25s cubic-bezier(.22, .61, .36, 1),
			visibility 0s linear .25s;
	}

	.executive-tabs.is-enhanced.is-open .executive-tabs__list {
		transition:
			opacity .2s ease,
			transform .25s cubic-bezier(.22, .61, .36, 1),
			visibility 0s;
	}
}

/*
 * Every panel sits in the same grid cell, so the set is as tall as its tallest
 * panel and switching tabs moves nothing else on the page. Hiding with display
 * would shrink the page under the pointer at the moment of the click, and the
 * page would appear to jump — which is exactly what it feels like.
 *
 * visibility rather than opacity, because visibility takes the panel out of the
 * accessibility tree and out of the tab order as well.
 */
.executive-tabs.is-enhanced .executive-tabs__panels {
	display: grid;
}

.executive-tabs.is-enhanced .executive-tabs__panel {
	grid-area: 1 / 1;
	visibility: hidden;
}

.executive-tabs.is-enhanced .executive-tabs__panel.is-active {
	visibility: visible;
}

/*
 * Before the script runs — and on a page where it never runs — every panel is
 * visible and the links are anchors to them. That is a readable section rather
 * than a broken control, so it only needs breathing room between the panels.
 */
.executive-tabs:not(.is-enhanced) .executive-tabs__panel + .executive-tabs__panel {
	margin-top: var(--wp--preset--spacing--large);
}

@media (prefers-reduced-motion: no-preference) {

	.executive-tabs.is-enhanced .executive-tabs__panel.is-active {
		animation: executive-tab-in .35s cubic-bezier(.22, .61, .36, 1);
	}
}

@keyframes executive-tab-in {

	from {
		opacity: 0;
		transform: translate3d(0, 10px, 0);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

/* Table
--------------------------------------------- */

.wp-block-table table {
	background: var(--wp--preset--color--canvas);
	border-radius: var(--executive-radius-control);
	border: solid 1px var(--wp--preset--color--border);
	font-size: var(--wp--preset--font-size--x-small);
}
.wp-block-table.is-style-stripes {
	border-bottom: none;
}

.wp-block-table.is-style-stripes tbody tr:nth-child(odd) {
	background-color: var(--wp--preset--color--surface);
}

.wp-block-table td, 
.wp-block-table th,
.wp-block-table thead {
	border: none;
	border-bottom: solid 1px color-mix(in srgb, var(--wp--preset--color--border) 50%, var(--wp--preset--color--canvas) 5%);
}

.wp-block-table th,
.wp-block-table tfoot {
	text-align: start;
	background-color: color-mix(in srgb, var(--wp--preset--color--surface) 75%, var(--wp--preset--color--canvas) 5%);
}

.wp-block-table td, 
.wp-block-table th {
	padding: .8em 1em;
}

.wp-block-table tfoot {
	border-top: solid 1px var(--wp--preset--color--border);
}

.wp-block-table tfoot td {
	border-bottom: none;
}

/* Motion
---------------------------------------------------------------------------- */

/*
 * Every class in this section is opt-in: add it under Block → Advanced →
 * Additional CSS class(es). Nothing here runs on JavaScript.
 *
 * Scroll-driven reveals sit behind two guards, and both are load-bearing. The
 * @supports guard prevents a browser that does not understand animation-timeline
 * from applying the `from` keyframe as a plain static style, which would leave
 * the content invisible with nothing to ever reveal it. The reduced-motion guard
 * is the usual courtesy. Outside either guard the content simply renders.
 */

/*
 * Lenis needs the document to size itself normally; it reads and writes the
 * real scroll position rather than transforming a wrapper.
 */
html.lenis,
html.lenis body {
	height: auto;
}

.lenis.lenis-stopped {
	overflow: clip;
}

.lenis.lenis-smooth [data-lenis-prevent] {
	overscroll-behavior: contain;
}

/*
 * The entrance.
 *
 * Further travel and a touch of scale read as weight rather than as a widget
 * sliding in. The easing is an expo-out: almost all of the distance is covered
 * early, then it settles for a long time. That long settle is what separates
 * "expensive" from "animated" — a linear or ease-out curve arrives and stops,
 * this one glides to rest.
 *
 * linear() approximates that curve for browsers that support it; the plain
 * `linear` fallback keeps the scroll-driven version honest, since a
 * scroll-driven animation is scrubbed by position and should not double-ease.
 */
/*
 * Movement only, deliberately.
 *
 * An earlier version faded opacity from 0 as well. That is one dropped frame,
 * one unsupported range, one odd element height away from leaving body text
 * sitting at half opacity — which reads as unreadable grey on white and looks
 * like a broken palette rather than a broken animation.
 *
 * Text is not allowed to depend on an animation finishing. Transform alone
 * still reads as arrival, and the worst case is a section that sits still.
 */
@keyframes executive-rise-in {
	from {
		transform: translate3d(0, 40px, 0) scale(0.985);
	}

	to {
		transform: none;
	}
}

/*
 * Two ways to reveal, and only ever one of them at a time.
 *
 * The scroll-driven version below is scrubbed by scroll position: it follows the
 * wheel, and it runs backwards when you scroll back up. That reads as reactive.
 * A section that plays once, on its own clock, and then stays put reads as
 * composed — which is the register this theme is after.
 *
 * So when the observer in executive-motion.js is available it marks the
 * document and takes over, and everything below stands down. Without JavaScript,
 * without IntersectionObserver, or with reduced motion asked for, the CSS
 * version is still there and nothing is lost.
 */
html.executive-motion-js .executive-reveal,
html.executive-motion-js .executive-reveal-children > * {
	animation: none !important;
}

/*
 * The JavaScript path.
 *
 * An element is only ever hidden by the script that is about to observe it —
 * `armed` is added element by element, immediately before observe(). That keeps
 * the guarantee the scroll-driven version makes further up: content cannot end
 * up invisible with nothing left to reveal it, because nothing is hidden until
 * the thing that will unhide it is already watching.
 */
@media (prefers-reduced-motion: no-preference) {
	.executive-armed {
		opacity: 0;
		transform: translate3d(0, 26px, 0) scale(0.99);
	}

	.executive-revealed {
		opacity: 1;
		transform: none;
		transition:
			transform 900ms cubic-bezier(.19, 1, .22, 1),
			opacity 900ms cubic-bezier(.19, 1, .22, 1);
		will-change: transform;
	}

	/*
	 * Stagger by 90 ms. Six is the limit: beyond that, the visitor waits for the
	 * final card instead of seeing the row arrive as a whole.
	 */
	.executive-reveal-children > .executive-revealed:nth-child(2) { transition-delay: 90ms; }
	.executive-reveal-children > .executive-revealed:nth-child(3) { transition-delay: 180ms; }
	.executive-reveal-children > .executive-revealed:nth-child(4) { transition-delay: 270ms; }
	.executive-reveal-children > .executive-revealed:nth-child(5) { transition-delay: 360ms; }
	.executive-reveal-children > .executive-revealed:nth-child(n+6) { transition-delay: 450ms; }
}

@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		.executive-reveal,
		.executive-reveal-children > * {
			animation: executive-rise-in linear both;
			animation-timeline: view();
			animation-range: entry 5% cover 32%;
			will-change: transform;
		}

		/*
		 * Each child gets its own view() timeline, so rows in a grid already
		 * arrive one after another. Within a single row they would land at the
		 * same moment, so the range start is nudged per position to spread them.
		 */
		.executive-reveal-children > *:nth-child(2) {
			animation-range: entry 11% cover 38%;
		}

		.executive-reveal-children > *:nth-child(3) {
			animation-range: entry 17% cover 44%;
		}

		.executive-reveal-children > *:nth-child(n+4) {
			animation-range: entry 23% cover 50%;
		}
	}
}

/*
 * Arrival from the side.
 *
 * The rise is the house entrance and it belongs on a section you meet head on:
 * a header, a grid, a band. It is the wrong move for two things standing beside
 * each other, because both halves lift at once and the eye has nowhere to go.
 * There they come in from their own side instead, which reads as the pair being
 * assembled rather than as a page loading.
 *
 * Half the travel of the rise, deliberately. Sideways movement is read faster
 * than vertical, so the same distance would look like a swipe.
 *
 * These are directional, so rtl.css swaps the two round.
 */
@keyframes executive-slide-in-start {

	from {
		transform: translate3d(-24px, 0, 0);
	}

	to {
		transform: none;
	}
}

@keyframes executive-slide-in-end {

	from {
		transform: translate3d(24px, 0, 0);
	}

	to {
		transform: none;
	}
}

@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {

		.executive-reveal-start {
			animation: executive-slide-in-start linear both;
			animation-timeline: view();
			animation-range: entry 5% cover 38%;
			will-change: transform;
		}

		.executive-reveal-end {
			animation: executive-slide-in-end linear both;
			animation-timeline: view();
			animation-range: entry 5% cover 38%;
			will-change: transform;
		}
	}
}

/*
 * A figure that counts up to itself.
 *
 * The number in the markup is the finished value, not a zero. Without
 * JavaScript it simply stands there and reads correctly, which is the whole
 * point: a statistic that renders as 0 because a script failed is worse than
 * one that never moved. The script only overwrites it while it is counting.
 *
 * Tabular figures stop the width jumping as the digits change, which otherwise
 * makes the whole row twitch on every frame.
 */
.executive-count {
	font-variant-numeric: tabular-nums;
}

/*
 * A bar that fills to its value.
 *
 * The value lives in a custom property on the element, so the bar is one class
 * and the number is data. Same reasoning as the counter: without the observer
 * the bar is already at its width, and the animation is added only once the
 * script has announced itself on the document element.
 */
.executive-bar {
	position: relative;
	display: block;
	block-size: 3px;
	inline-size: 100%;
	background-color: color-mix(in srgb, currentColor 18%, transparent);
}

.executive-bar::after {
	content: "";
	position: absolute;
	inset-block: 0;
	inset-inline-start: 0;
	inline-size: var(--executive-bar-value, 0%);
	background-color: var(--executive-accent);
}

@media (prefers-reduced-motion: no-preference) {

	html.executive-motion-js .executive-bar::after {
		inline-size: 0;
		transition: inline-size 1200ms cubic-bezier(.19, 1, .22, 1);
	}

	html.executive-motion-js .executive-bar.executive-filled::after {
		inline-size: var(--executive-bar-value, 0%);
	}
}

/*
 * A price list: a name, a leader of dots, an amount.
 *
 * The dots are a repeating gradient on the middle element rather than a row of
 * full stops, so they stay evenly spaced at any width and a screen reader has
 * nothing to read out. The amount never wraps under the name.
 */
.executive-price-row {
	display: flex;
	align-items: baseline;
	gap: .75rem;
	inline-size: 100%;
}

.executive-price-row > :first-child {
	flex: none;
	margin: 0;
}

.executive-price-row > :last-child {
	flex: none;
	margin: 0;
	font-variant-numeric: tabular-nums;
}

.executive-price-row::before {
	content: "";
	flex: 1 1 auto;
	align-self: end;
	block-size: 1px;
	margin-block-end: .35em;
	background-image: radial-gradient(circle, currentColor 1px, transparent 1px);
	background-size: 6px 1px;
	background-repeat: repeat-x;
	opacity: .35;
}

/*
 * A timeline.
 *
 * One rule down the middle on a wide screen, entries alternating either side of
 * it; one rule down the left on a narrow screen with everything beside it. The
 * markup is the same in both cases — a plain list of groups — because a
 * timeline that needs different blocks per breakpoint is a timeline nobody will
 * maintain.
 */
.executive-timeline {
	position: relative;
}

.executive-timeline::before {
	content: "";
	position: absolute;
	inset-block: 0;
	inset-inline-start: 0;
	inline-size: 1px;
	background-color: var(--wp--preset--color--border);
}

.executive-timeline > * {
	position: relative;
	padding-inline-start: var(--wp--preset--spacing--medium);
}

.executive-timeline > *::before {
	content: "";
	position: absolute;
	inset-inline-start: -4px;
	inset-block-start: .6em;
	inline-size: 9px;
	block-size: 9px;
	background-color: var(--executive-accent);
}

@media (min-width: 782px) {

	.executive-timeline::before {
		inset-inline-start: 50%;
	}

	.executive-timeline > * {
		inline-size: 50%;
		padding-inline-start: var(--wp--preset--spacing--large);
		margin-inline-start: 50%;
	}

	.executive-timeline > *::before {
		inset-inline-start: calc( var(--wp--preset--spacing--large) / -2 - 4px );
	}

	/* Every other entry moves to the left of the rule and reads right to left. */
	.executive-timeline > *:nth-child(odd) {
		margin-inline-start: 0;
		padding-inline-start: 0;
		padding-inline-end: var(--wp--preset--spacing--large);
		text-align: end;
	}

	.executive-timeline > *:nth-child(odd)::before {
		inset-inline-start: auto;
		inset-inline-end: calc( var(--wp--preset--spacing--large) / -2 - 4px );
	}
}

/*
 * The tile on the seam.
 *
 * A small filled square holding one line icon, sitting deliberately across the
 * join between two blocks — half on the image above, half on the panel below.
 * It is the one element that appears on nearly every section, and the reason it
 * works is that it refuses to respect the boundary: two flat planes meeting in a
 * straight line read as a layout, the same two with something laid across the
 * join read as a composition.
 *
 * Sized in one custom property so the pull is always exactly half, whatever the
 * tile measures. Set --executive-seam-size on the block to change it.
 */
.executive-seam-tile {
	--executive-seam-size: clamp(3.25rem, 6vw, 5.5rem);
	block-size: var(--executive-seam-size);
	inline-size: var(--executive-seam-size);
	display: grid;
	place-items: center;
	position: relative;
	z-index: 2;
	flex: none;
	margin-block-start: calc(var(--executive-seam-size) / -2);
	margin-block-end: 0;
	color: var(--executive-text);
}

/*
 * Een streep naast een link in plaats van een knop.
 *
 * De referentie zet onder bijna elke sectie zo'n stille verwijzing: een label in
 * kapitalen met een dunne lijn ernaast. Het is de tegenhanger van de knop — een
 * sectie die verder wil wijzen zonder om aandacht te vragen. Een <hr> tussen
 * flexitems zou zich uitrekken tot de volle breedte, dus de lengte staat vast.
 */
.executive-rule-inline {
	inline-size: clamp(2.5rem, 6vw, 5rem);
	block-size: 1px;
	border: 0;
	flex: none;
	align-self: center;
	opacity: .9;
}

/*
 * The icon lives here instead of in the pattern markup.
 *
 * An SVG in a Custom HTML block seemed like the shortest route, but it renders
 * in an isolated editor frame with a white background. The tile therefore broke
 * in the editor even though it looked right on the front end, and the icon would
 * be repeated in every instance of the markup.
 *
 * As a mask it is one CSS rule, inherits the surrounding colour through
 * currentColor, and lets a section choose another icon by overriding only
 * --executive-seam-icon.
 */
.executive-seam-tile::before {
	content: "";
	inline-size: 42%;
	aspect-ratio: 1;
	background-color: currentColor;
	-webkit-mask-image: var(--executive-seam-icon);
	mask-image: var(--executive-seam-icon);
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-size: contain;
	mask-size: contain;
}

:root {
	--executive-seam-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 5h14l2 10H3L5 5z'/%3E%3Cpath d='M8 5 7 15M12 5v10M16 5l1 10'/%3E%3Cpath d='M4 10h16'/%3E%3Cpath d='M12 15v4M9 19h6'/%3E%3C/svg%3E");
}

/*
 * Pulls a block up over whatever sits above it. The distance is a spacing preset
 * rather than a number, so it moves with the rest of the vertical rhythm instead
 * of drifting away from it the first time that scale changes.
 */
.executive-overlap {
	position: relative;
	z-index: 1;
	margin-block-start: calc(-1 * var(--wp--preset--spacing--x-large));
}

@media (max-width: 781px) {

	/*
	 * On one column an overlap has nothing to overlap: the two blocks are simply
	 * stacked, and pulling the lower one up would slide it over the image rather
	 * than across its edge.
	 */
	.executive-overlap {
		margin-block-start: 0;
	}

	.executive-seam-tile {
		margin-block-start: calc(var(--executive-seam-size) / -2);
	}
}

/*
 * A hero carrying a looping video.
 *
 * The loop runs a few seconds and never stops, which is exactly the kind of
 * motion someone with reduced motion asked not to see — and a background video
 * has no pause control to offer them. So it is removed rather than paused, and
 * the video's own poster frame takes over as a still. The path is relative to
 * this stylesheet, which sits in the theme root.
 */
@media (prefers-reduced-motion: reduce) {

	.executive-video-hero .wp-block-cover__video-background {
		display: none;
	}

	.executive-video-hero {
	background-image: url("assets/images/office-view.webp");
		background-position: center;
		background-size: cover;
	}
}

/*
 * A card that rises under the pointer.
 *
 * Same curve and the same shadow behaviour as the buttons, so a page does not
 * end up with two different ideas about what a hover feels like. Hover only —
 * there is nothing behind it to reach, so a touch screen loses nothing.
 */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {

	.executive-lift-hover {
		transition:
			transform .4s cubic-bezier(.22, .61, .36, 1),
			box-shadow .4s cubic-bezier(.22, .61, .36, 1);
	}

	.executive-lift-hover:hover {
		transform: translateY(-4px);
		box-shadow: var(--executive-card-shadow);
	}
}

/*
 * Media that drifts against the scroll.
 *
 * The image sits taller than its frame and moves a fraction of the distance the
 * page does, so it reads as depth rather than as a moving picture. Kept small
 * on purpose: past a few percent it stops looking like parallax and starts
 * looking like a bug.
 *
 * This replaces the Cover block's Fixed background option on anything you want
 * to feel layered — background-attachment jumps on iOS and cannot be eased.
 */
@keyframes executive-drift {
	from {
		transform: translate3d(0, -4%, 0) scale(1.12);
	}

	to {
		transform: translate3d(0, 4%, 0) scale(1.12);
	}
}

/*
 * The frame has to clip, otherwise the oversized photo spills past it. A cover
 * clips on its own; a plain image block does not, and that is the one worth
 * reaching for on a band of nothing but photo — it keeps lazy loading and
 * responsive sources, which a cover with a fixed background gives up.
 */
.executive-parallax-media {
	overflow: hidden;
}

@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		.executive-parallax-media :is(img, .wp-block-cover__image-background) {
			animation: executive-drift linear both;
			animation-timeline: view();
			animation-range: cover 0% cover 100%;
			will-change: transform;
		}
	}
}

/*
 * A photo the page scrolls over.
 *
 * The usual way to do this is a cover with a fixed background, and it costs more
 * than it looks: the photo becomes a CSS background, which gives up srcset and
 * lazy loading, and the block needs an empty text container that the editor then
 * fills with a placeholder nobody asked for.
 *
 * This keeps the image an image. clip-path on the frame makes it the containing
 * block for anything fixed inside it — that is what the spec says a clip-path
 * does — so the photo can be pinned to the viewport while the frame it shows
 * through scrolls away over it. No script, no background-attachment, and the
 * markup stays a plain image block with everything the browser normally does
 * for one.
 *
 * Behind both guards. Without clip-path support, and for anyone who asked for
 * less movement, the photo simply sits in the page as a photo.
 */
@supports (clip-path: inset(0)) {
	@media (prefers-reduced-motion: no-preference) {

		.executive-image-reveal {
			clip-path: inset(0);
			min-height: clamp(280px, 45vh, 520px);
		}

		.executive-image-reveal img {
			position: fixed;
			top: 0;
			left: 0;
			width: 100vw;
			height: 100vh;
			max-width: none;
			object-fit: cover;
		}
	}
}

/*
 * Media zoom on hover. The container needs its own overflow clip, otherwise the
 * scaled image spills past the rounded corners of a cover.
 */
.executive-zoom-media {
	overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
	.executive-zoom-media :is(img, .wp-block-cover__image-background) {
		transition: transform .5s cubic-bezier(.22, .61, .36, 1);
	}

	.executive-zoom-media:hover :is(img, .wp-block-cover__image-background),
	.executive-zoom-media:focus-within :is(img, .wp-block-cover__image-background) {
		transform: scale(1.04);
	}
}

/*
 * Caption that slides in over a tile.
 *
 * Content behind a hover is content some people never reach, so this reveals on
 * :focus-within for keyboard users and stays permanently visible on touch and
 * under reduced motion. The hover effect is decoration on top of markup that
 * already works without it.
 */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
	.executive-caption-slide .wp-block-cover__inner-container {
		opacity: 0;
		transform: translateX(-12%);
		transition: transform .5s cubic-bezier(.22, .61, .36, 1), opacity .35s ease;
	}

	.executive-caption-slide:hover .wp-block-cover__inner-container,
	.executive-caption-slide:focus-within .wp-block-cover__inner-container {
		opacity: 1;
		transform: none;
	}
}

/*
 * Fixed backgrounds use the Cover block's own "Fixed background" toggle, so no
 * class is needed to switch one on. These two rules only undo it where it does
 * not belong: iOS ignores background-attachment: fixed and renders a badly
 * cropped image, and a fixed background is exactly the kind of movement
 * reduced-motion asks us to drop.
 */
@media (max-width: 781px) {
	.wp-block-cover.has-parallax {
		background-attachment: scroll;
	}
}

@media (prefers-reduced-motion: reduce) {
	.wp-block-cover.has-parallax {
		background-attachment: scroll;
	}
}

/*
 * Buttons.
 *
 * A small lift with a shadow that grows underneath it. The shadow travels
 * further than the button does, which is what makes it read as rising off the
 * page rather than nudging sideways. Active pulls it straight back down so a
 * click feels connected to the press.
 */
@media (prefers-reduced-motion: no-preference) {
	.wp-element-button,
	.wp-block-button__link {
		transition:
			transform .35s cubic-bezier(.22, .61, .36, 1),
			box-shadow .35s cubic-bezier(.22, .61, .36, 1),
			background-color .25s ease;
	}

	.wp-element-button:hover,
	.wp-block-button__link:hover {
		transform: translateY(-2px);
		box-shadow: var(--executive-card-shadow);
	}

	.wp-element-button:active,
	.wp-block-button__link:active {
		transform: translateY(0);
		box-shadow: var(--executive-control-shadow);
	}
}

/*
 * Text links draw their underline from the start of the line instead of
 * switching it on. background-size animates on the compositor; text-decoration
 * does not, which is why this is drawn as a gradient rather than declared.
 */
@media (prefers-reduced-motion: no-preference) {
	.entry-content a:not(.wp-element-button):not([class*="wp-block-"]) {
		background-image: linear-gradient(currentColor, currentColor);
		background-position: 0 100%;
		background-repeat: no-repeat;
		background-size: 0 1px;
		text-decoration: none;
		transition: background-size .4s cubic-bezier(.22, .61, .36, 1);
	}

	.entry-content a:not(.wp-element-button):not([class*="wp-block-"]):hover {
		background-size: 100% 1px;
	}
}

/*
 * Sticky column, for a section index beside a long list of questions.
 * align-self is required because a column is a stretched flex item by default,
 * and a full-height item has no room left to stick within.
 */
@media (min-width: 782px) {
	.executive-sticky-aside {
		position: sticky;
		top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--executive-sticky-offset, 96px));
		align-self: start;
	}
}

/*
 * The header's "Request an Assessment" button, next to the wordmark and the
 * navigation's own hamburger toggle, has nowhere to go below the width the
 * core Navigation block itself collapses at (782px, wp-includes/blocks/
 * navigation/style.css): logo + toggle + a full-width button no longer fit
 * on one non-wrapping row. Below that point the assessment CTA is still one
 * tap away in the mobile menu and repeated throughout every page, so hiding
 * it here removes the overflow without removing the action.
 */
@media (max-width: 781px) {
	.solara-header-action {
		display: none;
	}
}

/*
 * Transparent header that fades to solid on scroll, for use together with
 * .executive-header-transparent.
 *
 * One registered custom property carries the whole effect: the veil runs 0 → 1
 * as the page scrolls, and background, border and text colour are all derived
 * from it with color-mix. Registering it with @property is what makes it
 * interpolate at all — an unregistered custom property jumps from 0 to 1.
 */
@property --executive-header-veil {
	syntax: "<number>";
	initial-value: 0;
	inherits: true;
}

@keyframes executive-header-veil-in {
	from {
		--executive-header-veil: 0;
	}

	to {
		--executive-header-veil: 1;
	}
}

.executive-header-solidify {
	background-color: color-mix(in srgb, var(--wp--preset--color--canvas) calc(var(--executive-header-veil) * 100%), transparent);
	border-bottom: 1px solid color-mix(in srgb, var(--wp--preset--color--border) calc(var(--executive-header-veil) * 100%), transparent);
}

/*
 * button belongs in this list. A navigation item that has a submenu is not a
 * link but a <button>, so leaving it out meant it never picked up the veil and
 * fell back to the inherited body colour — dark text on a dark hero, while
 * every item beside it was light.
 */
.executive-header-solidify :is(a, p, h1, h2, button, .wp-block-site-title):not(.wp-element-button) {
	color: color-mix(in srgb, var(--wp--preset--color--ink) calc(var(--executive-header-veil) * 100%), var(--wp--preset--color--ink-inverse));
}

/*
 * Desktop dropdowns opt out, for the same reason the mobile overlay does.
 *
 * A submenu is a child of the header, so the veil paints its links light while
 * the dropdown carries a light background of its own: white on white, and only
 * while the page is at the top, which is exactly when someone opens it.
 *
 * The dropdown never sits over the hero, so it states both its background and
 * its text colour instead of inheriting either. Scoped to the closed state, so
 * the mobile overlay keeps its own rule.
 */
.executive-header-solidify .wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__submenu-container {
	background-color: var(--wp--preset--color--canvas);
}

.executive-header-solidify .wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__submenu-container :is(a, button) {
	color: var(--wp--preset--color--ink);
}

/*
 * The mobile overlay is a child of the header, so the veil would tint its links
 * as well — light text on a light panel while the page is still at the top. The
 * overlay brings its own opaque background and is never transparent, so it opts
 * out of the veil completely.
 */
.executive-header-solidify .wp-block-navigation__responsive-container.is-menu-open :is(a, p, h1, h2, .wp-block-site-title) {
	color: var(--wp--preset--color--ink);
}

@supports (animation-timeline: scroll()) {
	@media (prefers-reduced-motion: no-preference) {
		.executive-header-solidify {
			animation: executive-header-veil-in linear both;
			animation-timeline: scroll();
			animation-range: 0 180px;
		}
	}
}

/*
 * Without scroll-driven animations the veil never moves, so the header would
 * stay transparent over whatever scrolls under it. Pin it to solid instead:
 * a header that is always readable beats one that is sometimes invisible.
 */
@supports not (animation-timeline: scroll()) {
	.executive-header-solidify {
		--executive-header-veil: 1;
	}
}

@media (prefers-reduced-motion: reduce) {
	.executive-header-solidify {
		--executive-header-veil: 1;
	}
}

/*
 * Headings on a dark band follow the band.
 *
 * A heading colour set in Global Styles lands on the h1–h6 elements themselves,
 * which beats the text colour a section sets on its wrapper — inheritance loses
 * to any declaration. So a brand coloured heading stayed brand inside a brand
 * band and disappeared into it, and sat at three to one on a dark section.
 *
 * The classes below are the ones that only ever appear as light text, so they
 * only appear on something dark: a heading inside them belongs to the section,
 * not to the site wide heading colour. A heading that carries its own colour is
 * left alone, and on a site that never recolours headings this changes nothing,
 * because inheriting is what already happened.
 */
:where(.has-canvas-color, .has-ink-inverse-color, .is-style-section-dark) :is(h1, h2, h3, h4, h5, h6):not(.has-text-color) {
	color: inherit;
}

/*
 * Buttons keep their own contrast when given a pale background.
 *
 * Button text is white by default, from the button element in theme.json, and
 * that colour stays put when only the background is changed in the editor. Pick
 * a pale background for a button and the label goes white on near white. These
 * are the pale entries in the palette; a button that was given a text colour of
 * its own is left alone.
 */
.wp-element-button.has-canvas-background-color:not(.has-text-color),
.wp-element-button.has-surface-background-color:not(.has-text-color),
.wp-element-button.has-brand-light-background-color:not(.has-text-color),
.wp-element-button.has-brand-alt-background-color:not(.has-text-color),
.wp-element-button.has-ink-inverse-background-color:not(.has-text-color) {
	color: var(--wp--preset--color--ink);
}

/* Keep editorial headings intact at narrow widths. */
:where(.wp-site-blocks, .editor-styles-wrapper) :is(h1, h2, h3, h4, h5, h6, .wp-block-heading) {
	hyphens: none;
	overflow-wrap: normal;
	word-break: normal;
}

/* Directional design primitives
---------------------------------------------------------------------------- */

/*
 * Direction files set these custom properties. Patterns only opt into a role
 * such as card, header frame or hero; they do not need to know whether the
 * active design is square, soft, elevated or nearly flat.
 */
:root {
	--executive-radius-control: var(--wp--custom--design--radius--control, var(--wp--preset--border-radius--md));
	--executive-radius-surface: var(--wp--custom--design--radius--surface, var(--wp--preset--border-radius--lg));
	--executive-radius-media: var(--wp--custom--design--radius--media, var(--wp--preset--border-radius--md));
	--executive-card-background: var(--wp--preset--color--surface);
	--executive-card-border: 1px solid var(--wp--preset--color--border);
	--executive-card-shadow: none;
	--executive-control-shadow: 0 1px 2px rgb(0 0 0 / 2%), 0 .5px 1px rgb(0 0 0 / 4%);
	--executive-hairline-shadow: 0 0 0 1px color-mix(in srgb, var(--wp--preset--color--text) 11%, transparent);
	--executive-header-shadow: none;
	--executive-hero-overlay: linear-gradient(90deg, color-mix(in srgb, var(--wp--preset--color--surface-strong) 84%, transparent), color-mix(in srgb, var(--wp--preset--color--surface-strong) 18%, transparent));
	--executive-motion-scale: 1;
	--executive-motion-lenis-duration: var(--wp--custom--design--motion--lenis-duration, 1.05);
	
}
:is(.wp-block-group, .wp-block-cover).is-style-section-dark,
:is(.wp-block-group, .wp-block-cover).is-style-section-light,
:is(.wp-block-group, .wp-block-cover).is-style-section-accent {
	border-radius: var(--executive-radius-surface);
	overflow: clip;
}

/* Semantic contexts are available to new patterns without removing legacy
 * palette slugs used by existing content. */
:is(
	.has-background-background-color,
	.has-surface-background-color,
	.has-surface-alt-background-color
):not(.has-text-color) {
	color: var(--wp--preset--color--text);
}

:is(
	.has-surface-strong-background-color,
	.has-primary-background-color,
	.has-secondary-background-color
):not(.has-text-color) {
	color: var(--wp--preset--color--text-on-strong);
}

:is(.has-surface-strong-background-color, .has-primary-background-color, .has-secondary-background-color) a:not(.wp-element-button):not(.has-text-color) {
	color: var(--wp--preset--color--text-on-strong);
}

.wp-element-button,
.wp-block-button__link {
	border-radius: var(--executive-radius-control);
}

.executive-card,
.is-style-card-surface,
.executive-card-grid > .wp-block-group,
.executive-card-grid > .wp-block-column > .wp-block-group,
.executive-card-grid > .wp-block-cover {
	background-color: var(--executive-card-background) !important;
	border: var(--executive-card-border) !important;
	border-radius: var(--executive-radius-surface) !important;
	box-shadow: var(--executive-card-shadow) !important;
}

.executive-card :is(img, .wp-block-cover),
.is-style-card-surface :is(img, .wp-block-cover),
.executive-card-grid :is(img, .wp-block-cover) {
	border-radius: var(--executive-radius-media);
}

.executive-card-grid > .wp-block-cover {
	box-shadow: var(--executive-card-shadow);
}

.executive-header-frame {
	border-radius: var(--executive-radius-control) !important;
	box-shadow: var(--executive-header-shadow) !important;
}

.executive-hero .wp-block-cover__background,
.executive-media-overlay .wp-block-cover__background {
	background: var(--executive-hero-overlay) !important;
}

.executive-hero :is(.wp-block-cover__image-background, .wp-block-cover__video-background) {
	border-radius: var(--executive-radius-media);
}

/*
 * Mobile hero: a scrim instead of a solid panel.
 *
 * On a wide screen the copy panel is a compact card wedged into one corner of
 * the photo (see hero-split.php / solara_hero()) — the rest of the image
 * carries the shot. Below 782px core-cover.css collapses the cover's height to
 * a fixed 430px, and the panel's own max-width has nothing left to constrain
 * it against, so it stretches close to full width instead of staying a corner
 * card. The same copy then needs more lines at that width, which pushes the
 * panel past the 430px floor — so the one thing meant to be a small accent
 * grows exactly when the screen shrinks, and ends up hiding most of the photo
 * behind a solid block instead of sitting in a corner of it.
 *
 * Making the panel translucent is the fix, not shrinking its content further:
 * the photo stays visible through it at any width, and legibility comes from
 * the tint rather than from opacity. --executive-hero-overlay already darkens
 * this cover left-to-right (see above); a uniform scrim on top of that is
 * enough contrast for white text without needing the panel to stay small.
 */
@media (max-width: 781px) {
	.executive-hero .wp-block-cover__inner-container .has-canvas-inverse-background-color {
		background-color: color-mix(in srgb, var(--wp--preset--color--canvas-inverse) 68%, transparent) !important;
	}
}

@media (prefers-reduced-motion: no-preference) {
	.wp-element-button,
	.wp-block-button__link,
	.executive-lift-hover,
	.executive-zoom-media :is(img, .wp-block-cover__image-background) {
	transition-duration: calc(.35s * var(--executive-motion-scale));
	}
}
