/* ==========================================================================
 * KC AI PET MATCH — THE OBSIDIAN NETWORK LAYER
 *
 * Appended to kcai-home.css. It is a LAYER, not a rewrite: every rule below
 * either (a) defines new `.kcm-*` masthead structure that simply does not
 * match until Page 11 carries the new markup, or (b) re-dresses an existing
 * `.kch-*` element that is already on the page.
 *
 * That split is deliberate and load-bearing. The plugin ships before the
 * guarded Page 11 write lands, so for a short window the NEW stylesheet is
 * live against the OLD markup. Everything in section 2 exists so that window
 * is an improvement rather than a broken page.
 *
 * THE MASTHEAD IS ALWAYS DARK, IN BOTH THEMES.
 * This is the same reasoning the original file applies to `--band`: the
 * product statement and footer are dark ON PURPOSE, not dark because the page
 * is. The logo is a transparent PNG whose silhouette is near-white with a
 * chrome bevel — on a light field it disappears. So the masthead carries its
 * own obsidian ground and never inverts. Light theme keeps its light page;
 * only this one band is fixed.
 *
 * NOTHING HERE DEPENDS ON JAVASCRIPT. Every effect is CSS. The static state is
 * the design; animation only adds a slow current on top of it.
 * ========================================================================== */


/* ------------------------------------------------------------ 1. THE TOKENS
 *
 * DECLARED ON :root, AND THAT IS DELIBERATE.
 *
 * The first draft scoped these to `.kcm, .kch-statement` to stop the obsidian
 * palette "leaking". That was wrong twice over. It cannot leak - every name is
 * `--kcm-*` and a custom property does nothing until something references it,
 * so an unreferenced token on :root has no effect on the light theme, the map
 * or wp-admin. And scoping it BROKE the navigation silently: rules like
 * `body.kcai .kch-nav{padding:var(--kcm-s1) 16px}` sit outside `.kcm`, so the
 * variable was undefined, the whole declaration was invalid at computed-value
 * time, and `padding` fell back to 0. Measured: the nav lost its 12px gutter,
 * the posting button went 296px -> 320.86px inside a 320px cell, and
 * documentElement.scrollWidth became 322 at a 320 viewport.
 *
 * The lesson generalises: scope the RULES, never the TOKENS.
 *
 * Measured against --kcm-void #05080C:
 *   --kcm-text   #E8F1F7   16.9:1   AAA
 *   --kcm-dim    #A7BBCB    8.9:1   AAA
 *   --kcm-cyan   #00A4FD    7.4:1   AA  (AAA at large sizes; the accent)
 *   --kcm-cyan-2 #8FD4FF   12.5:1   AAA (highlight, spark cores)
 *   --kcm-uv-t   #BCA6FF    7.4:1   AAA  (violet as TEXT; --kcm-uv is fill-only)
 *   --kcm-ink on --kcm-cyan  #04141A on #00A4FD = 6.9:1  (primary button)
 *
 * 1.3.3 RE-MEASURED THESE, because the accent changed. The old #35E4FF was a
 * cyan at 12.8:1; the logo's actual blue #00A4FD is darker and measures 7.4:1
 * on this ground, and the ink-on-accent button pairing drops 11.6 → 6.9. Both
 * still clear AA comfortably and the accent still clears AAA at large sizes,
 * which is what it is used for. These numbers are computed, not carried over -
 * copying an old ratio onto a new colour is how a palette quietly loses its
 * accessibility guarantee.
 *
 * --kcm-uv (#7C5CFF) is 3.4:1 and is NEVER used for text. Fills, borders,
 * gradients and glows only — the same discipline the base file applies to
 * --slate.
 */
:root {
	--kcm-black:   #000000;   /* 1.3.0: the masthead band and the logo disc ONLY.
	                           * A separate token rather than redefining
	                           * --kcm-void, which is referenced by cards, the nav
	                           * and the statement band — moving it would have
	                           * darkened all of those too. Scope the RULES. */
	--kcm-void:    #05080C;   /* true obsidian: everything else that was dark */
	--kcm-void-2:  #0A121B;   /* one step up: cards, nav bar */
	--kcm-text:    #E8F1F7;   /* cool silver — headline and body */
	--kcm-dim:     #A7BBCB;   /* dimmest passing text on --kcm-void */
	--kcm-cyan:    #00A4FD;   /* the LOGO blue, sampled: rgb(0,164,253) */
	--kcm-cyan-2:  #8FD4FF;   /* light tint of the SAME hue */
	--kcm-cyan-d:  #0077C8;   /* pressed / deep edge, same hue */
	--kcm-uv:      #7C5CFF;   /* ultraviolet — FILL AND GLOW ONLY */
	--kcm-uv-t:    #BCA6FF;   /* ultraviolet as TEXT — measured */
	--kcm-green:   #3DDC97;   /* recovery / confirmed positive ONLY */
	--kcm-amber:   #F5B841;   /* attention / warning ONLY */
	--kcm-ink:     #04141A;   /* text ON a cyan fill */

	--kcm-line:    rgba(150, 200, 225, .14);   /* structural hairline */
	--kcm-lit:     rgba(0, 164, 253, .34);    /* luminous edge */
	--kcm-lit-2:   rgba(124, 92, 255, .30);    /* ultraviolet edge */

	/* One shadow language: a tight contact shadow plus a wide soft lift. */
	--kcm-shadow:  0 1px 2px rgba(0, 0, 0, .6), 0 22px 60px rgba(0, 0, 0, .62);
	--kcm-halo:    0 0 60px rgba(0, 164, 253, .18);

	/* Rhythm. Everything below picks from this ladder; no stray magic numbers. */
	--kcm-s1: 8px;  --kcm-s2: 14px; --kcm-s3: 22px;
	--kcm-s4: 34px; --kcm-s5: 52px; --kcm-s6: 78px;
}


/* -------------------------------------------- 2. BACKWARD-COMPATIBILITY PASS
 *
 * These rules target the markup that is ON THE PAGE RIGHT NOW. They exist so
 * the deploy window (new CSS + old markup) is safe, and they are written so
 * that they stay harmless once the new markup replaces them.
 *
 * THE BUG THEY FIX, MEASURED:
 * `.kch-statement` was authored as a thin marquee strip — a fixed
 * `height:var(--stmt-h)`, `font-size:15px`, `white-space:nowrap`,
 * `overflow:hidden`, `text-overflow:ellipsis`. Rendered anonymously it
 * measured 11px at 320/375/390 and 15px at 768/1024/1440: SMALLER THAN BODY
 * TEXT, and smaller than the two buttons above it. It could never be the
 * dominant message while it was a fixed-height clipped band, so the fix is
 * structural, not a font-size bump.
 */
