/* ==========================================================================
   Dot Map — components
   Cascade position 4 of 5: tokens -> base -> layout -> COMPONENTS -> pages
   --------------------------------------------------------------------------
   Reusable .dm-* component classes, converted from the kit's React inline
   styles. Owned by the component agent. The foundation lane creates this file
   with only this header so the enqueue in inc/enqueue.php does not 404.

   BEFORE WRITING A LINE HERE, READ SECTION 0 OF assets/css/layout.css.
   It is the binding contract for this file. In short:

     - Root scope is REQUIRED: `.dm-page .dm-card { … }`, never `.dm-card { … }`.
       Elementor Pro stays installed and its `elementor-kit-{N}` typography
       sits at specificity (0,1,1); an unscoped single class loses to it.
     - Naming: .dm-block, .dm-block__element, .dm-block--modifier, .is-state.
     - Mobile-first, min-width only. Breakpoints: 640 / 900 / 1200. No
       max-width queries, no fifth breakpoint.
     - No `!important`. No IDs.
     - Sentence case everywhere, including CSS: no `text-transform: uppercase`.
     - Use tokens. Never hardcode a hex, radius, shadow or duration that
       tokens.css already defines, and never invent a new token.
     - Motion law: 120–180ms, colour and background-color only. No transform,
       opacity, scale or height transitions.
     - Do not redefine .dm-image-slot, .dm-media__img, .dm-container,
       .dm-section, .dm-grid, .dm-stack, .dm-eyebrow, .dm-site-header or
       .dm-site-footer — those are foundation-owned and live in layout.css.
       Extend them with your own modifier class instead.
   ========================================================================== */


/* ==========================================================================
   CONTENTS
   --------------------------------------------------------------------------
    0. Notes that apply to the whole file (READ THIS)
    1. Button              .dm-btn
    2. Icon button         .dm-icon-btn
    3. Card                .dm-card
    4. Badge               .dm-badge      (pill — Services uses this)
    5. Tag                 .dm-tag        (square — Work uses this)
    6. Form controls       .dm-field .dm-input .dm-select .dm-textarea
                           .dm-checkbox .dm-radio .dm-switch
    7. Tabs                .dm-tabs
    8. Feedback            .dm-tooltip .dm-toast .dm-dialog
    9. Section components  .dm-hero .dm-page-hero .dm-cta-band
                           .dm-service-card .dm-case-study-card
                           .dm-value .dm-process
   10. FORMIDABLE FORMS OVERRIDE LAYER  ← Agent 6 depends on this
   ========================================================================== */


/* ==========================================================================
   0. Notes that apply to the whole file
   --------------------------------------------------------------------------

   0.1 OFF-SCALE PIXEL VALUES ARE DELIBERATE
   The kit's components were authored with hand-set pixel padding and font
   sizes that are NOT on the 4px spacing scale and have no typography token:
   button padding 6/12, 10/18, 13/22; card titles at 17px and 19px; the CTA
   heading at 22px; field gaps at 6px. The build brief says the JSX is the
   spec and instructs "do not approximate", and docs/content-map.md §0.5
   confirms that where the kit hardcodes a px size with no token, the px value
   IS the spec. Those values are therefore reproduced literally and are
   commented at each site. Every colour, radius, shadow and duration still
   comes from a token — there is not one hardcoded hex in this file.

   0.2 THREE CONTRAST FIXES AGAINST THE KIT (measured, not guessed)
   BUILD-BRIEF §4 requires 4.5:1 text contrast and says plainly that the kit
   does not handle accessibility. Three kit values fail. Each is corrected by
   the smallest change that keeps the brand intent, and each is documented at
   the rule. Measured with the WCAG 2.x relative-luminance formula:

     a) Button.jsx `signal`: #fff on --brand-signal (amber-400) = 2.81:1, and
        2.81 is not a rounding problem, it is a hard fail on the site's single
        most important control ("Begin a project", "Submit your project").
        FIX: keep the amber background chain exactly as the brand defines it
        and change the LABEL to --navy-950.
        Rest 6.60:1 · hover 4.81:1. See §1.4.

     b) Button.jsx `accent`: #fff on --brand-accent (teal-500) = 4.47:1 —
        fails by a hair, but 14px button text needs 4.5.
        FIX: shift the accent BUTTON one step down the teal ramp
        (teal-600 → 700 → 800). Rest 6.13:1 · hover 8.10:1 · active 10.71:1.
        The --brand-accent token itself is untouched and stays correct for
        non-text use (rules, dots, icons, which need only 3:1). See §1.4.

     c) --brand-accent as MONO TEXT on --bg-sunken = 3.91:1. This is the
        process-step number (ServicesDetail.jsx) and the value label
        (AboutSection.jsx), both 13px.
        FIX: --teal-600 in those two places = 5.36:1 on sunken, 6.13:1 on
        surface, so it is safe on all three light bands. See §9.6 and §9.7.

   Everything else in the kit measured clean and ships unchanged: badge tones
   4.79–8.76:1, tag 7.39:1, hero eyebrow (teal-300 on navy-950) 9.14:1, hero
   lede (ink-200 on navy-950) 11.0:1, CTA band 13.69:1, primary button 11.7:1.

   0.3 FOCUS RINGS ARE BASE.CSS'S JOB — WITH ONE CORRECTION
   base.css §8 owns the focus indicator for the whole site. Nothing here
   redefines outline, outline-width or box-shadow on :focus-visible.

   That rule also sets `border-radius: var(--radius-focus-ring)` (6px) on the
   focused element, which is a no-op for a 6px control but visibly SQUARES a
   pill or a 10px card at the moment it receives focus. Where a component's
   natural radius is not 6px, this file restores it in a `:focus-visible` rule
   of its own — specificity (0,3,0) against base's (0,2,1), so it wins without
   touching base.css. Every such rule is commented `focus-radius`.

   0.4 STATE CLASSES
   `.is-*` and `.has-*` are never styled alone — always `.dm-field.has-error`.

   0.5 TRANSITIONS
   Only color, background-color and border-color, only at --duration-fast or
   --duration-base. No transform, opacity, width, height or position
   transitions anywhere in this file, including the switch knob.
   ========================================================================== */


/* ==========================================================================
   1. Button — .dm-btn
   --------------------------------------------------------------------------
   Source: components/core/Button.jsx.

   Variants: --primary --secondary --accent --signal --ghost
   Sizes:    --sm --md (default, no class needed) --lg
   Extras:   --block (full width)

   Works on <button> and on <a>. On an <a> it must beat base.css's prose
   underline: base is `body.dm-page a` (0,1,2) and `.dm-page .dm-btn` is
   (0,2,0), which wins on class count. The hover pair is (0,2,2) vs (0,3,0) —
   also ours. This is why the anchor form needs no extra class.

   TARGET SIZE. Rendered heights are 33.5 / 43 / 52px (padding + 1.5 line-box
   + 1px borders). All three clear the WCAG 2.2 AA 24×24 minimum (2.5.8);
   --md and --lg also clear the 44px AAA figure. Prefer --lg for the primary
   CTA on a phone.
   ========================================================================== */

.dm-page .dm-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2); /* kit: 8 */
	/* --md is the default size; the size modifiers below override it. */
	padding: 10px 18px; /* kit-literal, off the 4px scale — see §0.1 */
	border: var(--border-width-default) solid transparent;
	border-radius: var(--radius-md);
	background-color: transparent;
	font-family: var(--font-sans);
	font-size: var(--text-body-sm); /* kit: 14 */
	font-weight: var(--weight-semibold); /* kit: 600 */
	line-height: var(--leading-normal);
	/* Sentence case is law — no text-transform, and no letter-spacing change. */
	text-align: center;
	text-decoration: none;
	cursor: pointer;
	/* Colour and background only, per the motion law. */
	transition:
		background-color var(--duration-fast) var(--ease-standard),
		border-color var(--duration-fast) var(--ease-standard),
		color var(--duration-fast) var(--ease-standard);
}

/* An <a class="dm-btn"> must not pick up the prose underline in any state. */
.dm-page .dm-btn:hover,
.dm-page .dm-btn:focus {
	text-decoration: none;
}


/* --- 1.1 Sizes ----------------------------------------------------------- */

.dm-page .dm-btn--sm {
	padding: 6px var(--space-3); /* kit: 6px 12px */
	font-size: var(--text-mono-label); /* the 13px step; kit: 13 */
}

.dm-page .dm-btn--md {
	padding: 10px 18px; /* kit: 10px 18px */
	font-size: var(--text-body-sm); /* kit: 14 */
}

.dm-page .dm-btn--lg {
	padding: 13px 22px; /* kit: 13px 22px */
	font-size: var(--text-body); /* kit: 16 */
}

/*
 * Full-width. The contact submit button spans the form column, and on a phone
 * a stacked CTA pair reads better full-width than left-aligned and ragged.
 */
.dm-page .dm-btn--block {
	display: flex;
	width: 100%;
}


/* --- 1.2 Disabled -------------------------------------------------------- */

/*
 * Covers the real `disabled` attribute, the ARIA form used on links, and a
 * `.is-disabled` class. Opacity is a static value, not a transition.
 */
.dm-page .dm-btn:disabled,
.dm-page .dm-btn[aria-disabled="true"],
.dm-page .dm-btn.is-disabled {
	opacity: 0.5; /* kit: 0.5 */
	cursor: not-allowed;
	/* A disabled control must not react to the pointer at all. */
	pointer-events: none;
}


/* --- 1.3 Variant: primary (navy) ----------------------------------------- */

/*
 * The workhorse. navy-700 with white text measures 11.70:1.
 * Hover darkens one step, active one step further — brand law, no inversion.
 */
.dm-page .dm-btn--primary {
	border-color: var(--brand-primary);
	background-color: var(--brand-primary);
	color: var(--text-on-brand);
}

.dm-page .dm-btn--primary:hover {
	border-color: var(--brand-primary-hover);
	background-color: var(--brand-primary-hover);
	color: var(--text-on-brand);
}

.dm-page .dm-btn--primary:active {
	border-color: var(--brand-primary-active);
	background-color: var(--brand-primary-active);
}


/* --- 1.4 Variant: signal (amber) — CONTRAST FIX -------------------------- */

/*
 * THE PRIMARY CTA. Amber is reserved for exactly this (brand law), so the
 * background stays --brand-signal / --brand-signal-hover verbatim.
 *
 * The kit sets the label to #fff, which measures 2.81:1 on amber-400 and
 * 3.86:1 on amber-500 — both fail 4.5:1, on the most important control on the
 * site. Swapping the LABEL to --navy-950 keeps the amber swatch, the
 * darkens-one-step hover and the brand's own primary hue, and measures:
 *
 *     rest   navy-950 on amber-400   6.60:1  PASS
 *     hover  navy-950 on amber-500   4.81:1  PASS
 *
 * ACTIVE is the awkward one. amber-600 under navy-950 is 3.39:1 — a fail —
 * and switching the label to white at press time would be the colour
 * inversion the brand forbids. So the press state darkens the BORDER to
 * amber-700 instead, keeping the amber-500 field. Visible press feedback, no
 * contrast regression, no inversion, no transform.
 */
.dm-page .dm-btn--signal {
	border-color: var(--brand-signal);
	background-color: var(--brand-signal);
	color: var(--navy-950);
}

.dm-page .dm-btn--signal:hover {
	border-color: var(--brand-signal-hover);
	background-color: var(--brand-signal-hover);
	color: var(--navy-950);
}

.dm-page .dm-btn--signal:active {
	border-color: var(--amber-700);
	background-color: var(--brand-signal-hover);
}


/* --- 1.5 Variant: secondary (outlined) ----------------------------------- */

/*
 * White surface, navy label, --border-strong hairline (the brand specifies
 * the stronger hairline for outlined buttons). Per Button.jsx only the
 * BACKGROUND moves on hover for this variant — the border holds.
 */
.dm-page .dm-btn--secondary {
	border-color: var(--border-strong);
	background-color: var(--bg-surface);
	color: var(--brand-primary);
}

.dm-page .dm-btn--secondary:hover {
	background-color: var(--bg-sunken);
	color: var(--brand-primary);
}

.dm-page .dm-btn--secondary:active {
	background-color: var(--ink-100);
}


/* --- 1.6 Variant: accent (teal) — CONTRAST FIX --------------------------- */

/*
 * Teal is the technical accent and the brand says use it sparingly, for
 * secondary/technical actions. The marketing site may not use this variant at
 * all; it exists so the component set is complete.
 *
 * The kit pairs #fff with --brand-accent (teal-500) at 4.47:1 — short of 4.5
 * for 14px text. The button therefore sits one step down the ramp. The
 * --brand-accent TOKEN is deliberately not touched: it is still the right
 * value for rules, dots and icons, which need 3:1, not 4.5:1.
 *
 *     rest   white on teal-600   6.13:1
 *     hover  white on teal-700   8.10:1
 *     active white on teal-800  10.71:1
 */
