/*
 * AstroDexa catalogue — design tokens (plan §8).
 * Platform chrome only. The six astrologer mini-site schemes live in
 * $lib/minisite/schemes.ts and are applied as their own --ms-* variables.
 *
 * Visual direction: professional registry, not psychic hotline. Serif-led,
 * editorial, calm. No CSS framework — Svelte scoped styles over these tokens.
 */

/*
 * THEME STATES (plan §1a, 1 Sep; default flipped to light 2 Sep — Hendrik:
 * "default mode should be day mode"; narrowed to two states 4 Sep). Two of
 * them, and only two:
 *
 *   :root                                        light — the DEFAULT, always
 *   :root[data-theme='dark']                     dark, chosen, on any OS
 *
 * The absence of the attribute is load-bearing and it means LIGHT. A visitor
 * who has never chosen gets day mode on a dark-set OS too, which is the point.
 * Server-rendered HTML carries no attribute, so the default needs no JavaScript
 * and cannot flash.
 *
 * There is deliberately no OS-preference media query left in this file, and
 * theme.test.ts asserts as much. The third state — dark only because the OS
 * said so — was removed with the third stop on the toggle; the light/dark
 * decision is now the visitor's alone, and one selector answers it.
 *
 * No colour is ever defined only inside an attribute block, so a page renders
 * correctly before the boot script in app.html has run. `$lib/theme.ts` owns
 * the attribute.
 */