.kch-statement {
	height: auto;                /* was var(--stmt-h) — the clamp that capped it */
	min-height: 0;
	white-space: normal;         /* was nowrap — it could not wrap at all */
	overflow: visible;           /* was hidden — it was clipping itself */
	text-overflow: clip;
	display: block;
	background: var(--kcm-void);
	color: var(--kcm-text);
	text-align: center;
	text-wrap: balance;
	font-size: clamp(1.75rem, 6.2vw, 4.5rem);
	font-weight: 800;
	line-height: 1.06;
	letter-spacing: -.022em;
	padding: var(--kcm-s5) var(--kcm-s3);
	margin: 0;
}

.kch-statement__t {
	display: block;
	max-width: 20ch;
	margin: 0 auto;
	overflow: visible;
	text-overflow: clip;
}

/* The old markup wraps "AI-Powered" in <b>. Keep that meaning, restate it in
 * the new palette. `color` is set BEFORE the clip so forced-colors and any
 * engine without background-clip still gets a real colour, never invisible
 * text — this is the failure mode the base file hit at v1.0.1. */
.kch-statement b {
	color: var(--kcm-cyan);
	background-image: linear-gradient(96deg, var(--kcm-uv-t) 0%, var(--kcm-cyan) 46%, var(--kcm-cyan-2) 100%);
	font-weight: 900;
}

/* The three responsive overrides further up the base file re-assert
 * font-size/white-space at ≤900px and ≤600px. They are later in source order
 * than this layer is early, so they are neutralised here at equal specificity
 * by being *after* them in the concatenated file. */

/* THE BLOCK-THEME CONSTRAINED-LAYOUT TRAP.
 *
 * Twenty Twenty-Five wraps post content in `.is-layout-constrained`, which caps
 * every DIRECT CHILD at roughly 620px. The base file already lists each `.kch-*`
 * section by name to escape it. A new section is not covered by that list, so
 * `.kcm` rendered as a 645px column floating in a 1440px black page — measured,
 * not guessed. It must be named here too.
 *
 * This is the same trap recorded against the v1.0.1 visual foundation. Any
 * FUTURE new top-level section needs adding to both lists.
 */
body.kcai-home .is-layout-constrained > .kcm,
body.kcai-home .wp-block-post-content > .kcm,
body.kcai .is-layout-constrained > .kcm,
body.kcai .wp-block-post-content > .kcm {
	max-width: none;
	width: 100%;
	margin-left: 0;
	margin-right: 0;
}


/* ------------------------------------------------------- 3. THE MASTHEAD
 *
 * New structure. Nothing here matches until Page 11 ships `.kcm`.
 *
 * Order in the DOM is headline → logo → navigation, which is both the required
 * visual order and a sensible reading order, so no CSS reordering is needed on
 * small screens. On wide screens the headline and logo share one row via grid;
 * the headline keeps the larger column so it stays dominant.
 */
.kcm {
	position: relative;
	isolation: isolate;          /* contains the glow so it cannot bleed upward */
	background: var(--kcm-black); /* 1.3.0 — see --kcm-black. The band is the
	                               * ground the disc sits on, so it goes black
	                               * with it; anything else still on --kcm-void
	                               * is unchanged. */
	color: var(--kcm-text);
	padding: clamp(var(--kcm-s4), 7vw, var(--kcm-s6)) var(--kcm-s3) var(--kcm-s4);
	overflow: hidden;            /* SCOPED to the masthead — never on <body>.
	                              * Decorative layers below are intentionally
	                              * oversized; this clips them and nothing else. */
}

/* -- 3a. The metropolitan field -------------------------------------------
 * A faint survey grid with a few brighter nodes on it, suggesting a connected
 * metro without drawing a map. Pure gradients: no image request, no canvas,
 * no JS. Masked so it is densest behind the headline and gone at the edges,
 * which keeps the dark space the design depends on.
 */
/* Bleeds VERTICALLY only. An earlier version also bled -10% horizontally,
 * which pushed documentElement.scrollWidth to 322px at a 320px viewport - a
 * real 2px overflow that body{overflow-x:hidden} would have silently swallowed.
 * The mask already fades the edges, so the horizontal bleed bought nothing. */
