/*
 * dexa-style — shared global conventions.
 *
 * Everything here was lifted from astrodexa-catalogue at origin/main cb79148.
 * Each block names the file it came from; a change here is a change to BOTH
 * products and belongs in a tagged release both consume in one session
 * (README.md, "Versioning").
 *
 * WHAT IS DELIBERATELY *NOT* IN THIS FILE, AND WHY.
 *
 * RESKIN-PLAN.md §2 sketches base.css as "resets, focus-ring rule,
 * link/underline conventions, scrollbars". Four of those already ship in
 * tokens.css, because the catalogue's tokens.css carries its own `---- Base
 * ----` section and P0 copies that file VERBATIM so the catalogue stays
 * pixel-identical when it switches to the package. Restating them here would
 * put two copies of the focus ring in one stylesheet — the exact drift this
 * package exists to prevent. So they stay where they are, and this is the map:
 *
 *   box-sizing reset, `-webkit-text-size-adjust`   tokens.css, `---- Base ----`
 *   body defaults (--font-body, --paper, --ink,
 *     --step-0, --leading-normal, text-rendering)  tokens.css
 *   heading / p / ul / ol / img / form-control
 *     normalisation                                tokens.css
 *   PROSE link colour + `text-underline-offset:
 *     0.18em` + 1px decoration thickness           tokens.css, `a`
 *   the universal focus ring — 2px solid
 *     var(--focus-ring), 2px offset, on
 *     :where(a, button, input, select, textarea,
 *     summary, [tabindex]):focus-visible           tokens.css
 *   the global prefers-reduced-motion block        tokens.css
 *
 * If tokens.css is ever narrowed to declarations only (a P7-or-later
 * refactor), that Base section moves HERE unchanged — it does not get
 * rewritten on the way.
 *
 * The rule for adding to this file: additive only. A bare element selector
 * that changes how existing catalogue markup renders is not a shared
 * convention, it is a redesign of the catalogue — which is why the link
 * convention below is a class and not `a { text-decoration: none }`.
 */

/* legacy: removed once the catalogue account pages are on dx- units */
/* ======================================================================
 * ACCOUNT CARD
 * Source: astrodexa-catalogue/web/src/lib/styles/account.css — VERBATIM.
 *
 * Its own header comment explains why it is global: this is a surface, not
 * a page. RESKIN-PLAN.md §4 M4 puts the app's Login modal on it unchanged
 * (26rem centred card, labelled inputs, full-width uppercase accent button,
 * accent-left-border error banner), which is the whole reason it moved into
 * the shared package rather than staying catalogue-local.
 *
 * v1.5.1: every selector here is scoped with :where(.account-page), so the
 * scope adds no specificity (`:where(.account-page) button` is 0-0-1) and any
 * .dx-* class on the same element wins. A page moving onto the units needs no
 * override to get out from under these rules.
 * ==================================================================== */

:where(.account-page) {
	max-width: 26rem;
	margin-inline: auto;
	padding: var(--space-2xl) var(--space-m);
}

:where(.account-page) h1 {
	font-family: var(--font-display);
	font-size: var(--step-4);
}

:where(.account-page) .account-lede {
	margin-top: var(--space-s);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	line-height: var(--leading-normal);
	color: var(--ink-soft);
}

:where(.account-page) form {
	margin-top: var(--space-l);
}

:where(.account-page) .field {
	margin-top: var(--space-m);
}

:where(.account-page) label {
	display: block;
	margin-bottom: var(--space-3xs);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
}

:where(.account-page) input {
	display: block;
	width: 100%;
	padding: 0.45rem 0.6rem;
	font-family: var(--font-ui);
	/* `--step-control`, never `--step--1` — see the token's own note. A 13.3px
	   field zooms Mobile Safari the instant somebody taps it, and a sign-in form
	   is the first thing a new visitor taps. */
	font-size: var(--step-control);
	color: var(--ink);
	background: var(--paper-raised);
	border: var(--border-hairline) solid var(--rule-strong);
	border-radius: var(--radius-s);
}

:where(.account-page) .hint {
	margin-top: var(--space-3xs);
	font-family: var(--font-ui);
	font-size: var(--step--2);
	line-height: var(--leading-normal);
	color: var(--ink-faint);
}

/* DEPRECATED in v1.2.0, removed in the next MAJOR. The account card's own
 * error box painted itself with --accent-soft and an --accent bar because the
 * system had no status colours at all. It now has them, and --accent is
 * interaction only, so the alias below keeps the old SHAPE (a boxed message
 * with a side bar) in the CRITICAL tone. Both products still render
 * `.account-page .problem` on their sign-in pages, which is why this is an
 * alias and not a deletion: a MINOR may not strip the styling off a shipped
 * error message.
 *   .account-page .problem      -> .dx-notice .dx-notice--critical
 *   .account-page .field-error  -> .dx-field-error  (text under the field, no box)
 * Adopt lines: CHANGELOG.md v1.2.0. Ruling: FABLE-RULING-SYSTEM.md decision 7. */

:where(.account-page) .problem,
:where(.account-page) .field-error {
	margin-top: var(--space-2xs);
	padding: var(--space-2xs) var(--space-s);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	color: var(--status-critical);
	background: var(--status-critical-soft);
	border-inline-start: var(--border-thick) solid var(--status-critical);
	border-radius: var(--radius-xs);
}

:where(.account-page) .actions {
	margin-top: var(--space-l);
}

:where(.account-page) button {
	width: 100%;
	padding: 0.55rem 1rem;
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
	color: var(--accent-ink);
	background: var(--accent);
	border: var(--border-hairline) solid var(--accent);
	border-radius: var(--radius-s);
	cursor: pointer;
}

:where(.account-page) button:hover:not(:disabled) {
	background: var(--accent-hover);
	border-color: var(--accent-hover);
}

:where(.account-page) button:disabled {
	opacity: 0.6;
	cursor: progress;
}

:where(.account-page) .swap {
	margin-top: var(--space-l);
	padding-top: var(--space-m);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	color: var(--ink-faint);
	border-top: var(--border-hairline) solid var(--rule);
}

/* ======================================================================
 * CHROME LINKS — underline on hover, never at rest.
 * Source: the identical `a` / `a:hover` pair repeated across
 * astrodexa-catalogue/web/src/lib/components/{SiteHeader,SiteFooter,
 * Breadcrumbs,PersonCard,LocaleSwitcher}.svelte and routes/+page.svelte.
 *
 * TWO link idioms, and the difference is load-bearing. A link in PROSE keeps
 * its underline at rest — that rule is tokens.css's bare `a`, and it is what
 * makes a bio read as a document. A link that is CHROME (nav, breadcrumb,
 * footer, card title) has no underline at rest and grows one on hover, in
 * --accent. Nine components had written that pair out by hand.
 *
 * A class rather than an element selector on purpose: `a { text-decoration:
 * none }` here would strip every prose underline in the catalogue and break
 * P0b's pixel-identity check. Opt in per component; the offset and thickness
 * come from tokens.css's `a` and are not restated.
 * ==================================================================== */