.dm-page .dm-btn--accent {
	border-color: var(--teal-600);
	background-color: var(--teal-600);
	color: var(--text-on-brand);
}

.dm-page .dm-btn--accent:hover {
	border-color: var(--teal-700);
	background-color: var(--teal-700);
	color: var(--text-on-brand);
}

.dm-page .dm-btn--accent:active {
	border-color: var(--teal-800);
	background-color: var(--teal-800);
}


/* --- 1.7 Variant: ghost -------------------------------------------------- */

/*
 * Text-only inline action. Gains the light sunken background on hover, per
 * the brand's ghost/secondary rule. Never gains an underline — that is
 * reserved for real text links.
 */
.dm-page .dm-btn--ghost {
	border-color: transparent;
	background-color: transparent;
	color: var(--brand-primary);
}

.dm-page .dm-btn--ghost:hover {
	background-color: var(--bg-sunken);
	color: var(--brand-primary);
}

.dm-page .dm-btn--ghost:active {
	background-color: var(--ink-100);
}


/* --- 1.8 Buttons inside a dark band — REQUIRED, NOT OPTIONAL ------------- */

/*
 * WITHOUT THIS BLOCK THE CTA BAND IS BROKEN. Found by rendering, not by
 * reading — it is invisible to source inspection because it only affects the
 * ANCHOR form of a button inside .dm-on-dark.
 *
 * layout.css §4 sets `.dm-page .dm-on-dark a { color: var(--ink-0) }` so that
 * prose links stay legible on navy. That selector is (0,2,1). The variant
 * rules in §1.3–1.7 are (0,2,0) — same class count, but the element selector
 * in the layout rule breaks the tie against us. So every <a class="dm-btn">
 * inside a dark band had its label forced to white, which produced:
 *
 *   .dm-btn--secondary   white text on a WHITE surface -> 1:1, invisible.
 *                        A completely unreadable button.
 *   .dm-btn--signal      white text on amber -> 2.81:1, silently reverting
 *                        the whole §1.4 contrast fix.
 *
 * Both appear in template-parts/cta-band.php, which renders on Home, Services,
 * Work and About — the site's primary conversion path on four of five pages.
 *
 * These rules are (0,3,0) and win outright. They are not a duplicate of the
 * variant block: they are what keeps a button a button once it sits on navy.
 * The correct fix lives here rather than in layout.css because the dark-band
 * link rule is right for prose links; it is buttons that must opt out.
 */
.dm-page .dm-on-dark .dm-btn--primary,
.dm-page .dm-on-dark .dm-btn--primary:hover,
.dm-page .dm-on-dark .dm-btn--accent,
.dm-page .dm-on-dark .dm-btn--accent:hover {
	color: var(--text-on-brand);
}

/* Navy label on the amber plate — the §1.4 fix, restated so it survives. */
.dm-page .dm-on-dark .dm-btn--signal,
.dm-page .dm-on-dark .dm-btn--signal:hover {
	color: var(--navy-950);
}

/* Navy label on the white plate. Without this the button reads as blank. */
.dm-page .dm-on-dark .dm-btn--secondary,
.dm-page .dm-on-dark .dm-btn--secondary:hover {
	color: var(--brand-primary);
}

/*
 * Ghost is the one variant with no plate of its own, so navy-on-navy would be
 * unreadable. It is therefore the only variant whose colours legitimately
 * differ on a dark band: the white label layout.css wants is correct here, and
 * the hover swatch moves from --bg-sunken to a navy that reads on the band.
 */
.dm-page .dm-on-dark .dm-btn--ghost,
.dm-page .dm-on-dark .dm-btn--ghost:hover {
	color: var(--ink-0);
}

.dm-page .dm-on-dark .dm-btn--ghost:hover {
	background-color: var(--navy-700);
}


/* --- 1.9 Button group ---------------------------------------------------- */

/*
 * A row of buttons that stacks nowhere: it wraps. Used by the CTA band and by
 * any page that pairs two actions. On a phone the buttons sit side by side if
 * they fit and wrap to two rows if they do not — no max-width query needed.
 */
.dm-page .dm-btn-group {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3); /* kit: 12 */
}


/* ==========================================================================
   2. Icon button — .dm-icon-btn
   --------------------------------------------------------------------------
   Source: components/core/IconButton.jsx. Square, icon-only, ALWAYS needs an
   accessible name (aria-label or a .screen-reader-text child) — the kit's own
   prompt doc says so.

   The kit's default box is 36px. That is under the 44px comfortable touch
   target, so the base size here is 44 and --sm is the kit's 36 for dense
   desktop toolbars. Both clear the 24px WCAG 2.2 AA minimum.
   ========================================================================== */

.dm-page .dm-icon-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 44px;
	height: 44px;
	padding: 0;
	border: var(--border-width-default) solid transparent;
	border-radius: var(--radius-md);
	background-color: transparent;
	color: var(--text-secondary);
	cursor: pointer;
	transition:
		background-color var(--duration-fast) var(--ease-standard),
		color var(--duration-fast) var(--ease-standard);
}

.dm-page .dm-icon-btn--sm {
	width: 36px; /* kit: size=36 */
	height: 36px;
}

.dm-page .dm-icon-btn__icon {
	display: block;
	width: 20px;
	height: 20px;
	/* Lucide house style: stroked, never filled, rounded joins. */
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.dm-page .dm-icon-btn:hover {
	background-color: var(--ink-100);
	color: var(--brand-primary);
}

.dm-page .dm-icon-btn--filled {
	background-color: var(--bg-sunken);
	color: var(--brand-primary);
}

.dm-page .dm-icon-btn--primary {
	background-color: var(--brand-primary);
	color: var(--text-on-brand);
}

.dm-page .dm-icon-btn--primary:hover {
	background-color: var(--brand-primary-hover);
	color: var(--text-on-brand);
}

.dm-page .dm-icon-btn:disabled,
.dm-page .dm-icon-btn[aria-disabled="true"] {
	opacity: 0.5;
	cursor: not-allowed;
	pointer-events: none;
}


/* ==========================================================================
   3. Card — .dm-card
   --------------------------------------------------------------------------
   Source: components/core/Card.jsx, and the Brand specimen
   guidelines/spacing-card.card.html, which both state the same thing:
   white surface · 1px --border-default hairline · 10px radius ·
   --shadow-sm · 24px padding.

   Padding modifiers map to Card.jsx's pads{}: sm 16 · md 20 · lg 24 (default).

   NO HOVER LIFT. The brand forbids transform and scale, so an interactive
   card changes its border and shadow, never its position.
   ========================================================================== */

.dm-page .dm-card {
	display: flex;
	flex-direction: column;
	padding: var(--space-6); /* 24px — the specified card padding */
	border: var(--border-width-hairline) solid var(--border-default);
	border-radius: var(--radius-lg); /* 10px */
	background-color: var(--bg-surface);
	box-shadow: var(--shadow-sm);
	font-family: var(--font-sans);
}

.dm-page .dm-card--pad-sm {
	padding: var(--space-4); /* 16px */
}

.dm-page .dm-card--pad-md {
	padding: var(--space-5); /* 20px */
}

/* Elevated / interactive. Card.jsx: shadow-md instead of shadow-sm. */
.dm-page .dm-card--elevated {
	box-shadow: var(--shadow-md);
}

/*
 * Media-flush card: the image sits hard against the card edge and the text
 * block below carries the padding. Used by the case-study card.
 */
.dm-page .dm-card--flush {
	padding: 0;
	overflow: hidden;
}

/* --- Card parts ---------------------------------------------------------- */

.dm-page .dm-card__body {
	display: flex;
	flex-direction: column;
	gap: var(--space-2); /* kit: 8 */
	padding: var(--space-6);
}

.dm-page .dm-card__title {
	margin: 0;
	color: var(--text-primary);
}

.dm-page .dm-card__text {
	margin: 0;
	color: var(--text-secondary);
	font-size: var(--text-body-sm); /* kit: 14 */
	line-height: var(--leading-relaxed);
}

.dm-page .dm-card__footer {
	margin-block-start: auto;
	padding-block-start: var(--space-4);
}

/*
 * focus-radius (see §0.3): base.css squares the card to 6px on focus. Restore
 * the 10px card radius for a card that is itself focusable.
 */
.dm-page .dm-card:focus-visible {
	border-radius: var(--radius-lg);
}


/* ==========================================================================
   4. Badge — .dm-badge
   --------------------------------------------------------------------------
   Source: components/core/Badge.jsx.

   *** BADGE AND TAG ARE DIFFERENT COMPONENTS. DO NOT MERGE THEM. ***
   Agent 2 documented the split in docs/content-map.md §5.2 and §6.2:

     Badge  pill (999px), filled tone, NO border, 3px/10px padding
            -> the SERVICES page cards (tone="primary")
     Tag    square (4px radius), sunken fill, hairline BORDER, 4px/10px
            -> the WORK page cards

   Both are mono 12px. That is the only thing they share.

   Every tone below measures 4.79:1 or better on its own background, so all
   six ship exactly as the kit defines them.
   ========================================================================== */

.dm-page .dm-badge {
	display: inline-flex;
	align-items: center;
	padding: 3px 10px; /* kit: 3px 10px — both off the 4px scale, see §0.1 */
	border-radius: var(--radius-pill); /* 999px — badges are one of the three
	                                      places the brand permits a full pill */
	background-color: var(--ink-100);
	color: var(--ink-700);
	font-family: var(--font-mono);
	font-size: 12px; /* kit-literal: no 12px sans token; --text-caption is 12px
	                    but is a sans caption step — kept literal for clarity */
	font-weight: var(--weight-medium); /* kit: 500 */
	letter-spacing: var(--tracking-wide);
	line-height: var(--leading-snug);
	/* Sentence case: no text-transform. */
	white-space: nowrap;
}

/* tone="neutral" is the base rule above. */

.dm-page .dm-badge--primary {
	background-color: var(--navy-100);
	color: var(--navy-700); /* 8.76:1 */
}

.dm-page .dm-badge--accent {
	background-color: var(--teal-100);
	color: var(--teal-700); /* 6.60:1 */
}

.dm-page .dm-badge--success {
	background-color: var(--color-success-bg);
	color: var(--color-success); /* 4.79:1 */
}

.dm-page .dm-badge--warning {
	background-color: var(--color-warning-bg);
	color: var(--color-warning); /* 5.00:1 */
}

.dm-page .dm-badge--danger {
	background-color: var(--color-danger-bg);
	color: var(--color-danger); /* 5.25:1 */
}

/*
 * A row of badges. Wraps, because "Public Sector" + "Engineering" will not sit
 * on one line inside a 342px-wide card on a 390px phone.
 */
.dm-page .dm-badge-list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2); /* kit: 8 */
	margin: 0;
	padding: 0;
	list-style: none;
}


/* ==========================================================================
   5. Tag — .dm-tag
   --------------------------------------------------------------------------
   Source: components/core/Tag.jsx. Square corners, hairline border, sunken
   fill — visibly a different object from the pill Badge above. Its prompt doc
   calls out the distinction explicitly: "square corners (vs Badge's pill)".

   --text-secondary on --bg-sunken measures 7.39:1.
   ========================================================================== */

.dm-page .dm-tag {
	display: inline-flex;
	align-items: center;
	gap: 6px; /* kit: 6 — off-scale, see §0.1 */
	padding: var(--space-1) 10px; /* kit: 4px 10px */
	border: var(--border-width-hairline) solid var(--border-default);
	border-radius: var(--radius-sm); /* 4px — NOT the badge's pill */
	background-color: var(--bg-sunken);
	color: var(--text-secondary);
	font-family: var(--font-mono);
	font-size: 12px; /* kit: 12 */
	line-height: var(--leading-snug);
	white-space: nowrap;
}

/*
 * A tag that links (the Work tag archive). It must not inherit the prose
 * underline, and it darkens on hover rather than moving.
 */
.dm-page a.dm-tag {
	text-decoration: none;
	transition:
		background-color var(--duration-fast) var(--ease-standard),
		border-color var(--duration-fast) var(--ease-standard),
		color var(--duration-fast) var(--ease-standard);
}

.dm-page a.dm-tag:hover {
	border-color: var(--border-strong);
	background-color: var(--ink-100);
	color: var(--text-primary);
	text-decoration: none;
}

/* The optional remove affordance. Unicode × is trivial UI chrome, which the
   design system permits; it is never a stand-in for a real icon.

   2.5.8 (Agent 8): the kit's box is 16x16, under the 24x24 WCAG 2.2 AA
   minimum. The BOX grows to 24; the glyph does not, because font-size still
   sets the mark's size. Unused by any template today — fixed so the component
   set does not hand the next editor a failing control. */