.kcm__field {
	position: absolute;
	inset: -20% 0 auto 0;
	height: 150%;
	pointer-events: none;
	z-index: 0;
	opacity: .55;
	background-image:
		/* nodes */
		radial-gradient(2.5px 2.5px at 12% 22%, var(--kcm-cyan) 0, transparent 100%),
		radial-gradient(2px   2px   at 78% 16%, var(--kcm-uv)   0, transparent 100%),
		radial-gradient(2px   2px   at 34% 68%, var(--kcm-cyan) 0, transparent 100%),
		radial-gradient(1.5px 1.5px at 62% 46%, var(--kcm-cyan) 0, transparent 100%),
		radial-gradient(2px   2px   at 88% 72%, var(--kcm-uv)   0, transparent 100%),
		radial-gradient(1.5px 1.5px at 22% 88%, var(--kcm-cyan) 0, transparent 100%),
		/* grid */
		repeating-linear-gradient(90deg, var(--kcm-line) 0 1px, transparent 1px 72px),
		repeating-linear-gradient(0deg,  var(--kcm-line) 0 1px, transparent 1px 72px);
	-webkit-mask-image: radial-gradient(120% 78% at 50% 34%, #000 0%, rgba(0,0,0,.55) 46%, transparent 78%);
	        mask-image: radial-gradient(120% 78% at 50% 34%, #000 0%, rgba(0,0,0,.55) 46%, transparent 78%);
}

/* The band sweep. A system doing a pass, not a strobe. It never changes what
 * anything MEANS, so removing it under reduced-motion costs the design nothing.
 *
 * 1.3.0 sharpens it into an actual LINE. It was a 4%-of-tile soft wash at 14%
 * opacity — across a 700px-tall band that is a ~55px gradient smear, which at
 * that opacity is close to invisible and reads as a faint brightening rather
 * than as anything scanning. Now: a 0.35%-of-tile bright core (about 5px on the
 * same band) with a soft falloff either side, so there is a line to see, and a
 * slower 13s cycle so it stays a background event rather than a metronome.
 *
 * It is still deliberately dim. The disc carries the scan the eye is meant to
 * follow; this one only has to make the surrounding dark feel surveyed.
 */
.kcm__scan {
	position: absolute;
	inset: 0;
	pointer-events: none;
	z-index: 0;
	background-image: linear-gradient(180deg,
		rgba(0, 164, 253, 0)    44.0%,
		rgba(0, 164, 253, .04)  49.3%,
		rgba(143, 212, 255, .17) 49.88%,
		rgba(143, 212, 255, .17) 50.12%,
		rgba(0, 164, 253, .04)  50.7%,
		rgba(0, 164, 253, 0)    56.0%);
	background-size: 100% 260%;
	background-repeat: no-repeat;
	animation: kcm-scan 13s linear infinite;
}
@keyframes kcm-scan {
	0%   { background-position: 0 118%; }
	46%  { background-position: 0 -18%; }
	100% { background-position: 0 -18%; }
}

.kcm__inner {
	position: relative;
	z-index: 1;
	max-width: 1280px;
	margin: 0 auto;
	display: grid;
	gap: clamp(var(--kcm-s3), 4vw, var(--kcm-s5));
	justify-items: center;
	text-align: center;
}

/* -- 3b. The headline -----------------------------------------------------
 * clamp() rather than breakpoints: 28px at 320 and 88px at 1440, continuous
 * between. `text-wrap:balance` stops the ragged last-word line that a long
 * headline produces at tablet widths; the two structural lines stop
 * "Network" from ever being orphaned on its own line.
 */
.kcm__h1 {
	margin: 0;
	/* Capped at 5rem rather than 5.5rem. At 88px the second structural line
	 * broke as "Lost Pet / Recovery / Network" inside the headline column -
	 * three lines for three words. 80px holds "Lost Pet Recovery" together and
	 * leaves "Network" on its own line, which is the intended beat: four lines,
	 * with the electrified word landing last. */
	font-size: clamp(1.75rem, 6.4vw, 5rem);
	font-weight: 800;
	line-height: 1.04;
	letter-spacing: -.028em;
	color: var(--kcm-text);
	text-wrap: balance;
	max-width: 16ch;
}

.kcm__l1,
.kcm__l2 { display: block; }

.kcm__l1 { color: var(--kcm-dim); font-weight: 700; }

/* "AI-Powered" — cyan, but a real colour first. */
.kcm__ai {
	color: var(--kcm-cyan);
	font-weight: 900;
	font-style: normal;
}

.kcm__l2 { color: var(--kcm-text); }

/* -- 3c. "Network" — the electrical treatment ----------------------------
 * Four things stack, and the first three are STATIC. If animation never runs,
 * the word still reads as energised:
 *   1. a cyan→ultraviolet gradient fill
 *   2. a restrained outer glow
 *   3. a luminous current bar beneath it
 *   4. a bright segment that travels the bar  ← the only animated part
 *
 * `color` is declared before the gradient so the word is never invisible if
 * background-clip is unsupported or overridden.
 */
.kcm__net {
	position: relative;
	display: inline-block;
	color: var(--kcm-cyan-2);
	background-image: linear-gradient(96deg, var(--kcm-uv-t) 0%, var(--kcm-cyan) 44%, var(--kcm-cyan-2) 100%);
	-webkit-background-clip: text;
	        background-clip: text;
	text-shadow: 0 0 30px rgba(0, 164, 253, .30);
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
	.kcm__net { -webkit-text-fill-color: transparent; }
}

/* The current bar. `bottom` is in em so it tracks the clamped font size. */
.kcm__net::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: -.16em;
	height: max(2px, .045em);
	border-radius: 999px;
	background-image: linear-gradient(90deg,
		rgba(124, 92, 255, .30) 0%,
		rgba(0, 164, 253, .55) 30%,
		var(--kcm-cyan-2)       50%,
		rgba(0, 164, 253, .55) 70%,
		rgba(124, 92, 255, .30) 100%);
	background-size: 280% 100%;
	background-position: 50% 0;
	box-shadow: 0 0 14px rgba(0, 164, 253, .45);
	animation: kcm-current 5.4s linear infinite;
}
@keyframes kcm-current { from { background-position: 130% 0; } to { background-position: -30% 0; } }

/* -- 3d. The logo --------------------------------------------------------
 * The real asset, untouched, on a BLACK disc. The energy is added around and
 * on top of it — a halo, two hairline orbit rings, a scan pass and the current
 * — so the cat and dog are never redrawn, re-traced or approximated. The
 * artwork is already a node mesh; the treatment amplifies what is there.
 *
 * 1.3.0: the ground is now true black rather than the #0D1621 → #05080C lift
 * the first draft used. That lift was doing the disc no favours — a blue-grey
 * centre sits only a few steps below the artwork's own dark passages, so the
 * silhouette had to fight it. Black gives the symbol the full contrast range
 * and is what makes it "pop" without brightening anything.
 *
 * A pure flat #000 read as a hole punched in the page, so the gradient stays,
 * with a much smaller lift: #060A10 in the middle down to #000 by 62%. Enough
 * to keep the disc a lit object, not enough to compete.
 */
.kcm__logo {
	position: relative;
	display: grid;
	place-items: center;
	width:  clamp(168px, 30vw, 300px);
	aspect-ratio: 1;
	border-radius: 50%;
	background: radial-gradient(circle at 50% 46%, #060A10 0%, #000 62%);
	box-shadow:
		inset 0 0 0 1px var(--kcm-lit),
		inset 0 0 40px rgba(0, 164, 253, .10),
		var(--kcm-halo),
		var(--kcm-shadow);
}

/* The logo ships as <picture> so a WebP can be offered with a PNG fallback
 * (23 KB vs 54 KB at the size a phone actually paints). `.kcm__logo` is a grid
 * and `.kcm__logo img` is sized at 74% OF THAT GRID AREA - a <picture> wrapper
 * would become the grid item instead, and the 74% would resolve against a
 * shrink-to-fit box rather than the disc. `display:contents` removes the
 * wrapper from layout so the <img> stays the grid item it was measured as. */
.kcm__logo picture { display: contents; }

/* ...and `display:contents` has one consequence 1.1.0 did not measure: it makes
 * the <source> a participating child of the grid too. Chrome does not carry a
 * UA `display:none` for <source> - it simply never renders one inside a
 * normally-displayed <picture> - so with the wrapper removed from layout the
 * <source> became a REAL GRID ITEM and took a row of its own.
 *
 * Measured on the live page at 1440: grid-template-rows resolved to
 * "44.2031px 295.797px" instead of a single 340px row, which put the <img> in
 * row 2 and painted the symbol 22.1px BELOW the centre of its own disc. Delete
 * the <source> from the DOM and the rows collapse to "340px" and the offset
 * goes to zero - that is the whole cause.
 *
 * This is not a cosmetic tidy-up for the current: an overlay that traces the
 * artwork has to sit exactly where the artwork sits, and "centred in the disc"
 * has to actually be true for that. Resource selection is unaffected - the
 * browser picks between <source> and <img> in the HTML resource-selection
 * algorithm, which never consults CSS display. */
.kcm__logo picture source { display: none; }

.kcm__logo img {
	position: relative;
	z-index: 2;
	width: 74%;
	height: auto;
	display: block;
	filter: drop-shadow(0 0 18px rgba(0, 164, 253, .38));
}

/* THE SCAN PASS — this pseudo-element used to be the thick rotating conic
 * sweep, and now carries the scan line instead.
 *
 * WHY THE SWEEP WENT
 * It was the heaviest moving thing on the disc: a 55–95% opacity gradient wedge
 * a fifth of the rim wide, rotating every 11s. Against the current — which is a
 * few thin comets — the sweep won every time, and the brief puts the current
 * first. Removing it also frees the only pseudo-element the disc had spare.
 *
 * HOW THE LINE IS CLIPPED TO THE DISC
 * `.kcm__logo` cannot take `overflow:hidden` — the orbit rings deliberately sit
 * OUTSIDE it at inset -7% and would be cut off. So the line is a background on
 * this element, and the element's own `border-radius:50%` clips it to the
 * circle. No extra markup, no clip-path, nothing for the SVG stripper to eat.
 *
 * The tile is 200% of the disc height, so every stop below is HALF the fraction
 * of the disc it looks like: the 49.7→50.3% core is 0.6% of the tile and so
 * 1.2% of the disc — about 2.8px at the 232px desktop disc and 1.5px at the
 * 124px phone one. Sizing it in percent rather than pixels is what keeps it a
 * hairline at both ends of the range instead of a bar on mobile.
 *
 * `background-position: 0 P%` with an image taller than the box resolves to an
 * offset of P% × (box − image) = P% × −H, so P=100% puts the core at the top
 * edge and P=0% at the bottom. Hence the keyframes count DOWN, and overshoot
 * past both edges so the line enters and leaves cleanly rather than appearing
 * mid-disc.
 *
 * 7.4s against the current's 3.2s is deliberate: the ratio is not a simple
 * fraction, so the scan and the current never settle into a shared beat. The
 * pass itself takes 40% of that (~3s) and the remaining 4.4s is dead air. A
 * scanner that runs continuously reads as a barber pole; one that passes, then
 * waits, reads as an instrument taking a measurement.
 */
.kcm__logo::before {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 50%;
	z-index: 3;                 /* above the artwork: it scans the symbol */
	pointer-events: none;
	/* Measured on a filmstrip rather than guessed. The first pass used a .72
	 * core 0.8% of the tile wide with .11 ramps either side, and at disc size
	 * that painted a ~9px slab of light straight across the symbol — a bar, not
	 * a line, and it broke the "do not overpower the symbol" rule outright.
	 * Halved to a 0.4%-of-tile core (≈1.9px painted) at .42, with the ramps
	 * dropped to .07 so what surrounds the core is a glow rather than more bar. */
	background-image: linear-gradient(180deg,
		rgba(0, 164, 253, 0)    47.0%,
		rgba(0, 164, 253, .07)  49.4%,
		rgba(143, 212, 255, .42) 49.80%,
		rgba(143, 212, 255, .42) 50.20%,
		rgba(0, 164, 253, .07)  50.6%,
		rgba(0, 164, 253, 0)    53.0%);
	background-size: 100% 200%;
	background-repeat: no-repeat;
	animation: kcm-logo-scan 7.4s linear infinite;
}

@keyframes kcm-logo-scan {
	0%   { background-position: 0 116%; }   /* just above the top edge */
	40%  { background-position: 0 -16%; }   /* just below the bottom edge */
	100% { background-position: 0 -16%; }   /* hold — one pass per cycle */
}

/* Static ultraviolet rim so a ring exists before/without animation.
 *
 * 1.3.0 cuts the inset violet wash from .22 to .08. That value was chosen
 * against the old #0D1621 disc centre, where it was one tint among several;
 * on a black ground it became the most saturated thing in the composition and
 * filled the disc with a plum cast — which is the opposite of "black
 * background so the symbol pops". The 1px violet rim stays, because that is
 * the brand note; only the wash goes. */
.kcm__logo::after {
	content: "";
	position: absolute;
	inset: 6%;
	border-radius: 50%;
	z-index: 0;
	border: 1px solid var(--kcm-lit-2);
	box-shadow: 0 0 22px rgba(124, 92, 255, .08) inset;
}

@keyframes kcm-orbit { to { transform: rotate(360deg); } }

/* THE ORBIT RINGS — two thin circular lines, counter-rotating.
 *
 * 1.3.0 replaces the two heavily-dashed arcs (`40 26 7 31` and `13 47 27 21`,
 * which read as broken rings rather than as lines) with a hairline orbit plus
 * one short bright TRACER travelling along it. Outer clockwise at 26s, inner
 * counter-clockwise at 34s.
 *
 * The split matters. A complete circle is rotationally symmetric, so rotating
 * one is invisible — it is the tracer that carries all the motion, and it is
 * the only thing that rotates. The hairline underneath is static and exists so
 * the orbit is still a legible circle in the moment the tracer is elsewhere,
 * and so the composition does not collapse when animation is off.
 *
 * The dasharrays are the real circumferences: 2πr at r=93 is 584.34 and at
 * r=82 is 515.22, so `30 554.34` and `24 491.22` each put exactly ONE arc of
 * the stated length on the ring with no repeat and no seam.
 *
 * Both rings are cyan, at different opacities, rather than one cyan and one
 * violet as before. Two hues at this weight read as two unrelated objects; one
 * hue at two depths reads as one system.
 *
 * Drawn as an inline SVG in the markup because CSS cannot express a rotating
 * partial ring at this precision. Decorative; aria-hidden in the markup.
 */
.kcm__arcs {
	position: absolute;
	inset: -7%;
	z-index: 1;
	pointer-events: none;
}

/* The static orbit paths. */
.kcm__ring {
	stroke: var(--kcm-cyan);
	stroke-width: .6;
}
.kcm__ring--a { stroke-opacity: .20; }
.kcm__ring--b { stroke-opacity: .13; }

/* The moving part. One filter on the group, not one per circle. */
.kcm__tracers { filter: drop-shadow(0 0 2px rgba(0, 164, 253, .75)); }
.kcm__trace {
	stroke-linecap: round;
	transform-origin: 50% 50%;
}
.kcm__trace--a {
	stroke: var(--kcm-cyan-2);
	stroke-width: 1.1;
	stroke-opacity: .85;
	stroke-dasharray: 30 554.34;          /* 2πr − 30, r = 93 */
	animation: kcm-orbit 26s linear infinite;
}
.kcm__trace--b {
	stroke: var(--kcm-cyan);
	stroke-width: .9;
	stroke-opacity: .6;
	stroke-dasharray: 24 491.22;          /* 2πr − 24, r = 82 */
	animation: kcm-orbit 34s linear infinite reverse;
}

/* Four static gauge marks at the cardinal points. The cheapest possible
 * "instrument" cue: no motion, one <path>, and it reads as measurement rather
 * than as ornament. */
.kcm__tick {
	stroke: var(--kcm-cyan);
	stroke-width: 1;
	stroke-opacity: .30;
	stroke-linecap: round;
}


/* -- 3d.i THE CURRENT -------------------------------------------------
 *
 * Animated energy travelling through the pet symbol's own internal linework.
 *
 * WHY THIS IS AN OVERLAY AND NOT A REDRAW
 * The symbol ships as a raster (kcai-symbol-640.png / .webp) - there is no
 * vector source on the server or in the repo. So the mesh inside the artwork
 * was EXTRACTED from the 1024 master: 24 node discs found by thresholding the
 * blue ink and taking the components of its distance transform above a 10px
 * radius floor (lines sit at r~2-3, the thick muzzle mark at r~5-7, node discs
 * at r~14-17), plus 2 terminal points the art draws lines TO but never dots.
 * Edges are the node pairs whose connecting stroke is continuously inside the
 * ink, each fitted with a quadratic bow so the curved strokes - node 23's two
 * lower legs, the dog's muzzle mark - are TRACED rather than chorded. Mean ink
 * coverage 0.997, worst 0.873 (a whisker that fades out by design). 4.3% of the
 * blue ink is left unexplained and all of it is the muzzle wedge being wider
 * than its centreline. The cat and the dog are still never redrawn: the PNG
 * paints the symbol, this layer paints only the light moving along it.
 *
 * ALIGNMENT
 * The overlay is absolutely positioned to the SAME box the <img> occupies -
 * 74% of the logo disc, 1:1, centred - so its 1024-unit viewBox registers with
 * the artwork 1:1. Checked against the live derivative, not assumed: the ink
 * bounding box of kcai-symbol-640.png normalises to x .1859-.9078, y .1297-.8719
 * against the master's .1836-.9082 / .1299-.8730. A straight downscale, so the
 * trace lands at every size.
 *
 * THE TIMING MODEL - this is what makes it read as current and not as flicker
 * Two sources fire together: the dog's head node and the cat's head node.
 * Dijkstra over the real edge lengths gives every node its distance from the
 * nearer of the two, every edge is oriented low-distance-end to
 * high-distance-end, and each element's animation-delay is that distance
 * divided by the speed. The result is one coherent wavefront expanding out of
 * both heads that meets in the middle of the shared body - the matching
 * gesture, in motion - rather than 47 wires blinking independently.
 *
 * Each wire carries exactly one travelling dash per cycle:
 *   pathLength  = the edge's true arc length, so dash units ARE user units
 *   dasharray   = PULSE, then (752 - PULSE) of gap - one period per cycle
 *   dashoffset  animates 752 -> 0, linear
 * which places the lit segment at s = 235 * t on every path. That is the
 * load-bearing detail: a per-edge DURATION would have made short wires fast and
 * long wires slow, and the eye reads varying speed as noise. One shared
 * keyframe, one constant speed, 47 different phases.
 *
 * 752 = 235 u/s * 3.2s. It must stay larger than the longest edge (230u) or a
 * wire would show two pulses at once. The front clears the whole mesh at 521u
 * = 2.22s, leaving ~1s of settle before the next wave - so a visitor sees a
 * complete sweep within roughly two seconds of arriving.
 *
 * Delays are NEGATIVE on purpose (distance/speed - cycle, which is the same
 * phase modulo the cycle). A positive delay would park each element on its
 * un-animated first frame - a stationary lit stub at the head of every wire -
 * until its turn came round.
 *
 * INTENSITY
 * Three tiers, and they differ ONLY in pulse length, stroke weight and glow.
 * Speed and cycle are identical across all three because the per-element phase
 * is baked into the markup: a tier that changed the cycle would desynchronise
 * the wavefront it is supposed to be dressing. --strong is the same wave with
 * a second pulse exactly half a period behind (dasharray 150 226 150 226 sums
 * to 752), so the mesh never goes quiet.
 */
.kcm__current {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 74%;              /* the <img> box, exactly */
	aspect-ratio: 1;
	transform: translate(-50%, -50%);
	/* Above the artwork (2) AND above the scan pass (3): this is light ON the
	 * symbol, and the brief puts the current first. Both are explicit rather
	 * than left to DOM order — the scan is a ::before and the current is a real
	 * child, so at equal z-index the winner would depend on where the content
	 * filter happens to inject, which is not a thing to depend on. */
	z-index: 4;
	pointer-events: none;
	overflow: visible;
	/* 1.3.2 - `mix-blend-mode: screen` WAS HERE AND HAD TO COME OUT.
	 *
	 * The intent was additive light: screen on an obsidian ground makes the
	 * pulse ADD to the line it crosses rather than cover it. On the live
	 * homepage it did something else entirely - the blend composited against a
	 * backdrop that did not include the <img>, and the disc rendered the rings,
	 * the scan and the travelling current while painting NO pet artwork at all.
	 * The image was never the problem: it decoded 640x640 at 10.2%
	 * non-transparent, loaded, opacity 1, correct box, z-index 2.
	 *
	 * 1.3.1 tried `isolation: isolate` on `.kcm__logo` and that did NOT fix it -
	 * the artwork still did not paint with the blend active. That declaration
	 * has been reverted rather than left in place doing nothing.
	 *
	 * Removing the blend restores the artwork and costs very little: the pulse
	 * is near-white cyan on a black disc either way, so it still reads as light.
	 * Verified on the live page with the current visible, across two captures
	 * two seconds apart, with the comet positions differing between them - so
	 * the artwork is painting AND the current is still animating over it.
	 *
	 * The global header's `.kcai-gh__current` still uses screen and renders
	 * correctly, so this is specific to this disc's compositing context and not
	 * a reason to change the header.
	 *
	 * Note for anything added here later: a single screenshot is not evidence
	 * for a compositing fix. The first attempt at this looked fixed in one
	 * capture that turned out to be a stale frame. Take two, separated. */
}

.kcm__current .kcm__wires {
	fill: none;
	stroke: var(--kcm-cyan-2);
	stroke-linecap: round;
	/* One filter on the GROUP, not one per path: 47 element filters would be 47
	 * offscreen buffers a frame. */
	filter:
		drop-shadow(0 0 3px  rgba(0, 164, 253, .95))
		drop-shadow(0 0 11px rgba(0, 164, 253, .48));
}

.kcm__current .kcm__wires path {
	stroke-width: 9;                 /* the artwork's own lines are ~6 units */
	stroke-dasharray: 22 5 15 6 10 8 7 10 4 12 3 650;   /* medium */
	stroke-dashoffset: 752;
	animation: kcm-current-flow 3.2s linear infinite;
	animation-delay: var(--kcm-d);
}

@keyframes kcm-current-flow { to { stroke-dashoffset: 0; } }

/* The node flare. It fires at the instant the pulse reaches the node, because
 * dist[head] == dist[tail] + edge length by construction, so the same
 * distance/speed delay serves both. */
.kcm__current .kcm__sparks circle {
	fill: var(--kcm-cyan-2);
	opacity: 0;
	transform-box: fill-box;
	transform-origin: 50% 50%;
	animation: kcm-current-spark 3.2s linear infinite;
	animation-delay: var(--kcm-d);
}

@keyframes kcm-current-spark {
	0%   { opacity: 0;   transform: scale(.45); }
	4%   { opacity: .92; transform: scale(1.22); }
	18%  { opacity: 0;   transform: scale(.9); }
	100% { opacity: 0;   transform: scale(.45); }
}

/* -- 3d.ii Intensity tiers ------------------------------------------------ */
.kcm__current--subtle .kcm__wires path { stroke-width: 7; stroke-dasharray: 14 5 9 7 6 9 4 11 3 684; }
.kcm__current--subtle .kcm__wires {
	filter: drop-shadow(0 0 3px rgba(0, 164, 253, .7)) drop-shadow(0 0 9px rgba(0, 164, 253, .3));
}
.kcm__current--subtle .kcm__sparks circle { animation-name: kcm-current-spark-subtle; }
@keyframes kcm-current-spark-subtle {
	0%   { opacity: 0;   transform: scale(.45); }
	4%   { opacity: .6;  transform: scale(1.1); }
	18%  { opacity: 0;   transform: scale(.9); }
	100% { opacity: 0;   transform: scale(.45); }
}

.kcm__current--strong .kcm__wires path {
	stroke-width: 11;
	stroke-dasharray: 22 5 15 6 10 8 7 10 4 12 3 274 22 5 15 6 10 8 7 10 4 12 3 274;
}
.kcm__current--strong .kcm__wires {
	filter:
		drop-shadow(0 0 4px  rgba(0, 164, 253, 1))
		drop-shadow(0 0 14px rgba(0, 164, 253, .6))
		drop-shadow(0 0 30px rgba(124, 92, 255, .35));
}

/* Small screens paint this at ~124px across. The wave still reads; the widest
 * shadow does not, and it is the expensive one. */
@media (max-width: 480px) {
	.kcm__current .kcm__wires,
	.kcm__current--strong .kcm__wires {
		filter: drop-shadow(0 0 3px rgba(0, 164, 253, .9));
	}
}

/* -- 3e. Supporting copy and actions ------------------------------------- */
.kcm__lede {
	margin: 0;
	max-width: 56ch;
	font-size: clamp(1rem, 1.9vw, 1.3rem);
	line-height: 1.55;
	color: var(--kcm-dim);
}

.kcm__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--kcm-s2);
	justify-content: center;
}