/* ----------------------------------------------------------------------
 * dx-chrome-link — a link that is part of the page's furniture.
 * PARTS:     none.
 * MODIFIERS: --caps (the short tracked uppercase label: "SEE ALL IN LOVE →").
 * USE WHEN:  nav, breadcrumb, footer, a shelf's "see all", a card title.
 * NOT FOR:   a link inside running prose, which keeps its underline at rest
 *            (the bare `a` rule in tokens.css); an action — that is .dx-btn.
 * REPLACES:  the hand-written colour/underline pair in 5 files, 8 blocks.
 * SPEC:      FABLE-RULING.md §1 (the uppercase shelf label).
 * -------------------------------------------------------------------- */

.dx-chrome-link {
	color: var(--ink);
	text-decoration: none;
}

.dx-chrome-link:hover {
	color: var(--accent);
	text-decoration: underline;
}

.dx-chrome-link--caps {
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
}

/* ======================================================================
 * THE UNIT CATALOGUE — dexa-style v1.2.0, drop 1.
 *
 * WHY THESE EXIST AND WHY THEY ARE HERE RATHER THAN IN A SPECIMEN.
 * specimen.html has described a button since the reskin, and base.css shipped
 * none of it, so the spec was a demo and never code. Every author reaching for
 * a button retyped the recipe: 79 hand-rolled box-button blocks across 60
 * files, in 26 distinct padding spellings, none of them this one. The same
 * happened to every other repeating unit — 44 copies of the notice recipe in
 * 35 files, an eyebrow in 60 files where 39 chose bold over the specimen's
 * semibold. Shipping a class is NOT enough on its own: .dx-chrome-link was
 * shipped and has zero adopters to this day. What makes these stick is that
 * adopting one DELETES the local recipe, and the contract test fails a file
 * that keeps its own copy (STYLE-CONTRACT.md, rule R5).
 *
 * HOW TO READ A UNIT. Each block below carries the same header: what it is,
 * its PARTS (__part), its MODIFIERS (--modifier), USE WHEN, NOT FOR (which
 * names the neighbouring unit you probably want instead), how many hand-rolled
 * copies it replaces, and the ruling that specifies it. contract/manifest.js
 * parses those headers, so the shape of this comment is load-bearing.
 *
 * NAMING is BEM under a dx- namespace: dx-block__part--modifier. State is not
 * a modifier class — it rides ARIA (aria-pressed, aria-current, :disabled), so
 * there is one source of truth for a control's state and assistive tech sees
 * the same thing the eye does.
 *
 * SIZES AND PADDING come through the component hooks --dx-btn-pad-x,
 * --dx-btn-pad-y, --dx-btn-min-h, --dx-card-pad and the --dx-notice-ink /
 * --dx-notice-ground pair. Each is read WITH A FALLBACK and never declared on
 * :root, so a unit renders correctly where the hook is never set, and a
 * modifier or an ancestor (the app's density layer) only has to set the hook.
 *
 * Source: research/front-page-visual-2026-10-06/FABLE-RULING-SYSTEM.md
 * (decisions 1, 7, 9) and FABLE-RULING.md (§4 radius roles, §5 chips, §7
 * buttons). Counts: HAND-ROLLED-INVENTORY.md.
 * ==================================================================== */

/* ----------------------------------------------------------------------
 * dx-btn — a box that performs an action.
 * PARTS:     none.
 * MODIFIERS: --accent (the one filled primary per view), --quiet (every
 *            other button; tonal + hairline, never a transparent ghost),
 *            --compact (layout: 2rem, for a button repeated on every card),
 *            --stretch (layout: full width, 3.25rem, a booking slot list).
 *            The two layout modifiers never combine.
 *            A toggle (Save / Saved) is a quiet button with aria-pressed;
 *            pressed reads accent text and border, never a fill.
 * USE WHEN:  the thing performs an action or submits.
 * NOT FOR:   navigation that reads as prose — that is .dx-chrome-link.
 * REPLACES:  79 blocks in 60 files, 26 padding spellings.
 * SPEC:      FABLE-RULING.md §7; FABLE-RULING-SYSTEM.md decision 9;
 *            BOOKING-PICKER-BEST-PRACTICES.md (the slot size).
 *
 * The uppercase, letter-spaced, step--1 label is what makes a control read as
 * a button in this system. It is the reason a button here needs no shadow to
 * look pressable, and why losing the caps (as the home page's primary did)
 * makes the button look like someone else's. inline-flex and min-height are
 * not in the specimen, which only ever drew <button>: half the catalogue's
 * buttons are <a>, and an anchor needs the box and the underline reset to sit
 * level with a real one. 2.75rem is 44px, well above the 24px WCAG 2.2 floor.
 * The focus ring is NOT restated — tokens.css rings every :focus-visible, and
 * a second copy is the drift this file exists to prevent.
 * -------------------------------------------------------------------- */

.dx-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--dx-btn-min-h, 2.75rem);
	padding: var(--dx-btn-pad-y, 0.5rem) var(--dx-btn-pad-x, 1.1rem);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
	text-decoration: none;
	border: var(--border-hairline) solid transparent;
	border-radius: var(--radius-s);
	cursor: pointer;
}

.dx-btn--accent {
	color: var(--accent-ink);
	background: var(--accent);
	border-color: var(--accent);
}

.dx-btn--accent:hover:not(:disabled) {
	background: var(--accent-hover);
	border-color: var(--accent-hover);
}

.dx-btn--quiet {
	color: var(--ink);
	background: var(--paper-raised);
	border-color: var(--rule-strong);
}

.dx-btn--quiet:hover:not(:disabled),
.dx-btn--quiet[aria-pressed='true'] {
	color: var(--accent);
	border-color: var(--accent);
}

.dx-btn--compact {
	--dx-btn-min-h: 2rem;
	--dx-btn-pad-y: 0.3rem;
	--dx-btn-pad-x: 0.75rem;
}

/* A booking slot: the whole column wide and one size taller than regular,
 * so a list of start times reads as a column of equal targets. */
.dx-btn--stretch {
	--dx-btn-min-h: 3.25rem;
	width: 100%;
}

.dx-btn:disabled {
	opacity: 0.6;
	cursor: not-allowed;
}

