/* ==========================================================================
   Dot Map — base
   --------------------------------------------------------------------------
   Cascade position 2 of 5:  tokens -> BASE -> layout -> components -> pages

   Contents
     1. @font-face — self-hosted IBM Plex Sans + IBM Plex Mono
     2. Reset / normalize
     3. Document + body defaults
     4. Type scale (headings, body, mono)
     5. Links, lists, rules, tables, code
     6. Forms — inherited type only; component styling lives in components.css
     7. Media
     8. Focus ring (global, token-driven)
     9. Accessibility primitives — .screen-reader-text, .dm-skip-link
    10. Motion preferences

   SCOPE RULE FOR OTHER AGENTS
   This file styles ELEMENTS and the handful of a11y primitives named above.
   It must never contain a .dm-* component class. If you are writing
   `.dm-card`, `.dm-button`, `.dm-hero` — that belongs in components.css or
   pages.css, not here.

   ELEMENTOR DEFENCE
   Elementor Pro stays installed and active. Its `elementor-kit-{N}`
   stylesheet restyles body, h1–h6, a, p and the form elements at specificity
   (0,1,1), which beats a bare element selector. So from section 3 onward
   every rule here is written as `body.dm-page <element>` — specificity
   (0,1,2), enough to win, and paired with the priority-20 enqueue in
   inc/enqueue.php so an exact tie also resolves our way.

   `.dm-page` is on <body>, so this changes SPECIFICITY ONLY, never reach.

   Sections 1 and 2 (@font-face and the structural reset) and section 10
   (prefers-reduced-motion) are deliberately left UNSCOPED and
   low-specificity: Elementor's own widget CSS should keep winning inside
   Elementor-built content, and the structural reset is exactly the part that
   should yield.
   ========================================================================== */


/* ==========================================================================
   1. Fonts — self-hosted
   --------------------------------------------------------------------------
   Downloaded from Google's CDN on 2026-09-11 and served from this theme. No
   third-party request is made at runtime: nothing here points at
   fonts.googleapis.com or fonts.gstatic.com.

   IBM Plex Sans is distributed as a VARIABLE font (wght axis 100–700), so a
   single file covers 400/500/600/700 and the face is declared with a weight
   RANGE. IBM Plex Mono is still shipped as discrete static instances, so it
   gets one face per weight.

   Two unicode subsets per face: `latin` (U+0000–00FF and common punctuation)
   and `latin-ext`. The browser downloads latin-ext only if a glyph on the page
   needs it, which for English-language public-sector copy is almost never.

   font-display: swap — text paints immediately in the fallback stack and
   reflows once Plex arrives. On a trust-first civic site, readable-now beats
   invisible-then-perfect.

   Both families are declared under the exact names --font-sans and --font-mono
   already reference, so tokens.css needs no change.
   ========================================================================== */

/* --- IBM Plex Sans (variable, wght 100–700) --- */
@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 100 700;
	font-stretch: 100%;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 100 700;
	font-stretch: 100%;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- IBM Plex Mono (static instances, 400/500/600/700) --- */
@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-400-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-400-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-500-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-500-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-600-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-600-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-700-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-700-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* ==========================================================================
   2. Reset / normalize
   ========================================================================== */

*,
*::before,
*::after {
	box-sizing: border-box;
}

/* Reset the margins the UA gives block elements; rhythm is set deliberately
   by .dm-stack / .dm-section in layout.css, never inherited by accident. */
body,
h1, h2, h3, h4, h5, h6,
p,
figure,
blockquote,
dl, dd,
ol, ul,
pre {
	margin: 0;
}

ol[class],
ul[class] {
	padding: 0;
	list-style: none;
}

/* A list stripped of its bullets is no longer announced as a list by Safari +
   VoiceOver. Restoring a zero-width marker keeps the semantics. */
ol[class] > li::marker,
ul[class] > li::marker {
	content: "";
}

fieldset {
	margin: 0;
	padding: 0;
	border: 0;
	min-width: 0;
}

legend {
	padding: 0;
}

/* !important is unavoidable here. `hidden` is an accessibility-critical state
   toggled by nav.js, and any component rule that sets `display` on the same
   element — ours, Elementor's, or Formidable's — would otherwise unhide it.
   A hidden element that renders anyway is a correctness bug, not a style one. */
[hidden] {
	display: none !important;
}


/* ==========================================================================
   3. Document + body
   ========================================================================== */