.dm-page .dm-tag__remove {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--space-6);  /* 24px — WCAG 2.2 AA 2.5.8 */
	height: var(--space-6);
	padding: 0;
	border: 0;
	background: none;
	color: var(--text-muted);
	font-size: var(--text-mono-label); /* kit: 13 */
	line-height: 1;
	cursor: pointer;
	transition: color var(--duration-fast) var(--ease-standard);
}

.dm-page .dm-tag__remove:hover {
	color: var(--text-primary);
}

.dm-page .dm-tag-list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2); /* kit: 8 */
	margin: 0;
	padding: 0;
	list-style: none;
}


/* ==========================================================================
   6. Form controls
   --------------------------------------------------------------------------
   Source: components/forms/*.jsx.

   These matter more than anything else in this file, because §10 maps them
   onto Formidable's real DOM. Change a value here and change it there too —
   the two blocks are a matched pair, and Agent 6 styles the plugin's markup,
   not ours.

   Spec, from Input.jsx and docs/content-map.md §9.3:
     field wrapper  column flex, 6px gap
     label          13px, weight 600, --text-primary
     required mark  " *" in --color-danger (6.07:1 on white)
     control        10px/12px padding, 14px, 1px --border-default,
                    --radius-md, --bg-surface
     focus          border --border-focus (the RING comes from base.css)
     error          border --color-danger + a 12px --color-danger message

   ACCESSIBILITY, beyond the kit (content-map §9.0.3 item 6): the asterisk is
   colour-plus-glyph, and colour alone is not a sufficient indicator. Every
   required field must ALSO carry `required` on the control and a
   visually-hidden "(required)" in the label. The partial markup in §10 and
   the class API do this; do not drop it.
   ========================================================================== */

.dm-page .dm-field {
	display: flex;
	flex-direction: column;
	gap: 6px; /* kit: 6 — off-scale, see §0.1 */
	font-family: var(--font-sans);
}

.dm-page .dm-field__label {
	color: var(--text-primary);
	font-size: var(--text-mono-label); /* the 13px step; kit: 13 */
	font-weight: var(--weight-semibold); /* kit: 600 */
	line-height: var(--leading-snug);
}

/* The visible marker. Pair it with a visually-hidden "(required)". */
.dm-page .dm-field__required {
	color: var(--color-danger);
}

.dm-page .dm-field__help {
	color: var(--text-secondary);
	font-size: var(--text-caption); /* 12px */
	line-height: var(--leading-normal);
}

.dm-page .dm-field__error {
	color: var(--color-danger);
	font-size: var(--text-caption); /* kit: 12 */
	font-weight: var(--weight-medium);
	line-height: var(--leading-normal);
}


/* --- 6.1 Text controls --------------------------------------------------- */

/*
 * A11Y (1.4.11 Non-text Contrast, Agent 8) — CONFIRMED FAILURE, FIXED.
 *
 * The kit's control boundary is --border-default (--ink-200 #c3c8cc). Measured
 * in the browser on the contact card: the input fill is --bg-surface (#ffffff),
 * the card behind it is also --bg-surface, so fill-vs-surround is 1.00:1 and
 * the ONLY thing that identifies the control is that hairline — which measures
 * 1.69:1 against both sides. WCAG 2.2 AA requires 3:1 for the visual
 * information needed to identify a user interface component, and a white box
 * on a white card behind a 1.69:1 line is the canonical failing example.
 *
 * --ink-400 measures 3.74:1 on --bg-surface and 3.28:1 on --bg-sunken, so it
 * clears 3:1 on every ground a control sits on in this theme.
 *
 * SCOPE OF THE CHANGE IS DELIBERATELY NARROW. Only form CONTROLS move.
 * --border-default still draws every card, rule and panel hairline, because a
 * card is a container, not a component whose boundary has to be identifiable —
 * 1.4.11 does not reach it, and widening the change would repaint the brand.
 *
 * DESIGN-SYSTEM NOTE FOR THE OPERATOR: neither --border-default (1.69:1) nor
 * --border-strong (2.38:1) reaches 3:1 on white. tokens.css is untouched per
 * the build brief; the semantic border aliases need an operator ruling.
 *
 * §10.3 carries the same value on Formidable's DOM. The two are a matched
 * pair — change one and change the other.
 */
.dm-page .dm-input,
.dm-page .dm-select,
.dm-page .dm-textarea {
	display: block;
	width: 100%;
	padding: 10px var(--space-3); /* kit: 10px 12px */
	border: var(--border-width-default) solid var(--ink-400);
	border-radius: var(--radius-md); /* 6px */
	background-color: var(--bg-surface);
	color: var(--text-primary);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm); /* kit: 14 */
	line-height: var(--leading-normal);
	transition:
		border-color var(--duration-fast) var(--ease-standard),
		background-color var(--duration-fast) var(--ease-standard);
}

/*
 * Focus. base.css §8 owns the outline and the halo; all this does is move the
 * border to the focus colour, which is the kit's own behaviour. Do NOT add an
 * outline or a box-shadow here — you would be fighting the foundation.
 */
.dm-page .dm-input:focus,
.dm-page .dm-select:focus,
.dm-page .dm-textarea:focus {
	border-color: var(--border-focus);
}

.dm-page .dm-textarea {
	min-height: 7em; /* ~rows="4" at --leading-normal */
	resize: vertical;
}

/*
 * Native select arrow. No background-image, because the brand forbids
 * gradients and the CSP-free way to draw one is a data URI that then has to
 * recolour per state. The UA arrow is accessible, themed by the OS, and
 * honest. Padding-right leaves room for it.
 */
.dm-page .dm-select {
	padding-inline-end: var(--space-8);
}

/* Error state. Set `.has-error` on the wrapper AND `aria-invalid="true"` on
   the control — the attribute is what assistive technology actually reads. */
.dm-page .dm-field.has-error .dm-input,
.dm-page .dm-field.has-error .dm-select,
.dm-page .dm-field.has-error .dm-textarea,
.dm-page .dm-input[aria-invalid="true"],
.dm-page .dm-select[aria-invalid="true"],
.dm-page .dm-textarea[aria-invalid="true"] {
	border-color: var(--color-danger);
}

.dm-page .dm-input:disabled,
.dm-page .dm-select:disabled,
.dm-page .dm-textarea:disabled {
	background-color: var(--bg-sunken);
	color: var(--text-muted);
	cursor: not-allowed;
}

/*
 * Two fields on one row (First name / Last name). One column on a phone,
 * two from 640 — the same rule Formidable's name subfields get in §10.
 */
.dm-page .dm-field-row {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-4); /* kit: 16 */
}

@media (min-width: 640px) {
	.dm-page .dm-field-row--2 {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* The form column itself: 16px between fields, per ContactSection.jsx. */
.dm-page .dm-form {
	display: flex;
	flex-direction: column;
	gap: var(--space-4); /* kit: 16 */
}


/* --- 6.2 Checkbox and radio ---------------------------------------------- */

/*
 * Source: Checkbox.jsx / Radio.jsx — 18px box, accent-color --brand-primary,
 * 8px gap, 14px label. `accent-color` means the native control is themed
 * without being rebuilt out of divs, so it keeps its platform semantics,
 * its indeterminate state and its high-contrast-mode rendering.
 *
 * The whole label is the hit area and is at least 24px tall, so the 18px box
 * is not the target — WCAG 2.2 AA 2.5.8 is met by the row.
 */
.dm-page .dm-choice {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2); /* kit: 8 */
	min-height: var(--space-6); /* 24px target row */
	color: var(--text-primary);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm); /* kit: 14 */
	line-height: var(--leading-normal);
	cursor: pointer;
}

.dm-page .dm-checkbox,
.dm-page .dm-radio {
	flex: 0 0 auto;
	width: 18px; /* kit: 18 */
	height: 18px;
	margin: 0;
	accent-color: var(--brand-primary);
	cursor: pointer;
}

/* A vertical set of choices. */
.dm-page .dm-choice-list {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}


/* --- 6.3 Switch ---------------------------------------------------------- */

/*
 * Source: Switch.jsx — 38x22 pill track, 18px knob, navy when on.
 *
 * TWO DELIBERATE CHANGES FROM THE KIT:
 *
 * 1. The kit's track is a <span> with an onClick. That is not a control: no
 *    keyboard operation, no role, nothing announced. Here the real
 *    <input type="checkbox"> is visually hidden and drives the track through
 *    a sibling selector, so it is focusable, keyboard-operable and announced
 *    as a switch when you add role="switch".
 *
 * 2. The kit transitions the knob's `left`. The motion law is colour and
 *    background only, so the knob MOVES INSTANTLY and only the track colour
 *    transitions. This is the correct reading of a brand that bans transform
 *    and choreography; a 120ms slide would be the thin end of it.
 */
.dm-page .dm-switch {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3); /* kit: 10 -> nearest token, 12 */
	/* 2.5.8 (Agent 8): the kit's track is 38x22 and the 14px label row is 21px,
	   so the whole control measured under the 24x24 minimum. The label is the
	   hit area, so raising it is what the criterion actually needs; the track
	   keeps the kit's 38x22. Unused by any template today. */
	min-height: var(--space-6); /* 24px — WCAG 2.2 AA 2.5.8 */
	color: var(--text-primary);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm);
	cursor: pointer;
}

/* Visually hidden, but still focusable and still the real control. */
.dm-page .dm-switch__input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.dm-page .dm-switch__track {
	position: relative;
	display: inline-block;
	flex: 0 0 auto;
	width: 38px; /* kit: 38 */
	height: 22px; /* kit: 22 */
	border-radius: var(--radius-pill); /* switches are the third pill case */
	/* 1.4.11 (Agent 8): --ink-200 measures 1.69:1 on white, so an OFF switch
	   was the only visual marker of a control and did not reach 3:1. A hairline
	   in --ink-400 (3.74:1 on white) gives the track an identifiable boundary
	   while keeping the kit's light off-state fill. */
	border: var(--border-width-hairline) solid var(--ink-400);
	background-color: var(--ink-200);
	transition: background-color var(--duration-fast) var(--ease-standard);
}

/*
 * top/left are 1px, not the kit's 2px, BECAUSE the track now carries a 1px
 * border (see above). The knob's containing block is the track's PADDING box,
 * i.e. inside that border, so 1px here renders as the kit's 2px gap measured
 * from the track's outer edge. Measured after the change: 2px on all four
 * sides in both states. The checked offset below moved for the same reason.
 */
.dm-page .dm-switch__knob {
	position: absolute;
	top: 1px;
	left: 1px;
	width: 18px; /* kit: 18 */
	height: 18px;
	border-radius: var(--radius-pill);
	background-color: var(--ink-0);
	box-shadow: var(--shadow-xs);
	/* No transition: see the note above. */
}

.dm-page .dm-switch__input:checked + .dm-switch__track {
	background-color: var(--brand-primary);
}

.dm-page .dm-switch__input:checked + .dm-switch__track .dm-switch__knob {
	left: 17px; /* kit: 18, less the track's 1px border — see .dm-switch__knob */
}

.dm-page .dm-switch__input:disabled + .dm-switch__track {
	opacity: 0.5;
	cursor: not-allowed;
}

/*
 * The focus ring belongs on the track, because the input itself is 1px and
 * clipped. focus-radius (§0.3): keep the pill rather than base.css's 6px.
 */
.dm-page .dm-switch__input:focus-visible + .dm-switch__track {
	outline: var(--border-width-strong) solid var(--border-focus);
	outline-offset: 2px;
	border-radius: var(--radius-pill);
	box-shadow: var(--shadow-focus);
}


/* ==========================================================================
   7. Tabs — .dm-tabs
   --------------------------------------------------------------------------
   Source: components/navigation/Tabs.jsx, plus docs/content-map.md §9.2.

   Kit spec: flex row, 4px gap, 1px --border-default bottom rule on the strip.
   Each tab 10px/16px padding, 14px, weight 600. Active is --brand-primary
   with a 2px --brand-primary bottom border; inactive is --text-secondary with
   a transparent 2px border. margin-bottom -1px so the active underline sits
   on top of the strip rule rather than beside it.

   THE KIT HAS NO ARIA AT ALL. The brief requires role="tablist" / role="tab" /
   aria-selected / aria-controls / role="tabpanel" / roving tabindex. The CSS
   below hangs the active state off `aria-selected="true"` ON PURPOSE, so the
   visual state and the accessible state physically cannot drift apart — if
   the JS forgets the ARIA, the tab also stops looking selected.
   ========================================================================== */

.dm-page .dm-tabs__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1); /* kit: 4 */
	margin: 0;
	padding: 0;
	border-bottom: var(--border-width-hairline) solid var(--border-default);
	list-style: none;
}