/* 52px floor is the brief's, and it is a MIN — the buttons grow with their
 * padding rather than being pinned to it. */
.kcm__cta {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	min-height: 52px;
	padding: 10px var(--kcm-s4);
	border-radius: 12px;
	border: 1px solid transparent;
	font: inherit;
	font-weight: 800;
	line-height: 1.15;
	text-decoration: none;
	cursor: pointer;
	transition: transform .16s ease, box-shadow .16s ease, background-color .16s ease;
}

/* Dark ink on cyan measures 11.6:1. White on cyan would be 1.5:1 and fail —
 * this is the case the brief calls out, and the fill decides the label. */
.kcm__cta--primary {
	background: linear-gradient(180deg, var(--kcm-cyan-2) 0%, var(--kcm-cyan) 100%);
	color: var(--kcm-ink);
	box-shadow: 0 0 0 1px rgba(0, 164, 253, .5), 0 10px 30px rgba(0, 164, 253, .22);
}
.kcm__cta--secondary {
	background: rgba(232, 241, 247, .05);
	color: var(--kcm-text);
	border-color: var(--kcm-lit);
}

.kcm__cta__sub { font-size: .8em; font-weight: 600; opacity: .78; }

.kcm__cta:hover { transform: translateY(-1px); }
.kcm__cta--primary:hover { box-shadow: 0 0 0 1px var(--kcm-cyan-2), 0 14px 38px rgba(0, 164, 253, .34); }
.kcm__cta--secondary:hover { background: rgba(232, 241, 247, .10); border-color: var(--kcm-cyan); }
.kcm__cta:active { transform: translateY(0); }

