/*
Theme Name:   Edgewater Blocks
Theme URI:    https://edgewateraccounting.com
Description:  Edgewater Tax & Accounting block theme. A full-site-editing theme carrying the Edgewater brand system — Twilight Ridge palette, the stone gray ramp, semantic accents, and the four-tier type system (Fraunces Composed / DM Sans / Sentient / Commit Mono) — as native theme.json tokens, so the brand appears throughout the WordPress editor. Includes Team and Services post types and a set of on-brand section patterns.
Author:       Bigelow Systems, LLC
Version:      1.2.24
License:      GNU General Public License v2 or later
License URI:  https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:  edgewater-blocks
Tags:         full-site-editing, block-patterns, accounting, business, block-styles, custom-colors, custom-logo, custom-menu, editor-style, featured-images, threaded-comments, translation-ready, wide-blocks
Requires at least: 6.5
Tested up to: 6.8
Requires PHP: 7.4
*/

/* ==========================================================================
   Nearly all styling lives in theme.json. This file carries only what
   theme.json cannot express: the display tier's numeral features, the hero
   silhouette treatment, hover states, and a handful of utility classes the
   patterns use.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Display tier
   The Fraunces web font is pre-instanced to the Composed tuning
   (opsz 144, SOFT 0, WONK 0), so no font-variation-settings are needed here —
   only the numeral features, which have to be set explicitly so stacked
   figures align across renderers.
   -------------------------------------------------------------------------- */

h1,
.has-display-font-family,
.has-display-font-size,
.has-display-xl-font-size,
.ew-stat__figure {
	font-feature-settings: "lnum" 1, "tnum" 1;
	text-wrap: balance;
}

/* The accent word in a display headline. */
h1 em,
.has-display-font-family em {
	font-style: italic;
	color: var(--wp--preset--color--purple-light);
}

/* The h2/h3/h4 size-preset slugs contain a digit after a letter, which
   WordPress kebab-cases when generating the CSS custom property (h2 →
   --wp--preset--font-size--h-2) while the markup class keeps the raw slug
   (has-h2-font-size). The generated class rule therefore misses, and the
   heading collapses to body size. Bridge the two spellings here, with a
   fallback chain so either variable name resolves. */
.has-h2-font-size {
	font-size: var(--wp--preset--font-size--h-2, var(--wp--preset--font-size--h2, 1.875rem)) !important;
}

.has-h3-font-size {
	font-size: var(--wp--preset--font-size--h-3, var(--wp--preset--font-size--h3, 1.5rem)) !important;
}

.has-h4-font-size {
	font-size: var(--wp--preset--font-size--h-4, var(--wp--preset--font-size--h4, 1.25rem)) !important;
}

/* The display face never renders below its 28px floor. */
h1 {
	font-size: max(1.75rem, var(--wp--preset--font-size--display));
}

.ew-hero h1 {
	font-size: max(1.75rem, var(--wp--preset--font-size--display-xl));
}

/* Large stat figures — the display tier's only non-heading use. */
.ew-stat__figure {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 780;
	font-size: clamp(2.5rem, 1.8rem + 2.6vw, 3.75rem);
	line-height: 1;
	letter-spacing: -0.03em;
	margin: 0;
}

.ew-stat__label {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--tracking--label);
	margin: 0;
}

/* Money and data tables align on the decimal. */
.wp-block-table td,
.wp-block-table th,
.ew-money {
	font-feature-settings: "lnum" 1, "tnum" 1;
}

.wp-block-table td.ew-money,
.wp-block-table th.ew-money {
	text-align: right;
}

.wp-block-table tbody tr:nth-child(even) {
	background-color: var(--wp--preset--color--gray-1);
}

/* Table chrome follows edgewater-ui's Table: a small uppercase Granite header
   row, Quartz zebra rows, and an Alpenglow-tint wash on the hovered row. */
.wp-block-table th {
	font-size: 0.75rem;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--wp--preset--color--gray-5);
}

.wp-block-table tbody tr:hover {
	background-color: var(--ew-purple-tint);
}

/* --------------------------------------------------------------------------
   Editorial utilities used by the patterns
   -------------------------------------------------------------------------- */

.ew-eyebrow {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--tracking--label);
	margin-bottom: var(--wp--preset--spacing--20);
}

.ew-lede {
	max-width: 42rem;
	line-height: 1.6;
}

/* The post disclaimer joins the content flow (functions.php appends it to
   the_content), where it would otherwise take the same block gap as the
   paragraph above it and read as one more closing line. Air sets it apart, not
   a rule: Jetpack draws its own hairline above "Share this:" directly below,
   and two rules that close together read as clutter. */
.ew-post-disclaimer {
	margin-top: var(--wp--preset--spacing--50);
}

/* Constrained layouts auto-center any child narrower than the content width,
   which floats the capped lede into the middle while its heading stays on the
   column's left edge. Anchor the lede to the same left axis as the heading
   above it. (The extra child selector outweighs the layout rule's
   .wp-container specificity.) */
.wp-block-group > .ew-lede,
.wp-block-post-content > .ew-lede {
	margin-left: 0 !important;
	margin-right: auto !important;
}

/* Checklist of inclusions (pricing tiers, engagement scopes). Green because
   the accents are semantic: green marks gains and approvals. The dark-mode
   token remap keeps the mark legible on Night Ridge. */
.ew-checklist {
	list-style: none;
	padding-left: 0;
	margin: 0;
}

.ew-checklist li {
	position: relative;
	padding-left: 1.5rem;
}

.ew-checklist li + li {
	margin-top: 0.5rem;
}

.ew-checklist li::before {
	content: "\2713";
	position: absolute;
	left: 0;
	color: var(--wp--preset--color--green);
	font-weight: 700;
}

/* Short accent rule under a section heading. */
.wp-block-separator.ew-rule {
	width: 3.5rem;
	height: 3px;
	border: 0;
	opacity: 1;
	border-radius: 999px;
	background-color: var(--wp--preset--color--purple-light);
	margin: var(--wp--preset--spacing--40) 0;
	margin-left: 0;
}

/* --------------------------------------------------------------------------
   Hero — mountain silhouette
   The silhouette is a brand element, not a photograph: it sits at the base of
   a Twilight Ridge field, low-opacity, and never carries text on top of it.
   -------------------------------------------------------------------------- */

.ew-hero {
	position: relative;
	overflow: hidden;
	isolation: isolate;

	/* Room for the silhouette below the copy. The 8rem field padding itself is
	   the `80` preset, which is fluid in theme.json — so the top padding the
	   pattern writes inline needs no override here, and this one only has to
	   restate the same preset plus the allowance. !important because the value
	   it is replacing is an inline style. */
	/*padding-bottom: calc(var(--wp--preset--spacing--80) + 5rem) !important;*/
}

/*.ew-hero::after {*/
/*	content: "";*/
/*	position: absolute;*/
/*	inset-inline: 0;*/
/*	bottom: -1px;*/
/*	height: clamp(90px, 13vw, 170px);*/
/*	background: url("assets/images/mountain-silhouette-light.png") bottom center / 120% auto no-repeat;*/
/*	opacity: 0.16;*/
/*	z-index: -1;*/
/*	pointer-events: none;*/
/*}*/

.ew-hero__inner {
	position: relative;
	z-index: 1;
}

/* --------------------------------------------------------------------------
   Structural flow

   WordPress global styles apply a block-gap top margin to every top-level child
   of .wp-site-blocks:

       :where(.wp-site-blocks) > * { margin-block-start: var(--wp--style--block-gap); }

   That rule is right for content blocks stacked in a column and wrong for the
   page's structural regions: it opens a 1.5rem gap between the header and the
   first section, and again above the footer. Header, main, and footer butt up
   against each other by design — their own padding does the spacing.
   -------------------------------------------------------------------------- */

.wp-site-blocks > * + * {
	margin-block-start: 0;
}

/* Same reasoning one level in: the first section inside main sits flush against
   the header, whatever that section happens to be. */
.wp-site-blocks > main > :first-child,
.wp-site-blocks > header > :first-child,
.wp-site-blocks > footer > :first-child {
	margin-block-start: 0;
}