/* ----------------------------------------------------------------------
 * dx-chip — a tappable pill that filters, suggests or switches.
 * PARTS:     none.
 * MODIFIERS: none. Selected is NOT a modifier: it rides aria-pressed or
 *            aria-current, so the control has one source of truth.
 * USE WHEN:  tapping it changes what the page shows.
 * NOT FOR:   a word that only labels something and cannot be tapped — that
 *            is .dx-label, which is a rectangle on purpose.
 * REPLACES:  the chip/pill recipe in 22 files; 0 of them matched the
 *            specimen, and the home page alone carried two chip shapes.
 * SPEC:      FABLE-RULING.md §5; FABLE-RULING-SYSTEM.md decision 9.
 *
 * Pill radius is the token, never a literal 999px (4 files wrote the literal).
 * A chip is the ONLY unit allowed --radius-pill: a CTA is radius-s and a
 * label is radius-xs, so shape alone tells you whether a thing can be tapped.
 * -------------------------------------------------------------------- */

.dx-chip {
	display: inline-flex;
	align-items: center;
	min-height: 2.75rem;
	max-width: 100%;
	padding: var(--space-2xs) var(--space-s);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	text-align: left;
	text-decoration: none;
	overflow-wrap: anywhere;
	color: var(--ink);
	background: var(--paper-raised);
	border: var(--border-hairline) solid var(--rule-strong);
	border-radius: var(--radius-pill);
	cursor: pointer;
}

.dx-chip:hover {
	color: var(--accent);
	border-color: var(--accent);
}

.dx-chip[aria-pressed='true'],
.dx-chip[aria-current] {
	color: var(--accent);
	background: var(--accent-soft);
	border-color: var(--accent);
}

/* ----------------------------------------------------------------------
 * dx-label — a small word ABOUT something. Never tappable.
 * PARTS:     none.
 * MODIFIERS: --featured, --toprated, --verified, --unclaimed.
 * USE WHEN:  marking status or provenance: Ad, Featured, Verified.
 * NOT FOR:   anything clickable — that is .dx-chip.
 * REPLACES:  the label half of the 22-file chip/pill family.
 * SPEC:      FABLE-RULING.md §5 (labels stay radius-xs rectangles).
 *
 * Rectangular BY RULING, which is why this is not called a pill: a class may
 * not lie about its shape, and a reader must be able to tell a tappable chip
 * from a passive label without trying to tap it.
 * -------------------------------------------------------------------- */

.dx-label {
	display: inline-flex;
	align-items: center;
	/* A label is as wide as its word, also as an item in a flex column. */
	width: fit-content;
	padding: 0 var(--space-3xs);
	font-family: var(--font-ui);
	font-size: var(--step--2);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-caps);
	text-transform: uppercase;
	color: var(--ink-soft);
	background: var(--paper-sunken);
	border-radius: var(--radius-xs);
}

.dx-label--featured {
	color: var(--label-featured-ink);
	background: var(--label-featured-ground);
}

.dx-label--toprated {
	color: var(--label-toprated-ink);
	background: var(--label-toprated-ground);
}

.dx-label--verified {
	color: var(--verified);
	background: var(--verified-soft);
}

.dx-label--unclaimed {
	color: var(--unclaimed-ink);
	background: var(--unclaimed-ground);
}

/* ----------------------------------------------------------------------
 * dx-card — a content container that sits in flow.
 * PARTS:     __media, __body, __title, __meta, __foot, __action.
 * MODIFIERS: --link (the whole card is one link), --roomy (layout only),
 *            --tile (title becomes the UI face in caps; the topic strip).
 * USE WHEN:  a repeated block of content: a person, a topic tile, a panel.
 * NOT FOR:   an overlay — that is .dx-popover or .dx-dialog, radius-l.
 * REPLACES:  the card surface in 36 files.
 * SPEC:      FABLE-RULING.md §1, §4; FABLE-RULING-SYSTEM.md decision 9.
 *
 * radius-m is the M0 value the owner approved, and it is the reason controls
 * are radius-s: a 4px control nested in a 6px container is correct nesting,
 * and the step between them is the whole point of the scale.
 *
 * ELEVATION IS A SURFACE STEP PLUS A HAIRLINE, never a shadow. A grid of
 * soft-shadowed cards is the single most template-looking thing a page can do.
 *
 * --link stretches the title's link over the whole card (FABLE-RULING §1:
 * the card IS the link, so there is no "View profile" button repeated on every
 * one). EXACTLY ONE __action may sit above that layer — a Save toggle, say.
 * A second __action, or an <a> wrapped around the card, is a structural
 * violation that the markup guard reports, because a stretched link swallows
 * every control beneath it.
 * -------------------------------------------------------------------- */

.dx-card {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	height: 100%;
	padding: var(--dx-card-pad, var(--space-s));
	background: var(--paper-raised);
	border: var(--border-hairline) solid var(--rule);
	border-radius: var(--radius-m);
}

.dx-card--roomy {
	--dx-card-pad: var(--space-m);
}

.dx-card__media {
	overflow: hidden;
	border-radius: var(--radius-s);
}

.dx-card__body {
	display: flex;
	flex-direction: column;
	gap: var(--space-3xs);
	min-width: 0;
}

/* No font-family: a card title inherits the page's body face (Lora), which is
 * what the M0 person card the owner approved did. Playfair is never set here:
 * FABLE-RULING §6 keeps it out of cards. */
.dx-card__title {
	font-size: var(--step-2);
	font-weight: var(--weight-semibold);
}

/* A tile is a .dx-card carrying only __title and __meta — the topic strip under
 * the hero. Its title is the UI face in caps, which is what M0 shipped and what
 * the owner approved; a developer dropped the caps in OR1-S2 citing German
 * wrapping, with no design ruling behind it, and the English-only rule now
 * voids that justification (FABLE-RULING.md §3). The step-2 display title above
 * stays the default, because that is correct for a person card. */
.dx-card--tile .dx-card__title {
	font-family: var(--font-ui);
	font-size: var(--step-0);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
}

.dx-card__meta {
	font-family: var(--font-ui);
	font-size: var(--step--1);
	color: var(--ink-faint);
}

.dx-card__foot {
	margin-top: auto;
	padding-top: var(--space-xs);
	border-top: var(--border-hairline) solid var(--rule);
}

.dx-card--link {
	position: relative;
}

.dx-card--link .dx-card__title a {
	color: inherit;
	text-decoration: none;
}

.dx-card--link .dx-card__title a::after {
	content: '';
	position: absolute;
	inset: 0;
}

.dx-card--link:hover,
.dx-card--link:has(.dx-card__title a:focus-visible) {
	border-color: var(--accent);
}

.dx-card--link:hover .dx-card__title a {
	text-decoration: underline;
}

/* The one control allowed to float above the stretched link. It keeps its
 * own width; a flex column would otherwise stretch a compact Save across the
 * card. */
.dx-card__action {
	position: relative;
	z-index: var(--z-raised);
	align-self: flex-start;
}