/* -- 3f. Operational status strip ---------------------------------------
 * A live-looking indicator that is honest: it states what the platform IS,
 * never a fabricated count. The dot NEVER carries meaning alone — the word
 * beside it does. */
.kcm__status {
	display: inline-flex;
	align-items: center;
	gap: var(--kcm-s1);
	padding: 7px 14px;
	border-radius: 999px;
	border: 1px solid var(--kcm-line);
	background: rgba(10, 18, 27, .7);
	font-size: .82rem;
	font-weight: 600;
	letter-spacing: .02em;
	color: var(--kcm-dim);
}
.kcm__dot {
	width: 7px; height: 7px; border-radius: 50%;
	background: var(--kcm-green);
	box-shadow: 0 0 0 3px rgba(61, 220, 151, .16), 0 0 12px rgba(61, 220, 151, .7);
	animation: kcm-pulse 2.8s ease-in-out infinite;
}
@keyframes kcm-pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }


/* -------------------------------------------------- 4. LUMINOUS SEPARATOR
 * Replaces a flat divider. One hairline that brightens toward the centre. */
.kcm-rule {
	height: 1px;
	border: 0;
	margin: 0;
	background: linear-gradient(90deg, transparent, var(--kcm-lit) 22%, var(--kcm-cyan) 50%, var(--kcm-lit) 78%, transparent);
	opacity: .5;
}


