/* ao-design-system.css - the AO-owned token + primitive layer (2026-08-25, ui/perf-wave1-462)
 *
 * WHY THIS FILE EXISTS
 * --------------------
 * themes/css/index.css is 88,113 lines, 13,553 of which target 706 distinct webix_*
 * internal class names. Every new component built on top of it starts a specificity
 * war it did not pick. This file is the small owned layer new components consume
 * INSTEAD of reaching into that cascade: ~30 tokens and ~10 primitives, nothing more.
 *
 * SOURCE OF TRUTH
 * ---------------
 * The visual language is the live thinking map's - js/ao-thinking-map.js, the
 * `var CSS = [...]` block from line 248 (tm3- namespace). Every value below is
 * either lifted from it verbatim or is the --vux-* token that resolves to the same
 * colour on the default palette. The second consumer, themes/css/ao-ontology-graph.css,
 * distilled the same vocabulary independently; it now consumes this file.
 *
 * NAMESPACES (do not mix them)
 *   --tm-*    the thinking map's private tokens.  Scoped to #ao-tmap-stage. Not ours to use.
 *   --vux-*   the platform's.  Read them, never define them.
 *   --ao-*    ours.  Defined here, consumed everywhere.
 *
 * LOAD ORDER - DELIBERATE: THIS SHEET LOADS *BEFORE* themes/css/index.css.
 * ------------------------------------------------------------------------
 * index.html carries this as a static <link> in <head>; index.css is appended to
 * <head> at runtime by loadCss(), and ao-ontology-graph.css / ao-cuecards.css are
 * injected later still by the scripts that need them. So in document order this file
 * is FIRST, and the app wins every same-specificity tie against it.
 * That is the property we want: this layer is additive and must never be able to
 * restyle an existing screen by accident. A component that genuinely needs to beat
 * the app does it from its OWN sheet, which loads after index.css.
 * (Deliberately NOT wrapped in @layer: a browser without cascade-layer support drops
 * the whole block, which would be a silent total failure rather than a graceful one.)
 *
 * THEME AWARENESS - WHY THE TOKENS SIT ON `body` AND NOT ON `:root`
 * -----------------------------------------------------------------
 * The platform's palettes are injected at RUNTIME as <style id="vux-theme"> holding
 * `.<name>-theme{--vux-...}` rules, and ThemeUtil.switchTheme() applies the class with
 *     document.body.setAttribute('class', 'theme ' + name + '-theme')
 * i.e. always on <body>, and the class list is REPLACED wholesale on every switch.
 * A custom property's var() references are substituted when the property is computed
 * ON THE ELEMENT THAT DECLARES IT, and descendants inherit the already-substituted
 * result. Declaring these on :root would therefore freeze them to the :root defaults
 * in index.css and the theme switch would do nothing. Declaring them on `body` - the
 * same element the theme class lands on - is what makes them follow the live theme.
 * Corollary: NEVER move this block to :root, and never assume a token is available to
 * a rule that styles <html> or an element inside an iframe (the theme <style> is
 * injected into iframe documents separately; this sheet is not).
 *
 * Surface and ink are always PAIRED (--ao-surface with --ao-ink/--ao-mid/--ao-dim) so
 * a dark or high-contrast theme flips both together.
 */