/* A full-bleed section must reach the viewport edge even when the theme opts
   into root padding-aware alignments, so a dark field never shows a light
   sliver down its side. */
.wp-site-blocks > main > .alignfull {
	margin-left: calc(-1 * var(--wp--style--root--padding-left, 0px));
	margin-right: calc(-1 * var(--wp--style--root--padding-right, 0px));
}

/* Comments carry their own vertical rhythm instead of taking it from the group
   around them. `wp:comments` renders nothing at all on a post with comments
   closed — not an empty wrapper, no element — so 4rem of padding on that group
   painted 128px of blank page above the footer on every post that has them
   off. A margin on the block only exists when the block does. */
.wp-block-comments {
	margin-block: var(--wp--preset--spacing--60);
}

/* --------------------------------------------------------------------------
   Lists
   -------------------------------------------------------------------------- */

/* An X-Small gap between list items in a blog post. Core leaves them at the
   browser default, which packs items tight enough that a multi-line one reads
   as a paragraph rather than an entry. `li + li` rather than a bottom margin
   on every item, so a list does not push extra space at whatever follows it;
   descendant rather than child, so a nested list keeps the same rhythm.

   Scoped to post content deliberately. Site-wide it also stretched the privacy
   policy — an 11-item list there grew by ~350px — and a long legal document
   wants the rhythm the .ew-legal section below sets, not this one.
   `.single-post` narrows it to the blog, `.entry-content` keeps it inside the
   article and off anything a popup renders on the same page.

   .ew-checklist is excluded by name rather than left to source order: it sets
   its own tighter 0.5rem in the editorial utilities above at equal
   specificity, so this rule, being later in the file, would otherwise win. */
.single-post .entry-content .wp-block-list:not(.ew-checklist) li + li {
	margin-top: var(--wp--preset--spacing--30);
}

/* --------------------------------------------------------------------------
   Legal documents

   The privacy-policy pattern's root group carries className ew-legal and a
   75rem content size (the theme's wide width). Long-document rhythm: space
   above each section heading, a settled gap beneath it, and paragraph
   spacing beyond the default block gap. Child selectors keep the rules off
   the TOC card's own heading and list.
   -------------------------------------------------------------------------- */

.ew-legal > h2 {
	margin-block-start: var(--wp--preset--spacing--60);
	margin-block-end: var(--wp--preset--spacing--30);
}

.ew-legal > h3 {
	margin-block-start: var(--wp--preset--spacing--50);
	margin-block-end: var(--wp--preset--spacing--20);
}

.ew-legal > p,
.ew-legal > ul,
.ew-legal > ol,
.ew-legal > .wp-block-table {
	margin-block-start: var(--wp--preset--spacing--30);
}

/* --------------------------------------------------------------------------
   Buttons
   The button element is edgewater-ui's Button: the `md` box (44px tall —
   0.9375rem / 1.25rem padding around a 14px DM Sans label), 8px radius,
   Alpenglow fill for the primary rank. theme.json carries all of that; this
   section covers the secondary rank, which core's outline style owns.

   Core ships `border: 2px solid; padding: .667em 1.333em` on
   `.wp-block-button.is-style-outline > .wp-block-button__link`, a selector
   theme.json's element rule cannot outweigh, so an outline button beside a
   filled one rendered ~6px shorter and ~12px tighter. Restate the element
   padding minus the (now 1px, per edgewater-ui's secondary Button) border so
   both ranks occupy the same box. Keep these numbers in step with
   theme.json's elements.button.spacing.padding.
   -------------------------------------------------------------------------- */

.wp-block-button.is-style-outline > .wp-block-button__link,
.wp-block-button .wp-block-button__link.is-style-outline {
	border-width: 1px;
	padding: calc(0.9375rem - 1px) calc(1.25rem - 1px);
}

/* Secondary rank: Alpenglow text and border on a white surface, Alpenglow
   tint on hover (edgewater-ui Button `secondary`). Core leaves the outline
   button in currentColor/transparent, which is right on a brand fill and
   wrong on the canvas, so the colours are routed through custom tokens that
   the dark sections re-point below. Same specificity as core's rules; the
   theme sheet loads later and wins. */
.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-text-color),
.wp-block-button .wp-block-button__link.is-style-outline:not(.has-text-color) {
	color: var(--wp--custom--button--outline-text);
}

.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-background),
.wp-block-button .wp-block-button__link.is-style-outline:not(.has-background) {
	background-color: var(--wp--custom--button--outline-bg);
}

.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-background):hover,
.wp-block-button .wp-block-button__link.is-style-outline:not(.has-background):hover {
	background-color: var(--wp--custom--button--outline-hover);
}

/* On a brand fill the secondary rank outlines in Snowfield over a faint white
   wash (edgewater-ui NavBar `brand` tone). A media overlay is a brand fill for
   this purpose too — see the Media overlays section below. */
.ew-hero,
.ew-site-footer,
.wp-block-group.is-style-ew-dark,
.wp-block-cover.is-style-ew-overlay,
.wp-block-cover.is-style-ew-overlay-panel,
.has-brand-background-color {
	--wp--custom--button--outline-text: var(--wp--preset--color--light);
	--wp--custom--button--outline-bg: transparent;
	--wp--custom--button--outline-hover: rgba(255, 255, 255, 0.1);
}

/* --------------------------------------------------------------------------
   Monospace tier
   Commit Mono, the fourth voice: machine-shaped strings only — account and
   reference numbers, inline code, config blocks. Never body, headings, or
   labels. Tabular figures are set explicitly so stacked digits align.
   -------------------------------------------------------------------------- */

code,
kbd,
samp {
	font-family: var(--wp--preset--font-family--mono);
	font-feature-settings: "tnum" 1;
}

:where(p, li, td, th, dd, h2, h3, h4) > code {
	font-size: 0.875em;
	padding: 0.1em 0.4em;
	background-color: var(--wp--preset--color--gray-1);
	border-radius: var(--wp--custom--radius--sm);
}

/* --------------------------------------------------------------------------
   Form controls
   edgewater-ui's input "well": a white recessed surface with a Morning Fog
   hairline, Scree on hover, and a Lupine ring on focus (a text field is a
   neutral control, so it takes the neutral ring). :where() keeps the
   specificity at one class so any form plugin's own styling still wins.
   -------------------------------------------------------------------------- */

.wp-block-search__input,
.wp-site-blocks :where(input:not([type="submit"], [type="button"], [type="reset"], [type="checkbox"], [type="radio"], [type="file"], [type="range"], [type="color"], [type="hidden"]), textarea, select) {
	font-family: var(--wp--preset--font-family--body);
	font-size: 0.875rem;
	line-height: 1.4;
	color: var(--wp--preset--color--dark);
	background-color: var(--wp--custom--surface--well);
	border: 1px solid var(--wp--preset--color--gray-2);
	border-radius: var(--wp--custom--radius--base);
	min-height: 2.75rem;
	padding: 0.5rem 0.875rem;
	transition: border-color var(--wp--custom--transition);
}

.wp-block-search__input:hover,
.wp-site-blocks :where(input, textarea, select):hover {
	border-color: var(--wp--preset--color--gray-3);
}

.wp-block-search__input:focus-visible,
.wp-site-blocks :where(input, textarea, select):focus-visible {
	border-color: var(--wp--preset--color--purple-light);
	outline: 2px solid var(--wp--preset--color--purple-light);
	outline-offset: 2px;
}

.wp-site-blocks :where(input, textarea)::placeholder {
	color: var(--wp--preset--color--gray-3);
	opacity: 1;
}

.wp-site-blocks :where(textarea) {
	min-height: 6rem;
	padding: 0.625rem 0.875rem;
}

/* The search block with the button inside draws the well on its wrapper, so
   the ring moves there too and the inner input goes bare. */
.wp-block-search__button-inside .wp-block-search__inside-wrapper {
	border-color: var(--wp--preset--color--gray-2);
	border-radius: var(--wp--custom--radius--base);
	background-color: var(--wp--custom--surface--well);
	padding: 0.25rem;
	transition: border-color var(--wp--custom--transition);
}