:root {
	color-scheme: light;

	/* ---- Typography ------------------------------------------------------ */
	--font-body: 'Lora', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Times New Roman',
		serif;
	--font-ui: 'Source Sans 3', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
		Arial, sans-serif;
	--font-display: 'Playfair Display', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
	--font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

	/*
	 * Type scale — 1.2 minor third off a 1rem base, plus a display step.
	 *
	 * FLUID FROM v1.1.0 (16 Sep). The scale used to be plain rem, which meant a
	 * 47.8px `--step-6` headline rendered at 47.8px on a 390px phone: `html {
	 * -webkit-text-size-adjust: 100% }` below removes the browser's own
	 * text-inflation safety net, so whatever a rule declares is exactly what a
	 * phone draws. Consumers were patching that one component at a time with
	 * hand-written `@media` overrides, and the two highest-visibility headings on
	 * the catalogue (the claimed astrologer and school h1s) were simply missed.
	 *
	 * Utopia-style `clamp()`, poles 320px → 1240px, and the rule that makes this
	 * safe to adopt in a shipped product: **the MAX of every step is the value
	 * that step had before**. Nothing above 1240px moves by a pixel — desktop is
	 * unchanged by construction, and only narrow viewports shrink. The minimum
	 * pole is its own ~1.11 ramp, tight enough that a display heading still reads
	 * as display type at 320px.
	 *
	 * `--step-0` AND BELOW ARE DELIBERATELY NOT FLUID. Body copy is already at
	 * its floor, and `--step-0` is what text inputs are sized in: anything under
	 * 16px makes Mobile Safari zoom the page on focus. A fluid step-0 would put
	 * that regression one rounding error away, so it stays a literal 1rem.
	 *
	 * The preferred term mixes rem with vw on purpose (Utopia's form): the rem
	 * part keeps the scale responding to a visitor's own font-size setting, so
	 * this is not the "text that refuses to zoom" failure mode of a pure-vw
	 * scale.
	 */
	--step--2: 0.694rem;
	--step--1: 0.833rem;
	--step-0: 1rem;
	--step-1: clamp(1.0938rem, 1.0568rem + 0.1848vw, 1.2rem);
	--step-2: clamp(1.2188rem, 1.1418rem + 0.3848vw, 1.44rem);
	--step-3: clamp(1.3438rem, 1.2101rem + 0.6683vw, 1.728rem);
	--step-4: clamp(1.5rem, 1.3004rem + 0.9983vw, 2.074rem);
	--step-5: clamp(1.6875rem, 1.4091rem + 1.3922vw, 2.488rem);
	--step-6: clamp(1.875rem, 1.4886rem + 1.9322vw, 2.986rem);

	/*
	 * THE SIZE A FORM CONTROL'S TEXT IS SET IN, and the one token in this file
	 * that answers to a viewport rather than to a scale (v1.1.0).
	 *
	 * A quiet control — the header's search box, a filter's select, a support
	 * form — is drawn at `--step--1` on purpose. At 13.3px Mobile Safari zooms
	 * the whole page the moment the control takes focus, which is the "why did
	 * the page jump when I tapped search" bug, and the viewport-meta "fixes" for
	 * it disable pinch-zoom and fail WCAG. So the size is the quiet one wherever
	 * there is room for it and 16px where there is not, decided once here rather
	 * than in every component that owns an input.
	 */
	--step-control: var(--step--1);

	--leading-tight: 1.15;
	--leading-snug: 1.3;
	--leading-normal: 1.55;
	--leading-prose: 1.7;

	--tracking-tight: -0.011em;
	--tracking-wide: 0.02em;
	--tracking-caps: 0.09em;

	--weight-regular: 400;
	--weight-medium: 500;
	--weight-semibold: 600;
	--weight-bold: 700;

	/* Measure — bios read as prose. */
	--measure: 68ch;
	--measure-narrow: 54ch;

	/* ---- Spacing ---------------------------------------------------------
	 *
	 * THE BIG FOUR ARE FLUID, THE SMALL FIVE ARE NOT (v1.1.0). The small steps
	 * are gaps between things that belong together — a glyph and its label, a
	 * badge row — and they are already at the size where shrinking them reads as
	 * a bug rather than as a narrower screen. The large four are SECTION rhythm:
	 * `--space-3xl` is 96px of hero padding, which on a 390px phone is a quarter
	 * of the screen spent before the first word. Same poles and the same MAX-is-
	 * the-old-value rule as the type scale above, so desktop rhythm is untouched.
	 */
	--space-3xs: 0.25rem;
	--space-2xs: 0.5rem;
	--space-xs: 0.75rem;
	--space-s: 1rem;
	--space-m: 1.5rem;
	--space-l: clamp(1.5rem, 1.3261rem + 0.8696vw, 2rem);
	--space-xl: clamp(2rem, 1.6522rem + 1.7391vw, 3rem);
	--space-2xl: clamp(2.5rem, 1.9783rem + 2.6087vw, 4rem);
	--space-3xl: clamp(3.25rem, 2.2935rem + 4.7826vw, 6rem);

	/* ---- Radii, borders, shadow ------------------------------------------ */
	--radius-xs: 2px;
	--radius-s: 4px;
	--radius-m: 6px;
	--radius-l: 10px;
	--radius-pill: 999px;

	--border-hairline: 1px;
	--border-thick: 2px;

	--shadow-low: 0 1px 2px color-mix(in srgb, var(--ink) 8%, transparent);
	--shadow-mid: 0 2px 10px color-mix(in srgb, var(--ink) 10%, transparent);

	/* The dim layer behind a modal (v1.4.0). Read by .dx-dialog::backdrop.
	   Ink-tinted rather than black in the light theme, so the page under a
	   dialog darkens in its own colour instead of going grey. */
	--scrim: rgb(27 32 48 / 45%);

	/* ---- Stacking (v1.4.0) ------------------------------------------------
	 * Six layers and no others. The catalogue had eleven z-index values
	 * (1, 2, 3, 4, 5, 20, 30, 35, 40, 100, 10000) for what are really these
	 * five jobs, and two menus disagreed about which one sat on top. A modal
	 * <dialog> needs none of them: the browser's top layer is above all.
	 *   --z-raised   a control lifted over a stretched card link, a sticky cell
	 *   --z-sticky   in-page sticky bars (a filter bar, a table head)
	 *   --z-header   the site header and the platform bar
	 *   --z-progress the top-of-page navigation progress bar: over the header,
	 *                never over an open popover
	 *   --z-popover  menus and popovers, which must open over the header
	 *   --z-skip     the skip link, above everything when focused */
	--z-raised: 1;
	--z-sticky: 20;
	--z-header: 40;
	--z-progress: 45;
	--z-popover: 50;
	--z-skip: 100;

	/* ---- Layout ---------------------------------------------------------- */
	--width-page: 1120px;
	--width-wide: 1320px;
	--width-prose: 44rem;

	/* ---- Palette — light ------------------------------------------------- */
	/* Warm-neutral paper ground, oxford-ink text, one restrained accent. */
	--paper: #f7f4ee;
	--paper-raised: #fffdf9;
	--paper-sunken: #efeae0;
	--ink: #1b2030;
	--ink-soft: #454b5c;
	--ink-faint: #6d7384;
	--rule: #ddd6c8;
	--rule-strong: #c6bdab;

	--accent: #7a2331;
	--accent-hover: #611c27;
	--accent-soft: #f3e6e6;
	--accent-ink: #ffffff;

	--link: #6d3b45;
	--link-visited: #5a3a52;

	/* The AstroDexa green, off the product site's own palette (astrodexa.com
	   css/styles.css: --primary-light #22c55e is what paints half its wordmark).
	   That value is a dark-ground green, so the light state uses the deep end of
	   the same ramp: it has to clear 4.5:1 not only on platform paper but on the
	   darkest astrologer scheme ground (#F2E9DD), where the folded provenance
	   mark renders it at 11px. #166534 does (5.9:1); #15803d does not (4.2:1). */
	--brand-green: #166534;
	/* The FILLED pair, for the rare control painted green rather than lettered in
	   it — the platform bar's Directory ⇄ App switch is the first of them. Same
	   voice as `--accent` / `--accent-hover` / `--accent-ink` so a solid green
	   pill is written the same way a solid accent one is. White on #166534 is
	   7.1:1; the hover is one step further down the same ramp. */
	--brand-green-hover: #14532d;
	--brand-green-ink: #ffffff;

	/* Status: designed as status, never decoration (§8). */
	--verified: #2f5d4a;
	--verified-soft: #e6efe9;
	--unclaimed-ink: #5c5c5c;
	--unclaimed-ground: #eceae5;
	--unclaimed-rule: #d3d0c9;
	--label-featured-ink: #4a3c14;
	--label-featured-ground: #f3ecd6;
	--label-toprated-ink: #26414f;
	--label-toprated-ground: #e4edf1;

	/* ---- Status — three tones, no info ------------------------------------
	 * Ruled 6 Oct 2026 (research/front-page-visual-2026-10-06/
	 * FABLE-RULING-SYSTEM.md, decision 7). Until this release there were NO
	 * status colours in the system at all, so every error surface in both
	 * products borrowed --accent, which is reserved for interaction. That is
	 * why the account card's .problem reads oxblood today.
	 *
	 * Success ALIASES --verified rather than minting a second green: the ✓ on
	 * a profile and a "saved" confirmation are the same idea, and two greens
	 * would drift apart within a release.
	 *
	 * There is deliberately no --status-info. The README's "no blue, ever"
	 * rule holds, and an informational notice is the neutral block (--ink on
	 * --paper-raised). Each ink reaches 4.5:1 on its own soft ground, which
	 * tokens.test.ts asserts with real arithmetic rather than trusting.
	 */
	--status-critical: #b42318;
	--status-critical-soft: #fbe9e7;
	/* #905f00, not the ruled #9a6700: that value measures 4.44:1 on its own
	 * soft ground and 4.43:1 on --paper, so it failed the 4.5:1 the same
	 * ruling demands. Nearest passing amber with headroom (5.01:1 on both).
	 * Deviation from FABLE-RULING-SYSTEM.md decision 7, for Fable at review. */
	--status-warning: #905f00;
	--status-warning-soft: #fff4d6;
	--status-success: var(--verified);
	--status-success-soft: var(--verified-soft);

	--focus-ring: #2f5d4a;
	/* A LENGTH, not a colour. It was `var(--paper)` until 6 Oct 2026, and
	 * base.css:125 plus 11 catalogue files fed it to `outline-offset`, which
	 * is invalid CSS — so every one of those rings silently sat at offset 0.
	 * Found by the hand-rolled inventory; pinned by tokens.test.ts, which now
	 * asserts this resolves to a length. */
	--focus-ring-offset: 2px;

	--transition-fast: 120ms;
	--transition-mid: 220ms;

	/* ---- Mini-site scheme resolution -------------------------------------
	 * The six astrologer schemes (see $lib/minisite/schemes.ts) are applied as
	 * two triples of custom properties — `--ms-*-l` and `--ms-*-d` — wherever
	 * a scheme is in force: on the `.minisite` element, and on `:root` itself
	 * for the whole-viewport takeover on /astrologers/{slug}. This block and
	 * the dark one below are what collapse the pair down to the live `--ms-*`
	 * variables under the SAME two theme states as the platform palette, so a
	 * scheme honours the visitor's choice with no second render and no client
	 * JS. Since 4 Sep the two triples may come from DIFFERENT schemes — a
	 * profile can hold one colourway for day and another for night — which
	 * changes nothing here: this file only ever picks a side.
	 *
	 * Harmless where no scheme is set: `--ms-bg` then resolves to nothing and
	 * nothing on the page asks for it. */
	--ms-bg: var(--ms-bg-l);
	--ms-ink: var(--ms-ink-l);
	--ms-accent: var(--ms-accent-l);
}