/* ------------------------------------------- 4b. THE WIDE COMPOSITION
 *
 * Below 1000px the masthead is one centred column and the reading order IS the
 * visual order. At 1000px and up the logo moves into its own column beside the
 * headline, which does two things: it stops the masthead growing to a
 * screen-and-a-half of vertical scroll, and it lets the headline take the
 * larger column so it stays unambiguously dominant.
 *
 * Grid AREAS, not reordering: the DOM order (headline → logo → lede → actions
 * → status) is already the correct accessible order, and nothing here moves an
 * element past another in the tab or reading sequence.
 */
@media (min-width: 1000px) {
	.kcm { padding: clamp(var(--kcm-s5), 5.5vw, 96px) var(--kcm-s4) var(--kcm-s5); }

	.kcm__inner {
		grid-template-columns: minmax(0, 1.5fr) minmax(0, .85fr);
		grid-template-areas:
			"head logo"
			"lede logo"
			"acts logo"
			"stat logo";
		align-items: center;
		justify-items: start;
		text-align: left;
		column-gap: clamp(var(--kcm-s4), 5vw, 72px);
		row-gap: var(--kcm-s3);
	}

	/* 17ch, not 15ch: at 15ch the second line broke as "Lost Pet / Recovery /
	 * Network" - three lines for four words, with "Network" orphaned. 17ch
	 * holds "Lost Pet Recovery" together and keeps the headline to four lines. */
	.kcm__h1      { grid-area: head; max-width: 17ch; }
	.kcm__logo    { grid-area: logo; justify-self: center; width: clamp(240px, 24vw, 340px); }
	.kcm__lede    { grid-area: lede; }
	.kcm__actions { grid-area: acts; justify-content: flex-start; }
	.kcm__status  { grid-area: stat; }
}

/* Give the headline room to breathe before the two-column switch. */
@media (min-width: 620px) and (max-width: 999px) {
	.kcm__h1 { max-width: 18ch; }
}


/* ------------------------------------------------------ 4c. THE NAVIGATION
 *
 * The nav now sits BENEATH the masthead, so it stops being the loudest thing
 * on the page and becomes the working toolbar it should always have been.
 * It keeps every destination and every control it already had.
 *
 * The two posting buttons were a saturated orange and a saturated blue —
 * the brightest objects on the old page, competing with each other and with
 * the message. In the new system the primary action is cyan and the secondary
 * is an outline, so there is one clear first action and one clear second.
 */