.wp-block-search__button-inside .wp-block-search__inside-wrapper:hover {
	border-color: var(--wp--preset--color--gray-3);
}

.wp-block-search__button-inside .wp-block-search__inside-wrapper:focus-within {
	border-color: var(--wp--preset--color--purple-light);
	outline: 2px solid var(--wp--preset--color--purple-light);
	outline-offset: 2px;
}

.wp-block-search__button-inside .wp-block-search__input {
	background-color: transparent;
	min-height: 2.25rem;
}

.wp-block-search__button-inside .wp-block-search__input:focus-visible {
	outline: none;
}

/* Jetpack Forms (the Contact page) — edgewater-ui's FormField: a DM Sans
   label in Obsidian, Garnet for the required marker and error text, and a
   Garnet border + ring on an invalid field. Jetpack's own label rules sit at
   one class of specificity (`.contact-form :is(:where(label))`) and the
   required marker at (0,2,2), so two and three classes respectively are
   enough. The asterisk itself comes from the jetpack_required_field_text
   filter in functions.php. */
.wp-block-jetpack-contact-form.is-layout-flex {
	gap: 1.25rem;
}

.wp-block-jetpack-contact-form .grunion-field-label {
	font-family: var(--wp--preset--font-family--heading);
	font-size: 0.875rem;
	font-weight: 500;
	line-height: 1.4;
	color: var(--wp--preset--color--dark);
	margin-bottom: 0.375rem;
}

.wp-block-jetpack-contact-form .grunion-field-label .grunion-label-required {
	color: var(--wp--preset--color--red);
	font-size: inherit;
	font-weight: 500;
	margin-left: 0.125rem;
}

.wp-block-jetpack-contact-form .contact-form__input-error {
	font-family: var(--wp--preset--font-family--heading);
	font-size: 0.8125rem;
	color: var(--wp--preset--color--red);
}

.wp-block-jetpack-contact-form :where(input, textarea, select)[aria-invalid="true"] {
	border-color: var(--wp--preset--color--red);
}

.wp-block-jetpack-contact-form :where(input, textarea, select)[aria-invalid="true"]:focus-visible {
	border-color: var(--wp--preset--color--red);
	outline-color: var(--wp--preset--color--red);
}

/* Jetpack pins the textarea at 200px; without that its rows attribute makes
   it twice the height the library's Textarea sits at. */
.wp-block-jetpack-contact-form :where(textarea) {
	height: 10rem;
	min-height: 6rem;
	resize: vertical;
}

/* Jetpack Sharing ("Share this:" under a post) — sharedaddy paints its
   buttons as white chips with a grey ring and #2c3338 text, all literals, so
   the row reads as two lit cards on Night Ridge. Restate them as the
   library's neutral chip: card surface, gray-2 hairline, the body text tone,
   and the Alpenglow tint + border on hover that the outline button already
   uses. Every colour is a token that the dark sections re-point, so one set
   of rules covers both modes.

   Specificity: Jetpack's winning rule is
   `.sd-social-icon-text .sd-content ul li a.sd-button` (0,5,1) and it sets
   `color` with !important, so the label colour needs !important back; the
   `div.sharedaddy` prefix carries the rest. The title rules take a `body`
   prefix because Jetpack prints those in an inline <style> that WordPress
   emits after the theme sheet, where equal specificity would lose. */
div.sharedaddy .sd-content ul li a.sd-button,
div.sharedaddy .sd-social-icon-text .sd-content ul li a.sd-button {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	font-family: var(--wp--preset--font-family--heading);
	font-size: 0.875rem;
	font-weight: 600;
	line-height: 1;
	color: var(--wp--preset--color--dark) !important;
	background: var(--wp--custom--surface--card);
	border: 1px solid var(--wp--preset--color--gray-2);
	border-radius: var(--wp--custom--radius--base);
	box-shadow: none;
	padding: 0.5rem 0.875rem;
	transition: background-color var(--wp--custom--transition), border-color var(--wp--custom--transition);
}

div.sharedaddy .sd-content ul li a.sd-button:hover,
div.sharedaddy .sd-content ul li a.sd-button:active,
div.sharedaddy .sd-social-icon-text .sd-content ul li a.sd-button:hover,
div.sharedaddy .sd-social-icon-text .sd-content ul li a.sd-button:active {
	background: var(--wp--custom--button--outline-hover);
	border-color: var(--wp--preset--color--purple);
	box-shadow: none;
}

div.sharedaddy .sd-content ul li a.sd-button:focus-visible {
	outline: 2px solid var(--wp--preset--color--purple);
	outline-offset: 2px;
}

/* The icon is a social-logos glyph on ::before and the label a sibling span;
   Jetpack aligns them with a 6px margin and a 2px nudge, which the flex row
   above replaces. */
div.sharedaddy .sd-content ul li a.sd-button > span,
div.sharedaddy .sd-social-icon-text .sd-content ul li a.sd-button > span {
	margin-left: 0;
	line-height: 1;
}

div.sharedaddy .sd-content ul li a.sd-button::before,
div.sharedaddy .sd-social-icon-text .sd-content ul li a.sd-button::before {
	position: static;
	top: auto;
	font-size: 16px;
	line-height: 1;
}

/* "Share this:" / "Like this:" — a 9pt bold run in Jetpack, an eyebrow here.
   The rule above it is Jetpack's h3::before hairline, hard-coded #dcdcde. */
body div.sharedaddy h3.sd-title {
	font-family: var(--wp--preset--font-family--heading);
	font-size: var(--wp--preset--font-size--caption);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--tracking--label);
	color: var(--wp--preset--color--gray-5);
}

body div.sharedaddy h3.sd-title::before {
	border-top-color: var(--wp--preset--color--gray-2);
}

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */

/* edgewater-ui's Card (`elevated`): a white surface inside a Morning Fog
   hairline with the subtle shadow at rest, sitting on the Snowfield canvas
   (1.21:1 — the lift the gallery shell gives it; on white the card had only
   its hairline and read flat). Cards that hold a link step one
   shadow up on hover — no lift, no border change, so the surface reads the
   same as it does in the component library. The hairline and 12px radius
   come from the block's own border attributes in the patterns. */
.ew-card {
	background-color: var(--wp--custom--surface--card);
	box-shadow: var(--wp--preset--shadow--sm);
	transition: box-shadow var(--wp--custom--transition);
}

.ew-card:hover {
	box-shadow: var(--wp--preset--shadow--md);
}

.ew-card .wp-block-post-featured-image img {
	border-radius: var(--wp--custom--radius--base);
}

.ew-card--team .wp-block-post-featured-image img {
	border-radius: var(--wp--custom--radius--lg);
}

/* A team member's name on the archive card is the same object as the name on
   edgewater/team-members' card, and takes the same treatment: a heading that
   happens to link, not a link in prose. The reasoning, and why dropping the
   underline is safe, is in that block's style.scss — hover restores it and the
   global :focus-visible outline below is untouched. */
.ew-card--team .wp-block-post-title a {
	color: inherit;
	text-decoration: none;
}

.ew-card--team .wp-block-post-title a:hover,
.ew-card--team .wp-block-post-title a:focus-visible {
	text-decoration: underline;
}

/* The job title on the team archive and on a member's own page is a paragraph
   bound to the ew_role post meta (core/post-meta), which still renders its
   element when the meta is empty. A member without a job title should close
   the gap rather than hold one open under their name. The credentials line on
   the member's page is the same construction bound to ew_credentials, and
   gets the same rule. */
.ew-team-role:empty,
.ew-team-credentials:empty {
	display: none;
}

/* "CPA, MAcc" under the name is a label, not a sentence: the heading face at
   600 with the label tracking, so it reads as the letters after a name rather
   than as the first line of the bio. */
.ew-team-credentials {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
}

/* On a card the role and the bio are both Granite at the small preset, which
   left the title reading as the bio's first line. The heading face at 600 is
   what separates them in edgewater-sections' team-members block; the archive
   card is the same object and gets the same treatment. Not applied to the
   single, where the role is a lede under the display heading and the body face
   is right — the same split edgewater-brand's own templates make. */