.dm-page .dm-tabs__tab {
	/* -1px pulls the 2px underline over the strip's hairline. */
	margin-bottom: -1px;
	padding: 10px var(--space-3); /* kit: 10px 16px; 12px inline on a phone */
	border: 0;
	border-bottom: var(--border-width-strong) solid transparent;
	background: none;
	color: var(--text-secondary);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm); /* kit: 14 */
	font-weight: var(--weight-semibold); /* kit: 600 */
	line-height: var(--leading-normal);
	text-align: center;
	text-decoration: none;
	cursor: pointer;
	transition:
		color var(--duration-fast) var(--ease-standard),
		border-color var(--duration-fast) var(--ease-standard);
}

.dm-page .dm-tabs__tab:hover {
	color: var(--brand-primary);
	text-decoration: none;
}

.dm-page .dm-tabs__tab[aria-selected="true"] {
	border-bottom-color: var(--brand-primary);
	color: var(--brand-primary);
}

/*
 * focus-radius (§0.3): a tab is a flat-bottomed strip item, so the 6px
 * rounding base.css applies on focus looks like a bug. Square it back.
 */
.dm-page .dm-tabs__tab:focus-visible {
	border-radius: 0;
	outline-offset: -2px;
}

.dm-page .dm-tabs__panel {
	/* content-map §9.2: a 24px spacer between the strip and the panel. */
	padding-block-start: var(--space-6);
}

/*
 * A panel that is given tabindex="0" — the APG pattern for a panel with NO
 * focusable content — must not paint a ring when it is focused by script or by
 * a mouse click. It MUST still paint one for a keyboard user.
 *
 * :focus:not(:focus-visible), not a bare :focus (Agent 8). The bare form was
 * the same trap base.css section 9 carried on [tabindex="-1"]: at (0,3,0) it
 * outranks base.css section 8's ring at (0,2,1), so the day someone follows
 * content-map section 9.2's drafted "focusable container" note and adds
 * tabindex="0" here, they get a tab stop with no visible focus indicator and
 * nothing warns them. page-contact.php currently omits the tabindex on purpose
 * — both panels are whole forms — so this is prevention, not a live defect.
 * (0,4,0) still beats base.css, and yields the moment :focus-visible matches.
 */
.dm-page .dm-tabs__panel:focus:not(:focus-visible) {
	outline: none;
}

@media (min-width: 640px) {
	.dm-page .dm-tabs__tab {
		padding-inline: var(--space-4); /* kit: 16 */
	}
}


/* ==========================================================================
   8. Feedback — tooltip, toast, dialog
   --------------------------------------------------------------------------
   Source: components/feedback/*.jsx. The marketing site may use none of
   these; they are here so the component set is complete and so a later
   client-portal kit has somewhere to start. Kept compact on purpose.
   ========================================================================== */

/* --- 8.1 Tooltip --------------------------------------------------------- */

/*
 * Hover AND focus-within, because the kit's mouse-only tooltip is unreachable
 * from a keyboard. The bubble is a plain navy plate — no blur, no arrow
 * pseudo-element, no fade.
 */
.dm-page .dm-tooltip {
	position: relative;
	display: inline-flex;
}

.dm-page .dm-tooltip__bubble {
	position: absolute;
	bottom: calc(100% + 6px);
	left: 50%;
	z-index: 10;
	display: none;
	/* The one translate in this file, and it is static positioning maths on a
	   hidden element, not an animation. */
	transform: translateX(-50%);
	padding: 5px var(--space-2); /* kit: 5px 8px */
	border-radius: var(--radius-sm);
	background-color: var(--navy-950);
	color: var(--ink-0);
	font-family: var(--font-mono);
	font-size: 12px; /* kit: 12 */
	line-height: var(--leading-snug);
	white-space: nowrap;
}

.dm-page .dm-tooltip:hover .dm-tooltip__bubble,
.dm-page .dm-tooltip:focus-within .dm-tooltip__bubble {
	display: block;
}

/* --- 8.2 Toast ----------------------------------------------------------- */

/*
 * KIT CONFLICT, RESOLVED AGAINST THE KIT.
 * Toast.jsx draws tone with `borderLeft: 3px solid <accent>`. The design
 * system README states, without qualification: "Borders never appear as a
 * colored left-accent strip — avoid that pattern entirely." The component
 * violates the brand's own rule.
 *
 * Resolution: the tone is carried by a small mono status dot instead — which
 * the README explicitly sanctions ("small mono status dots") — over a full
 * hairline border. Same information, same colour token, no banned pattern.
 */
.dm-page .dm-toast {
	display: flex;
	align-items: center;
	gap: var(--space-3); /* kit: 10 -> 12 */
	max-width: 360px; /* kit: 360 */
	padding: var(--space-3) var(--space-4); /* kit: 12px 16px */
	border: var(--border-width-hairline) solid var(--navy-800);
	border-radius: var(--radius-md);
	background-color: var(--navy-950);
	box-shadow: var(--shadow-lg);
	color: var(--ink-0);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm); /* kit: 14 */
}

.dm-page .dm-toast__dot {
	flex: 0 0 auto;
	width: var(--space-2);
	height: var(--space-2);
	border-radius: var(--radius-pill);
	background-color: var(--teal-400); /* info, per Toast.jsx's info accent */
}

.dm-page .dm-toast--success .dm-toast__dot {
	background-color: var(--color-success);
}

.dm-page .dm-toast--danger .dm-toast__dot {
	background-color: var(--color-danger);
}

.dm-page .dm-toast__message {
	flex: 1 1 auto;
}

/* 2.5.8 (Agent 8): this shipped as a bare <button> with no box, so its target
   was the glyph's own 14px line box. Unused by any template today. */
.dm-page .dm-toast__close {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: var(--space-6);  /* 24px — WCAG 2.2 AA 2.5.8 */
	height: var(--space-6);
	color: var(--ink-300);
	font-size: var(--text-body-sm);
	line-height: 1;
}

.dm-page .dm-toast__close:hover {
	color: var(--ink-0);
}

/* --- 8.3 Dialog ---------------------------------------------------------- */

/*
 * The scrim is the ONLY translucent surface the brand permits, and it is
 * solid alpha — no backdrop-filter, no blur. rgb(10 20 30 / 0.55) is
 * --navy-950 at 55%, exactly as the README specifies it; it is written as a
 * literal because a token cannot carry an alpha channel.
 */
.dm-page .dm-dialog {
	position: fixed;
	inset: 0;
	z-index: 50;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--space-4);
	background-color: rgb(10 20 30 / 0.55); /* --navy-950 at 55% */
}

.dm-page .dm-dialog__panel {
	display: flex;
	flex-direction: column;
	width: 420px; /* kit: 420 */
	max-width: 100%;
	max-height: 100%;
	overflow: auto;
	border-radius: var(--radius-lg);
	background-color: var(--bg-surface);
	box-shadow: var(--shadow-lg);
	font-family: var(--font-sans);
}

.dm-page .dm-dialog__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-4);
	padding: 18px 22px; /* kit: 18px 22px */
	border-bottom: var(--border-width-hairline) solid var(--border-default);
}

.dm-page .dm-dialog__title {
	margin: 0;
	color: var(--text-primary);
	font-size: var(--text-body); /* kit: 16 */
	font-weight: var(--weight-bold); /* kit: 700 */
}

.dm-page .dm-dialog__body {
	padding: 22px; /* kit: 22 */
	color: var(--text-primary);
	font-size: var(--text-body-sm); /* kit: 14 */
}

.dm-page .dm-dialog__footer {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: var(--space-2); /* kit: 8 */
	padding: 14px 22px; /* kit: 14px 22px */
	border-top: var(--border-width-hairline) solid var(--border-default);
}

/* focus-radius (§0.3): keep the panel's 10px radius when it takes focus. */
.dm-page .dm-dialog__panel:focus-visible {
	border-radius: var(--radius-lg);
}


/* ==========================================================================
   9. Section components
   --------------------------------------------------------------------------
   These are the CSS halves of the partials in template-parts/. They live here
   rather than in pages.css because every one of them is used on two or more
   pages — .dm-page-hero on four, .dm-cta-band on four.

   Each takes its horizontal rhythm from .dm-container (layout.css), so none
   of them sets padding-inline. The kit's `padding: Npx 48px` therefore
   becomes padding-block only; the 48px is the container's gutter.
   ========================================================================== */

/* --- 9.1 Hero (home) — .dm-hero ------------------------------------------ */

/*
 * Hero.jsx: navy-950 field, 96px/48px padding, column flex, 20px gap.
 * Pairs with .dm-on-dark (layout.css) for the colour and the light focus ring.
 *
 * 96px of padding on a 390px screen is a quarter of the viewport before a
 * word appears, so the rhythm steps up rather than starting there — the same
 * approach layout.css §2 takes for .dm-section.
 */
.dm-page .dm-hero {
	padding-block: var(--space-16); /* 64 */
}

.dm-page .dm-hero__inner {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-5); /* kit: 20 */
}

/*
 * The kit uppercases this eyebrow. It does not ship uppercased: BUILD-BRIEF
 * §8.3 settles the contradiction in favour of sentence case, and the base
 * .dm-eyebrow in layout.css already omits text-transform. All this class adds
 * is the hero's teal on navy — 9.14:1.
 */
.dm-page .dm-hero__eyebrow {
	color: var(--teal-300);
}

/*
 * base.css already makes h1 clamp(--text-h1, 6vw, --text-display), which
 * reaches the kit's --text-display on any desktop viewport. That IS the
 * responsive layer, so the size is deliberately not overridden here — only
 * the measure is.
 */
.dm-page .dm-hero__title {
	max-width: 720px; /* kit: 720 */
	margin: 0;
}

.dm-page .dm-hero__lede {
	max-width: 600px; /* kit: 600 */
	margin: 0;
	color: var(--ink-200); /* 11.0:1 on navy-950 */
	font-size: var(--text-body-lg);
	line-height: var(--leading-relaxed);
}

@media (min-width: 640px) {
	.dm-page .dm-hero {
		padding-block: var(--space-20); /* 80 */
	}
}

@media (min-width: 900px) {
	.dm-page .dm-hero {
		padding-block: var(--space-24); /* 96 — the kit's figure */
	}
}


/* --- 9.2 Page hero — .dm-page-hero --------------------------------------- */

/*
 * PageHero.jsx: navy-950, 64px/48px padding, 14px gap, title at --text-h1
 * (NOT --text-display, which is what separates it from the home hero).
 *
 * The description is optional and the About page ships without one — that is
 * the reference case the partial is built against.
 */
.dm-page .dm-page-hero {
	padding-block: var(--space-12); /* 48 */
}

.dm-page .dm-page-hero__inner {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 14px; /* kit: 14 — off-scale, see §0.1 */
}

.dm-page .dm-page-hero__eyebrow {
	color: var(--teal-300);
}

/*
 * Here the base h1 clamp MUST be overridden: it tops out at --text-display,
 * and the page hero's spec is --text-h1. Both ends of the clamp are real
 * tokens, so no new value is introduced and the heading still scales.
 */
.dm-page .dm-page-hero__title {
	max-width: 680px; /* kit: 680 */
	margin: 0;
	font-size: clamp(var(--text-h2), 5vw, var(--text-h1));
}

.dm-page .dm-page-hero__description {
	max-width: 560px; /* kit: 560 */
	margin: 0;
	color: var(--ink-200);
	font-size: var(--text-body-lg);
	line-height: var(--leading-relaxed);
}

@media (min-width: 900px) {
	.dm-page .dm-page-hero {
		padding-block: var(--space-16); /* 64 — the kit's figure */
	}
}


/* --- 9.3 CTA band — .dm-cta-band ----------------------------------------- */

/*
 * CTABand.jsx: navy-800 (NOT navy-950 — it is deliberately a step lighter
 * than the hero so the two dark bands on a page read as different objects),
 * 56px/48px padding, heading left, buttons right, wrap, 24px gap.
 *
 * It carries .dm-on-dark for the white headings and the light focus ring, and
 * overrides only the background. (0,3,0) here beats layout.css's (0,2,0).
 *
 * 56px is not on the token scale, so the rhythm brackets it with real tokens:
 * 48 on a phone, 64 from 900. White on navy-800 measures 13.69:1.
 */
.dm-page .dm-cta-band.dm-on-dark {
	background-color: var(--navy-800);
}

.dm-page .dm-cta-band {
	padding-block: var(--space-12); /* 48 */
}

.dm-page .dm-cta-band__inner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-6); /* kit: 24 */
}

.dm-page .dm-cta-band__heading {
	max-width: 520px; /* kit: 520 */
	margin: 0;
	font-size: var(--text-h4); /* 20px on a phone */
}

@media (min-width: 900px) {
	.dm-page .dm-cta-band {
		padding-block: var(--space-16); /* 64 */
	}

	.dm-page .dm-cta-band__heading {
		font-size: 22px; /* kit: 22 — no token at this step, see §0.1 */
	}
}


/* --- 9.4 Service card — .dm-service-card --------------------------------- */