body.kcai .kch-nav {
	height: auto;
	min-height: var(--nav-h);
	row-gap: var(--kcm-s1);
	/* The base file only enables wrapping inside its small-width media query, so
	 * between ~900px and ~1200px the bar is `nowrap` with `.kch-nav__actions`
	 * pinned right by `margin-left:auto`. Every quiet-nav link added there is
	 * pushed straight off the right edge instead of moving to a second row:
	 * measured at 178px of overflow at 960, 114px at 1024, 38px at 1100 once the
	 * quiet nav carried its five real destinations. `height:auto`, `min-height`
	 * and `row-gap` directly above were already written for a bar that wraps —
	 * this is the declaration that lets them do anything. Above ~1200px nothing
	 * wraps because nothing needs to, so the wide layout is unchanged. */
	flex-wrap: wrap;
	/* BLOCK AXIS ONLY. The inline gutter is left exactly as the base file set
	 * it (12px at small widths) and must not be widened here: the small-width
	 * rule `.kch-nav__actions{flex:1 0 100%}` sizes against this box, the
	 * posting button inside is `white-space:nowrap` with `min-width:auto`, and
	 * its min-content is ~272px. At a 12px gutter it fits 296px exactly; at
	 * 16px it does not, and the button overflows its own grid cell. */
	padding-block: var(--kcm-s1);
	background: var(--kcm-void-2);
	border-bottom: 1px solid var(--kcm-line);
	border-top: 1px solid var(--kcm-line);
	color: var(--kcm-text);
	position: sticky;
	top: 0;
}

body.kcai .kch-nav__brand   { color: var(--kcm-text); }
body.kcai .kch-nav__brand b { color: var(--kcm-cyan); }

/* The old mark was a conic blob in the previous palette. Retire the blob and
 * use the real symbol, small, so the brand reads the same here and above. */
/* The chip carried a conic gradient in the old palette and read as a coloured
 * blob once everything around it went obsidian.
 *
 * It is NOT given the real symbol as a background image, deliberately. The
 * chip is 22px, and this artwork was measured as excellent at >=180px,
 * readable at 48, marginal at 32 and pure noise at 16 - the node mesh stops
 * resolving. A 22px copy would be a smudge pretending to be a logo. It gets a
 * cyan node-and-ring glyph instead, which is legible at this size and still
 * says "network". */