.ew-card--team .ew-team-role {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 600;
}

/* --------------------------------------------------------------------------
   The team member page's hero — headshot beside the facts
   -------------------------------------------------------------------------- */

/* The member page used to be a 30%/70% column pair, the profile card on the
   left and the bio on the right, which left the lower half of the bio column
   empty for anyone whose bio ran shorter than their card. The card is now one
   wide row at the top of the page — headshot, then the name and the Team
   Member Details beside it — with the bio full width beneath. The two sides
   are balanced by construction rather than by padding out the short one.

   Flex, not core/columns: a member with no headshot renders no
   post-featured-image at all, and a column layout would hold the empty 20rem
   open. Here the facts simply take the whole row.

   display/wrap/align/gap are the block's own flex layout in the template, not
   set here: the group declares layout type flex so core generates them, and a
   single blockGap value gives the row gap as well as the column one — the
   stacked case has to bring its own gap (Invariant 19), and taking both axes
   from one value is what guarantees it. The surface and shadow come from
   .ew-card; the hairline, radius and padding were the profile block's before
   the card moved out here, and stay in CSS so the empty-card rule below can
   take them back off. */
.ew-team-hero {
	border: 1px solid var(--wp--preset--color--gray-2);
	border-radius: var(--wp--custom--radius--lg);
	padding: var(--wp--preset--spacing--40);
}

/* .ew-card lifts a card one shadow step on hover, which is right for a card
   that is a link to somewhere else. This one is the top of the page the
   visitor is already on, and a full-width surface lifting as the cursor
   crosses it reads as a misfire rather than as affordance. */
.ew-team-hero:hover {
	box-shadow: var(--wp--preset--shadow--sm);
}

/* A third of the row, near enough: at the 75rem wide measure that is a ~26rem
   headshot against a ~44rem facts column, which is the proportion the card
   needs to read as a pair rather than as a thumbnail with a list beside it.
   It shrinks but never grows, so the facts take any slack.

   `align-self: stretch` is what closes the gap under the photo. The block
   carries no `aspectRatio` — a square crop left the headshot ending 160px
   above the button beside it, which read as a hole in the card — so the figure
   takes the flex line's height, which is the taller of the two columns, and
   the image covers it. When the facts are the shorter side the line is the
   image's own square, so this never stretches a photo taller than it needs to
   be. */
.ew-team-hero > .wp-block-post-featured-image {
	align-self: stretch;
	flex: 0 1 26rem;
	margin: 0;
	max-width: 100%;
}

/* Radius lg, not the base radius .ew-card gives every other card's image: the
   headshot here is the same object as the one on the team archive card, and
   .ew-card--team already makes that exception for the same reason. `cover`
   against a stretched box crops the sides of a square source, which is the
   right crop for a face; core's own `height: auto` is a :where() rule, so this
   overrides it without !important. */
.ew-team-hero > .wp-block-post-featured-image img {
	border-radius: var(--wp--custom--radius--lg);
	height: 100%;
	object-fit: cover;
	width: 100%;
}

/* The facts take the rest of the row, and all of it when there is no headshot.
   The basis is what decides when the row wraps to a stack. */
.ew-team-hero__facts {
	flex: 1 1 22rem;
	min-width: 0;
}

/* A member with neither a headshot nor a single Team Member Detail would
   otherwise get a white card holding nothing but their name. The same
   reasoning as .ew-team-role:empty above: close the gap, do not hold it open.
   The seeded priya-nair fixture is exactly this case. */
.ew-team-hero:not(:has(.wp-block-post-featured-image, .ew-team-profile)) {
	background: none;
	border: 0;
	box-shadow: none;
	padding: 0;
}

/* The bio runs under the hero on the same left rail the card sits on, at 58rem
   rather than the 46rem global content size: it sits under a 75rem card, and
   the narrower measure read as a column that had lost its page.

   Two rules, and the first is the one that is easy to miss. A constrained
   layout centres what it caps, so anchoring only `.ew-team-bio > *` fixes the
   top level and nothing else: a bio an author wrapped in a group block kept
   the centred text, because the wrapper got anchored while the wrapper's own
   constrained layout re-capped its children at the *global* 46rem and centred
   them inside the 58rem wrapper. Redefining that variable on the container is
   what makes the anchor depth-proof — every level below the first then caps at
   exactly the width it already has, so its auto margins compute to zero and
   there is nothing left to centre. Capping the container itself would also
   work, but it would stop an alignwide block inside the bio from breaking out,
   which is a surprise an author should not have to discover.

   tests/seed/seed.sh keeps one member's bio wrapped in a group for this. */
.ew-team-bio {
	--wp--style--global--content-size: 58rem;
}

.ew-team-bio > *:not(.alignwide):not(.alignfull) {
	margin-left: 0 !important;
	margin-right: auto !important;
}

/* --------------------------------------------------------------------------
   Status pills and notices — semantic accents applied by meaning
   -------------------------------------------------------------------------- */

.ew-pill {
	display: inline-flex;
	align-items: center;
	gap: 0.375rem;
	font-family: var(--wp--preset--font-family--heading);
	font-size: 0.75rem;
	font-weight: 500;
	line-height: 1;
	min-height: 1.5rem;
	padding: 0 0.625rem;
	border-radius: 999px;
	white-space: nowrap;
}