body {
	/* ---- brand ramp ------------------------------------------------------ *
	 * Fallbacks are the thinking map's light values (--tm-p / -pd / -pl / -p50 / -p40).
	 * NOTE the ramp is NOT reliably monotonic across themes - see --ao-tint-* below. */
	--ao-primary: var(--vux-primary-500, #0079e1);
	--ao-primary-deep: var(--vux-primary-700, #005bab);
	--ao-primary-light: var(--vux-primary-300, #7fb2e6);
	--ao-primary-50: var(--vux-primary-50, #f0f7fd);
	--ao-primary-40: var(--vux-primary-40, #e8f2fc);

	/* ---- ink: three steps, always paired with --ao-surface --------------- */
	--ao-ink: var(--vux-text-500, #1e2429);
	--ao-mid: var(--vux-text-400, #5e7081);
	--ao-dim: var(--vux-text-300, #94a3b1);

	/* ---- semantic accents (tm3 warm / ok / bad) -------------------------- */
	--ao-warm: var(--vux-loader-500, #f57c00);
	--ao-ok: var(--vux-secondary-500, #00a3a3);
	--ao-bad: var(--vux-error-500, #d4437a);

	/* ---- surfaces -------------------------------------------------------- */
	--ao-surface: var(--vux-bg, #fff);
	--ao-hairline: var(--vux-border-500, #dde5ee);

	/* Chrome translucency. See the @supports block below: keyed off --vux-rgb-bg
	   these are wrong in the stock dark theme, and that is repaired there. */
	--ao-glass: rgba(var(--vux-rgb-bg, 255, 255, 255), .86);
	--ao-veil: rgba(var(--vux-rgb-bg, 255, 255, 255), .55);

	/* The constellation field: a soft radial that darkens outward, never a flat
	   fill. Degrades EXACTLY to the thinking map's stage gradient. */
	--ao-field: radial-gradient(120% 90% at 50% 8%,
			var(--vux-bg, #ffffff) 0%,
			var(--ao-tint-near) 42%,
			var(--ao-tint-far) 100%);

	/* ---- elevation ------------------------------------------------------- *
	 * --ao-shadow    neutral rest lift: chrome sitting above the field.
	 * --ao-elevation primary-tinted lift: something is active / hovered / live. */
	--ao-shadow: 0 4px 14px rgba(var(--vux-rgb-bg-inverse, 0, 0, 0), .10);
	--ao-elevation: 0 4px 14px rgba(0, 121, 225, .22);

	/* ---- motion ---------------------------------------------------------- */
	--ao-ease: cubic-bezier(.2, .7, .3, 1);
	/* standard: state changes */
	--ao-ease-pop: cubic-bezier(.34, 1.32, .5, 1);
	/* entrances: slight overshoot */
	--ao-ease-glide: cubic-bezier(.22, .61, .36, 1);
	/* long travel: layout, halos */

	/* ---- shape ----------------------------------------------------------- */
	--ao-radius-pill: 26px;
	/* .tm3-chip, #ao-tmap-strip, every chrome pill */
	--ao-radius-card: 14px;
	/* .tm3-card-pop, .tm3-pill - the only card radius in the reference */
	--ao-blur: 6px;
	/* the one backdrop blur the reference uses */

	/* ---- type ------------------------------------------------------------ *
	 * A deliberately PARTIAL scale: exactly the four sizes that recur verbatim in
	 * both reference files. If a new size is needed, argue for it in
	 * ui-perf-wave1/design-system.md before adding it here. */
	--ao-font: Inter, var(--vux-ff-regular, "Roboto"), "Helvetica Neue", Arial, sans-serif;
	--ao-fs-micro: 10.5px;
	/* metadata: badges, edge labels, hints, notes */
	--ao-fs-small: 11px;
	/* chip / legend labels */
	--ao-fs-body: 12.5px;
	/* running copy inside a card */
	--ao-fs-name: 13px;
	/* object name / card title */

	/* ---- internal: the two field stops, ordered near -> far -------------- *
	 * Not part of the public contract. They exist ONLY so the purple theme can
	 * swap them; consume --ao-field, not these. */
	--ao-tint-near: var(--vux-primary-50, #f4f8fd);
	--ao-tint-far: var(--vux-primary-40, #eaf1fa);
}

/* INHERITED ODDITY 1 - the purple ramp is not monotonic.
   Every other palette has primary-50 lighter than primary-40, so surface -> 50 -> 40
   darkens outward. Purple ships --vux-primary-50 #ECE4F8 DARKER than --vux-primary-40
   #F5F1FB, which turns the field into a visible dark ring with a lighter rim. Swapping
   the two stops (and nothing else - no colour is hardcoded here) restores the ramp. */
body.purple-theme {
	--ao-tint-near: var(--vux-primary-40, #eaf1fa);
	--ao-tint-far: var(--vux-primary-50, #f4f8fd);
}

/* INHERITED ODDITY 2 - --vux-rgb-bg is not the rgb of --vux-bg.
   In the stock dark theme --vux-rgb-bg is `0,0,0` while --vux-bg is #1c1c1c, so any
   glass keyed to the rgb token paints DARKER than the panel behind it. rgba() cannot
   take a hex custom property, which is why the whole codebase reaches for the rgb
   token; color-mix() can, so where it is supported we derive translucency from the
   surface colour itself and the defect disappears.
   The same trick makes --ao-elevation follow the live primary instead of being the
   AO blue for ever.
   All three resolve to values IDENTICAL to the fallbacks above on the default, light,
   purple and high-contrast palettes - this block only changes dark, and only fixes it. */
@supports (background: color-mix(in srgb, red, blue)) {
	body {
		--ao-glass: color-mix(in srgb, var(--ao-surface) 86%, transparent);
		--ao-veil: color-mix(in srgb, var(--ao-surface) 55%, transparent);
		--ao-elevation: 0 4px 14px color-mix(in srgb, var(--ao-primary) 22%, transparent);
	}
}

/* ==================================================================== *
 * PRIMITIVES
 *
 * The handful of shapes the reference keeps re-deriving - each one is something
 * js/ao-thinking-map.js and/or ao-ontology-graph.css already writes out by hand more
 * than once. This is not a component library: it is a floor, and it stays short.
 * Composition over configuration - .ao-glass carries no geometry, .ao-pill carries
 * no colour, so a chrome pill is `class="ao-pill ao-glass"`.
 * ==================================================================== */

/* The stage. A soft radial field, the AO type stack, paired ink. */
.ao-field {
	background: var(--ao-field);
	font-family: var(--ao-font);
	color: var(--ao-ink);
}

/* The object. Surface fill + one cool hairline, never a shadow by default -
   the reference earns depth from the field behind the card, not from drop shadows. */
.ao-card {
	background: var(--ao-surface);
	border: 1px solid var(--ao-hairline);
	border-radius: var(--ao-radius-card);
	color: var(--ao-ink);
}

/* Opt-in resting elevation for a card that floats over content (a popover). */
.ao-card-raised {
	box-shadow: var(--ao-shadow);
}

/* Chrome surface treatment: translucent, blurred, hairlined, lifted. No geometry -
   pair with .ao-pill for a chip, or set your own radius/padding for a bar. */
.ao-glass {
	background: var(--ao-glass);
	border: 1px solid var(--ao-hairline);
	-webkit-backdrop-filter: blur(var(--ao-blur));
	backdrop-filter: blur(var(--ao-blur));
	box-shadow: var(--ao-shadow);
}

/* Pill geometry. No background, no border - stack .ao-glass on it for chrome. */
.ao-pill {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 3px 11px;
	border-radius: var(--ao-radius-pill);
	font-family: var(--ao-font);
	font-size: var(--ao-fs-small);
	line-height: 15px;
	letter-spacing: .1px;
	white-space: nowrap;
	color: var(--ao-mid);
}

/* A toned status token. Solid surface (a badge must stay legible over the field),
   tone carried by text + hairline, tinted fill where color-mix is available. */
.ao-badge {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	padding: 2px 9px;
	border: 1px solid var(--ao-hairline);
	border-radius: var(--ao-radius-pill);
	background: var(--ao-surface);
	font-family: var(--ao-font);
	font-size: var(--ao-fs-micro);
	line-height: 15px;
	letter-spacing: .2px;
	color: var(--ao-mid);
	white-space: nowrap;
}

.ao-badge-primary {
	color: var(--ao-primary-deep);
	border-color: var(--ao-primary-light);
	background: var(--ao-primary-50);
}

.ao-badge-ok {
	color: var(--ao-ok);
	border-color: var(--ao-ok);
}

.ao-badge-warm {
	color: var(--ao-warm);
	border-color: var(--ao-warm);
}

.ao-badge-bad {
	color: var(--ao-bad);
	border-color: var(--ao-bad);
}

@supports (background: color-mix(in srgb, red, blue)) {

	/* pale tint + softened hairline, derived from the tone rather than hand-picked,
	   so the badge tracks whatever palette the platform injected. */
	.ao-badge-ok {
		background: color-mix(in srgb, var(--ao-ok) 8%, var(--ao-surface));
		border-color: color-mix(in srgb, var(--ao-ok) 34%, var(--ao-surface));
	}

	.ao-badge-warm {
		background: color-mix(in srgb, var(--ao-warm) 8%, var(--ao-surface));
		border-color: color-mix(in srgb, var(--ao-warm) 34%, var(--ao-surface));
	}

	.ao-badge-bad {
		background: color-mix(in srgb, var(--ao-bad) 8%, var(--ao-surface));
		border-color: color-mix(in srgb, var(--ao-bad) 34%, var(--ao-surface));
	}
}

/* Edge fade. Softens where the field meets its frame and takes the glare off
   anything sliding under the chrome. Deliberately WEAK: at the thinking map's .96
   a card near an edge reads as dimmed, which in a graph is a real state.
   Host must be positioned. */
.ao-scrim {
	position: absolute;
	left: 0;
	right: 0;
	height: 40px;
	pointer-events: none;
}

.ao-scrim-top {
	top: 0;
	background: linear-gradient(180deg, var(--ao-veil) 0%, transparent 100%);
}

.ao-scrim-bot {
	bottom: 0;
	background: linear-gradient(0deg, var(--ao-veil) 0%, transparent 100%);
}

/* Hairline divider. Works on <hr> or a bare <div>. */
.ao-rule {
	border: 0;
	border-top: 1px solid var(--ao-hairline);
	margin: 0;
	height: 0;
}

/* Ghost icon button: circular, transparent at rest, tints and lifts on hover.
   Sized for a 15px stroked glyph (see the svg rule). */
.ao-iconbtn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 27px;
	height: 27px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: transparent;
	color: var(--ao-mid);
	font-family: var(--ao-font);
	cursor: pointer;
	transition: background-color .2s ease, color .2s ease, transform .25s var(--ao-ease);
}

.ao-iconbtn svg {
	width: 15px;
	height: 15px;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.6;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.ao-iconbtn:hover {
	background: var(--ao-primary-50);
	color: var(--ao-primary-deep);
	transform: translateY(-1px);
}

.ao-iconbtn[aria-pressed="true"] {
	color: var(--ao-primary-deep);
	background: var(--ao-primary-40);
}

/* Focus affordance. :focus-visible only - a mouse click must not draw a ring.
   .ao-iconbtn gets it without opting in. */
.ao-focus:focus-visible,
.ao-iconbtn:focus-visible {
	outline: 2px solid var(--ao-primary);
	outline-offset: 2px;
}

/* Hover raise, for anything that is not a card or a button (a row, a tile). */
.ao-lift {
	transition: box-shadow .25s var(--ao-ease), transform .25s var(--ao-ease);
}

.ao-lift:hover {
	box-shadow: var(--ao-elevation);
	transform: translateY(-1px);
}

/* Selected affordance, shared by cards and tiles. */
.ao-selected {
	border-color: var(--ao-primary);
	box-shadow: var(--ao-elevation);
}

/* Screen-reader-only live region: narrate without stealing focus (cf. .tm3-sr). */
.ao-sr {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

@media (prefers-reduced-motion: reduce) {

	.ao-iconbtn,
	.ao-lift {
		transition: none;
	}

	.ao-iconbtn:hover,
	.ao-lift:hover {
		transform: none;
	}
}