body.kcai .kch-nav__mark {
	background:
		radial-gradient(circle at 50% 50%, var(--kcm-cyan) 0 2.5px, transparent 2.5px),
		radial-gradient(circle at 50% 50%, #12202D 0%, #05080C 72%);
	box-shadow:
		inset 0 0 0 1px var(--kcm-lit),
		inset 0 0 0 4px rgba(124, 92, 255, .18),
		0 0 10px rgba(0, 164, 253, .25);
}

body.kcai .kch-nav__quiet a {
	color: var(--kcm-dim);
	border-radius: 8px;
	padding: 8px 10px;
	min-height: 44px;
	display: inline-flex;
	align-items: center;
}
body.kcai .kch-nav__quiet a:hover { color: var(--kcm-cyan); background: rgba(0, 164, 253, .07); }

body.kcai .kch-nav a:focus-visible,
body.kcai .kch-nav button:focus-visible {
	outline: 3px solid var(--kcm-cyan-2);
	outline-offset: 2px;
	border-radius: 10px;
}

/* 52px, not the base file's 44px: these are primary actions and the brief sets
 * the higher floor for those. */
body.kcai .kch-post {
	min-height: 52px;
	border-radius: 11px;
	color: var(--kcm-ink);
	box-shadow: none;
}
body.kcai .kch-post--missing {
	background: linear-gradient(180deg, var(--kcm-cyan-2) 0%, var(--kcm-cyan) 100%);
	color: var(--kcm-ink);
	box-shadow: 0 0 0 1px rgba(0, 164, 253, .5), 0 6px 20px rgba(0, 164, 253, .2);
}
body.kcai .kch-post--found {
	background: transparent;
	color: var(--kcm-text);
	box-shadow: inset 0 0 0 1px var(--kcm-lit);
}
body.kcai .kch-post--found:hover { background: rgba(0, 164, 253, .09); }
body.kcai .kch-post:hover { filter: none; }

/* The theme toggle keeps working; it just stops being a light-grey pill on a
 * dark bar. */
body.kcai .kch-theme {
	color: var(--kcm-dim);
	min-height: 44px;
}


/* ------------------------------------------------------- 5. FOCUS, ALWAYS
 * One visible ring for every interactive thing in the masthead, on top of
 * whatever the element's own background is. Two-tone so it survives both the
 * obsidian ground and a cyan button fill.
 */
.kcm a:focus-visible,
.kcm button:focus-visible,
.kcm__cta:focus-visible {
	outline: 3px solid var(--kcm-cyan-2);
	outline-offset: 3px;
	border-radius: 10px;
}


/* ------------------------------------------------ 6. REDUCED MOTION
 * Everything animated above is decorative, so all of it stops. What remains
 * is the gradient, the glow, the current bar, the halo and the rings — the
 * design as drawn. Nothing is lost but movement.
 */
@media (prefers-reduced-motion: reduce) {
	.kcm__net::after { animation: none; background-position: 50% 0; }

	/* Both scans stop. A stopped scan line would otherwise freeze mid-pass and
	 * park a bright stripe across the band and across the symbol — which is
	 * worse than no scan at all, because a stationary line reads as a rendering
	 * fault rather than as an instrument. So they are removed outright, not
	 * merely paused. */
	.kcm__scan,
	.kcm__logo::before { animation: none; background-image: none; }

	/* The rings keep their hairlines and lose only the tracers, so two clean
	 * circles remain around the disc. */
	.kcm__trace { animation: none; display: none; }
	.kcm__ring--a { stroke-opacity: .30; }
	.kcm__ring--b { stroke-opacity: .20; }

	/* The current stops travelling, but the wires do not go dark: the trace is
	 * held lit at a low, even level so the reduced-motion visitor still gets a
	 * symbol whose linework reads as energised, just not moving. */
	.kcm__current .kcm__wires path {
		animation: none;
		stroke-dasharray: none;
		stroke-dashoffset: 0;
		stroke-width: 6;
		opacity: .30;
	}
	.kcm__current .kcm__sparks circle {
		animation: none;
		opacity: .28;
		transform: none;
	}
	.kcm__dot { animation: none; }
	.kcm__cta { transition: none; }
	.kcm__cta:hover { transform: none; }
}


/* ------------------------------------------------- 7. FORCED COLORS
 * In a forced-colours mode the system palette wins and every decorative
 * gradient becomes noise. Strip the effects, hand the text back to the OS,
 * and keep the structure legible.
 */
@media (forced-colors: active) {
	.kcm__field,
	.kcm__scan,
	.kcm__arcs,
	.kcm__current { display: none; }

	.kcm__net,
	.kch-statement b,
	.kcm__ai {
		background-image: none;
		-webkit-text-fill-color: revert;
		color: LinkText;
		text-shadow: none;
	}
	.kcm__net::after { display: none; }

	.kcm__logo,
	.kcm__logo::before,
	.kcm__logo::after {
		background: none;
		box-shadow: none;
		border-color: CanvasText;
		animation: none;
	}
	.kcm__logo img { filter: none; }

	.kcm__cta {
		border: 1px solid ButtonText;
		background: ButtonFace;
		color: ButtonText;
		box-shadow: none;
	}
	.kcm-rule { background: CanvasText; opacity: 1; }
	.kcm__dot { background: CanvasText; box-shadow: none; }
}

/* ==========================================================================
   8. ICON RESTORATION — recovered artwork, delivered from CSS on purpose
   ==========================================================================

   A recurring process rewrites page 11's stored content and strips EVERY <svg>
   out of it. Proven from the revision history: author=0 saves at 14:45:01 GMT
   (7 Aug, rev 399), 14:45:03 (8 Aug, rev 401), 14:45:08 (31 Jul, rev 254) and
   14:44:48 (12 Aug, rev 525) each took the preceding revision's SVG count to
   zero. This is why the icons "were deleted and never restored" - they were
   restored, and deleted again.

   So the icons are NOT put back into the page. They are painted from here,
   where nothing rewrites them. mask-image + currentColor keeps the original
   behaviour: each glyph inherits the button's colour exactly as the inline
   <svg fill="currentColor"> did.

   The artwork is the EXACT pre-damage artwork, recovered from revisions 398
   and 400 and verified byte-identical in both before use. Nothing is redrawn.

   kch-search__go is deliberately NOT restored: it is a submit button with no
   form and no input, and its markup has already been removed.
*/

body.kcai {
	--kcm-ic-geo: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2v3M12 19v3M2 12h3M19 12h3' stroke='black' stroke-width='2' stroke-linecap='round' fill='none'/%3E%3Ccircle cx='12' cy='12' r='5.5' stroke='black' stroke-width='2' fill='none'/%3E%3Ccircle cx='12' cy='12' r='1.8' fill='black'/%3E%3C/svg%3E");
	--kcm-ic-shelter: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3.2 3.4 9.1v1.9h1.5V20h13.2v-9h1.5V9.1z' fill='black'/%3E%3C/svg%3E");
	--kcm-ic-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6L6 18' stroke='black' stroke-width='2.2' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
	--kcm-ic-sun: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='4.2' fill='black' stroke='none'/%3E%3Cpath d='M12 1.8v2.6M12 19.6v2.6M1.8 12h2.6M19.6 12h2.6M4.8 4.8l1.9 1.9M17.3 17.3l1.9 1.9M19.2 4.8l-1.9 1.9M6.7 17.3l-1.9 1.9'/%3E%3C/svg%3E");
	--kcm-ic-moon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M20.7 14.6A8.6 8.6 0 0 1 9.4 3.3a8.7 8.7 0 1 0 11.3 11.3z'/%3E%3C/svg%3E");
}

/* Shared glyph mechanics. `background: currentColor` behind a mask reproduces
 * fill="currentColor" exactly, including the :hover and .is-on colour changes
 * the base file already defines. */
body.kcai .kch-tool::before,
body.kcai .kch-panel__x::before,
body.kcai .kch-theme__sw i::before {
	content: "";
	display: block;
	flex: none;
	background: currentColor;
	-webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
	-webkit-mask-position: center;  mask-position: center;
	-webkit-mask-size: contain;     mask-size: contain;
}

/* 17px is the size the base file gives `.kch-tool svg` and `.kch-panel__x svg`,
 * so the restored glyphs occupy exactly the space the originals did and the
 * 7px flex gap still lands where it was designed to. */
body.kcai .kch-tool::before,
body.kcai .kch-panel__x::before { width: 17px; height: 17px; }

body.kcai #kch-geo::before        { -webkit-mask-image: var(--kcm-ic-geo);     mask-image: var(--kcm-ic-geo);     color: var(--kcm-cyan); }
body.kcai #kch-shelters::before   { -webkit-mask-image: var(--kcm-ic-shelter); mask-image: var(--kcm-ic-shelter); color: var(--kcm-cyan); }

/* The close button is centred by the base file's grid; it had no glyph at all
 * after the strip - a 44px dark disc with an aria-label and nothing to see. */
body.kcai .kch-panel__x {
	display: grid;
	place-items: center;
}
body.kcai .kch-panel__x::before { -webkit-mask-image: var(--kcm-ic-close); mask-image: var(--kcm-ic-close); }

/* Sun/moon inside the toggle knob. 11px is the base file's `.kch-theme__sw svg`
 * size, and the swap is driven by the same [aria-pressed] state it always was. */
body.kcai .kch-theme__sw i::before { width: 11px; height: 11px; }
body.kcai .kch-theme[aria-pressed="false"] .kch-theme__sw i::before { -webkit-mask-image: var(--kcm-ic-sun);  mask-image: var(--kcm-ic-sun); }
body.kcai .kch-theme[aria-pressed="true"]  .kch-theme__sw i::before { -webkit-mask-image: var(--kcm-ic-moon); mask-image: var(--kcm-ic-moon); }

/* ---- the accessible-name defect the icons were hiding --------------------
 * At <=560px the base file does `.kch-tool .kch-tool__t { display: none }`, and
 * at <=900px the same for the shelter button. display:none removes the text
 * from the ACCESSIBILITY TREE, not just from view - and these buttons carry no
 * aria-label. Measured live at 390px: #kch-geo was a visible 44x44 button with
 * innerText "" and no accessible name at all. Clipping instead of hiding keeps
 * it icon-only on screen and gives it its name back.
 */
@media (max-width: 900px) {
	body.kcai .kch-tool--shelter .kch-tool__t { display: block; }
}
@media (max-width: 560px) {
	body.kcai .kch-tool .kch-tool__t { display: block; }
}
@media (max-width: 900px) {
	body.kcai .kch-tool--shelter .kch-tool__t,
	body.kcai .kch-tool .kch-tool__t {
		position: absolute;
		width: 1px; height: 1px;
		margin: -1px; padding: 0; border: 0;
		overflow: hidden;
		clip: rect(0 0 0 0);
		clip-path: inset(50%);
		white-space: nowrap;
	}
}

/* Forced colors: a masked glyph is painted with `background`, which forced-colors
 * overrides - the icon would vanish. Repaint it with the system button text
 * colour so every restored control keeps a visible mark. */
@media (forced-colors: active) {
	body.kcai .kch-tool::before,
	body.kcai .kch-panel__x::before,
	body.kcai .kch-theme__sw i::before { background: ButtonText; }
}