.ew-pill--positive { background: #ddf7e1; color: #206033; }
.ew-pill--info     { background: #dbf1ff; color: #11426c; }
.ew-pill--pending  { background: #f9eed1; color: #160f1e; }
.ew-pill--overdue  { background: #ffe4e7; color: #860930; }
.ew-pill--brand    { background: #f4e9ff; color: #3e215d; }

/* Aspen fills the band; dark text sits on top. Aspen is never text on light. */
.ew-notice--deadline { background-color: #f9eed1; }
.ew-notice--urgent   { background-color: #ffe4e7; }
.ew-notice           { background-color: #dbf1ff; }

/* --------------------------------------------------------------------------
   Header & footer chrome
   -------------------------------------------------------------------------- */

/* The bar is a white surface on the Snowfield canvas (edgewater-ui's light
   NavBar: `bg-white` under a Morning Fog hairline) and Ridge Surface on dark.
   Set here rather than as a preset because the palette has no "white" slug —
   `base` is the canvas, and the surface tone lives in the custom token. */
.ew-site-header {
	background-color: var(--wp--custom--surface--card);
	position: sticky;
	top: 0;
	z-index: 100;
}

.admin-bar .ew-site-header {
	top: 32px;
}

@media (max-width: 782px) {
	.admin-bar .ew-site-header { top: 46px; }
}

/* The current page carries a 2px accent underline (edgewater-ui NavBar's
   active link); hover is a colour change only, set in theme.json. On light
   the label is Alpenglow (the `light` tone); on dark Lupine measures 3.65:1
   on Night Ridge, so the label goes Snowfield over a Lupine rule instead —
   the library's `brand` tone. The two tokens are set in the dark blocks. */
.ew-site-header .wp-block-navigation .current-menu-item > a,
.ew-site-header .wp-block-navigation .current-menu-ancestor > a {
	color: var(--ew-nav-active-text);
	text-decoration: underline;
	text-decoration-color: var(--wp--preset--color--purple);
	text-decoration-thickness: 2px;
	text-underline-offset: 0.6em;
}

.ew-site-footer .wp-block-separator.ew-footer__divider {
	border-color: rgba(235, 233, 237, 0.18);
	opacity: 1;
}

/* The divider's spacing is set on the block, in `parts/footer.html`, so it
   stays editable in the site editor — but only half of it was ever the
   separator's to give. The space *below* it is the block-gap margin WordPress
   puts on the following sibling, and adjacent margins collapse to the larger
   of the two, so a 1rem bottom margin lost to a 1.5rem block gap and the
   editor's value silently did nothing. Zeroing the sibling's margin makes the
   number set on the separator the number that renders, in both directions.
   Removing this rule does not restore a default — it re-opens that trap. */
.ew-site-footer .wp-block-separator.ew-footer__divider + * {
	margin-block-start: 0;
}

.ew-site-footer .wp-block-navigation a:hover {
	color: var(--wp--preset--color--light);
}

/* The footer sits on the brand band in both schemes; the global Lakewater
   link tone measures ~2:1 there. Bare links in footer text take the muted
   light tone instead (~6.3:1 on the band). */
.ew-site-footer p a {
	color: var(--wp--preset--color--gray-3);
}

.ew-site-footer p a:hover {
	color: var(--wp--preset--color--light);
}

/* --------------------------------------------------------------------------
   Accessibility
   -------------------------------------------------------------------------- */

:where(a):focus-visible,
:where(button):focus-visible,
:where(.wp-element-button):focus-visible {
	outline: 2px solid var(--wp--preset--color--purple);
	outline-offset: 2px;
	border-radius: var(--wp--custom--radius--sm);
}

.ew-hero :focus-visible,
.ew-site-footer :focus-visible {
	outline-color: var(--wp--preset--color--purple-light);
}

@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		transition-duration: 0.01ms !important;
	}
}

/* --------------------------------------------------------------------------
   Consultation scheduler (third-party booking embed)
   -------------------------------------------------------------------------- */

/* The Reclaim booking page is a cross-origin iframe. Nothing inside it can be
   measured or restyled from here — no auto-height, no brand palette, no dark
   mode — so the only thing worth doing well is the box around it, and the box
   is exactly what the hand-placed embed got wrong: a flat height="700px" cut
   the widget off mid-content and gave the visitor a second scrollbar nested
   inside the page.

   The height is therefore declared here, once, and generously: taller on
   narrow viewports where the widget stacks its panes, shorter once there is
   room to sit them side by side. If Reclaim changes the booking page,
   --ew-scheduler-height is the only number that needs touching. */
.ew-scheduler {
	--ew-scheduler-height: 78rem;

	/* The widget paints its own white canvas in every colour scheme, so the
	   frame matches the WIDGET rather than the page. The literal is
	   deliberate and must not become a token: a token would follow the site
	   into Twilight and draw a dark ring around a white rectangle. */
	--ew-scheduler-canvas: #ffffff;

	margin-top: var(--wp--preset--spacing--50);
}

@media (min-width: 48rem) {
	.ew-scheduler { --ew-scheduler-height: 62rem; }
}

.ew-scheduler__frame {
	position: relative;
	overflow: hidden;
	border: 1px solid var(--wp--preset--color--gray-2);
	border-radius: var(--wp--custom--radius--lg);
	background: var(--ew-scheduler-canvas);
}

/* Something to look at while a 60rem-tall box is still empty. It sits behind
   the iframe, which paints over it as soon as Reclaim has content, so the
   loading state clears itself with no JS and no load handler. The one
   assumption: Reclaim's page has an opaque canvas of its own. */
.ew-scheduler__frame::before {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--wp--preset--color--gray-1);
	animation: ew-scheduler-pulse 1.6s ease-in-out infinite;
}

@keyframes ew-scheduler-pulse {
	0%, 100% { opacity: 1; }
	50%      { opacity: 0.55; }
}

.ew-scheduler__embed {
	/* Positioned so it paints above the loading fill: an absolutely
	   positioned ::before otherwise covers an in-flow child permanently. */
	position: relative;
	z-index: 1;

	display: block; /* kills the inline-baseline gap under the frame */
	width: 100%;
	height: var(--ew-scheduler-height);
	border: 0;

	/* The document advertises "light dark" (functions.php, colour-scheme
	   boot). Without this, a visitor on a dark OS gets a dark initial iframe
	   canvas flashing before Reclaim's white arrives. */
	color-scheme: light;
}

.ew-scheduler__fallback {
	padding: var(--wp--preset--spacing--50);
	text-align: center;
}

/* --------------------------------------------------------------------------
   Responsive — the header row and stacked button ranks

   The theme carried no breakpoint of its own below 48rem, and two shipped
   layouts assumed a desktop row. Both were reported from a phone, 2026-08-30:

   1. The header wrapped. At 393px the row wants logo (190px) + hamburger +
      toggle + "Schedule a Consultation" (193px) ≈ 516px against 353px of
      content width, so the nav group dropped to a second line and left a dead
      band beside the logo — a 195px sticky bar, a quarter of the screen. A
      width sweep found the same wrap above 600px too, where the nav is inline
      again but the row is still over budget; where exactly it clears depends
      on how many items the menu has. Only the phone case was reported, but it
      is one defect, not two.
   2. A hero button rank stacked at its natural widths (238px over 151px),
      which reads as two misaligned boxes rather than two ranks.

   The phone breakpoint is 600px because that is core's, not ours: the
   header's Navigation block is `overlayMenu: "mobile"`, which swaps to the
   hamburger at exactly 600px. Keying the toggle swap to any other number
   would put it and the burger on opposite sides of the same transition.
   -------------------------------------------------------------------------- */

/* A wrapping header row is what produced the dead band beside the logo:
   `space-between` right-aligns nothing once a line holds a single item, so a
   wrapped row strands the logo on line one and drops the nav group to line
   two, left-aligned under it. The same single-line result is expressed the
   other way round — justify to the end, and let an auto margin push the logo
   left. Identical while the row fits, and when it does not, the nav group
   hugs the right edge on its own line instead of sitting left of nothing.

   This is deliberately not inside a breakpoint. Above 600px the nav is inline
   and its width is site content — how many items the menu has — so the row
   can run out of room at any width the stylesheet cannot predict. It is still
   allowed to wrap there (a taller header beats a horizontally scrolling one);
   it is not allowed to wrap untidily. The inner group gets the same treatment
   because it wraps for the same reason: a long menu puts the links on one
   line and the toggle and CTA on the next. */
.ew-site-header .alignwide { justify-content: flex-end; }
.ew-site-header .alignwide > .wp-block-site-logo { margin-inline-end: auto; }
.ew-site-header .alignwide > .wp-block-group { justify-content: flex-end; }

/* The toggle's home is the header; the footer copy exists only for the
   overlay-nav range below. Hidden unconditionally so a desktop footer never
   paints a second control. */
.ew-site-footer .ew-scheme-toggle { display: none; }

/* The control is designed for the light canvas: a gray-2 hairline and a
   Granite glyph both disappear into the brand band, which is the same problem
   the footer's link rule solves a few sections up. Non-text contrast needs
   3:1 and gray-3 measures ~6.3:1 on this ground; hover goes Snowfield rather
   than the header's Lupine, which is 3.65:1 here. The auto margin parks it at
   the right of whichever line of the bottom bar it lands on — with the
   copyright and the privacy link already filling one line at phone widths, it
   wraps to its own, and stranded bottom-left it reads as a stray glyph. */
.ew-site-footer .ew-scheme-toggle {
	color: var(--wp--preset--color--gray-3);
	border-color: rgba(235, 233, 237, 0.28);
	margin-inline-start: auto;
}

.ew-site-footer .ew-scheme-toggle:hover {
	color: var(--wp--preset--color--light);
	border-color: var(--wp--preset--color--light);
}

.ew-site-footer .ew-scheme-toggle:focus-visible {
	outline-color: var(--wp--preset--color--purple-light);
}

/* The logo is capped at every width, not just on a phone.

   Two earlier versions of this rule were wrong in opposite directions, which is
   worth keeping written down. The first *set* a width (`clamp(7rem, 14vw,
   11.875rem)`) and so enlarged a small logo to 112px on mobile — bigger there
   than on a desktop. The second made it a `max-width` but left it inside the
   64rem breakpoint, which fixed the phone and left the desktop untouched at
   `parts/header.html`'s declared 190px — a 190×128 mark in a ~160px-tall
   header, which is what "the desktop logo looks broken" meant.

   The cap belongs outside any breakpoint because the thing it guards against
   is not a viewport, it is an oversized value in the site-logo block — and that
   value can come from a template part customised in the editor, which no theme
   file can reach. `max-width` only ever takes away, so a correctly sized logo
   is untouched.

   Sized on the img rather than the wrapper — that is where core prints the
   width and height attributes — with core's own `height: auto` keeping the
   aspect ratio. */
.ew-site-header .wp-block-site-logo img { max-width: 75px; }

/* Everything below 64rem gets the compact header metrics. */
@media (max-width: 63.99rem) {

	/* Block gap is 1.5rem in both header groups — 72px of a 353px row spent on
	   air. Two classes outrank the single-class layout rule core generates. */
	.ew-site-header .wp-block-group { gap: 0.75rem; }

	/* The CTA is the tightest fit in the row and the one item that must not
	   wrap — it is the practice's lead capture. The header pattern serialises
	   its font size and padding as inline styles, so nothing short of
	   !important reaches them. */
	.ew-site-header .wp-block-button__link {
		font-size: 0.8125rem !important;
		padding-inline: 0.75rem !important;
		white-space: nowrap;
	}
}

/* Columns stack at 782px — core's breakpoint, applied with `!important` on the
   children's flex-basis — and a stacked column rank needs a vertical gap it
   never asked for. A `wp:columns` block that sets only `blockGap.left` (the
   horizontal gutter is the one an author thinks about) makes WordPress fill
   the row axis from the *global* block gap instead: the generated rule reads
   `gap: 1.5rem var(--wp--preset--spacing--60)`. 24px is narrower than the
   paragraph line-height above it, so each pillar's heading read as the tail of
   the previous pillar rather than the head of its own — reported from a phone,
   2026-08-30, against the why-us section.

   `patterns/why-us.php` now declares `blockGap.top` and the `edgewater/why-us`
   block always did (one `gap`, both axes), but a pattern already inserted into
   a page is a fork that tracks neither (Invariant 1), so the shipped homepage
   needs this rule to reach it. It matches what the block does: when the rank
   stacks, the vertical gap equals the horizontal one.

   Scoped to `main` so the footer keeps the row gap it sets deliberately — the
   footer columns declare `blockGap.top` and WordPress emits a `gap` shorthand
   for them, which this would otherwise outrank. An explicit row gap on a
   columns block inside `main` would be outranked; there are none today, and
   the block is where a new section should be built anyway. */
@media (max-width: 781.98px) {
	main .wp-block-columns { row-gap: var(--wp--preset--spacing--60); }
}

/* Below 600px the nav is an overlay and there is no room for a fourth control
   in the row, so the scheme toggle moves to the footer. Only one of the two is
   ever in flow — the header's above 600px, the footer's below — so the control
   stays reachable and is never duplicated. Dark mode does not depend on it
   either way: with no stored choice the OS `prefers-color-scheme` still drives
   the page (Invariant 7). */
@media (max-width: 599.98px) {
	.ew-site-header .ew-scheme-toggle { display: none; }
	.ew-site-footer .ew-scheme-toggle { display: inline-flex; }

	/* With the toggle gone the row is logo + hamburger + CTA, which is short
	   enough to guarantee on one line — so here, and only here, the row is
	   forbidden to wrap. Wrapping is what produced the dead band: under
	   `space-between` a wrapped row strands the logo alone on line one and
	   drops the whole nav group to line two, left-aligned under it. Making the
	   logo the compressible item keeps one line — hamburger and CTA hold their
	   size, the mark gives up pixels below ~390px — with a floor so it stays a
	   mark. `min-width` is doing the real work: a flex item will not shrink
	   below its content width without it, and a wrappable row never shrinks at
	   all, it just wraps.

	   Above 600px the nav is inline and its length is site content, not
	   something the stylesheet can bound, so the row keeps core's `wrap` there
	   — a taller header beats a horizontally scrolling one. */
	.ew-site-header .alignwide { flex-wrap: nowrap; }

	.ew-site-header .alignwide > .wp-block-site-logo {
		flex-shrink: 1;
		min-width: 3.5rem;
	}

	.ew-site-header .alignwide > .wp-block-group { flex-shrink: 0; }

	/* 0.625rem rather than the 0.75rem above: at 320px the floor-width logo,
	   the burger and the CTA come to 281px against 280px of content, and this
	   is the 4px that buys it. */
	.ew-site-header .wp-block-button__link { padding-inline: 0.625rem !important; }

	/* Two ranks stacked read as a pair only when they share both edges.
	   `:has()` scopes this to rows that actually have a second button, so a
	   lone button (the footer's "Get in touch") keeps its natural width; the
	   `main` scope keeps the header CTA out of it.

	   `row-gap` is not belt-and-braces — without it the two buttons touch. The
	   homepage's hero was tuned in the editor to `gap: 0 var(spacing-30)`:
	   a horizontal gutter and a deliberate *zero* vertical one, which is
	   invisible on a desktop where the rank is a single row and only the
	   horizontal value is ever used. Stacking the rank is what makes the zero
	   visible, so the rule that stacks it has to supply the gap too, rather
	   than inherit whatever the page content happens to carry. Same reasoning,
	   and the same shape of page-content trap, as the columns rule above. */
	main .wp-block-buttons:has(.wp-block-button + .wp-block-button) {
		flex-direction: column;
		align-items: stretch;
		row-gap: var(--wp--preset--spacing--30);
	}

	main .wp-block-buttons:has(.wp-block-button + .wp-block-button) .wp-block-button__link {
		display: block;
		text-align: center;
	}
}

/* --------------------------------------------------------------------------
   Print
   -------------------------------------------------------------------------- */

@media print {
	.ew-site-header,
	.ew-site-footer,
	.ew-hero::after { display: none !important; }

	body { color: #000; background: #fff; font-size: 11pt; }
	a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; }
}

/* --------------------------------------------------------------------------
   Block style variations registered in functions.php
   -------------------------------------------------------------------------- */

.wp-block-group.is-style-ew-dark {
	background-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--light);
	padding: var(--wp--preset--spacing--70) var(--wp--preset--spacing--40);
}

.wp-block-group.is-style-ew-dark :where(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--preset--color--light);
}

.wp-block-group.is-style-ew-dark a:where(:not(.wp-element-button)) {
	color: #5b91c4;
}

.wp-block-group.is-style-ew-surface {
	background-color: var(--wp--preset--color--gray-1);
	padding: var(--wp--preset--spacing--70) var(--wp--preset--spacing--40);
}

.wp-block-separator.is-style-ew-rule {
	width: 3.5rem;
	height: 3px;
	border: 0;
	opacity: 1;
	border-radius: 999px;
	background-color: var(--wp--preset--color--purple-light);
	margin-left: 0;
}

.wp-block-heading.is-style-ew-eyebrow {
	font-family: var(--wp--preset--font-family--heading);
	font-size: var(--wp--preset--font-size--caption);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--tracking--label);
	/* Lupine measures 4.10:1 on the Snowfield canvas — below AA at caption size —
	   so the eyebrow takes the accent token: Alpenglow on light, the brighter
	   lavender on dark, the same routing as .ew-eyebrow.has-purple-color. */
	color: var(--ew-eyebrow-accent);
	margin-bottom: var(--wp--preset--spacing--20);
}

/* --------------------------------------------------------------------------
   Media overlays — text on a photograph

   A Cover block puts text on top of an image whose tones nobody controls: the
   author chooses the photograph, the dim slider is a guess, and the same
   markup renders on the light canvas and on Night Ridge. Each of those is a
   way for the words to disappear, and each has shipped at least once.

   Two named styles take the guesswork out. Both treat the overlay as a field
   of its own, so — like the scheduler frame in Invariant 14 — it does not
   follow the visitor's colour scheme: it renders identically in all three
   states, and the tones below are computed against the worst case rather than
   against whatever image happens to be on the page.

   `ew-overlay` (Twilight scrim). A gradient scrim covers the whole image
   whatever dim ratio the author set. The worst case is a pure white
   photograph, where the weakest point of the scrim composites to #645573;
   Snowfield measures 5.64:1 there. That composite is a mid-tone, not a dark
   canvas, so nothing dimmer survives it — Scree lands at 3.14, every accent
   below 3.4 — and the scrim therefore carries exactly one text tone. Every
   text-role preset is remapped to it rather than left to fail, which is
   Invariant 8's reasoning (Granite resolves to Snowfield on dark rather than
   being dimmed).

   `ew-overlay-panel` (Twilight panel). The image keeps the author's dim and
   the content sits on an opaque Twilight Ridge card. Because that ground is
   opaque the contrast is exact rather than worst-case — Snowfield 11.33:1 —
   and a second tone survives: Scree measures 6.30:1, so an attribution or a
   caption can still sit a step back. Reach for this one when the photograph
   is worth seeing; the scrim buries it by design.

   Both guarantees are recomputed from the tokens declared below by
   tests/checks/overlay-scrim.mjs, so changing a value fails `pnpm check`
   rather than a page.
   -------------------------------------------------------------------------- */

.wp-block-cover.is-style-ew-overlay,
.wp-block-cover.is-style-ew-overlay-panel {
	--ew-overlay-ink: #ebe9ed;   /* Snowfield — the guaranteed tone */
	--ew-overlay-muted: #ebe9ed; /* the scrim has no second tone; the panel does */

	/* Keeps the z-index below from escaping into the page. */
	isolation: isolate;

	/* Every preset that carries text in this theme resolves to a tone the
	   overlay can guarantee. This is what actually fixes the shipped bug: a
	   paragraph coloured Obsidian keeps its class and its !important and
	   still comes out legible, because the variable behind it has moved.
	   Field tokens (base, brand, light) are deliberately left alone — they
	   paint backgrounds inside the overlay and have no contrast to promise. */
	--wp--preset--color--dark: var(--ew-overlay-ink);
	--wp--preset--color--gray-5: var(--ew-overlay-ink);
	--wp--preset--color--purple: var(--ew-overlay-ink);
	--wp--preset--color--green: var(--ew-overlay-ink);
	--wp--preset--color--blue: var(--ew-overlay-ink);
	--wp--preset--color--red: var(--ew-overlay-ink);
	--wp--preset--color--gray-3: var(--ew-overlay-muted);
	--wp--preset--color--gray-4: var(--ew-overlay-muted);
	--ew-eyebrow-accent: var(--ew-overlay-muted);

	/* Semantic accent *text* is gone once the tokens above are remapped, and
	   that is the intended trade: no accent clears AA on the scrim composite.
	   A status pill still reads correctly — it brings its own opaque tint. */

	/* Buttons take the light rank the hero and CTA banner already use on a
	   brand field: a Snowfield fill under a Twilight label. The dark-mode
	   Lupine fill is wrong here — it measures 1.69:1 against the worst-case
	   scrim, so the button's own edge would vanish. */
	--wp--custom--button--bg: var(--ew-overlay-ink);
	--wp--custom--button--text: var(--wp--preset--color--brand);
	--wp--custom--button--hover: #ffffff;
	--wp--custom--button--active: #d5d1da;
}

.wp-block-cover.is-style-ew-overlay {
	--ew-overlay-scrim-top: rgba(56, 37, 76, 0.78);    /* Twilight Ridge */
	--ew-overlay-scrim-bottom: rgba(22, 15, 30, 0.86); /* Obsidian */
}

/* The scrim is a layer of its own rather than a rewrite of core's dim span,
   for two reasons: the author's overlay colour and dim ratio keep working and
   can only ever darken this further, and the layer is there even when the dim
   is set to zero. ::after is the last child in the box tree, so it paints over
   the inner container until the container is lifted — hence the z-index below,
   kept local by the isolation above. */
.wp-block-cover.is-style-ew-overlay::after {
	content: "";
	position: absolute;
	inset: 0;
	pointer-events: none;
	background-image: linear-gradient(
		to bottom,
		var(--ew-overlay-scrim-top),
		var(--ew-overlay-scrim-bottom)
	);
}

.wp-block-cover.is-style-ew-overlay > .wp-block-cover__inner-container {
	position: relative;
	z-index: 1;
}

.wp-block-cover.is-style-ew-overlay-panel {
	--ew-overlay-panel-bg: #38254c; /* Twilight Ridge, opaque */
	--ew-overlay-muted: #b3aebb;    /* Scree — 6.30:1 on the panel */
}

.wp-block-cover.is-style-ew-overlay-panel > .wp-block-cover__inner-container {
	max-width: 44rem;
	padding: var(--wp--preset--spacing--50);
	background-color: var(--ew-overlay-panel-bg);
	border-radius: var(--wp--custom--radius--lg);
	box-shadow: var(--wp--preset--shadow--lg);
}

/* Text that carries no colour class of its own. :where() keeps the added
   specificity at the two classes on the cover, so an author's preset class —
   which core generates with !important — still wins, and lands on a remapped
   token. */
.wp-block-cover.is-style-ew-overlay :where(p, h1, h2, h3, h4, h5, h6, li, dt, dd, cite, figcaption, blockquote),
.wp-block-cover.is-style-ew-overlay-panel :where(p, h1, h2, h3, h4, h5, h6, li, dt, dd, cite, figcaption, blockquote) {
	color: var(--ew-overlay-ink);
}

/* Links take the ink tone with an underline rather than the Lakewater the
   `ew-dark` group style uses: Bright Lakewater measures 2.04:1 on the
   worst-case scrim. */
.wp-block-cover.is-style-ew-overlay a:where(:not(.wp-element-button)),
.wp-block-cover.is-style-ew-overlay-panel a:where(:not(.wp-element-button)) {
	color: var(--ew-overlay-ink);
	text-decoration: underline;
}

/* ==========================================================================
   DARK MODE

   Every colour in this theme resolves through a theme.json preset variable, so
   redefining those variables re-themes the entire site — templates, patterns,
   and any block an author styled with a palette colour — without touching a
   single component rule.

   Three states, not two. An explicit choice stamps data-theme on <html>; the
   default "system" setting stamps nothing, and only prefers-color-scheme
   separates light from dark. So the tokens are declared twice: once behind the
   media query guarded by :not([data-theme="light"]) so an explicit light choice
   beats a dark OS, and once behind [data-theme="dark"] so the toggle wins in
   the other direction.

   Note what the remapping does to two slugs: "dark" is the primary-text token,
   so on a dark canvas it becomes Snowfield; "gray-5" (Granite) fails contrast
   on dark entirely, so it resolves to Snowfield rather than being dimmed. This
   follows the brand's dark-mode spec, which is purpose-built rather than an
   inversion.
   ========================================================================== */

@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		--wp--preset--color--base: #1b1228;        /* Night Ridge — canvas */
		--wp--preset--color--dark: #ebe9ed;        /* text token flips to Snowfield */
		--wp--preset--color--gray-1: #2a1c3d;      /* Ridge Surface */
		--wp--preset--color--gray-2: #524268;      /* Ridge Line — borders, 1.76:1 on the surface */
		--wp--preset--color--gray-3: #b3aebb;      /* Scree — the one shared token */
		--wp--preset--color--gray-4: #b3aebb;      /* Slate is too dim on dark */
		--wp--preset--color--gray-5: #ebe9ed;      /* Granite fails on dark (2.33) */
		--wp--preset--color--purple: #865eb1;      /* Lupine */
		--wp--preset--color--green: #5fa873;       /* Bright Evergreen */
		--wp--preset--color--blue: #5b91c4;        /* Bright Lakewater */
		--wp--preset--color--red: #d46a7e;         /* Bright Garnet */
		--wp--custom--surface--card: #2a1c3d;
		--wp--custom--surface--raised: #2a1c3d;
		--ew-link-hover: #7aa8d4;
		--wp--custom--surface--well: #0f0a16;   /* Deep Obsidian — input wells */
		--wp--custom--button--bg: #ebe9ed;       /* Snowfield fill, see note below */
		--wp--custom--button--text: #38254c;     /* Twilight Ridge label, 11.33:1 */
		--wp--custom--button--hover: #ffffff;    /* brightens on dark, label 12.5:1 */
		--wp--custom--button--active: #ffffff;
		--wp--custom--button--outline-hover: #2f223d; /* Lupine tint */
		--ew-purple-tint: #2f223d;
		--ew-nav-active-text: #ebe9ed;           /* Snowfield over the Lupine rule */
		--ew-pill-positive-bg: #14301c; --ew-pill-positive-fg: #7ec891;
		--ew-pill-info-bg: #132b40;     --ew-pill-info-fg: #79b0e5;
		--ew-pill-pending-bg: #332707;  --ew-pill-pending-fg: #fad161;
		--ew-pill-overdue-bg: #3e1e23;  --ew-pill-overdue-fg: #f7899c;
		--ew-pill-brand-bg: #2f223d;    --ew-pill-brand-fg: #a47cd2;
		--ew-eyebrow-accent: #a47cd2;
		color-scheme: dark;
	}
}