/* ----------------------------------------------------------------------
 * dx-notice — a block that tells the reader the state of something.
 * PARTS:     __title.
 * MODIFIERS: --critical, --warning, --success (tone); --bar (layout, for an
 *            edge-to-edge strip: sandbox, draft, moved, stuck).
 * USE WHEN:  a message about the page or an action, in its own block.
 * NOT FOR:   a message under one form field — that is .dx-field-error.
 * REPLACES:  44 rule blocks in 35 files; 48 files carry the family.
 * SPEC:      FABLE-RULING-SYSTEM.md decision 7.
 *
 * The block with NO tone is the informational one: --ink on --paper-raised.
 * There is deliberately no --critical by default and no info tone at all, so
 * a neutral message cannot be mistaken for an alarm.
 *
 * Tone is set through two hooks rather than by restating colours per tone, so
 * a mini-site scheme can set them on its own root and every notice inside it
 * follows without a new class. --accent NEVER appears here: it is interaction
 * only, and the account card's .problem borrowing it is exactly the bug this
 * unit retires.
 * -------------------------------------------------------------------- */

.dx-notice {
	padding: var(--space-2xs) var(--space-s);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	color: var(--dx-notice-ink, var(--ink));
	background: var(--dx-notice-ground, var(--paper-raised));
	border-inline-start: var(--border-thick) solid var(--dx-notice-ink, var(--ink));
	border-radius: var(--radius-xs);
}

.dx-notice__title {
	display: block;
	font-weight: var(--weight-bold);
}

.dx-notice--critical {
	--dx-notice-ink: var(--status-critical);
	--dx-notice-ground: var(--status-critical-soft);
}

.dx-notice--warning {
	--dx-notice-ink: var(--status-warning);
	--dx-notice-ground: var(--status-warning-soft);
}

.dx-notice--success {
	--dx-notice-ink: var(--status-success);
	--dx-notice-ground: var(--status-success-soft);
}

/* Edge to edge: no side border, no radius, it spans its container. */
.dx-notice--bar {
	border-inline-start: 0;
	border-block-end: var(--border-hairline) solid var(--dx-notice-ink, var(--ink));
	border-radius: 0;
	text-align: center;
}

/* ----------------------------------------------------------------------
 * dx-field-error — the message under one form control.
 * PARTS:     none.
 * MODIFIERS: none.
 * USE WHEN:  one field is wrong and the message sits beneath it.
 * NOT FOR:   a message about the whole page — that is .dx-notice--critical.
 * REPLACES:  the error-text half of the 48-file notice family; 12 files
 *            currently paint it with --accent.
 * SPEC:      FABLE-RULING-SYSTEM.md decision 7.
 *
 * Text, not a box: a field already has a box, and a second one around its
 * error turns a form into a stack of alarms.
 * -------------------------------------------------------------------- */

.dx-field-error {
	display: block;
	margin-top: var(--space-3xs);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	color: var(--status-critical);
}

/* ----------------------------------------------------------------------
 * dx-eyebrow — the small caps line above a heading.
 * PARTS:     none.
 * MODIFIERS: none.
 * USE WHEN:  labelling the section a heading belongs to.
 * NOT FOR:   status words on a thing — that is .dx-label.
 * REPLACES:  the recipe in 60 files, 39 of which chose bold.
 * SPEC:      FABLE-RULING.md DRIFT #15 — semibold, not bold.
 *
 * 600 settles the specimen-vs-catalogue split: an eyebrow is a signpost, and
 * at 700 it competes with the heading it is supposed to introduce.
 * -------------------------------------------------------------------- */

.dx-eyebrow {
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-caps);
	text-transform: uppercase;
	color: var(--ink-faint);
}

/* ======================================================================
 * THE KIT — dexa-style v1.3.0, drop 4: forms, page structure, layout.
 *
 * The drop-1 units were the things a reader sees on a card. These are the
 * things every page is built FROM: the column it sits in, its head, its
 * sections, its grids, its forms and its button rows. Each one replaces a
 * recipe the catalogue retyped dozens of times under dozens of names (45
 * class names for the page column alone), so the shapes here are the modes
 * of HAND-ROLLED-INVENTORY.md, not new designs: where the tree agreed, the
 * unit says what the tree already said.
 *
 * Source: research/front-page-visual-2026-10-06/FABLE-RULING-SYSTEM.md
 * decision 9 ("later drops") and section (c) drop 4; HAND-ROLLED-INVENTORY.md
 * rows 1, 4, 6, 8, 9, 10, 14, 16, 18, 19; research/style-system-2026-10-06/
 * BOOKING-PICKER-BEST-PRACTICES.md for the stretched slot button.
 * ==================================================================== */

/* ----------------------------------------------------------------------
 * dx-field — one form field: label, control, hint and error as one block.
 * PARTS:     __label, __control, __hint (and a .dx-field-error inside).
 * MODIFIERS: --check (layout: a checkbox or radio before its label).
 * USE WHEN:  any labelled input, select or textarea, or one checkbox/radio;
 *            a group of radios is a <fieldset class="dx-field"> whose
 *            <legend class="dx-field__label"> asks the question.
 * NOT FOR:   a search box with its button beside it, which keeps its own
 *            row around __control; an action that looks like a toggle chip
 *            — that is .dx-chip with aria-pressed.
 * REPLACES:  control + label + hint in 39 files, 54 extra distinct values
 *            (12 padding spellings, 3 grounds, 4 focus treatments).
 * SPEC:      HAND-ROLLED-INVENTORY.md rows 1 and 6, §3.3-3.5; the
 *            .account-page input it generalises.
 *
 * The control is sized in --step-control, never --step--1: below 16px
 * Mobile Safari zooms the page on focus. 2.75rem is the same 44px target as
 * a button, so a field and the button beside it sit level.
 *
 * ONE FOCUS RULE, and it is not here: tokens.css rings every control on
 * :focus-visible. Four catalogue files replaced that ring with a 1px accent
 * border and `outline: none`, which is weaker than the ring it removed, so
 * this unit deliberately restates nothing about focus.
 *
 * Invalid rides aria-invalid (it is the state assistive tech announces), and
 * the message is a .dx-field-error the control names in aria-describedby.
 * Select keeps the browser's own arrow: drawing one would be a hand-made
 * glyph, which the house rules forbid.
 *
 * A BARE CONTROL IS ALLOWED (v1.5.0, Fable, 6 Oct 2026). Where a control has
 * no visible label of its own (a select in a toolbar, a filter beside a
 * table) it carries .dx-field__control without a .dx-field around it, and
 * its name comes from aria-label. This is the one part that may stand outside
 * its block (STYLE-CONTRACT D11).
 *
 * SIZE HOOKS (v1.5.0): --dx-control-min-h and --dx-control-pad-y, read with a
 * fallback, so a product's density layer can set a denser control in one
 * place (the app's desktop workbench sets 28 px).
 * -------------------------------------------------------------------- */