html {
	/* No smooth scroll: the brand permits colour/background transitions only. */
	scroll-behavior: auto;
	-webkit-text-size-adjust: 100%;
	text-size-adjust: 100%;
	/* Breathing room above an in-page anchor target. The header is not sticky
	   (see layout.css section 6), so this only needs to be comfortable, not
	   header-height. If the header is ever made sticky, raise this to match. */
	scroll-padding-top: var(--space-6);
}

body.dm-page {
	margin: 0;
	min-height: 100vh;
	background-color: var(--bg-page);
	color: var(--text-primary);
	font-family: var(--font-sans);
	font-size: var(--text-body);
	font-weight: var(--weight-regular);
	line-height: var(--leading-normal);
	letter-spacing: var(--tracking-normal);
	text-rendering: optimizeLegibility;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	/* Long words (URLs, "modernization") must never push the 390px layout wide. */
	overflow-wrap: break-word;
}


/* ==========================================================================
   4. Type scale
   --------------------------------------------------------------------------
   The design system's type tokens are single fixed values — the kit is
   desktop-only and has no fluid type at all. The two largest steps would
   overflow a 390px viewport, so h1 and h2 interpolate between two REAL tokens
   with clamp(). No new size value is introduced: both endpoints of every
   clamp are existing tokens.

     h1   --text-h1 (36px)  ->  --text-display (52px)
     h2   --text-h3 (24px)  ->  --text-h2      (30px)

   h3 and below already fit at 390px and stay fixed.

   Overriding these on a component class is expected and fine — e.g. a hero
   title that wants the display step at every width. Do it in components.css
   or pages.css, not here.
   ========================================================================== */

body.dm-page h1,
body.dm-page h2,
body.dm-page h3,
body.dm-page h4,
body.dm-page h5,
body.dm-page h6 {
	font-family: var(--font-sans);
	color: var(--text-primary);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-normal);
	/* Keep a lone word from being stranded on its own line in a heading. */
	text-wrap: balance;
}

body.dm-page h1 {
	font-size: clamp(var(--text-h1), 6vw, var(--text-display));
	font-weight: var(--weight-bold);
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-tight);
}

body.dm-page h2 {
	font-size: clamp(var(--text-h3), 4vw, var(--text-h2));
	line-height: var(--leading-tight);
	letter-spacing: var(--tracking-tight);
}

body.dm-page h3 {
	font-size: var(--text-h3);
}

body.dm-page h4 {
	font-size: var(--text-h4);
}

body.dm-page h5 {
	font-size: var(--text-body-lg);
}

body.dm-page h6 {
	font-size: var(--text-body);
}

body.dm-page p {
	line-height: var(--leading-relaxed);
	/* Stop a single trailing word landing alone on the last line. */
	text-wrap: pretty;
}

body.dm-page small,
body.dm-page .dm-text-sm {
	font-size: var(--text-body-sm);
}

body.dm-page strong,
body.dm-page b {
	font-weight: var(--weight-semibold);
}


/* ==========================================================================
   5. Links, lists, rules, tables, code
   ========================================================================== */

body.dm-page a {
	color: var(--text-link);
	/* Underline text links by default. Buttons opt out in components.css —
	   the brand explicitly reserves underline-on-hover for text links only. */
	text-decoration: underline;
	text-underline-offset: 0.18em;
	text-decoration-thickness: 1px;
	/* 120ms, colour only. No transform, no opacity fade. */
	transition: color var(--duration-fast) var(--ease-standard);
}

body.dm-page a:hover {
	color: var(--text-link-hover);
	text-decoration-thickness: 2px;
}

/* Never let an image-only or nav link inherit the prose underline. */
body.dm-page a:has(> img),
body.dm-page a:has(> svg) {
	text-decoration: none;
}

body.dm-page hr {
	height: 0;
	margin: 0;
	border: 0;
	border-top: var(--border-width-hairline) solid var(--border-default);
}

/* Bare content lists (post body, not class-bearing UI lists) keep bullets.
   :where() keeps .dm-prose at zero specificity so that when the_content()
   returns Elementor-wrapped markup, Elementor's own list styling still wins
   inside its widgets — which is the intended behaviour. */
body.dm-page :where(.dm-prose) ul,
body.dm-page :where(.dm-prose) ol {
	padding-left: var(--space-6);
}

body.dm-page :where(.dm-prose) li + li {
	margin-top: var(--space-2);
}

body.dm-page blockquote {
	/* Never a coloured left-accent strip — brand law forbids that pattern.
	   A full hairline box carries the same emphasis without it. */
	padding: var(--space-6);
	border: var(--border-width-hairline) solid var(--border-default);
	border-radius: var(--radius-lg);
	background-color: var(--bg-surface);
	color: var(--text-secondary);
	font-size: var(--text-body-lg);
}