:root[data-theme="dark"] {
	--wp--preset--color--base: #1b1228;
	--wp--preset--color--dark: #ebe9ed;
	--wp--preset--color--gray-1: #2a1c3d;
	--wp--preset--color--gray-2: #524268;
	--wp--preset--color--gray-3: #b3aebb;
	--wp--preset--color--gray-4: #b3aebb;
	--wp--preset--color--gray-5: #ebe9ed;
	--wp--preset--color--purple: #865eb1;
	--wp--preset--color--green: #5fa873;
	--wp--preset--color--blue: #5b91c4;
	--wp--preset--color--red: #d46a7e;
	--wp--custom--surface--card: #2a1c3d;
	--wp--custom--surface--raised: #2a1c3d;
	--ew-link-hover: #7aa8d4;
	--wp--custom--surface--well: #0f0a16;   /* Deep Obsidian — input wells */
	--wp--custom--button--bg: #ebe9ed;       /* Snowfield fill, see note below */
	--wp--custom--button--text: #38254c;     /* Twilight Ridge label, 11.33:1 */
	--wp--custom--button--hover: #ffffff;    /* brightens on dark, label 12.5:1 */
	--wp--custom--button--active: #ffffff;
	--wp--custom--button--outline-hover: #2f223d; /* Lupine tint */
	--ew-purple-tint: #2f223d;
	--ew-nav-active-text: #ebe9ed;           /* Snowfield over the Lupine rule */
	--ew-pill-positive-bg: #14301c; --ew-pill-positive-fg: #7ec891;
	--ew-pill-info-bg: #132b40;     --ew-pill-info-fg: #79b0e5;
	--ew-pill-pending-bg: #332707;  --ew-pill-pending-fg: #fad161;
	--ew-pill-overdue-bg: #3e1e23;  --ew-pill-overdue-fg: #f7899c;
	--ew-pill-brand-bg: #2f223d;    --ew-pill-brand-fg: #a47cd2;
	--ew-eyebrow-accent: #a47cd2;
	color-scheme: dark;
}