.dx-field {
	display: flex;
	flex-direction: column;
	gap: var(--space-3xs);
	min-width: 0;
}

.dx-field + .dx-field {
	margin-top: var(--space-m);
}

fieldset.dx-field {
	min-inline-size: 0;
	margin: 0;
	padding: 0;
	border: 0;
}

/* Options of one question sit close; a new question gets the full gap. */
.dx-field--check + .dx-field--check {
	margin-top: var(--space-3xs);
}

.dx-field__label {
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	color: var(--ink);
}

.dx-field__control {
	display: block;
	width: 100%;
	min-height: var(--dx-control-min-h, 2.75rem);
	padding: var(--dx-control-pad-y, var(--space-2xs)) var(--space-xs);
	font-family: var(--font-ui);
	font-size: var(--step-control);
	line-height: var(--leading-normal);
	color: var(--ink);
	background: var(--paper-raised);
	border: var(--border-hairline) solid var(--rule-strong);
	border-radius: var(--radius-s);
}

textarea.dx-field__control {
	min-height: 6rem;
	resize: vertical;
}

.dx-field__control[aria-invalid='true'] {
	border-color: var(--status-critical);
}

.dx-field__control:disabled {
	color: var(--ink-faint);
	background: var(--paper-sunken);
	cursor: not-allowed;
}

.dx-field__hint {
	font-family: var(--font-ui);
	font-size: var(--step--2);
	line-height: var(--leading-normal);
	color: var(--ink-faint);
}

/* The field's gap already spaces the error; its own margin would double it. */
.dx-field > .dx-field-error {
	margin-top: 0;
}

/* A checkbox or radio sits BEFORE its label, and the whole row is the 44px
 * target because the label is clickable. Hint and error line up under the
 * label, not under the box. */
.dx-field--check {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	column-gap: var(--space-2xs);
	align-items: start;
}

.dx-field--check > :not(.dx-field__control) {
	grid-column: 2;
}

.dx-field--check .dx-field__control {
	width: 1.125rem;
	height: 1.125rem;
	min-height: 0;
	margin: 0.2rem 0 0;
	padding: 0;
	accent-color: var(--accent);
}

.dx-field--check .dx-field__label {
	min-height: 1.5rem;
	font-size: var(--step-0);
	font-weight: var(--weight-regular);
	cursor: pointer;
}

/* ----------------------------------------------------------------------
 * dx-container — the centred column a page sits in.
 * PARTS:     none.
 * MODIFIERS: --prose (a reading column, 44rem), --wide (1320px).
 * USE WHEN:  the outermost column of a page or a full-width band's content.
 * NOT FOR:   the 26rem sign-in card — that is .account-page; spacing between
 *            sections — that is .dx-section.
 * REPLACES:  75 rule blocks in 50 files under 45 class names.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 4, §3.12.
 *
 * The default is --width-page (38 of the 50 files). The gutter is 16px on a
 * phone and grows to --space-m by 640px without a media query; it is inline
 * padding only, so a band can set its own vertical rhythm on the same element.
 * -------------------------------------------------------------------- */

.dx-container {
	width: 100%;
	max-width: var(--width-page);
	margin-inline: auto;
	padding-inline: clamp(var(--space-s), 4vw, var(--space-m));
}

.dx-container--prose {
	max-width: var(--width-prose);
}

.dx-container--wide {
	max-width: var(--width-wide);
}

/* ----------------------------------------------------------------------
 * dx-page-head — the top of a page: eyebrow, h1, lede.
 * PARTS:     __title.
 * MODIFIERS: --hero (step-6, the front page and landing bands), --tool
 *            (step-4, search, browse and comparison pages), --task (step-3,
 *            account, booking and sign-in pages).
 * USE WHEN:  the one h1 of a page, with its optional eyebrow and lede.
 * NOT FOR:   a section heading further down — that is .dx-section__title.
 * REPLACES:  31 h1 rules in 31 files that agreed on the face and differed
 *            only in size; the lede beside them in 34 files.
 * SPEC:      HAND-ROLLED-INVENTORY.md rows 8 and 19, §3.13-3.14;
 *            FABLE-RULING.md §6 (Playfair: page h1, step-3 and up).
 *
 * FOUR SIZES AND NO MORE: step-6 hero, step-5 the default content page (12
 * of the 31 files), step-4 tool pages, step-3 task pages. The tree used
 * exactly these four plus one step-2 outlier, which drops to --task.
 * Compose it with the column: <header class="dx-page-head dx-container">.
 * -------------------------------------------------------------------- */

.dx-page-head {
	display: flex;
	flex-direction: column;
	gap: var(--space-2xs);
	padding-block: var(--space-xl) var(--space-l);
}

.dx-page-head__title {
	font-family: var(--font-display);
	font-size: var(--step-5);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-tight);
}

.dx-page-head--hero .dx-page-head__title {
	font-size: var(--step-6);
}

.dx-page-head--tool .dx-page-head__title {
	font-size: var(--step-4);
}

.dx-page-head--task .dx-page-head__title {
	font-size: var(--step-3);
}

/* ----------------------------------------------------------------------
 * dx-lede — the standfirst paragraph under a page or section title.
 * PARTS:     none.
 * MODIFIERS: --display (one step larger, for a hero or a landing band).
 * USE WHEN:  the one paragraph that says what the page is for.
 * NOT FOR:   body copy, which is plain <p>; small print — that is .dx-note.
 * REPLACES:  41 rule blocks in 34 files under six names (.lede, .sub,
 *            .intro, .lead-in, .preamble, .errand-lede).
 * SPEC:      HAND-ROLLED-INVENTORY.md row 8, §3.13.
 *
 * The body face (Lora) at step-1, --ink-soft, capped at the reading measure:
 * the modes of the tree. It inherits its face rather than naming it, so a
 * lede inside a mini-site scheme follows the scheme.
 * -------------------------------------------------------------------- */

.dx-lede {
	max-width: var(--measure);
	font-size: var(--step-1);
	line-height: var(--leading-normal);
	color: var(--ink-soft);
}

.dx-lede--display {
	font-size: var(--step-2);
	line-height: var(--leading-snug);
}

/* ----------------------------------------------------------------------
 * dx-note — helper text, captions and small print.
 * PARTS:     none.
 * MODIFIERS: --fine (one step smaller and fainter: legal lines, as-of dates).
 * USE WHEN:  a quiet sentence beside or under something: a caption, a policy
 *            line, "prices include tax".
 * NOT FOR:   the hint under one form field — that is .dx-field__hint; a
 *            message the reader must act on — that is .dx-notice.
 * REPLACES:  79 rule blocks in 57 files (.note, .fine, .caption, .policy,
 *            .disclaimer, .asof and more).
 * SPEC:      HAND-ROLLED-INVENTORY.md row 6, §3.8.
 * -------------------------------------------------------------------- */