body.dm-page table {
	width: 100%;
	border-collapse: collapse;
	font-variant-numeric: tabular-nums;
}

body.dm-page th,
body.dm-page td {
	padding: var(--space-3) var(--space-4);
	border-bottom: var(--border-width-hairline) solid var(--border-default);
	text-align: left;
}

body.dm-page th {
	font-weight: var(--weight-semibold);
	color: var(--text-secondary);
}

body.dm-page code,
body.dm-page kbd,
body.dm-page samp,
body.dm-page pre {
	font-family: var(--font-mono);
	font-size: var(--text-body-sm);
}

body.dm-page code {
	padding: 0.1em 0.35em;
	border-radius: var(--radius-sm);
	background-color: var(--bg-sunken);
	color: var(--text-primary);
}

body.dm-page pre {
	overflow-x: auto;
	padding: var(--space-4);
	border: var(--border-width-hairline) solid var(--border-default);
	border-radius: var(--radius-lg);
	background-color: var(--bg-sunken);
}

body.dm-page pre code {
	padding: 0;
	background: none;
}


/* ==========================================================================
   6. Forms — inheritance only
   --------------------------------------------------------------------------
   Component-level input styling belongs in components.css. All this section
   does is stop the UA from substituting its own font and colour, which is a
   reset concern, not a design one.
   ========================================================================== */

body.dm-page input,
body.dm-page button,
body.dm-page textarea,
body.dm-page select,
body.dm-page optgroup {
	font: inherit;
	color: inherit;
	letter-spacing: inherit;
}

body.dm-page textarea {
	resize: vertical;
}

body.dm-page button {
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
	/* Buttons never carry the prose underline. */
	text-decoration: none;
}

/* An explicit label association is required everywhere; this just makes the
   label itself a comfortable click target. */
body.dm-page label {
	cursor: pointer;
}

/*
 * A11Y (1.4.3, Agent 8): --text-muted (--ink-400 #7d858c) measures 3.83:1 on
 * --bg-surface, which fails the 4.5:1 minimum for text. Placeholder text IS
 * text. No template in this theme sets a placeholder attribute — searchform.php
 * deliberately uses a real label instead — but Formidable fields and Elementor
 * widgets can both carry one, and this is the rule that would colour it.
 * --text-secondary measures 8.45:1 and is still visibly lighter than the
 * --text-primary of entered text, which is the distinction that matters.
 */
body.dm-page ::placeholder {
	color: var(--text-secondary);
	opacity: 1; /* Firefox dims placeholders by default, wrecking contrast. */
}


/* ==========================================================================
   7. Media
   ========================================================================== */

body.dm-page img,
body.dm-page picture,
body.dm-page video,
body.dm-page canvas,
body.dm-page svg,
body.dm-page iframe {
	display: block;
	max-width: 100%;
}

body.dm-page img,
body.dm-page video {
	height: auto;
}

body.dm-page svg {
	fill: currentColor;
}


/* ==========================================================================
   8. Focus ring — global, token-driven
   --------------------------------------------------------------------------
   The kit ships no focus styles whatsoever. This is the single source of
   focus visibility for the whole site.

   Two layers, deliberately:
     - a SOLID 2px --border-focus outline, which is what actually carries the
       3:1 non-text contrast WCAG 2.2 requires;
     - the --shadow-focus token's soft 35%-alpha halo on top, which is the
       brand's specified look but is too low-contrast to pass on its own.

   :focus-visible, not :focus, so a mouse click on a button does not leave a
   ring behind. The @supports block restores :focus for engines without
   :focus-visible rather than leaving them with no indicator at all.

   Do not remove or override this per-component. If a component needs a
   different ring OFFSET (an inset ring on a dark band, say), override
   outline-offset only.
   ========================================================================== */

body.dm-page :focus-visible {
	outline: var(--border-width-strong) solid var(--border-focus);
	outline-offset: 2px;
	box-shadow: var(--shadow-focus);
}

@supports not selector(:focus-visible) {
	body.dm-page :focus {
		outline: var(--border-width-strong) solid var(--border-focus);
		outline-offset: 2px;
		box-shadow: var(--shadow-focus);
	}
}

/* On the navy-950 footer and any dark band, a navy-500 ring disappears into
   the background. Swap to the light end of the ink scale there. */
body.dm-page .dm-on-dark :focus-visible,
body.dm-page .dm-site-footer :focus-visible {
	outline-color: var(--ink-0);
	box-shadow: none;
}