/* Light-mode defaults for the tokens theme.json cannot express. */
:root {
	--ew-link-hover: #234866;
	--ew-purple-tint: #f4e9ff;
	--ew-nav-active-text: var(--wp--preset--color--purple);
	--ew-pill-positive-bg: #ddf7e1; --ew-pill-positive-fg: #206033;
	--ew-pill-info-bg: #dbf1ff;     --ew-pill-info-fg: #11426c;
	--ew-pill-pending-bg: #f9eed1;  --ew-pill-pending-fg: #160f1e;
	--ew-pill-overdue-bg: #ffe4e7;  --ew-pill-overdue-fg: #860930;
	--ew-pill-brand-bg: #f4e9ff;    --ew-pill-brand-fg: #3e215d;
	--ew-eyebrow-accent: #3e215d;
}

/* A purple eyebrow (pricing's "Most popular") reads Alpenglow on light, but
   the dark remap's Lupine lands at 3.19:1 on the dark card surface — small
   text needs 4.5. Route it through its own token so dark mode can take the
   brighter lavender the brand pills already use. !important matches the
   preset class this overrides. */
.ew-eyebrow.has-purple-color {
	color: var(--ew-eyebrow-accent) !important;
}

/* Pills and notices carried hardcoded tints; route them through the tokens so
   they follow the mode like everything else. */