/* The visitor chose dark. It wins on a light-set OS too, and it is the only
   way to get here — the OS setting is not consulted (see the note above). */
/* .dark alias: app migration bridge — remove in P7 */
:root[data-theme='dark'],
:root.dark {
	color-scheme: dark;

	--paper: #14161d;
	--paper-raised: #1c1f28;
	--paper-sunken: #0f1116;
	--ink: #ece7dc;
	--ink-soft: #b9b4a9;
	--ink-faint: #8d8981;
	--rule: #333744;
	--rule-strong: #4a4f5e;

	--accent: #c4626b;
	--accent-hover: #d67c84;
	--accent-soft: #2a1c20;
	--accent-ink: #14161d;

	--link: #d09aa2;
	--link-visited: #bd9bb4;

	/* astrodexa.com's own --primary-light, unmodified: it was picked for a dark
	   ground and it clears 7:1 on every dark ground here, platform and scheme. */
	--brand-green: #22c55e;
	/* Filled green inverts on a dark ground exactly as `--accent` does: the fill
	   brightens on hover and the lettering goes dark, because #22c55e is far too
	   light to carry white text (1.9:1) and carries #14161d at 9.9:1. */
	--brand-green-hover: #4ade80;
	--brand-green-ink: #14161d;

	--verified: #8fc0aa;
	--verified-soft: #1c2822;
	--unclaimed-ink: #a3a099;
	--unclaimed-ground: #1a1c20;
	--unclaimed-rule: #33353b;
	--label-featured-ink: #e2cea0;
	--label-featured-ground: #2a2517;
	--label-toprated-ink: #a9c8d6;
	--label-toprated-ground: #182530;

	--status-critical: #f97066;
	--status-critical-soft: #3a1a17;
	--status-warning: #d29922;
	--status-warning-soft: #3a2d10;
	--status-success: var(--verified);
	--status-success-soft: var(--verified-soft);

	--focus-ring: #8fc0aa;
	--focus-ring-offset: 2px;

	--shadow-low: 0 1px 2px rgb(0 0 0 / 35%);
	--shadow-mid: 0 2px 10px rgb(0 0 0 / 45%);
	--scrim: rgb(0 0 0 / 60%);

	--ms-bg: var(--ms-bg-d);
	--ms-ink: var(--ms-ink-d);
	--ms-accent: var(--ms-accent-d);
}