/*
 * Two shapes, one component, per docs/content-map.md §4.2 and §5.2:
 *
 *   base (Services page)  19px/700 title, badges, NO icon
 *   --teaser (Home page)  17px/600 title, icon slot, NO badges
 *
 * Do not add an icon to the Services cards and do not add badges to the Home
 * cards — Agent 2 called out both explicitly.
 *
 * Layered on .dm-card, so the surface/hairline/radius/shadow/padding all come
 * from §3 and are guaranteed identical to every other card on the site.
 */
.dm-page .dm-service-card {
	gap: 10px; /* kit: 10 — off-scale, see §0.1 */
}

.dm-page .dm-service-card__title {
	margin: 0;
	color: var(--text-primary);
	font-size: 19px; /* kit: 19 — no token at this step */
	font-weight: var(--weight-bold); /* kit: 700 */
	line-height: var(--leading-snug);
}

.dm-page .dm-service-card__text {
	margin: 0;
	color: var(--text-secondary);
	font-size: var(--text-body-sm); /* kit: 14 */
	line-height: var(--leading-relaxed);
}

.dm-page .dm-service-card .dm-badge-list {
	margin-block-start: var(--space-1); /* kit: marginTop 4 */
}

/*
 * The Home teaser card's icon block. In the kit this is an EMPTY 40x40
 * navy-50 square — there is no icon set and none has been invented. It is a
 * styled slot waiting for a real icon (gap G24); centring is already in place
 * so dropping a 20px Lucide glyph inside needs no further CSS.
 */
.dm-page .dm-service-card__icon {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 40px; /* kit: 40 */
	height: 40px;
	margin-block-end: var(--space-2);
	border-radius: var(--radius-md);
	background-color: var(--navy-50);
	color: var(--brand-primary);
}

.dm-page .dm-service-card__icon svg {
	width: 20px;
	height: 20px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.dm-page .dm-service-card--teaser {
	gap: var(--space-2); /* kit: 8 between title and text */
}

.dm-page .dm-service-card--teaser .dm-service-card__title {
	font-size: 17px; /* kit: 17 */
	font-weight: var(--weight-semibold); /* kit: 600 */
}


/* --- 9.5 Case-study card — .dm-case-study-card --------------------------- */

/*
 * WorkGrid.jsx: no padding on the container, image flush to the edge,
 * overflow hidden so the card's 10px radius clips the image's top corners,
 * then a --space-6 text block below.
 *
 * The kit's image is a fixed 200px tall in a ~580px-wide card — 2.9:1. That
 * ratio is preserved as an aspect-ratio rather than a fixed height so the
 * image scales with the card instead of getting letterboxed on a phone.
 */
.dm-page .dm-case-study-card__media {
	display: block;
	overflow: hidden;
	background-color: var(--bg-sunken);
}

.dm-page .dm-case-study-card__media .dm-media__img {
	aspect-ratio: 29 / 10; /* the kit's 580x200 */
	width: 100%;
	object-fit: cover;
}

.dm-page .dm-case-study-card__title {
	margin: 0;
	color: var(--text-primary);
	font-size: 17px; /* kit: 17 */
	font-weight: var(--weight-semibold); /* kit: 600 */
	line-height: var(--leading-snug);
}

/*
 * content-map §6.2: link the TITLE, not the whole card — a whole-card link
 * needs its own accessible name and swallows the tag links inside it.
 */
/*
 * inline-block + min-height is WCAG 2.2 AA 2.5.8 Target Size (Agent 8).
 * Measured at 171.6 x 22 on the Work page, the case-study archive and the tag
 * archive: 17px at --leading-snug is a 22.1px line box, and this link is the
 * whole heading rather than a phrase inside a sentence, so the inline
 * exception does not apply to it. inline-block rather than inline-flex on
 * purpose — a two-line title must still wrap, which a flex container would
 * stop it doing. A title that already wraps is well over 24px and this changes
 * nothing for it.
 */
.dm-page .dm-case-study-card__title a {
	display: inline-block;
	min-height: var(--space-6); /* 24px — WCAG 2.2 AA 2.5.8 */
	color: inherit;
	text-decoration: none;
	/*
	 * `anywhere`, not `break-word`. base.css §5 already sets `break-word` on
	 * body, but per CSS Text 3 that does NOT reduce the min-content intrinsic
	 * width — only `anywhere` does. This anchor is `inline-block` (load-bearing:
	 * it supplies the 24px target above), so it is shrink-to-fit and sizes to
	 * its widest unbreakable token. A case-study title containing one long
	 * spaceless word — an ordinary product or system name — therefore overhangs
	 * the card and is clipped at the viewport edge on a phone.
	 * Same treatment as the project link in pages.css.
	 */
	overflow-wrap: anywhere;
}

.dm-page .dm-case-study-card__title a:hover {
	color: var(--text-link);
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

.dm-page .dm-case-study-card__text {
	margin: 0;
	color: var(--text-secondary);
	font-size: var(--text-body-sm); /* kit: 14 */
	line-height: var(--leading-relaxed);
}

.dm-page .dm-case-study-card .dm-tag-list {
	margin-block-start: var(--space-1); /* kit: marginTop 4 */
}


/* --- 9.6 Value — .dm-value ----------------------------------------------- */

/*
 * AboutSection.jsx values row. content-map §7.3 is emphatic: these are NOT
 * cards — no surface, no border, no shadow, no padding. Plain columns.
 *
 * CONTRAST FIX (§0.2c): the kit's label is --brand-accent (teal-500) on
 * --bg-sunken = 3.91:1 at 13px. --teal-600 measures 5.36:1 on sunken and
 * 6.13:1 on white, so it is safe wherever a page agent puts this block.
 *
 * Note --tracking-wide here, NOT the --tracking-widest the hero eyebrows use.
 * And no text-transform, per BUILD-BRIEF §8.3.
 */
.dm-page .dm-value {
	display: flex;
	flex-direction: column;
	gap: var(--space-2); /* kit: 8 */
}

.dm-page .dm-value__label {
	margin: 0;
	color: var(--teal-600);
	font-family: var(--font-mono);
	font-size: var(--text-mono-label); /* 13px; kit: 13 */
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-wide);
	line-height: var(--leading-snug);
}

.dm-page .dm-value__text {
	margin: 0;
	color: var(--text-secondary);
	font-size: var(--text-body-sm); /* kit: 14 */
	line-height: var(--leading-relaxed);
}


/* --- 9.7 Process — .dm-process ------------------------------------------- */

/*
 * ServicesDetail.jsx process band: centred flex row, 32px gap, wrapping,
 * each step a 140px centred column.
 *
 * Ships as an <ol> because it is a sequence (content-map §5.3). The visible
 * "01" string is content, so the list marker is suppressed — otherwise every
 * step reads "1. 01 Discovery".
 *
 * Wrapping gives the mobile layout for free: four 140px steps with a 32px gap
 * lay out 2 x 2 at 390px and 4-up from roughly 740px. No breakpoint needed.
 *
 * CONTRAST FIX (§0.2c): the number is --teal-600, not --brand-accent.
 */
.dm-page .dm-process {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-8); /* kit: 32 */
	margin: 0;
	padding: 0;
	list-style: none;
}

.dm-page .dm-process__step {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px; /* kit: 6 */
	width: 140px; /* kit: 140 */
	text-align: center;
}

.dm-page .dm-process__number {
	display: block;
	margin: 0;
	color: var(--teal-600);
	font-family: var(--font-mono);
	font-size: var(--text-mono-label); /* 13px; kit: 13 */
	font-variant-numeric: tabular-nums;
	line-height: var(--leading-snug);
}

.dm-page .dm-process__label {
	margin: 0;
	color: var(--text-primary);
	font-size: 15px; /* kit: 15 — no token at this step */
	font-weight: var(--weight-semibold); /* kit: 600 */
	line-height: var(--leading-snug);
}

/*
 * There is no step description in the kit and none has been written (gap
 * G31). The rule exists so that when the operator supplies the copy it lands
 * styled instead of falling back to a browser default — a class referenced by
 * a template but defined in no stylesheet is invisible to every static check.
 */
.dm-page .dm-process__text {
	margin: 0;
	color: var(--text-secondary);
	font-size: var(--text-body-sm);
	line-height: var(--leading-relaxed);
}


/* ==========================================================================
   10. FORMIDABLE FORMS OVERRIDE LAYER
   --------------------------------------------------------------------------
   *** AGENT 6: THIS IS YOURS. READ ALL OF IT BEFORE STYLING THE CONTACT PAGE.
       You should not have to write a single Formidable rule yourself. ***

   WHY THIS EXISTS
   BUILD-BRIEF §8.2: the Contact page embeds the EXISTING Formidable forms by
   shortcode — `[formidable id=3]` and `[formidable id=4]` — so that every
   entry, notification, reCAPTCHA key and the 10MB upload keep working through
   cutover. That means the DOM on the contact page is Formidable's, not ours,
   and the kit's ContactSection.jsx is a visual spec only (its field list is
   wrong — see content-map §9.0).

   So the controls in §6 have to be re-expressed against the plugin's class
   names. §6 and §10 are a MATCHED PAIR: change a padding or a radius in one
   and change it in the other, or the contact page will visibly drift from
   every other form on the site.

   NEVER DEQUEUE FORMIDABLE'S STYLESHEET (BUILD-BRIEF §8.4.3). It carries the
   error-state and Dropzone behaviour. This layer overrides it. It also never
   uses !important — it wins on specificity alone.

   ---------------------------------------------------------------------------
   ANCHORED ON FORMIDABLE'S OWN WRAPPER — AND WHY IT CHANGED
   ---------------------------------------------------------------------------

   This layer used to be anchored on a theme-supplied `<div class="dm-formidable">`
   around the shortcode. That was DEAD CODE on the live site and nobody noticed
   for three releases.

   The contact page is built in Elementor, and the class was set through the
   layout's `_css_classes` setting. Elementor 4.2.4 does not emit that key on
   CONTAINERS: measured on the live page,
   `document.querySelectorAll('.dm-formidable').length === 0`. So every rule
   below matched nothing, the contact form rendered in raw Formidable styling,
   and keyboard focus had no visible indicator at all.

   It is now anchored on the plugin's own wrapper, which is always in the DOM:

       <div class="frm_forms with_frm_style frm_style_... " id="frm_form_3_container">

   Specificity: `.dm-page` + `.frm_forms.with_frm_style` + `.frm_form_field`
   + `input:focus-visible` = (0,5,1). Formidable's own
   `.frm_style_big-text.with_frm_style .form-field input:focus { outline: none }`
   is (0,4,1), so we win — which is the whole point, because that rule is what
   was removing the focus ring.

   TRADE-OFF, ACCEPTED: `with_frm_style` is only added while Formidable's own
   styling is enabled. If someone turns styling off in the Formidable admin,
   these rules stop applying — but so does the plugin rule they exist to beat,
   so the theme's base focus ring takes over and the form stays usable.

   Do not re-introduce a dependency on a theme-authored class here unless you
   have confirmed it actually reaches the rendered DOM.

   ---------------------------------------------------------------------------
   CLASSES COVERED — all confirmed in Formidable / Formidable Pro markup
   ---------------------------------------------------------------------------
     .frm_form_field            per-field wrapper
     .frm_primary_label         the field label
     .frm_required              the required asterisk inside the label
     .frm_description           help text under a control
     .frm_error                 per-field validation message
     .frm_blank_field           added to the WRAPPER of a failed required field
     .frm_error_style           form-level error banner
     .frm_form_subfield-first   name field, first-name half
     .frm_form_subfield-last    name field, last-name half
     .frm_dropzone              Formidable Pro Dropzone uploader
     .frm_submit                submit wrapper
     .frm_button_submit         the submit button itself
     plus the option containers (.frm_opt_container, .frm_checkbox,
     .frm_radio) and the plugin's own screen-reader helper.

   ---------------------------------------------------------------------------
   WHAT AGENT 6 STILL OWNS (CSS cannot do these)
   ---------------------------------------------------------------------------
     1. The tab shell around the two forms — markup, ARIA, and the JS. §7 has
        the styling; the roles and roving tabindex are yours.
     2. Anything inside Formidable's own form CONFIG — field labels, the
        required flag, the prompt option on the request-type select. Those are
        edited in the Formidable admin, never in theme CSS (BUILD-BRIEF §8.3
        exempts them from the sentence-case pass for exactly this reason).
     3. The `Customer or Business Name` label wording (content-map §9.0.2) and
        the five missing fields on form 4 — admin work, not CSS.
   ========================================================================== */

/* --- 10.1 Form shell ----------------------------------------------------- */

.dm-page .frm_forms.with_frm_style {
	font-family: var(--font-sans);
}

/* Formidable's fieldset carries a UA border and padding; base.css zeroes bare
   fieldsets but the plugin re-adds them at higher specificity. */
.dm-page .frm_forms.with_frm_style fieldset {
	margin: 0;
	padding: 0;
	border: 0;
}

/* Field rhythm — the 16px column gap from ContactSection.jsx, expressed as a
   margin because Formidable's fields are siblings, not flex children. */