.dx-note {
	font-family: var(--font-ui);
	font-size: var(--step--1);
	line-height: var(--leading-normal);
	color: var(--ink-soft);
}

.dx-note--fine {
	font-size: var(--step--2);
	color: var(--ink-faint);
}

/* ----------------------------------------------------------------------
 * dx-section — a band of a page, set off by a hairline rule above it.
 * PARTS:     __title.
 * MODIFIERS: none.
 * USE WHEN:  the page has more than one topic: a profile's sections, a
 *            settings page's groups, the home page's shelves.
 * NOT FOR:   a boxed block — that is .dx-card; one row of a list — the
 *            list row is .dx-row.
 * REPLACES:  54 rule blocks in 38 files; the rule-plus-space-m body is
 *            verbatim in 25 of them.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 10, §3.11; FABLE-RULING.md §6.
 *
 * __title is the band's one display h2 that FABLE-RULING §6 allows. The rule
 * colour is a hook (--dx-section-rule) because a mini-site scheme draws its
 * rules from its own ink, which the platform --rule would clash with.
 * -------------------------------------------------------------------- */

.dx-section {
	margin-top: var(--space-xl);
	padding-top: var(--space-m);
	border-top: var(--border-hairline) solid var(--dx-section-rule, var(--rule));
}

.dx-section__title {
	margin-bottom: var(--space-s);
	font-family: var(--font-display);
	font-size: var(--step-3);
	font-weight: var(--weight-semibold);
}

/* ----------------------------------------------------------------------
 * dx-grid — a responsive grid of equal columns.
 * PARTS:     none.
 * MODIFIERS: --tight (layout: --space-s gap for dense tiles).
 * USE WHEN:  a set of cards or tiles that should reflow by available width.
 * NOT FOR:   a page layout with named areas (main + sidebar), which is the
 *            page's own grid; a single row that scrolls — that is .dx-scroll-x.
 * REPLACES:  27 rule blocks in 24 files, 11 different minimum widths.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 9, §3.25.
 *
 * The minimum column width is the one thing that legitimately varies, so it
 * is a hook: style="--dx-grid-min: 12rem" on the element, or a rule that sets
 * it. Default 16rem, the tree's mode. min(…, 100%) keeps a column from
 * overflowing a container narrower than the minimum. auto-fill, not
 * auto-fit, so a shelf with two cards keeps card-sized columns instead of
 * two half-page cards. The gap is a hook too (v1.5.0), --dx-grid-gap,
 * default --space-m; --tight sets it to --space-s, and a dense surface (the
 * app's boards) sets its own.
 * -------------------------------------------------------------------- */

.dx-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(var(--dx-grid-min, 16rem), 100%), 1fr));
	gap: var(--dx-grid-gap, var(--space-m));
}

.dx-grid--tight {
	--dx-grid-gap: var(--space-s);
}

/* ----------------------------------------------------------------------
 * dx-actions — a row of buttons.
 * PARTS:     none.
 * MODIFIERS: --end (layout: aligned to the end, for a form's submit row or
 *            a dialog footer; the accent button goes last).
 * USE WHEN:  two or more buttons that belong together: Save and Cancel, a
 *            card's actions, a dialog's footer.
 * NOT FOR:   a row of chips — use .dx-scroll-x or a wrapping flex row of
 *            .dx-chip; one button needs no row.
 * REPLACES:  15 rule blocks in 15 files; the account card's .actions.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 14, §3.26.
 *
 * Wraps rather than squeezes, so on a phone the buttons stack in reading
 * order instead of shrinking their labels. WHERE THE ACCENT BUTTON GOES
 * (Fable, 6 Oct 2026): in the default start-aligned row it is FIRST; in a
 * --end row it is LAST, the rightmost, which is the dialog and form-footer
 * convention (Back ... Continue). The row only aligns; the order is the
 * markup's, so write it in that order.
 * -------------------------------------------------------------------- */

.dx-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-2xs) var(--space-s);
	margin-top: var(--space-m);
}

.dx-actions:first-child {
	margin-top: 0;
}

.dx-actions--end {
	justify-content: flex-end;
}

/* ----------------------------------------------------------------------
 * dx-link-button — a <button> that reads as a link.
 * PARTS:     none.
 * MODIFIERS: none.
 * USE WHEN:  a small action inside a sentence or beside a value: "Resend
 *            the code", "Use a different email", "Change".
 * NOT FOR:   going somewhere, which is an <a> (a prose link or
 *            .dx-chrome-link); a standalone action — that is .dx-btn--quiet.
 * REPLACES:  20 rule blocks in 20 files (.linky, .link-button), one body.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 16, §3.24.
 *
 * The underline, thickness and offset are the prose link's from tokens.css,
 * so a link-button in a sentence cannot be told from a link by its look,
 * only by what it does.
 * -------------------------------------------------------------------- */

.dx-link-button {
	padding: 0;
	font: inherit;
	color: var(--link);
	text-decoration: underline;
	text-decoration-thickness: var(--border-hairline);
	text-underline-offset: 0.18em;
	background: none;
	border: 0;
	cursor: pointer;
}

.dx-link-button:hover {
	color: var(--accent-hover);
}

/* ----------------------------------------------------------------------
 * dx-sr-only — text for screen readers only.
 * PARTS:     none.
 * MODIFIERS: none.
 * USE WHEN:  a label or live-region message that has no visible twin: an
 *            icon button's name, "8 times available", a table caption.
 * NOT FOR:   the skip link, which must appear on focus — that is .skip;
 *            hiding from everyone — that is the hidden attribute.
 * REPLACES:  22 rule blocks in 22 files, three spellings of the clip.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 18, §3.23.
 * -------------------------------------------------------------------- */

.dx-sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ======================================================================
 * OVERLAYS AND LISTS — dexa-style v1.4.0, drop 5.
 *
 * The overlay radius is --radius-l and the overlay shadow is --shadow-mid, as
 * FABLE-RULING.md §4 ruled. The catalogue never used either (0 of 195 files
 * set --radius-l): every panel and dialog chose radius-m or radius-s and
 * three wrote their own shadow. That is not evidence against the rule; it is
 * what happens when nothing ships the class. Overlays are the ONE place a
 * shadow is allowed, because they really do sit above the page.
 *
 * Source: FABLE-RULING-SYSTEM.md decision 9 and section (c) drop 5;
 * HAND-ROLLED-INVENTORY.md rows 11, 12, 13, 15, 17 and §3.10, §3.15-3.21.
 * ==================================================================== */