/* The other half of `--step-control`, and the only viewport query in this file.
   640px is the width at which a two-column layout has already become one
   everywhere in both products, so it is the width at which "there is room for a
   quiet control" stops being true. */
@media (max-width: 640px) {
	:root {
		--step-control: var(--step-0);
	}
}

/* ---- Base ------------------------------------------------------------- */

*,
*::before,
*::after {
	box-sizing: border-box;
}

html {
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	background: var(--paper);
	color: var(--ink);
	font-family: var(--font-body);
	font-size: var(--step-0);
	line-height: var(--leading-normal);
	font-synthesis-weight: none;
	text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4,
h5,
h6 {
	margin: 0;
	font-weight: var(--weight-semibold);
	line-height: var(--leading-snug);
	letter-spacing: var(--tracking-tight);
	text-wrap: balance;
}

p {
	margin: 0;
}

a {
	color: var(--link);
	text-decoration-thickness: 1px;
	text-underline-offset: 0.18em;
}

a:hover {
	color: var(--accent-hover);
}

img {
	max-width: 100%;
	height: auto;
}

ul,
ol {
	margin: 0;
	padding: 0;
}

button,
input,
select,
textarea {
	font: inherit;
	color: inherit;
}

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: var(--border-thick) solid var(--focus-ring);
	outline-offset: var(--focus-ring-offset);
	border-radius: var(--radius-xs);
}

[hidden] {
	display: none !important;
}

/* View transitions (plan §2a.2) — a soft crossfade, never for reduced-motion. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}