.dm-page .frm_forms.with_frm_style .frm_form_field {
	margin-block-end: var(--space-4); /* 16 */
}

.dm-page .frm_forms.with_frm_style .frm_form_field:last-child {
	margin-block-end: 0;
}


/* --- 10.2 Labels and the required marker --------------------------------- */

/*
 * Mirrors .dm-field__label exactly: 13px / 600 / --text-primary.
 * `display: block` so the control always starts on its own line, whatever
 * inline layout the plugin's theme had in mind.
 */
.dm-page .frm_forms.with_frm_style .frm_primary_label {
	display: block;
	margin-block-end: 6px; /* the .dm-field 6px gap */
	padding: 0;
	color: var(--text-primary);
	font-family: var(--font-sans);
	font-size: var(--text-mono-label); /* 13px */
	font-weight: var(--weight-semibold);
	line-height: var(--leading-snug);
}

/*
 * The asterisk. --color-danger measures 6.07:1 on white, so the marker is
 * legible on its own — but colour is never the only signal: Formidable also
 * puts `required` and aria-required on the control, and content-map §9.0.3
 * item 6 requires a visible "Required" affordance to survive at 4.5:1. This
 * rule makes the glyph itself pass; the belt-and-braces text is form config.
 */
.dm-page .frm_forms.with_frm_style .frm_required {
	color: var(--color-danger);
	font-weight: var(--weight-semibold);
}

/* The plugin's sub-labels under a combo field ("First", "Last"). */
.dm-page .frm_forms.with_frm_style .frm_form_subfield-first .frm_primary_label,
.dm-page .frm_forms.with_frm_style .frm_form_subfield-last .frm_primary_label {
	margin-block: var(--space-1) 0;
	color: var(--text-secondary);
	font-size: var(--text-caption); /* 12px */
	font-weight: var(--weight-regular);
}

/* Help text under a control — same spec as .dm-field__help. */
.dm-page .frm_forms.with_frm_style .frm_description {
	margin-block-start: var(--space-1);
	color: var(--text-secondary);
	font-size: var(--text-caption);
	line-height: var(--leading-normal);
}


/* --- 10.3 Text controls -------------------------------------------------- */

/*
 * The §6.1 spec, re-expressed on Formidable's DOM. Every attribute selector
 * is listed rather than using a bare `input`, because `input[type=checkbox]`
 * and `input[type=radio]` must NOT get a 100%-wide text-field box.
 */
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="text"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="email"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="tel"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="url"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="number"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="password"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="search"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="date"],
.dm-page .frm_forms.with_frm_style .frm_form_field textarea,
.dm-page .frm_forms.with_frm_style .frm_form_field select {
	display: block;
	width: 100%;
	max-width: 100%;
	height: auto; /* the plugin sets a fixed height on inputs */
	padding: 10px var(--space-3); /* 10px 12px — matches .dm-input */
	/* --ink-400, not --border-default: 1.4.11 needs 3:1 on a control boundary
	   and --border-default measures 1.69:1 white-on-white. See §6.1. */
	border: var(--border-width-default) solid var(--ink-400);
	border-radius: var(--radius-md); /* 6px */
	background-color: var(--bg-surface);
	color: var(--text-primary);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm); /* 14px */
	line-height: var(--leading-normal);
	box-shadow: none; /* the plugin ships an inset shadow on inputs */
	transition: border-color var(--duration-fast) var(--ease-standard);
}

.dm-page .frm_forms.with_frm_style .frm_form_field textarea {
	min-height: 7em;
	resize: vertical;
}

.dm-page .frm_forms.with_frm_style .frm_form_field select {
	padding-inline-end: var(--space-8);
}

/*
 * Focus.
 *
 * Formidable sets `outline: none` on focused controls in its own stylesheet,
 * and we deliberately do not dequeue that stylesheet (it carries error-state
 * and Dropzone behaviour). Omitting the rule here therefore does NOT remove
 * it — whether base.css §8's ring survives on this page is purely a question
 * of specificity against a selector we do not control.
 *
 * So restore the ring EXPLICITLY rather than relying on base.css reaching
 * this far. The `.dm-page .frm_forms.with_frm_style .frm_form_field` chain plus
 * :focus-visible lands at (0,4,1), matching the most specific plausible shape
 * of the plugin's own rule; enqueue priority 20 prints this sheet after
 * Formidable's, so an exact tie still resolves to us.
 *
 * :focus-visible, not :focus — a mouse click must not leave a ring behind.
 * The :focus rule below only moves the border, which is correct for both.
 *
 * VERIFY AT LAUNCH QA against the real formidableforms.css: confirm a keyboard
 * Tab into each control shows the 2px navy ring. This was modelled against a
 * reconstruction of the plugin's CSS, not the plugin itself.
 */
.dm-page .frm_forms.with_frm_style .frm_form_field input:focus-visible,
.dm-page .frm_forms.with_frm_style .frm_form_field textarea:focus-visible,
.dm-page .frm_forms.with_frm_style .frm_form_field select:focus-visible {
	outline: var(--border-width-strong) solid var(--border-focus);
	outline-offset: 2px;
	box-shadow: var(--shadow-focus);
}

.dm-page .frm_forms.with_frm_style .frm_form_field input:focus,
.dm-page .frm_forms.with_frm_style .frm_form_field textarea:focus,
.dm-page .frm_forms.with_frm_style .frm_form_field select:focus {
	border-color: var(--border-focus);
}

/*
 * THE SUBMIT BUTTON AND THE DROPZONE WERE OUTSIDE THE RESTORE ABOVE (Agent 8).
 *
 * The rule above is anchored on .frm_form_field, and neither of these lives
 * inside one: the submit button is in .frm_submit, and the Dropzone well is a
 * sibling of the field's controls. So on this page the most important control
 * on the site — the button that files a lead — was relying on base.css §8
 * reaching it at (0,2,1). Any Formidable rule of the shape
 * `.with_frm_style .frm_form .frm_submit button:focus { outline: none }`
 * (0,3,1) would beat that and take the ring away, and that stylesheet is
 * deliberately not dequeued.
 *
 * These land at (0,5,1) / (0,4,1) and print after the plugin's sheet, so they
 * win on specificity and again on order. Colours and offset are base.css §8's,
 * restated — not a different ring, the same one, reasserted where the chain
 * above could not reach.
 *
 * VERIFY AT LAUNCH QA with the real formidableforms.css, same as the rule
 * above: Tab onto the submit button and onto the upload well and confirm the
 * 2px navy ring appears.
 */
.dm-page .frm_forms.with_frm_style .frm_submit .frm_button_submit:focus-visible,
.dm-page .frm_forms.with_frm_style .frm_submit input[type="submit"]:focus-visible,
.dm-page .frm_forms.with_frm_style .frm_submit button[type="submit"]:focus-visible,
.dm-page .frm_forms.with_frm_style .frm_form_field .frm_dropzone:focus-visible {
	outline: var(--border-width-strong) solid var(--border-focus);
	outline-offset: 2px;
	box-shadow: var(--shadow-focus);
}


/* --- 10.4 Name subfields (first / last) ---------------------------------- */

/*
 * Both live forms use Formidable's combo Name field, which renders
 * .frm_form_subfield-first and .frm_form_subfield-last inside one wrapper.
 * The kit puts First and Last in a 2-column grid with a 16px gap — matched
 * here, and mobile-first: stacked below 640, side by side above it.
 *
 * Formidable floats these with its own grid classes (.frm6, .frm_grid_first,
 * .frm_grid). Grid on the parent neutralises the float layout cleanly, and
 * the width reset stops the plugin's percentage widths fighting the columns.
 */
.dm-page .frm_forms.with_frm_style .frm_combo_inputs_container {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-4); /* 16 */
}

.dm-page .frm_forms.with_frm_style .frm_form_subfield-first,
.dm-page .frm_forms.with_frm_style .frm_form_subfield-last {
	width: auto;
	margin: 0;
	padding: 0;
	float: none;
}

@media (min-width: 640px) {
	.dm-page .frm_forms.with_frm_style .frm_combo_inputs_container {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}


/* --- 10.5 Error states --------------------------------------------------- */

/*
 * .frm_blank_field is added by the plugin to the field WRAPPER when a
 * required field is submitted empty, so it is the hook for the red border —
 * and it is why Formidable's stylesheet must never be dequeued: this class,
 * and the message text inside .frm_error, are plugin behaviour, not ours.
 */
.dm-page .frm_forms.with_frm_style .frm_blank_field input[type="text"],
.dm-page .frm_forms.with_frm_style .frm_blank_field input[type="email"],
.dm-page .frm_forms.with_frm_style .frm_blank_field input[type="tel"],
.dm-page .frm_forms.with_frm_style .frm_blank_field input[type="url"],
.dm-page .frm_forms.with_frm_style .frm_blank_field input[type="number"],
.dm-page .frm_forms.with_frm_style .frm_blank_field textarea,
.dm-page .frm_forms.with_frm_style .frm_blank_field select {
	border-color: var(--color-danger);
	background-color: var(--bg-surface);
}

.dm-page .frm_forms.with_frm_style .frm_blank_field .frm_primary_label {
	color: var(--text-primary);
}

/* Per-field message — matches .dm-field__error. */
.dm-page .frm_forms.with_frm_style .frm_error {
	margin-block-start: var(--space-1);
	color: var(--color-danger);
	font-family: var(--font-sans);
	font-size: var(--text-caption); /* 12px */
	font-weight: var(--weight-medium);
	line-height: var(--leading-normal);
}

/*
 * Form-level error banner. A full hairline box in the danger palette — NOT a
 * coloured left-accent strip, which the brand forbids outright.
 */
.dm-page .frm_forms.with_frm_style .frm_error_style {
	margin-block-end: var(--space-4);
	padding: var(--space-3) var(--space-4);
	border: var(--border-width-hairline) solid var(--color-danger);
	border-radius: var(--radius-md);
	background-color: var(--color-danger-bg);
	color: var(--color-danger); /* 5.25:1 on its own background */
	font-size: var(--text-body-sm);
	line-height: var(--leading-normal);
}

/* The plugin's success message after an AJAX submit. */
.dm-page .frm_forms.with_frm_style .frm_message {
	padding: var(--space-4);
	border: var(--border-width-hairline) solid var(--color-success);
	border-radius: var(--radius-md);
	background-color: var(--color-success-bg);
	color: var(--color-success); /* 4.79:1 */
	font-size: var(--text-body-sm);
}


/* --- 10.6 Checkboxes, radios and the Dropzone uploader ------------------- */

.dm-page .frm_forms.with_frm_style .frm_opt_container {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

.dm-page .frm_forms.with_frm_style .frm_checkbox label,
.dm-page .frm_forms.with_frm_style .frm_radio label {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	min-height: var(--space-6); /* 24px target row */
	color: var(--text-primary);
	font-size: var(--text-body-sm);
	font-weight: var(--weight-regular);
	line-height: var(--leading-normal);
	cursor: pointer;
}

.dm-page .frm_forms.with_frm_style .frm_form_field input[type="checkbox"],
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="radio"] {
	flex: 0 0 auto;
	width: 18px;
	height: 18px;
	margin: 0;
	accent-color: var(--brand-primary);
	cursor: pointer;
}

/*
 * Formidable Pro's Dropzone uploader (form 4's 10MB file field). Styled as a
 * dashed hairline well over the sunken surface — the same visual language as
 * .dm-image-slot in layout.css, so an empty upload target and an empty image
 * slot read as the same kind of object.
 *
 * ONLY the chrome is restyled. Dropzone's own behaviour classes
 * (.dz-message, .dz-preview, .dz-error) keep the plugin's JavaScript working,
 * which is the second reason its stylesheet must stay enqueued.
 */
.dm-page .frm_forms.with_frm_style .frm_dropzone {
	min-height: var(--space-24); /* 96px — never collapses */
	padding: var(--space-6);
	/* 1.4.11 (Agent 8): --border-strong measured 2.38:1 against the card
	   outside and 2.08:1 against the well's own --bg-sunken fill, and that
	   dashed edge is the only thing marking where the drop target begins.
	   --ink-400 measures 3.74:1 / 3.28:1 on those two grounds. */
	border: var(--border-width-hairline) dashed var(--ink-400);
	border-radius: var(--radius-md);
	background-color: var(--bg-sunken);
	color: var(--text-secondary);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm);
	text-align: center;
	transition:
		border-color var(--duration-fast) var(--ease-standard),
		background-color var(--duration-fast) var(--ease-standard);
}

.dm-page .frm_forms.with_frm_style .frm_dropzone:hover,
.dm-page .frm_forms.with_frm_style .frm_dropzone.dz-drag-hover {
	border-color: var(--border-focus);
	background-color: var(--ink-50);
}

.dm-page .frm_forms.with_frm_style .frm_dropzone .dz-message {
	margin: 0;
	color: var(--text-secondary);
}