/* Windows High Contrast / forced-colors: let the OS draw the indicator. */
@media (forced-colors: active) {
	body.dm-page :focus-visible {
		outline: 3px solid CanvasText;
		box-shadow: none;
	}
}


/* ==========================================================================
   9. Accessibility primitives
   ========================================================================== */

/* WordPress's canonical visually-hidden class. Core, plugins and comment
   markup all expect this exact name, so it is not renamed to a dm- prefix.
   .dm-visually-hidden is provided as an alias for new markup. */
body.dm-page .screen-reader-text,
body.dm-page .dm-visually-hidden {
	/* !important is unavoidable here and is the WordPress core convention for
	   this class. Plugin and Elementor markup routinely set position on the
	   same element; if any of them wins, the "hidden" text renders on screen
	   in the middle of the layout. Failing visible is not an option for a
	   class whose entire job is to be invisible. */
	position: absolute !important;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	border: 0;
	white-space: nowrap;
	word-wrap: normal;
}

/* A visually-hidden element that can receive focus must become visible when
   it does — otherwise keyboard focus vanishes off-screen. */
body.dm-page .screen-reader-text:focus,
body.dm-page .dm-visually-hidden:focus {
	display: block;
	top: var(--space-2);
	left: var(--space-2);
	z-index: 100000;
	width: auto;
	height: auto;
	padding: var(--space-3) var(--space-5);
	overflow: visible;
	clip: auto;
	clip-path: none;
	border-radius: var(--radius-md);
	background-color: var(--bg-surface);
	box-shadow: var(--shadow-md);
	color: var(--text-link);
	font-size: var(--text-body-sm);
	font-weight: var(--weight-semibold);
	text-decoration: underline;
	line-height: normal;
}

/* --- Skip to content ---------------------------------------------------- */

/* Off-screen until focused. Uses the same mechanics as the WP class above but
   is styled as a proper navy plate, because it is the first thing a keyboard
   user sees on every page. */
body.dm-page .dm-skip-link {
	position: absolute;
	top: var(--space-2);
	left: var(--space-2);
	z-index: 100000;
	padding: var(--space-3) var(--space-5);
	transform: translateY(-200%);
	border-radius: var(--radius-md);
	background-color: var(--brand-primary);
	box-shadow: var(--shadow-md);
	color: var(--text-on-brand);
	font-family: var(--font-sans);
	font-size: var(--text-body-sm);
	font-weight: var(--weight-semibold);
	text-decoration: none;
	/* The one transform in the theme, and it is on a control that is invisible
	   until focused — there is no visible motion for a sighted user to see. */
	transition: transform var(--duration-fast) var(--ease-out);
}

body.dm-page .dm-skip-link:focus,
body.dm-page .dm-skip-link:focus-visible {
	transform: translateY(0);
	color: var(--text-on-brand);
}

/*
 * The <main> landmark is the skip target. Landing there must not paint a ring
 * around the whole page.
 *
 * TWO CORRECTIONS (Agent 8, both found by rendering, not by reading):
 *
 * 1. box-shadow was not suppressed. The outline was, so the comment here used
 *    to claim no ring is drawn — but :focus-visible in section 8 also sets
 *    box-shadow: var(--shadow-focus), and that IS a ring. Measured after
 *    activating the skip link with a real Enter press: <main id="dm-main">
 *    computed `box-shadow: rgba(52, 95, 129, 0.35) 0 0 0 3px`, a halo around
 *    the entire content region. Both properties are cleared now.
 *
 * 2. The selector was the bare attribute, so it caught every roving-tabindex
 *    control too — including the inactive contact tab, which tabs.js parks at
 *    tabindex="-1". Measured on the rendered contact page: focusing that tab
 *    computed `outline-style: none`. It does not bite today only because
 *    tabs.js writes tabindex="0" before it calls focus(); a rule whose
 *    correctness depends on another file's statement order is a trap. Scoped
 *    to <main>, which is the only element this was ever meant to cover.
 */
body.dm-page main[tabindex="-1"]:focus {
	outline: none;
	box-shadow: none;
}


/* ==========================================================================
   10. Motion preferences
   --------------------------------------------------------------------------
   The brand's motion budget is already near zero — 120–180ms colour and
   background transitions only, no choreography — so there is very little to
   strip. This is a safety net covering anything a later agent adds, plus any
   plugin markup that lands inside the theme.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {html:not(.dm-motion-opt-in) *,
	html:not(.dm-motion-opt-in) *::before,
	html:not(.dm-motion-opt-in) *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}