/* On dark, a pill label takes its accent's documented "active" step (+0.10 L),
   not the base. The base accents were tuned to sit on the canvas, not on top of
   their own tint wash — against the tint they land at 3.0–4.4, which fails for
   12px semibold text. Every active step clears 4.5. */
.ew-pill--positive { background: var(--ew-pill-positive-bg); color: var(--ew-pill-positive-fg); }
.ew-pill--info     { background: var(--ew-pill-info-bg);     color: var(--ew-pill-info-fg); }
.ew-pill--pending  { background: var(--ew-pill-pending-bg);  color: var(--ew-pill-pending-fg); }
.ew-pill--overdue  { background: var(--ew-pill-overdue-bg);  color: var(--ew-pill-overdue-fg); }
.ew-pill--brand    { background: var(--ew-pill-brand-bg);    color: var(--ew-pill-brand-fg); }

.ew-notice           { background-color: var(--ew-pill-info-bg); }
.ew-notice--deadline { background-color: var(--ew-pill-pending-bg); }
.ew-notice--urgent   { background-color: var(--ew-pill-overdue-bg); }

/* Primary buttons on dark are a Snowfield fill under a Twilight Ridge label,
   brightening to white on hover — the pairing styles/dark.json has always
   specified. No step of the Lupine ladder carries a 14px label: Obsidian on
   Lupine is 3.79:1, on the hover step 4.68 and falling as the fill brightens;
   Snowfield on Lupine is 4.10 and the ladder brightens away from it. Snowfield
   on the card measures 13.07:1 with an 11.33:1 label, and it is the one light
   object on the page, which is what makes the primary read as primary when
   canvas, card, border and accent are all the same plum. The tokens are set
   in the two dark blocks above; edgewater-ui's Button should follow. */

/* The site logo swaps with the mode: the purple mark cannot sit on Night Ridge.
   A filter rather than a second image, because the Site Logo block renders
   whatever the admin uploaded. The mark is monochrome, so nothing is lost.
   The footer logo is excluded — it is already white on a purple field in both
   modes. */
:root[data-theme="dark"] .wp-block-site-logo img {
	filter: brightness(0) invert(93%) sepia(6%) saturate(180%) hue-rotate(232deg);
}
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .wp-block-site-logo img {
		filter: brightness(0) invert(93%) sepia(6%) saturate(180%) hue-rotate(232deg);
	}
}

/* Service icons get the same treatment as the logo, and for the same reason:
   they are uploaded monochrome line art, drawn dark, invisible on Night
   Ridge. */
:root[data-theme="dark"] .ew-service-icon img {
	filter: brightness(0) invert(93%) sepia(6%) saturate(180%) hue-rotate(232deg);
}
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .ew-service-icon img {
		filter: brightness(0) invert(93%) sepia(6%) saturate(180%) hue-rotate(232deg);
	}
}

/* --------------------------------------------------------------------------
   The toggle itself
   -------------------------------------------------------------------------- */

.ew-scheme-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.5rem;
	height: 2.5rem;
	padding: 0;
	border: 1.5px solid var(--wp--preset--color--gray-2);
	border-radius: 999px;
	background: transparent;
	color: var(--wp--preset--color--gray-5);
	cursor: pointer;
	flex-shrink: 0;
}

.ew-scheme-toggle:hover {
	border-color: var(--wp--preset--color--purple);
	color: var(--wp--preset--color--purple);
}

.ew-scheme-toggle:focus-visible {
	outline: 2px solid var(--wp--preset--color--purple);
	outline-offset: 2px;
}

.ew-scheme-toggle svg {
	width: 18px;
	height: 18px;
	stroke: currentColor;
	fill: none;
	stroke-width: 1.5;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* Show the icon for the mode the button will switch TO, so the control
   describes its action rather than the current state. */
.ew-scheme-toggle .ew-icon-sun { display: none; }
:root[data-theme="dark"] .ew-scheme-toggle .ew-icon-sun { display: block; }
:root[data-theme="dark"] .ew-scheme-toggle .ew-icon-moon { display: none; }

@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .ew-scheme-toggle .ew-icon-sun { display: block; }
	:root:not([data-theme="light"]) .ew-scheme-toggle .ew-icon-moon { display: none; }
}

.ew-should-be-title-link > a {
	text-decoration: none;
	color: var(--wp--preset--color--dark);
}