/*
 * A11Y (1.4.10 Reflow, Agent 8) — CONFIRMED FAILURE, FIXED.
 *
 * Dropzone keeps a real <input type="file" class="frm_transparent"> inside the
 * well so the field still submits. A file input has no author width, so it
 * takes its intrinsic width from the UA — measured at 286px in Chrome. Inside
 * a 24px-padded well, inside a 24px-padded card, inside the page gutter, that
 * is wider than a 320px viewport: the document measured scrollWidth 352 against
 * clientWidth 305, i.e. horizontal scrolling of the whole page.
 *
 * It is invisible on first paint because tabs.js hides the panel that holds
 * form 4. Switch to the "Current customers" tab at 320px and the page starts
 * scrolling sideways — reproduced by clicking the tab and re-measuring.
 *
 * The listed input types in §10.3 deliberately exclude file, so this is the
 * only rule that reaches it. max-width alone is enough; the well itself is what
 * Dropzone binds its click handler to, so constraining the input changes no
 * behaviour.
 */
.dm-page .frm_forms.with_frm_style .frm_form_field input[type="file"] {
	max-width: 100%;
}

/* The "max 10MB" line the plugin renders under the dropzone. */
.dm-page .frm_forms.with_frm_style .frm_small_text,
.dm-page .frm_forms.with_frm_style .frm_upload_text {
	color: var(--text-secondary);
	font-size: var(--text-caption);
}


/* --- 10.7 Submit button -------------------------------------------------- */

/*
 * The submit button IS the primary CTA ("Submit your project" / "Send us your
 * support request"), so it gets the signal variant — including the §1.4
 * contrast fix. The declarations are repeated rather than referenced because
 * the plugin's markup cannot be given a .dm-btn class from CSS; keep them in
 * sync with §1 and §1.4 by hand.
 *
 * Specificity: (0,4,0) against Formidable's `.with_frm_style .frm_submit
 * button` at (0,2,1). No !important needed.
 */
.dm-page .frm_forms.with_frm_style .frm_submit {
	margin-block-start: var(--space-6);
}

.dm-page .frm_forms.with_frm_style .frm_submit .frm_button_submit,
.dm-page .frm_forms.with_frm_style .frm_submit input[type="submit"],
.dm-page .frm_forms.with_frm_style .frm_submit button[type="submit"] {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-2);
	width: auto;
	padding: 13px 22px; /* .dm-btn--lg */
	border: var(--border-width-default) solid var(--brand-signal);
	border-radius: var(--radius-md);
	background-color: var(--brand-signal);
	color: var(--navy-950); /* the §1.4 fix: 6.60:1, not #fff's 2.81:1 */
	font-family: var(--font-sans);
	font-size: var(--text-body); /* 16 */
	font-weight: var(--weight-semibold);
	line-height: var(--leading-normal);
	text-decoration: none;
	cursor: pointer;
	transition:
		background-color var(--duration-fast) var(--ease-standard),
		border-color var(--duration-fast) var(--ease-standard);
}

.dm-page .frm_forms.with_frm_style .frm_submit .frm_button_submit:hover,
.dm-page .frm_forms.with_frm_style .frm_submit input[type="submit"]:hover,
.dm-page .frm_forms.with_frm_style .frm_submit button[type="submit"]:hover {
	border-color: var(--brand-signal-hover);
	background-color: var(--brand-signal-hover);
	color: var(--navy-950); /* 4.81:1 */
}

.dm-page .frm_forms.with_frm_style .frm_submit .frm_button_submit:active,
.dm-page .frm_forms.with_frm_style .frm_submit input[type="submit"]:active,
.dm-page .frm_forms.with_frm_style .frm_submit button[type="submit"]:active {
	border-color: var(--amber-700);
	background-color: var(--brand-signal-hover);
}

.dm-page .frm_forms.with_frm_style .frm_submit .frm_button_submit:disabled,
.dm-page .frm_forms.with_frm_style .frm_submit button[type="submit"]:disabled {
	opacity: 0.5;
	cursor: not-allowed;
}

/* On a phone the submit button spans the form column. */
@media (min-width: 640px) {
	.dm-page .frm_forms.with_frm_style .frm_submit .frm_button_submit,
	.dm-page .frm_forms.with_frm_style .frm_submit input[type="submit"],
	.dm-page .frm_forms.with_frm_style .frm_submit button[type="submit"] {
		width: auto;
	}
}

/*
 * reCAPTCHA. The widget is a fixed-width third-party iframe (302px) that
 * overflows a 342px-wide card once the card has padding. It is not resizable
 * from here, so it gets a horizontal escape hatch rather than being allowed
 * to push the page wide.
 */
.dm-page .frm_forms.with_frm_style .frm_form_field .g-recaptcha,
.dm-page .frm_forms.with_frm_style .frm_captcha {
	max-width: 100%;
	overflow-x: auto;
}


/* --- 10.9 Formidable honeypot ---------------------------------------------
 *
 * Formidable ships an anti-spam honeypot field labelled "If you are human,
 * leave this field blank." and normally hides it with its own JavaScript.
 *
 * On this site that JS was not loading (see inc/formidable.php), so the field
 * rendered as a visible, labelled, 46px-tall text input directly beneath the
 * submit button -- exposed in the accessibility tree, reachable by Tab, and
 * given a focus ring by our own base styles. A keyboard or screen-reader user
 * tabbing through the form lands on it, and anyone who types in it has their
 * enquiry discarded in silence.
 *
 * Hiding it in CSS is what Formidable itself does, and doing it here means the
 * field stays hidden whether or not the plugin's JavaScript loads.
 *
 * `left: -9999px` rather than `display: none`: some spam bots specifically skip
 * fields that are display:none, which would defeat the honeypot's purpose. This
 * keeps the field in the form and submitted, while removing it from view, from
 * the tab order (tabindex -1 below is not available to CSS, so the field is
 * moved off-screen and shrunk instead), and from the accessibility tree.
 * ------------------------------------------------------------------------- */

.dm-page .frm_verify,
.dm-page .frm_form_field.frm_verify,
.dm-page #frm_field_52_container {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}


/* --- 10.10 Elementor kit bleed into the contact tabs -----------------------
 *
 * The tabs widget inherits --e-global-typography-primary-font-size from the
 * legacy Elementor Kit 8: measured 65px Courier Prime on the live site, which
 * clipped "Current customers" mid-word. Setting the widget's own typography in
 * the layout JSON did not take, so it is pinned here instead.
 *
 * Elementor also draws the active-tab underline with `width: 999em`, which on a
 * 574px tablist produced scrollWidth 65,315px. Clamped.
 * ------------------------------------------------------------------------- */

.dm-page .elementor-widget-tabs .elementor-tab-title,
.dm-page .elementor-widget-tabs .elementor-tab-mobile-title {
	font-family: var(--font-sans);
	font-size: var(--text-body-sm);
	font-weight: var(--weight-semibold);
	line-height: 1.4;
	text-transform: none;
	letter-spacing: 0;
	white-space: nowrap;
}

.dm-page .elementor-widget-tabs .elementor-tab-title::after,
.dm-page .elementor-widget-tabs .elementor-tab-title::before {
	max-width: 100%;
}

.dm-page .elementor-widget-tabs .elementor-tabs-wrapper {
	max-width: 100%;
	overflow: hidden;
}

/* Formidable's required marker ships a red that is neither brand nor semantic. */
.dm-page .frm_forms.with_frm_style .frm_required {
	color: var(--color-danger);
}


/* ===========================================================================
 * 11. INTERACTION STATES ON ELEMENTOR'S DOM
 *
 * WHY THIS SECTION EXISTS
 *
 * Everything above targets the theme's own `.dm-*` markup. On the live site
 * that markup does not exist: the five pages are Elementor layouts, and a DOM
 * audit found ZERO instances of .dm-btn, .dm-card, .dm-tabs or any component
 * class on any page. Buttons render as `.elementor-button`, full stop.
 *
 * The consequence was not cosmetic. There was no :hover, :focus or :active
 * rule for any button anywhere on the site -- measured with a real pointer, the
 * amber CTA was rgb(226,130,47) at rest and rgb(226,130,47) hovered. The brand
 * law does not merely permit those states, it SPECIFIES them ("background
 * darkens one step"). The site was below the floor, not pressing the ceiling.
 *
 * SPECIFICITY BUDGET
 * Elementor prints per-page CSS (post-{N}.css) AFTER this file, at (0,4,0):
 *   .elementor-20 .elementor-element.elementor-element-abc123 .elementor-button
 * So every rule here must reach at least (0,5,0) to win. The `body.dm-page`
 * prefix plus the variant hook does that.
 *
 * ALL TRANSITIONS ARE COLOUR/BACKGROUND ONLY, 120ms, per the brand's motion law.
 * No transform, no opacity, no scale, no choreography.
 * ======================================================================== */

/* --- 11.1 Transition budget ---------------------------------------------- */

body.dm-page .elementor-element .elementor-button {
	transition:
		background-color var(--duration-fast) var(--ease-standard),
		border-color var(--duration-fast) var(--ease-standard),
		color var(--duration-fast) var(--ease-standard);
}

/*
 * Elementor ships every container with `box-shadow .3s, transform .4s`.
 * Both exceed the 180ms ceiling and `transform` is banned by name. It is
 * invisible today only because nothing triggers it. Pin it shut now rather
 * than discover it the first time someone adds a hover.
 */
body.dm-page .elementor-element.e-con {
	transition:
		background-color var(--duration-base) var(--ease-standard),
		border-color var(--duration-base) var(--ease-standard);
}

/* --- 11.2 Signal (amber) — the primary CTA ------------------------------- */

/*
 * WHITE LABEL IN EVERY STATE, per operator request.
 *
 * The label was navy-950 because white on amber-400 measures 2.81:1 and fails
 * AA. Rather than ship a failing primary CTA, the FILL moved one step down the
 * ramp instead: white on amber-600 is 5.47:1, on amber-700 7.91:1. Same hue
 * family, a shade deeper, and the label is white at rest, on hover and on
 * press as asked.
 *
 * Elementor's frontend.min.css still ships `.elementor-button:hover{color:#fff}`
 * — which now agrees with us, but the colour is pinned here anyway so the
 * button does not silently depend on a third-party default.
 */
body.dm-page .dm-btn-signal .elementor-button,
body.dm-page .dm-btn-signal .elementor-button:link,
body.dm-page .dm-btn-signal .elementor-button:visited {
	background-color: var(--amber-600);
	border-color: var(--amber-600);
	color: var(--ink-0);
}

body.dm-page .dm-btn-signal .elementor-button:hover,
body.dm-page .dm-btn-signal .elementor-button:focus-visible {
	background-color: var(--amber-700); /* one step darker, per the brand law */
	border-color: var(--amber-700);
	color: var(--ink-0);
}

body.dm-page .dm-btn-signal .elementor-button:active {
	background-color: #6A360F; /* one further; white holds at 9.78:1 */
	border-color: #6A360F;
	color: var(--ink-0);
}

/* --- 11.3 Secondary (outlined) ------------------------------------------- */

body.dm-page .dm-btn-secondary .elementor-button:hover,
body.dm-page .dm-btn-secondary .elementor-button:focus-visible {
	background-color: var(--bg-sunken); /* ghost/secondary gain a sunken fill */
	border-color: var(--border-strong);
	color: var(--brand-primary-hover);
}

body.dm-page .dm-btn-secondary .elementor-button:active {
	background-color: var(--ink-100);
	color: var(--brand-primary-active);
}

/* --- 11.4 Primary (navy) ------------------------------------------------- */

body.dm-page .dm-btn-primary .elementor-button:hover,
body.dm-page .dm-btn-primary .elementor-button:focus-visible {
	background-color: var(--brand-primary-hover);
	border-color: var(--brand-primary-hover);
	color: var(--text-on-brand);
}

body.dm-page .dm-btn-primary .elementor-button:active {
	background-color: var(--brand-primary-active);
	border-color: var(--brand-primary-active);
}

/* --- 11.5 Contact tabs --------------------------------------------------- */

/*
 * The tab strip computed `transition: all 0s`, and the inactive tab's rest and
 * hover colours were the same value -- a control that gave no sign it was a
 * control. Transitioning the active tab's border is the most restrained
 * "this changed" signal available, and it is squarely a colour transition.
 */
body.dm-page .elementor-widget-tabs .elementor-tab-title {
	transition:
		color var(--duration-fast) var(--ease-standard),
		border-color var(--duration-fast) var(--ease-standard),
		background-color var(--duration-fast) var(--ease-standard);
}

body.dm-page .elementor-widget-tabs .elementor-tab-title:not(.elementor-active):hover {
	color: var(--brand-primary);
	background-color: var(--bg-sunken);
}

/* --- 11.6 Focus indicators must never fade in ---------------------------- */