/* ----------------------------------------------------------------------
 * dx-popover — a floating panel anchored to the control that opened it.
 * PARTS:     none.
 * MODIFIERS: --end (layout: aligned to the anchor's end edge instead of its
 *            start, for a trigger near the right of the screen).
 * USE WHEN:  a small panel that opens from a trigger and leaves the page
 *            usable: a menu's list, a "how we rank" explainer, filter options.
 * NOT FOR:   anything that needs the reader's full attention or a decision —
 *            that is .dx-dialog; a panel that sits in flow — that is .dx-card.
 * REPLACES:  8 panels in 8 files: three radii, four shadows, five z-indexes.
 * SPEC:      FABLE-RULING.md §4 (radius-l is overlays only);
 *            HAND-ROLLED-INVENTORY.md row 11, §3.17.
 *
 * Positioned against its nearest positioned ancestor; .dx-menu is one, and
 * any other anchor needs `position: relative`. It opens below the anchor and
 * never wider than the screen. Opening, closing on Escape and on a click
 * outside are behaviour, and belong to the component that owns the trigger.
 *
 * Absolute positioning is deliberate for now (Fable, 6 Oct 2026). The future
 * path is the `popover` attribute plus CSS anchor positioning, which removes
 * the positioned-ancestor requirement and the z-index; it waits until every
 * browser both products support has anchor positioning.
 * -------------------------------------------------------------------- */

.dx-popover {
	position: absolute;
	top: calc(100% + var(--space-3xs));
	inset-inline-start: 0;
	z-index: var(--z-popover);
	min-width: 12rem;
	max-width: min(24rem, calc(100vw - 2 * var(--space-s)));
	padding: var(--space-s);
	color: var(--ink);
	background: var(--paper-raised);
	border: var(--border-hairline) solid var(--rule-strong);
	border-radius: var(--radius-l);
	box-shadow: var(--shadow-mid);
}

.dx-popover--end {
	inset-inline-start: auto;
	inset-inline-end: 0;
}

/* ----------------------------------------------------------------------
 * dx-menu — a disclosure that opens a list of choices.
 * PARTS:     __trigger, __list, __item.
 * MODIFIERS: none.
 * USE WHEN:  a short list of links or actions behind one control: the
 *            language switcher, an account menu, "sort by".
 * NOT FOR:   choosing a form value — that is a <select> in a .dx-field; a
 *            "show more" text disclosure, which is a plain <details>.
 * REPLACES:  the menu-trigger half of the 11-file details/summary family.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 17, §3.21.
 *
 * Built on <details>/<summary>, so it opens and closes with no script and
 * the keyboard works by default. The list is a .dx-popover; the current
 * choice carries aria-current. The trigger draws no caret of its own: a
 * caret is a UI icon, and icons come from the licensed set (FABLE-RULING.md
 * §8), so the trigger leaves room for one and a component puts it there.
 * Trigger and rows read --dx-menu-row-min-h (v1.5.0, default 2.75rem), so a
 * density layer can set a denser menu without restating the unit.
 * -------------------------------------------------------------------- */

.dx-menu {
	position: relative;
	display: inline-block;
}

.dx-menu__trigger {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3xs);
	min-height: var(--dx-menu-row-min-h, 2.75rem);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	font-weight: var(--weight-semibold);
	color: var(--ink);
	list-style: none;
	cursor: pointer;
}

.dx-menu__trigger::-webkit-details-marker {
	display: none;
}

.dx-menu__trigger:hover,
.dx-menu[open] > .dx-menu__trigger {
	color: var(--accent);
}

.dx-menu__list {
	padding: var(--space-3xs);
	list-style: none;
}

.dx-menu__item {
	display: flex;
	align-items: center;
	width: 100%;
	min-height: var(--dx-menu-row-min-h, 2.75rem);
	padding: 0 var(--space-xs);
	font-family: var(--font-ui);
	font-size: var(--step--1);
	text-align: start;
	text-decoration: none;
	color: var(--ink);
	background: none;
	border: 0;
	border-radius: var(--radius-s);
	cursor: pointer;
}

.dx-menu__item:hover {
	color: var(--accent);
	background: var(--paper-sunken);
}

.dx-menu__item[aria-current] {
	font-weight: var(--weight-semibold);
	color: var(--accent);
}

/* ----------------------------------------------------------------------
 * dx-dialog — a modal on the native <dialog> element.
 * PARTS:     __head, __title, __close, __body, __foot.
 * MODIFIERS: --wide (layout: 44rem for a form with two columns or a
 *            booking step), --sheet (a full-height panel from the end edge,
 *            for an editor beside the page).
 * USE WHEN:  the reader must finish or dismiss something before going on:
 *            a sign-in, a booking step, a confirmation of consequence.
 * NOT FOR:   a hint or a menu that can stay open while the page is used —
 *            that is .dx-popover; a message — that is .dx-notice.
 * REPLACES:  4 dialog rules (two byte-identical), 4 scrims in two colours,
 *            4 close-button recipes.
 * SPEC:      FABLE-RULING.md §4; FABLE-RULING-SYSTEM.md decision 9;
 *            HAND-ROLLED-INVENTORY.md row 11, §3.18-3.20.
 *
 * Open it with showModal(): the browser then traps focus, closes it on
 * Escape, makes the page behind it inert and puts it in the top layer, so it
 * needs no z-index. The dim layer is the --scrim token, theme-aware. The
 * title is the UI face, not Playfair: FABLE-RULING §6 keeps display type off
 * sheets and dialogs. __close is a quiet compact button with a visible word;
 * an icon-only close arrives with the icon set and keeps an aria-label.
 * __foot is a .dx-actions--end, so the accent button is last.
 * -------------------------------------------------------------------- */

.dx-dialog {
	width: min(30rem, calc(100vw - 2 * var(--space-s)));
	max-width: none;
	max-height: min(44rem, calc(100dvh - 2 * var(--space-s)));
	padding: var(--space-m);
	color: var(--ink);
	background: var(--paper-raised);
	border: var(--border-hairline) solid var(--rule-strong);
	border-radius: var(--radius-l);
	box-shadow: var(--shadow-mid);
}

.dx-dialog::backdrop {
	background: var(--scrim);
}

.dx-dialog__head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--space-s);
	margin-bottom: var(--space-s);
}

.dx-dialog__title {
	font-family: var(--font-ui);
	font-size: var(--step-1);
	font-weight: var(--weight-semibold);
}

.dx-dialog__close {
	flex: 0 0 auto;
}

.dx-dialog__body > * + * {
	margin-top: var(--space-s);
}

.dx-dialog__foot {
	margin-top: var(--space-m);
}

.dx-dialog--wide {
	width: min(44rem, calc(100vw - 2 * var(--space-s)));
}

.dx-dialog--sheet {
	width: min(34rem, 100vw);
	height: 100dvh;
	max-height: none;
	margin: 0;
	margin-inline-start: auto;
	border-block: 0;
	border-inline-end: 0;
	border-radius: 0;
}

/* ----------------------------------------------------------------------
 * dx-avatar — a person's or a school's picture, or its initials.
 * PARTS:     none (an <img> inside fills it).
 * MODIFIERS: --round (a circle, for people in a list or a thread).
 * USE WHEN:  a face or logo beside a name; the initials when there is none.
 * NOT FOR:   the photo at the top of a person card — that is
 *            .dx-card__media; a decorative picture.
 * REPLACES:  29 rule blocks in 13 files, ten sizes, a monogram in Playfair
 *            on --accent-soft.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 12, §3.15; FABLE-RULING.md §6 and
 *            scope (the monogram loses Playfair and --accent-soft).
 *
 * The size is the one thing that varies, so it is a hook: --dx-avatar-size,
 * default 3rem; the tree's sizes are 2.25rem, 3rem, 4rem and 6rem. The
 * initials scale with it. The stand-in ground is --paper-sunken, never the
 * accent: a block of oxblood on most cards is accent as decoration.
 * -------------------------------------------------------------------- */

.dx-avatar {
	display: inline-grid;
	flex: 0 0 auto;
	place-items: center;
	width: var(--dx-avatar-size, 3rem);
	height: var(--dx-avatar-size, 3rem);
	overflow: hidden;
	font-family: var(--font-ui);
	font-size: calc(var(--dx-avatar-size, 3rem) * 0.38);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-wide);
	color: var(--ink-soft);
	background: var(--paper-sunken);
	border: var(--border-hairline) solid var(--rule);
	border-radius: var(--radius-s);
}

.dx-avatar > img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.dx-avatar--round {
	border-radius: 50%;
}

/* ----------------------------------------------------------------------
 * dx-empty — what a list or page says when it has nothing to show.
 * PARTS:     __title.
 * MODIFIERS: --boxed (a raised block in place of the missing content: a
 *            search with no results, an empty shelf).
 * USE WHEN:  a list, table or page section has no items yet.
 * NOT FOR:   a failure — that is .dx-notice--critical; a hint beside a
 *            control — that is .dx-note.
 * REPLACES:  25 rule blocks in 21 files (.empty, .none, .empty-block,
 *            .empty-door): 18 quiet lines, 3 boxes.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 13, §3.16.
 *
 * The quiet line is the default because it is what 18 of the 21 files
 * chose. A next step (a button or a link) goes inside in a .dx-actions.
 * -------------------------------------------------------------------- */

.dx-empty {
	font-family: var(--font-ui);
	font-size: var(--step--1);
	line-height: var(--leading-normal);
	color: var(--ink-faint);
}

.dx-empty__title {
	display: block;
	font-size: var(--step-0);
	font-weight: var(--weight-semibold);
	color: var(--ink);
}

.dx-empty--boxed {
	padding: var(--space-m);
	text-align: center;
	background: var(--paper-raised);
	border: var(--border-hairline) solid var(--rule);
	border-radius: var(--radius-m);
}

.dx-empty--boxed .dx-actions {
	justify-content: center;
}

/* ----------------------------------------------------------------------
 * dx-row — one row of a list, divided from the next by a hairline.
 * PARTS:     __main, __aside.
 * MODIFIERS: --tight (layout: --space-2xs block padding for dense lists).
 * USE WHEN:  a vertical list of like items: settings switches, courses,
 *            shared links, invitations, a thread list.
 * NOT FOR:   a grid of boxed items — that is .dx-card in a .dx-grid; tabular
 *            data with columns, which stays a <table>.
 * REPLACES:  19 rule blocks in 18 files.
 * SPEC:      HAND-ROLLED-INVENTORY.md row 15, §3.10.
 *
 * __main takes the room and may wrap; __aside (a date, a status label, a
 * button) sits at the end and drops under __main on a phone rather than
 * squeezing it. The rule colour is a hook, --dx-row-rule, for the same
 * mini-site reason as .dx-section.
 * -------------------------------------------------------------------- */

.dx-row {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-2xs) var(--space-s);
	padding-block: var(--space-s);
	border-bottom: var(--border-hairline) solid var(--dx-row-rule, var(--rule));
}

.dx-row__main {
	flex: 1 1 16rem;
	min-width: 0;
}

.dx-row__aside {
	flex: 0 0 auto;
}

.dx-row--tight {
	padding-block: var(--space-2xs);
}

/* ======================================================================
 * SKIP LINK
 * Source: astrodexa-catalogue/web/src/routes/+layout.svelte (scoped `.skip`).
 *
 * Off-screen until focused. The catalogue's own scoped copy still wins on
 * specificity and carries identical values; the app gets it from here so the
 * two shells cannot drift on the one control a keyboard user meets first.
 * ==================================================================== */

.skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: var(--z-skip);
	padding: var(--space-2xs) var(--space-s);
	font-family: var(--font-ui);
	background: var(--paper-raised);
	border: var(--border-thick) solid var(--focus-ring);
}

.skip:focus {
	left: var(--space-s);
	top: var(--space-s);
}

/* ----------------------------------------------------------------------
 * dx-scroll-x — one line of items that scrolls sideways, scrollbar hidden.
 * PARTS:     none.
 * MODIFIERS: none.
 * USE WHEN:  a single row of links or chips wider than a phone: the header's
 *            anchor row, a chip strip.
 * NOT FOR:   a panel, a table or anything taller than one line of type,
 *            where a hidden scrollbar hides content; let those scroll normally.
 * REPLACES:  `scrollbar-width: none` written by hand in 2 files.
 * SPEC:      astrodexa-catalogue SiteHeader.svelte (`.theirs ul`).
 *
 * The comment in the source is the whole justification: a permanent gutter
 * under a one-line anchor row would half again its height. The links inside
 * stay focusable, so the region is still reachable and scrollable from the
 * keyboard, which is the condition under which hiding a scrollbar is
 * legitimate at all.
 * -------------------------------------------------------------------- */

.dx-scroll-x {
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scrollbar-width: none;
}

.dx-scroll-x::-webkit-scrollbar {
	display: none;
}

/* ======================================================================
 * VIEW TRANSITIONS — the root crossfade.
 * Source: astrodexa-catalogue/web/src/routes/+layout.svelte
 * (`:global(::view-transition-old/new(root))`).
 *
 * 180ms, the catalogue's route-transition duration, and the same number
 * RESKIN-PLAN.md §1 gives the app's workspace tab switch. The reduced-motion
 * guard is not here — it is in motion.js, which declines to start a
 * transition at all, and in tokens.css's global reduced-motion block.
 *
 * Named idents are the CALLER's business, never this file's; the reserved
 * ones are listed in motion.js's JSDoc.
 * ==================================================================== */

::view-transition-old(root) {
	animation-duration: 180ms;
}

::view-transition-new(root) {
	animation-duration: 180ms;
}