/*
 * base.css paints the focus halo with `box-shadow: var(--shadow-focus)`. A
 * frame-by-frame sample proved that ANY box-shadow transition makes the
 * indicator animate from nothing:
 *
 *     t=0ms    rgba(0,0,0,0) 0 0 0 0          <- no indicator at all
 *     t=117ms  rgba(52,95,129,.345) 0 0 0 2.96px
 *     t=184ms  rgba(52,95,129,.35) 0 0 0 3px
 *
 * A keyboard user must see where they are on the frame focus lands, not 180ms
 * later. This guard is required alongside any shadow work anywhere in the theme.
 */
body.dm-page :focus-visible {
	transition-duration: 0s;
}

/* --- 11.7 Ghost on dark — the CTA band's secondary ------------------------ */

body.dm-page .dm-btn-ghost-dark .elementor-button,
body.dm-page .dm-btn-ghost-dark .elementor-button:link,
body.dm-page .dm-btn-ghost-dark .elementor-button:visited {
	color: var(--ink-0);
	background-color: transparent;
}

body.dm-page .dm-btn-ghost-dark .elementor-button:hover,
body.dm-page .dm-btn-ghost-dark .elementor-button:focus-visible {
	background-color: rgb(255 255 255 / 0.10);
	border-color: var(--ink-0);
	color: var(--ink-0);
}

body.dm-page .dm-btn-ghost-dark .elementor-button:active {
	background-color: rgb(255 255 255 / 0.16);
}

/* --- 11.8 Buttons share a height and stack evenly ------------------------ */

body.dm-page .elementor-element .elementor-button {
	min-height: 48px;
}

@media (max-width: 639px) {
	/* Stacked CTAs of different label lengths rendered at different widths,
	   which reads as two unrelated controls rather than a pair. */
	body.dm-page .dm-cta-band .elementor-widget-button,
	body.dm-page .dm-cta-band .elementor-button {
		width: 100%;
	}
}


/* --- 11.9 Card shadow: restore the second layer -------------------------- */

/*
 * Elementor's box-shadow control takes ONE layer, so the generator could only
 * encode the first half of --shadow-sm. The missing layer is the tight contact
 * shadow that seats a card on its surface; without it the card floats.
 *
 * It matters more here than it normally would: the card fill gives almost no
 * figure/ground -- white on --bg-page measures 1.06:1 -- so the card is read
 * almost entirely through its edge treatment. The edge treatment should be the
 * whole token.
 */
/*
 * NOT IMPLEMENTED, deliberately, and this is the honest reason:
 *
 * There is no way to target only the card containers. Elementor 4.2.4 drops
 * `_css_classes` on CONTAINERS (proven by the .dm-formidable failure), so the
 * cards carry no hook of our own -- only `.e-con`, which every container on
 * every page shares. A `.e-con { box-shadow: ... }` rule would put a card
 * shadow on the hero, the CTA band and every layout wrapper.
 *
 * Writing a selector that matches nothing, or one that matches everything,
 * are both worse than leaving it. The single-layer shadow is a small loss;
 * dead or over-broad CSS is how this codebase already lost an entire form
 * styling layer for three releases.
 *
 * Fix properly if a container-level class hook is ever found, or by moving
 * the cards to a theme-rendered partial.
 */

/* --- 11.10 Form inputs: 16px on mobile, or iOS zooms the page ------------ */

/*
 * Inputs were 14px (--text-body-sm). Mobile Safari zooms the viewport on focus
 * for ANY input under 16px, which on /contact/ at 390 throws the visitor into a
 * zoomed, horizontally scrolled form halfway through typing. 16px is the
 * threshold, not a preference.
 */
@media (max-width: 767px) {
	body.dm-page .frm_forms.with_frm_style .frm_form_field input,
	body.dm-page .frm_forms.with_frm_style .frm_form_field select,
	body.dm-page .frm_forms.with_frm_style .frm_form_field textarea {
		font-size: var(--text-body);
	}
}

/* --- 11.11 Dropzone hover was a literal no-op ---------------------------- */

/*
 * The upload well's hover background was --ink-50, which IS --bg-sunken: the
 * same colour it already had. It looked like a hover state in the stylesheet
 * and did nothing on screen.
 */
body.dm-page .frm_forms.with_frm_style .frm_dropzone:hover,
body.dm-page .frm_forms.with_frm_style .frm_dropzone.dz-drag-hover {
	background-color: var(--ink-100);
	border-color: var(--border-focus);
}

/* Formidable's submit carries its own flat shadow; the theme's button spec has none. */
body.dm-page .frm_forms.with_frm_style .frm_submit .frm_button_submit {
	box-shadow: none;
}


/* --- 11.12 Contact tab panel cross-fade ---------------------------------
 *
 * APPROVED EXCEPTION TO THE MOTION LAW, and a closed one.
 *
 * The brand law permits transitions "on colour/background changes". Opacity is
 * neither, so this needed a ruling and got one. The exception is exactly this
 * wide and no wider:
 *
 *     opacity · 120ms · tab panels · nowhere else.
 *
 * Why it earns the exception: both panels are forms that open with the same
 * Name / Email / Phone block and differ in height by 355px. Swapped with zero
 * signal, a mis-click can leave someone typing into the wrong form and only
 * discovering it at submit. This is the page that carries every lead.
 *
 * Implemented as a keyframe on the newly-active panel rather than a transition,
 * because Elementor toggles `display`, and opacity cannot transition from a
 * display:none start.
 * ------------------------------------------------------------------------- */

@keyframes dm-panel-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}

body.dm-page .elementor-widget-tabs .elementor-tab-content.elementor-active {
	animation: dm-panel-in var(--duration-fast) var(--ease-out);
}

/*
 * Reduced motion needs its OWN rule here, not the global duration collapse.
 * Zeroing the duration on a keyframe that starts at opacity:0 strands the panel
 * invisible for a frame and then snaps it in — worse than no animation at all.
 */
@media (prefers-reduced-motion: reduce) {html:not(.dm-motion-opt-in) body.dm-page .elementor-widget-tabs .elementor-tab-content.elementor-active {
		animation: none;
	}
}

/* --- 11.13 Card hover elevation ----------------------------------------- */

/*
 * Within the motion law, not an exception. The Cards clause itself defines
 * shadow-md as the "elevated/interactive" state, and the three prohibitions
 * (bounce, scale-pop, choreography) are named and exhaustive — a shadow is
 * none of them. The ban is on the LIFT, not on the shadow: the card never moves.
 *
 * CAVEAT worth knowing: the Work cards are not links yet. Hover feedback on a
 * surface that cannot be clicked is a false affordance, so this is kept to a
 * border and shadow shift — a change of state, not an invitation — until the
 * case-study cards get real destinations.
 */
/*
 * NOT SHIPPED as a blanket rule, for the same reason as the card shadow in
 * 11.9: the only selector available for a card is `.e-con`, which every
 * container on every page shares. A hover shadow on `.e-con` would put one on
 * the hero, the CTA band and every layout wrapper.
 *
 * What IS shipped is hover on the things that are genuinely interactive —
 * a linked card title. When the case-study cards gain real destinations, the
 * card-level elevation becomes both targetable and honest.
 */
body.dm-page .elementor-widget-heading a {
	transition: color var(--duration-fast) var(--ease-standard);
}

body.dm-page .elementor-widget-heading a:hover,
body.dm-page .elementor-widget-heading a:focus-visible {
	color: var(--text-link-hover);
}


/* ===========================================================================
 * 12. THE HOVER LAYER
 *
 * Everything here is a colour, background or box-shadow change. Nothing
 * transforms, nothing moves position, nothing bounces -- the brand's three
 * named prohibitions are untouched.
 * ======================================================================== */

/* --- 12.1 Cards lift ------------------------------------------------------
 *
 * shadow-sm -> shadow-md is the state the Cards clause itself defines as
 * "elevated/interactive", and the card never moves: the ban is on the lift,
 * not on the shadow. The border warms toward navy at the same time so the
 * change is legible even where shadows are hard to see.
 *
 * Scoped through :has() to containers that actually hold a card index, so it
 * cannot leak onto the hero, the CTA band or a layout wrapper -- which is what
 * blocked this in 11.13 when `.e-con` was the only hook available.
 * ------------------------------------------------------------------------ */

/*
 * SELECTOR CORRECTED AFTER MEASUREMENT.
 *
 * This was `:has(> .elementor-widget-heading:first-child)`, which read as
 * "a container whose first child is a heading" -- i.e. a card. Measured on
 * /services/, it matched 13 of 21 visible containers, including the 34px tag
 * rows INSIDE each card. Those rows have `border-width: 0` and no background,
 * so hovering the pills painted a full card-elevation drop shadow around a
 * strip with no box behind it: a shadow cast by nothing, nested inside the
 * card that was already shadowed.
 *
 * `.dm-card-index` is the mono numeral widget that only a card contains, so
 * the selector now identifies a card by something only cards have.
 */
body.dm-page .e-con:has(> .dm-card-index) {
	transition:
		box-shadow var(--duration-base) var(--ease-standard),
		border-color var(--duration-base) var(--ease-standard);
}

body.dm-page .e-con:has(> .dm-card-index):hover {
	box-shadow:
		0 4px 10px -2px rgb(18 35 51 / 0.10),
		0 2px 4px -2px rgb(18 35 51 / 0.06);
	border-color: var(--navy-500);
}

/* --- 12.2 Text links draw their own underline ----------------------------
 *
 * The underline is a BACKGROUND gradient animated through background-size,
 * not a transform and not a border. That keeps it inside "transitions on
 * colour/background changes" while still growing from the left, and it means
 * the rule degrades to a plain static underline if the transition is dropped.
 *
 * Buttons are excluded by selector: the brand reserves underline-on-hover for
 * text links and forbids it on buttons.
 * ------------------------------------------------------------------------ */

body.dm-page .dm-nav__link {
	background-image: linear-gradient(var(--teal-300), var(--teal-300));
	background-repeat: no-repeat;
	background-position: left calc(100% - 6px);
	background-size: 0% 2px;
	transition:
		background-size var(--duration-base) var(--ease-out),
		color var(--duration-fast) var(--ease-standard);
}

body.dm-page .dm-nav__link:hover,
body.dm-page .dm-nav__link:focus-visible,
body.dm-page .dm-nav__item > .dm-nav__link[aria-current="page"] {
	background-size: 100% 2px;
}

/*
 * NOTE: an earlier navy-800 hover fill for these links was removed from
 * layout.css after measurement showed it produced 0.00px of coloured ground
 * either side of the label. Do not reinstate it here -- the teal label plus
 * the drawn underline above is the whole state, and it is why the
 * current-page treatment always looked correct.
 */

/* --- 12.3 Tag pills acknowledge the pointer ------------------------------ */

body.dm-page .elementor-widget-heading .elementor-heading-title {
	transition:
		background-color var(--duration-fast) var(--ease-standard),
		border-color var(--duration-fast) var(--ease-standard),
		color var(--duration-fast) var(--ease-standard);
}

/* --- 12.4 Reduced motion ------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	/*
	 * The underline still appears on hover -- it is information about where
	 * you are, not decoration -- it simply appears at once instead of drawing.
	 */
	html:not(.dm-motion-opt-in) body.dm-page .dm-nav__link {
		transition-duration: 0s;
	}
}


/* --- 12.5 Press states that are actually different ----------------------- */

/*
 * Measured: :active set --amber-500, which :hover had already set. Both states
 * computed rgb(198,105,31), so pressing a button looked identical to hovering
 * it. A press with no feedback is the single most common "this site feels
 * cheap" signal, and it was free to fix.
 *
 * Border-darkening rather than fill, per G45: the amber ramp has no third step
 * that keeps the navy label above AA.
 */
body.dm-page .dm-btn-signal .elementor-button:active {
	border-color: var(--amber-700);
}

body.dm-page .dm-btn-secondary .elementor-button:active,
body.dm-page .dm-btn-ghost-dark .elementor-button:active {
	border-color: var(--navy-500);
}

/* --- 12.6 The logos respond ---------------------------------------------- */

/*
 * Both logo links changed `color` on hover -- on a link whose only content is
 * a PNG. Nothing visible happened. Opacity is the one honest affordance for an
 * image link, and it is neither a transform nor a scale.
 */
.dm-page .dm-logo-link {
	transition: opacity var(--duration-fast) var(--ease-standard);
}

.dm-page .dm-logo-link:hover,
.dm-page .dm-logo-link:focus-visible {
	opacity: 0.82;
}

/* --- 12.7 Mobile nav opens, rather than cutting -------------------------- */

/*
 * The panel computed `transition: all 0s` -- a hard cut. Colour-only per the
 * brand law, so the panel fades its background and its links their colour;
 * the disclosure itself stays instant, which is also what keeps it accessible.
 */
@media (max-width: 899px) {
	.dm-page .dm-nav {
		transition: background-color var(--duration-base) var(--ease-out);
	}
}
