# desktop-mode/1.1.3/assets/css/variables.css

OpenStation: Desktop Windows, Dock &amp; Virtual Desktops for WP Admin, version 1.1.3. 1,684 lines.

- Page: https://pluginprobe.com/plugins/desktop-mode/1.1.3/code/assets/css/variables.css
- Raw: https://pluginprobe.com/plugins/desktop-mode/1.1.3/raw/assets/css/variables.css
- Modified: 2026-08-24T15:46:58+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/desktop-mode/1.1.3/code/assets/css/variables.css#L10-L20`.

```css
/**
 * OpenStation — CSS Custom Properties.
 *
 * All design tokens for the desktop shell, carrying the OpenStation
 * brand: https://nuriapenya.github.io/open-station-brand/
 *
 * ## The palette
 *
 *   Pulse      #f252fc  neon identity accent
 *   Nebula     #ec9bff  soft identity accent
 *   Sirius     #c2f1f1  aqua contrast accent
 *   Starlight  #fffbff  lightest — primary text on the station
 *   Void       #0c0b0f  darkest — the base everything sits on
 *   Obsidian   #1a1721  surfaces: window bodies, cards, rows
 *
 *   Shade ramp, Void to Starlight, for text hierarchy and lines:
 *   Astro #33303a · Silver #4d4a52 · Pewter #66636b ·
 *   Osmium #99969c · Ash #b3afb5 · Cloud #ccc8ce · Mist #e6e2e6
 *
 * The brand's own composition rule, followed here literally: "Void as
 * the base, Obsidian for surfaces, Pulse and Nebula for identity
 * moments, Sirius and Starlight for contrast, the Shade palette for
 * text hierarchy and lines, meshes reserved for hero surfaces."
 *
 * ## Why these are declared at all
 *
 * Every rule in the tree reads its token as
 * `var( --token, <literal> )`, and those literals are the look the
 * shell had before the brand: WordPress-admin greys and blues. They
 * are still there, still correct, and still what renders if this file
 * fails to load. Declaring the tokens here is what puts the station on
 * top of them — one declaration retints every surface that reads it.
 *
 * The way back is a theme, not an edit: **Desktop Mode (Legacy)**
 * (`assets/desktop-themes/legacy/theme.json`) is a frozen snapshot of
 * those pre-brand literals, and picking it in OS Settings → Themes
 * restores the old look wholesale. A desktop theme's compiled selector
 * weighs (0,2,0) against this file's `:root` (0,1,0) and loads after
 * it, so any theme — Legacy or a third party's — outranks everything
 * below.
 *
 * ## Semantic colours
 *
 * The brand defines no status palette, so danger / success / warning /
 * info are derived rather than quoted: the hues the shell already used,
 * lifted until they carry on a Void-to-Obsidian background, and where
 * the brand does have a colour that means the right thing it is used
 * instead — Sirius for info, and Holomesh's mint and warm for success
 * and warning.
 */

/*
 * ---- Typefaces -------------------------------------------------
 *
 * Geist and Geist Mono, the brand faces, self-hosted from
 * `assets/fonts/` under the SIL Open Font License 1.1 (the licence
 * text ships beside them in `OFL.txt`). Both are VARIABLE fonts: one
 * file covers 100–900, which is why a whole typographic scale costs
 * ~70 kB per family rather than one file per weight.
 *
 * `font-display: swap` paints fallback text immediately and swaps when
 * the face arrives — on admin chrome that is almost always right,
 * where `block` would hide every dock label until the download lands.
 *
 * The `url()`s are relative to THIS stylesheet, which is what keeps
 * them correct however the plugin directory is renamed or wherever it
 * is symlinked from.
 */
@font-face {
	font-family: 'Geist';
	src: url('../fonts/Geist-Variable.woff2') format('woff2');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Geist Mono';
	src: url('../fonts/GeistMono-Variable.woff2') format('woff2');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}

/*
 * ---- Scope: the shell document, and nothing else -----------------
 *
 * `body.os-active` rather than `:root`, and that is
 * load-bearing rather than tidy.
 *
 * This stylesheet is a dependency of `chromeless.css`, so it also
 * loads INSIDE every iframe window — a real `wp-admin` document. A
 * palette on `:root` would reach that document and repaint WordPress's
 * own UI: `--wp-admin-theme-color` alone would turn Core's primary
 * buttons, links and focus rings across every admin screen, with
 * white button text landing on a colour it was never contrast-tested
 * against.
 *
 * Iframe documents carry `body.os-chromeless` instead, so
 * they match nothing here and every rule in the tree falls back to the
 * literal it was always written with — the WordPress-admin values.
 * **An admin page inside a window looks exactly as it does outside
 * one.** That is deliberate: the station is the chrome around the
 * page, not a reskin of the page.
 *
 * Specificity: (0,1,1). A desktop theme compiles to
 * `body.os-desktop-theme-<slug>`, also (0,1,1) on the same
 * element, and its stylesheet declares `os-variables` as a
 * dependency — so it always prints after this file and wins the tie.
 * Do not remove that dependency; see the note in
 * `openstation_enqueue_desktop_theme_style()`.
 */
body.os-active {
	/*
	 * ---- Desk --------------------------------------------------
	 *
	 * `Space`, the brand's quiet deep-space backdrop: a linear
	 * gradient from near-black through to a faint violet horizon.
	 *
	 * Note this is the desk's CSS default, not usually what you see:
	 * the wallpaper layer writes the ACTIVE wallpaper into this same
	 * property as an inline style, and the shipped default is the
	 * `Galaxy` artwork (`includes/wallpapers.php`). This value is what
	 * paints before that runs, and for anyone whose wallpaper is
	 * unavailable.
	 */
	--os-bg: linear-gradient(160deg, #010101 0%, #111114 58%, #1e1d23 100%);
	/*
	 * Flat colour painted on the shell itself, behind every other
	 * layer including the wallpaper.
	 *
	 * The shell is `position: fixed` over the classic admin page, and
	 * that page is WHITE. Anything that leaves a layer unpainted for a
	 * single frame — a canvas wallpaper's context being disturbed, the
	 * compositor re-rasterising after a large GPU layer goes away —
	 * shows it. This is the floor that makes that impossible: the worst
	 * a dropped frame can now look like is Void.
	 *
	 * Deliberately a solid rather than `--os-bg`: it is never
	 * seen when things are working, so it should cost one flat fill,
	 * not a second gradient rasterisation on every shell repaint.
	 */
	--os-backstop: #0c0b0f;
	--os-area-inset: 0;

	/*
	 * ── The icon grid ────────────────────────────────────────────
	 *
	 * ONE grid, everywhere placements are laid out: the wallpaper,
	 * folder windows, and every canvas in the site folder. Before
	 * these tokens each surface carried its own pitch and its own
	 * tile width, and the two drifted apart — the desktop ended up
	 * with an 88px tile in a 96px cell whose 8px of air the tile's
	 * own padding ate, so icons touched edge to edge while the site
	 * folder had 20px between them.
	 *
	 * The cell is derived, never declared: `cell = tile + gap`. A
	 * gap you can see is the whole point, so it is the number that
	 * gets tuned; the pitch follows.
	 *
	 * The TypeScript side mirrors these in `src/desktop-files/grid.ts`
	 * (layout maths can't read CSS), and
	 * `tests/vitest/grid-metrics.test.ts` parses this file to prove
	 * the two agree. Change a number here and that test tells you
	 * exactly which constant to move with it.
	 */
	--os-tile-w: 88px;
	/*
	 * A FIXED height, and it has to fit the tallest a tile can get:
	 * 8px padding + 48px icon well + 6px gap + two clamped label
	 * lines (2 × 12px × 1.2 = 28.8px) + 8px padding = 98.8px, so
	 * 104px with a little slack for font metrics.
	 *
	 * Fixed rather than minimum because the selection ring is drawn
	 * around the tile box: let the box follow its label and a row of
	 * selected icons is a ragged run of different-height rectangles,
	 * one per label that happened to wrap.
	 */
	--os-tile-h: 104px;
	--os-grid-gap-x: 20px;
	--os-grid-gap-y: 16px;
	/* Gutter from the top / inline-start edge of any icon canvas. */
	--os-grid-padding: 16px;
	/*
	 * Image-led sections opt into a bigger tile (`tileSize: 'large'`)
	 * — a shop's products read as a catalogue, not a file list. Same
	 * gaps, bigger tile.
	 */
	--os-tile-w-large: 132px;
	--os-tile-h-large: 160px;

	/* Window chrome — Obsidian body inside a Starlight hairline. */
	--os-window-bg: #1a1721;
	--os-window-border: rgba(255, 251, 255, 0.12);
	/*
	 * Window corner. Softer than the 8px the shell carried before, so
	 * a window reads as an object on the desk rather than a panel
	 * pinned to it.
	 *
	 * The tab strip's own radius is deliberately NOT tied to this one.
	 * A tab is 30px tall, so a corner anywhere near this size eats the
	 * whole straight edge and turns the tab into a capsule — it stops
	 * reading as a folder tab attached to the page, which is the one
	 * thing the tab has to say. `--os-tabs-radius` stays smaller and
	 * independent on purpose.
	 */
	--os-window-radius: 16px;
	/*
	 * Shadows stay black and go deeper than they were: on a Void desk
	 * a soft grey shadow is invisible, and depth is the only thing
	 * separating a window from the sky behind it.
	 */
	--os-window-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
	--os-window-shadow-focused: 0 12px 48px rgba(0, 0, 0, 0.6), 0 4px 12px rgba(0, 0, 0, 0.35);
	/*
	 * Fill of the surface a window's content is revealed from once it
	 * finishes loading (OS Settings → Effects → "Window reveal").
	 *
	 * Obsidian, so a window uncovers from its own colour rather than
	 * flashing white on a dark desk. The surface has to be opaque or
	 * there is nothing to reveal FROM — the content would simply be
	 * visible the whole time and the clip animation would paint
	 * nothing. `transparent` is a legitimate value meaning "no
	 * covering surface"; the shell then skips the layer rather than
	 * animating something invisible.
	 *
	 * A reveal may override this for itself when the paint IS the
	 * reveal; `obturator` (Camera shutter) is the one built-in that
	 * does, because near-black blades are what make it a shutter.
	 */
	--os-window-reveal-surface: #1a1721;
	/*
	 * Fill of the reveal's leading edge — the band that travels with
	 * the clip boundary and draws each reveal's shape (six lines on
	 * Blinds, an opening ring on Iris, a rotating spoke on Radar).
	 *
	 * `transparent` by default: the reveal reads as the page arriving,
	 * and a hard graphic edge on top of that is a deliberate look
	 * rather than the neutral one. A theme (or any stylesheet) opts in
	 * by giving this a colour — no JS and no per-reveal configuration
	 * needed, because the band follows whatever shape the active
	 * reveal has. While it computes to fully transparent the shell
	 * skips the layer altogether, so the default costs nothing.
	 */
	--os-window-reveal-edge: transparent;

	/*
	 * ---- Title bar ---------------------------------------------
	 *
	 * Obsidian when focused, Void when not: the focused window's
	 * chrome sits one step up the Shade ramp while its unfocused
	 * neighbours sink toward the desk they are lying on. That is the
	 * entire focus signal, and it costs no colour — which matters,
	 * because the accent is spoken for.
	 *
	 * The pair used to be Astro-over-Obsidian, a step higher. It came
	 * down so a focused window's title bar and its tab strip are the
	 * SAME Obsidian, and the only thing lifting out of that surface is
	 * the tab itself. The step is the same size; the whole ramp moved.
	 */
	--os-titlebar-bg: #0c0b0f;
	--os-titlebar-bg-focused: #1a1721;
	--os-titlebar-color: #99969c;
	--os-titlebar-color-focused: #fffbff;
	--os-titlebar-height: 40px;
	/* Hairline between page chrome and window chrome. */
	--os-titlebar-divider: rgba(255, 251, 255, 0.12);
	--os-titlebar-divider-unfocused: rgba(255, 251, 255, 0.06);

	/*
	 * The status ring — leading mark of the title bar, where the app
	 * icon used to be. Four states: the resting ring is Starlight,
	 * work and success are both Pulse, failure is the danger colour.
	 *
	 * Pulse twice over is not an oversight. In flight and landed
	 * differ in FILL — an open ring breathing, then a solid disc with
	 * a check — and failure differs from both in fill AND glyph AND
	 * hue. Shape carries the distinction that colour alone cannot for
	 * a user who can't separate the two hues, and the accent stays the
	 * shell's own voice for "this is your station working".
	 *
	 * Failure resolves through `--os-ui-danger` and is the one value
	 * here that must not be quietened: a failure that whispers is a
	 * failure the user misses.
	 *
	 * The resting ring is one value in both title-bar states, not two:
	 * the ring reports a phase, and dimming it on an unfocused window
	 * would make `idle` say something different depending on which
	 * window you last clicked.
	 */
	--os-titlebar-activity-idle-color: #fffbff;
	--os-titlebar-activity-color: var(--os-ui-accent, #2271b1);
	--os-titlebar-activity-saved-color: var(--os-ui-accent, #2271b1);
	--os-titlebar-activity-failed-color: var(--os-ui-danger, #d63638);
	--os-titlebar-activity-size: 16px;

	/*
	 * Title-bar control glyphs — minimise / maximise / close, the ⋯
	 * menu trigger, and the screen-meta cluster — in each of the two
	 * title-bar states.
	 *
	 * Both halves are named explicitly. The unfocused half would
	 * otherwise derive itself from `--os-titlebar-color` by
	 * a `color-mix()` that only runs while a desktop theme is active,
	 * and the focused half cannot derive at all: its glyphs sit on
	 * `-bg-focused`, and CSS has no contrast-safe function of a
	 * background colour.
	 *
	 * Close-button red is deliberately not in either set — it is
	 * semantic signal, not chrome, and both states resolve it through
	 * `--os-ui-danger`.
	 */
	--os-titlebar-btn-color: rgba(255, 251, 255, 0.55);
	--os-titlebar-btn-color-hover: #fffbff;
	--os-titlebar-btn-bg-hover: rgba(255, 251, 255, 0.1);
	--os-titlebar-btn-bg-active: rgba(255, 251, 255, 0.16);
	--os-titlebar-btn-focused-color: rgba(255, 251, 255, 0.72);
	--os-titlebar-btn-focused-color-hover: #fffbff;
	--os-titlebar-btn-focused-bg-hover: rgba(255, 251, 255, 0.14);
	--os-titlebar-btn-focused-bg-active: rgba(255, 251, 255, 0.2);
	--os-titlebar-btn-focused-outline: #f252fc;

	/*
	 * The control cluster and each button face stay TRANSPARENT, so a
	 * themed title-bar texture runs edge to edge underneath them and
	 * the glyphs float on the artwork. A plate behind the controls is
	 * a choice a theme makes, not a default the framework imposes.
	 * The knobs for building one:
	 *
	 *   Cluster   --os-titlebar-controls-bg
	 *             --os-titlebar-controls-image (+ -repeat,
	 *               -size, -position)
	 *             --os-titlebar-controls-radius
	 *             --os-titlebar-controls-padding (inline)
	 *             --os-titlebar-controls-gap
	 *   Screen-meta cluster
	 *             --os-titlebar-meta-bg / -image / -radius
	 *               (radius falls through to the controls radius)
	 *   Each face --os-ui-btn-bg / --os-ui-btn-bg-image / --os-ui-btn-radius
	 */

	/*
	 * Admin accent. Pulse, the brand's identity colour, and the value
	 * behind the `pulse` swatch that OS Settings → Appearance ships as
	 * its default (`includes/accents.php`). The accent picker writes
	 * the user's choice into this same property as an inline style, so
	 * this is the pre-JS value and the floor under every scheme block
	 * below.
	 */
	--wp-admin-theme-color: #f252fc;

	/* Resize handle — larger hit area than visual glyph; corners
	   are transparent until hover so they don't compete with the
	   window's own drop shadow.  */
	--os-resize-size: 20px;
	--os-resize-color: rgba(255, 251, 255, 0.18);

	/*
	 * ---- The `--os-ui-*` UI palette ------------------------------
	 *
	 * The single vocabulary for every surface, text tone, border, and
	 * status colour inside window BODIES — the `<os-*>` component kit
	 * AND the feature stylesheets both read these. Setting one value
	 * retints everything that uses it, which is what makes a
	 * whole-OS repaint eleven declarations rather than a thousand.
	 *
	 * It reaches further than window bodies: the shell's own
	 * body-mounted overlays — toasts, confirm dialogs, context menus,
	 * and the command palette — read it too.
	 *
	 * Roughly 190 component-local tokens (`--os-ui-card-bg`,
	 * `--os-ui-table-header-bg`, …) fall through to these, e.g.
	 *
	 *     background: var( --os-ui-card-bg, var( --os-ui-surface, #fff ) );
	 *
	 * so the ones named further down are only those whose own literal
	 * does NOT chain through the palette and would otherwise stay a
	 * light-mode value on a dark station.
	 */

	/* Surfaces — Obsidian on Void, one step up the ramp for elevated. */
	--os-ui-surface: #1a1721;
	--os-ui-surface-elevated: #33303a;
	--os-ui-surface-sunken: #0c0b0f;
	--os-ui-surface-raised: rgba(255, 251, 255, 0.06);
	--os-ui-surface-subtle: rgba(255, 251, 255, 0.03);
	--os-ui-bg: #1a1721;
	--os-ui-bg-subtle: rgba(255, 251, 255, 0.03);

	/*
	 * Fields — the surface an input paints, for markup in the LIGHT
	 * DOM.
	 *
	 * The kit's own controls don't need these: `<os-text-field>` and
	 * friends resolve `--os-window-bg` inside their shadow roots,
	 * which the window chrome sets. A plugin writing a plain
	 * `<input>` into a native window's body has neither, and the
	 * palette had no general answer — no `surface` variant means
	 * "input". What it had was `--os-ui-modal-field-bg`, which is a
	 * MODAL token: right value, wrong scope, and the sort of thing a
	 * plugin author finds by grepping for a colour that looks close.
	 *
	 * Consumers keep the pre-brand WordPress-admin literals as their
	 * fallbacks — `#fff`, `#8c8f94`, `#2c3338` — so an unstyled page
	 * lands on the classic admin's own field, not on nothing:
	 *
	 *     background: var( --os-ui-field-bg, #fff );
	 *     border: 1px solid var( --os-ui-field-border, #8c8f94 );
	 *     color: var( --os-ui-field-fg, #2c3338 );
	 */
	--os-ui-field-bg: #33303a;
	--os-ui-field-border: #4d4a52;
	--os-ui-field-fg: #fffbff;

	/* Text — Starlight down through the Shade ramp. */
	--os-ui-fg: #fffbff;
	--os-ui-fg-muted: #b3afb5;
	--os-ui-fg-faint: #66636b;
	--os-ui-text-muted: rgba(255, 251, 255, 0.6);
	/*
	 * Text on a filled surface — and the name undersells it. Around
	 * forty rules read this token, and only about half of them sit on
	 * the accent: the rest are white-on-dark chips that never had a
	 * name of their own — the toast, the drag hint, the widget picker,
	 * the file-tile lock, the overview labels, every scrim-backed
	 * caption in OS Settings.
	 *
	 * So it is Starlight. Void reads better ON Pulse (5.9:1 against
	 * 2.9:1) and was the first choice here, but it turned all of those
	 * chips into black text on a near-black wash. One token cannot be
	 * both, and the dark-chip half is both larger and the half that
	 * fails catastrophically rather than merely weakly.
	 *
	 * The surfaces that really are a bright fill — ribbons and step
	 * chips — name their own dark text below, which is the escape
	 * hatch this token leaves open.
	 */
	--os-ui-fg-on-accent: #fffbff;

	/* Lines. */
	--os-ui-border: #33303a;
	--os-ui-border-strong: #4d4a52;

	/* Interaction — light washes, since the surface underneath is dark. */
	--os-ui-hover: rgba(255, 251, 255, 0.06);
	--os-ui-scrim: rgba(12, 11, 15, 0.72);

	/*
	 * ---- Text selection ------------------------------------------
	 *
	 * Lagoon, the brand's violet-blue, at 60%. It is the one primary
	 * colour in the guidelines the shell had no job for, and this is
	 * the right job: violet-blue is what every OS has trained people
	 * to read as "selected", so it needs no learning.
	 *
	 * The alpha is where the accessibility is, and it is squeezed from
	 * both ends. A selection has to satisfy two constraints at once:
	 *
	 *   1. the selected TEXT stays readable on it — Starlight over
	 *      Lagoon@60% is 5.1:1 on Obsidian, 5.5:1 on Void; and
	 *   2. the highlight is distinguishable from the unselected
	 *      background — 3.4:1 and 3.5:1 respectively.
	 *
	 * Raising the alpha helps (2) and hurts (1); lowering it does the
	 * reverse. 60% is the middle of the band where both clear their
	 * bar on both surfaces. An accent wash cannot do this at all: the
	 * Pulse family only separates from the background above 75%, by
	 * which point it is a fill rather than a highlight.
	 *
	 * The text colour is Starlight — the colour body text already is.
	 * A selection that also RECOLOURS the text destroys every
	 * distinction the text was carrying: syntax highlighting, a red
	 * error, a muted timestamp. Highlighting is not restyling.
	 */
	--os-ui-selection-bg: rgba(159, 152, 255, 0.6);
	--os-ui-selection-fg: #fffbff;

	/* Accent — the identity moment. */
	--os-ui-accent: #f252fc;
	--os-ui-accent-strong: #ec9bff;
	--os-ui-accent-soft: color-mix(in srgb, var(--os-ui-accent-dim) 14%, transparent);
	/*
	 * Pulse, one step back — and the single knob for how loud the
	 * station is.
	 *
	 * Pulse is not a contrast problem: #f252fc carries 6.2:1 against
	 * Obsidian, better than most accents in a dark UI manage. What it
	 * is, is SATURATED — HSL 296, 97, 65 — and the places that hurt
	 * are the ones where it is spread rather than stated: a bloom
	 * behind a focused control, a 18% wash under a selected row, a
	 * fill wider than a chip. Ten of those on one panel and the whole
	 * surface hums.
	 *
	 * So the fix is not to move Pulse. It is to spend it where it is
	 * a statement and to spend THIS where it is atmosphere: the same
	 * hue with saturation and lightness pulled down together (S 77,
	 * L 53), which is what keeps it recognisably Pulse rather than a
	 * different purple. 4.6:1 on Obsidian, and Starlight over it goes
	 * from 2.8:1 to 3.8:1 — so the wash also stopped eating text.
	 *
	 * Everything ambient below resolves through it: the glows, the
	 * soft wash, the focus bloom. Turning the station up or down is
	 * one edit here.
	 *
	 * `--os-ui-accent` itself stays #f252fc. That one is the brand's,
	 * not ours — it is what the guidelines name and what
	 * `brand-palette.test.ts` pins, and moving it is a brand decision
	 * rather than a UI one.
	 */
	--os-ui-accent-dim: #d92ee3;

	/*
	 * ---- The meshes ---------------------------------------------
	 *
	 * The brand ships five mesh gradients as SVG artwork. These are
	 * those same meshes transcribed into CSS: the base linear plus
	 * every radial glow, at the offsets, radii, colours and alphas
	 * the vector files declare, converted from the 1440×960 artboard
	 * into percentages so they scale to any box.
	 *
	 * Transcribed rather than referenced on purpose. A `url()` to an
	 * SVG cannot be animated, cannot be tinted, costs a request, and
	 * cannot resize its glows independently of the box — a mesh
	 * painted on a 40 px switch track would be one flat corner of the
	 * artwork. As gradient stacks they rasterise at whatever size the
	 * control happens to be, and `background-position` can slide them,
	 * which is the entire holographic effect: a foil that shifts as
	 * you tilt it.
	 *
	 * Layer order is inverted from the SVG. In SVG the last `<rect>`
	 * paints on top; in a CSS `background-image` list the FIRST layer
	 * is on top. So each mesh below reads bottom-glow-first and ends
	 * with its base linear.
	 *
	 * The brand's own rule — "meshes reserved for hero surfaces" — is
	 * why none of these is a surface colour. They are what the shell
	 * paints at an identity moment: a switch that is on, the primary
	 * action in a dialog, the filled part of a progress bar. See the
	 * `--os-ui-holo-*` block below for the tokens components actually
	 * read.
	 */

	/*
	 * Holomesh. Lavender base under eight glows — white, cyan, pink,
	 * warm, mint, white, pink, blue. The default holographic fill.
	 */
	--os-mesh-holo:
		radial-gradient(ellipse 21.7% 39.6% at 96.5% 13.5%, rgba(159, 214, 255, 0.6) 0%, rgba(159, 214, 255, 0) 100%),
		radial-gradient(ellipse 26.3% 57.2% at 83.3% 56.2%, rgba(243, 181, 236, 0.75) 0%, rgba(243, 181, 236, 0) 100%),
		radial-gradient(ellipse 18.2% 47.3% at 73.6% 64.5%, rgba(255, 253, 255, 0.7) 0%, rgba(255, 253, 255, 0) 100%),
		radial-gradient(ellipse 18.4% 34.1% at 29.8% 83.4%, rgba(147, 240, 198, 0.8) 0%, rgba(147, 240, 198, 0) 100%),
		radial-gradient(ellipse 17.4% 35.7% at 23% 67.7%, rgba(248, 242, 182, 0.85) 0%, rgba(248, 242, 182, 0) 100%),
		radial-gradient(ellipse 23.3% 45.9% at 45.2% 45.8%, rgba(245, 159, 232, 1) 0%, rgba(245, 168, 234, 0.55) 55%, rgba(245, 168, 234, 0) 100%),
		radial-gradient(ellipse 29.9% 53.6% at 53.5% 7.3%, rgba(125, 239, 245, 0.9) 0%, rgba(142, 233, 247, 0.45) 60%, rgba(142, 233, 247, 0) 100%),
		radial-gradient(ellipse 26.9% 70.7% at 29.8% 44.8%, rgba(255, 253, 255, 0.95) 0%, rgba(255, 253, 255, 0.5) 55%, rgba(255, 253, 255, 0) 100%),
		linear-gradient(124deg, #afa2e8 0%, #b7abea 55%, #c3b8ef 100%);

	/*
	 * Pulsemesh. The loud one — a blue-to-violet base torn open by
	 * magenta. Reserved for the moments Holomesh would undersell.
	 */
	--os-mesh-pulse:
		radial-gradient(ellipse 39.1% 48.6% at 92% 94.5%, rgba(255, 253, 255, 0.95) 0%, rgba(255, 240, 252, 0.55) 50%, rgba(255, 240, 252, 0) 100%),
		radial-gradient(ellipse 33.3% 50% at 61.1% 62.5%, rgba(251, 134, 236, 0.85) 0%, rgba(251, 134, 236, 0) 100%),
		radial-gradient(ellipse 36.1% 54.2% at 10.4% 39.6%, rgba(250, 61, 248, 1) 0%, rgba(250, 61, 248, 0) 100%),
		radial-gradient(ellipse 87.3% 104.8% at 10.6% 100%, rgba(240, 75, 245, 1) 0%, rgba(240, 75, 245, 0.75) 43%, rgba(240, 75, 245, 0) 100%),
		radial-gradient(ellipse 36.1% 45.8% at 92.4% 12.5%, rgba(125, 239, 245, 0.95) 0%, rgba(125, 239, 245, 0) 100%),
		radial-gradient(ellipse 43.1% 54.2% at 81.3% 34.4%, rgba(108, 143, 245, 0.6) 0%, rgba(108, 143, 245, 0) 100%),
		linear-gradient(236deg, #8d9bf3 0%, #c878f0 100%);

	/* Auromesh. Lavender through mint into cyan — the calm mesh. */
	--os-mesh-auro: linear-gradient(80deg, #d1c6f8 9.5%, #cefada 50%, #b8ffff 90.5%);

	/* Starmesh. A Starlight glow falling away into Cloud. */
	--os-mesh-star:
		radial-gradient(ellipse 72.7% 72.7% at 50% 12.5%, #fffbff 0%, #fffbff 67.8%, #ccc8ce 100%);

	/* Miomesh. The mascot's own sweep. Mio's, and used sparingly. */
	--os-mesh-mio: linear-gradient(90deg, #f252fc 7%, #aa67ff 48.3%, #a580ff 70.7%, #4b3eff 93%);

	/*
	 * ---- Holographic controls -----------------------------------
	 *
	 * What a control reads when it wants to be holographic. Every
	 * `<os-*>` resolves these through a private `--_holo-*` alias (see
	 * `src/ui/holo.ts`), so a desktop theme can re-point any of them
	 * and every control in the kit changes together.
	 *
	 * The holographic state is a MOMENT, not a skin. A control paints
	 * `--os-ui-holo-fill` when it is on, selected, primary or filled —
	 * the one instant it is speaking for the brand — and wears
	 * ordinary Obsidian the rest of the time. That is the difference
	 * between a station and a novelty; a panel where every control is
	 * iridescent has no identity moments left to spend.
	 */

	/* The fill itself. Holomesh, so the mesh is the default look. */
	--os-ui-holo-fill: var(--os-mesh-holo);
	/*
	 * A hero FIELD, as distinct from a hero control.
	 *
	 * `--os-ui-holo-fill` is sized for a surface a control wears — a
	 * button, a chip, a progress bar. Stretch that same value across
	 * a few hundred square pixels of panel and a theme's answer stops
	 * working: Legacy's `#f0f6fc -> #f6f7f7` reads as "on" on a 40px
	 * chip and as nothing at all on a rail, where it lands within a
	 * few units of the surface behind it.
	 *
	 * So a large decorative field reads THIS instead, and a theme
	 * gets to answer the two cases separately — how a control looks
	 * when it is on, and how much of itself the station shows in the
	 * background. Legacy sets a flat pre-brand wash here while
	 * leaving its control fill alone.
	 *
	 * The default is an aurora, and it is built differently from the
	 * five meshes on purpose. Those are OPAQUE light surfaces — the
	 * darkest stop in Holomesh still sits above 60% luminance — which
	 * is right for a control that has to read as lit against whatever
	 * is behind it. Spread one across a rail and the only way to calm
	 * it down is `opacity`, which drags every hue toward the surface
	 * underneath: on Void that is grey, and the mesh dies as a
	 * grey-mauve smear.
	 *
	 * So the alpha lives in the COLOUR STOPS here, over transparent.
	 * The desk shows through instead of being covered, the hues keep
	 * their chroma at any strength, and the field stays dark the way
	 * an aurora is dark — light added to a night sky, not a pale
	 * sheet laid over one. Hues are the brand's: violet and Nebula
	 * through the upper curtains, Sirius cyan and Holomesh's mint
	 * below, one wide Pulse bloom tying them together.
	 */
	--os-ui-hero-mesh:
		radial-gradient(ellipse 62% 38% at 34% 20%, rgba(170, 103, 255, 0.58) 0%, rgba(170, 103, 255, 0) 74%),
		radial-gradient(ellipse 54% 33% at 58% 36%, rgba(236, 155, 255, 0.45) 0%, rgba(236, 155, 255, 0) 76%),
		radial-gradient(ellipse 68% 30% at 26% 50%, rgba(125, 239, 245, 0.32) 0%, rgba(125, 239, 245, 0) 74%),
		radial-gradient(ellipse 58% 28% at 46% 66%, rgba(147, 240, 198, 0.26) 0%, rgba(147, 240, 198, 0) 76%),
		radial-gradient(ellipse 96% 62% at 40% 42%, rgba(242, 82, 252, 0.18) 0%, rgba(242, 82, 252, 0) 82%);
	/*
	 * Glyphs and text ON that fill. Void, not Starlight — every mesh
	 * in the brand is a LIGHT surface (its darkest stop, Holomesh's
	 * #afa2e8, still sits at 62% luminance), so Starlight on it is a
	 * white-on-lilac 1.4:1 that looks fine in a mock and is unreadable
	 * on a screen. Void carries at 8.6:1 against the same stop.
	 */
	--os-ui-holo-ink: #0c0b0f;
	/*
	 * The hover film. A control that is NOT in its holographic state
	 * still hints at one under the pointer: a low-alpha sweep of the
	 * mesh's own hues, laid over whatever the surface already is.
	 *
	 * Alphas are deliberately in the 6–12% range. This lands on
	 * Obsidian, where anything stronger stops reading as a sheen and
	 * starts reading as a second, wrongly-coloured surface.
	 */
	--os-ui-holo-sheen: linear-gradient(
		124deg,
		rgba(159, 214, 255, 0.1) 0%,
		rgba(236, 155, 255, 0.12) 34%,
		rgba(242, 82, 252, 0.1) 58%,
		rgba(147, 240, 198, 0.09) 100%
	);
	/*
	 * The iridescent hairline. Painted as a gradient on a padding-box
	 * mask rather than a `border-color`, because a border cannot hold
	 * a gradient and `border-image` loses the corner radius.
	 */
	--os-ui-holo-edge: linear-gradient(
		124deg,
		rgba(154, 242, 255, 0.7) 0%,
		rgba(236, 155, 255, 0.85) 38%,
		rgba(242, 82, 252, 0.7) 62%,
		rgba(159, 152, 255, 0.6) 100%
	);
	/* Resting edge — the same hairline, quieted, for an idle control. */
	--os-ui-holo-edge-quiet: linear-gradient(
		124deg,
		rgba(154, 242, 255, 0.22) 0%,
		rgba(236, 155, 255, 0.28) 38%,
		rgba(242, 82, 252, 0.22) 62%,
		rgba(159, 152, 255, 0.2) 100%
	);
	/*
	 * The bloom around a holographic surface. Pulse, because on a Void
	 * desk the glow is what separates "lit" from "light-coloured".
	 */
	--os-ui-holo-glow: 0 0 0 1px color-mix(in srgb, var(--os-ui-accent-dim) 20%, transparent),
		0 2px 10px color-mix(in srgb, var(--os-ui-accent-dim) 15%, transparent);
	--os-ui-holo-glow-strong: 0 0 0 1px color-mix(in srgb, var(--os-ui-accent-dim) 30%, transparent),
		0 4px 18px color-mix(in srgb, var(--os-ui-accent-dim) 26%, transparent),
		0 1px 3px rgba(12, 11, 15, 0.6);
	/*
	 * The unlit half — switch tracks, empty progress, the unchecked
	 * checkbox.
	 *
	 * This was a sunken Void well, and it was a mistake that measured
	 * badly: `rgba(12, 11, 15, 0.55)` composites to #121017 on an
	 * Obsidian panel, which is **1.07:1** against that panel. An off
	 * switch was, to a very good approximation, not drawn. WCAG 1.4.11
	 * asks for 3:1 on the boundary of a control, and this was not in
	 * the same postcode.
	 *
	 * So the well is LIFTED rather than sunk: a Starlight wash, which
	 * also means it tracks whatever surface a theme puts behind it
	 * instead of pinning a grey that only works on Obsidian. Every
	 * dark OS does the same thing with its switches for the same
	 * reason — on a dark UI the visible thing is the lighter one.
	 *
	 * The fill alone still only reaches ~1.6:1, which is a *look*
	 * rather than a boundary. The 3:1 comes from the edge below, and
	 * the two together are what make an off switch findable.
	 */
	--os-ui-holo-track: rgba(255, 251, 255, 0.16);
	/*
	 * The unlit half's boundary. Pewter, and not a rounder-looking
	 * neighbour, because Pewter is the first step on the Shade ramp
	 * that reaches 3.00:1 against Obsidian — Silver manages 2.03 and
	 * Astro 1.37. This is the declaration that makes an off control
	 * satisfy WCAG 1.4.11.
	 */
	--os-ui-holo-track-edge: #66636b;
	/*
	 * Focus. One ring for the whole kit — Pulse, offset, over a soft
	 * halo, so it reads on Obsidian, on a mesh, and on the near-white
	 * of a legacy theme alike.
	 *
	 * Note which layer dims and which does not. The RING is full Pulse
	 * and stays there: it is the only thing on screen saying where the
	 * keyboard is, and a focus indicator is the last place to trade
	 * legibility for calm. Only the bloom behind it comes down, from
	 * 45% to 30%, and onto the dim — which is the layer that was
	 * making a focused control look like it was on fire.
	 */
	--os-ui-focus-ring: 0 0 0 2px rgba(12, 11, 15, 0.9),
		0 0 0 4px var(--os-ui-accent, #f252fc),
		0 0 12px 2px color-mix(in srgb, var(--os-ui-accent-dim) 30%, transparent);
	/*
	 * …and one for FIELDS, which is a different problem. A text input
	 * already has a border to thicken and it sits in a column of other
	 * inputs, so the ring above — designed to survive landing on a
	 * bright mesh — reads as an alarm on a settings form. This one
	 * tightens the field's own edge to Pulse and puts a soft halo
	 * outside it: unmistakable, and quiet enough to live in a stack of
	 * twelve.
	 */
	--os-ui-focus-ring-field: 0 0 0 1px var(--os-ui-accent, #f252fc),
		0 0 0 4px color-mix(in srgb, var(--os-ui-accent-dim) 16%, transparent);

	/*
	 * The selected row of a VERTICAL `<os-tabs>`: the sidebar in
	 * OpenStation Preferences, and any native window that lays its
	 * pages down the side rather than across the top.
	 *
	 * Three layers, and the split is the whole point. Colour repeated
	 * down nine rows is wallpaper, so the accent is spent as a
	 * two-pixel EDGE and the row itself gets only a wash that falls
	 * away: it appears where it is cheap to look at and expensive to
	 * ignore.
	 *
	 * `-edge` is the accent the user picked, flat. It used to be
	 * Miomesh, and a mesh does hold up at 2px where a radial one
	 * would crop to an arbitrary lilac — but the edge is the loudest
	 * thing on the page that says "this row", and a row that says it
	 * in Pulse while the controls beside it say it in the chosen
	 * accent reads as two systems.
	 *
	 * `-wash` and `-bloom` are AMBIENT accent, so both resolve through
	 * `--os-ui-accent-dim` like every other glow in the kit. Turning
	 * the station down is still one edit. The Starlight stop in the
	 * middle of the wash is what keeps the row from reading as a pink
	 * smear: it lifts the surface under the label rather than tinting
	 * it, and it is gone by the time the row ends.
	 */
	--os-ui-tab-edge: linear-gradient(
		var(--os-ui-accent, #f252fc),
		var(--os-ui-accent, #f252fc)
	);
	--os-ui-tab-wash: linear-gradient(
		90deg,
		color-mix(in srgb, var(--os-ui-accent-dim) 16%, transparent) 0%,
		rgba(255, 251, 255, 0.04) 42%,
		transparent 100%
	);
	--os-ui-tab-bloom: linear-gradient(
		90deg,
		color-mix(in srgb, var(--os-ui-accent-dim) 26%, transparent),
		transparent
	);

	/*
	 * ---- Motion --------------------------------------------------
	 *
	 * Four durations and three curves, and every animated thing in the
	 * kit picks from them. Not for tidiness: a panel where the switch
	 * settles in 220ms, the segmented thumb in 300 and the tab
	 * underline in 150 does not read as three well-tuned controls, it
	 * reads as one surface that cannot keep time.
	 *
	 * The scale is roughly logarithmic because perceived duration is:
	 * 90 and 140 are distinguishable, 220 and 240 are not.
	 */
	/* A state flip with no travel — a colour, an opacity, a tick. */
	--os-ui-motion-fast: 140ms;
	/*
	 * How long the foil takes to tilt, and the kit's default. One
	 * duration for every holographic transition, so a panel full of
	 * controls moves as one surface rather than as thirty independent
	 * ones.
	 */
	--os-ui-holo-transition: 220ms;
	/* Something crossing a distance: a thumb, a drawer, a card. */
	--os-ui-motion-slow: 340ms;
	/* An ambient loop — a shimmer, a drift. Long enough to ignore. */
	--os-ui-motion-ambient: 12s;

	/*
	 * The curves. `spring` overshoots ~9% and settles; it is what makes
	 * a knob feel thrown rather than moved, and it is wrong for
	 * anything that changes size (an overshooting width reads as a
	 * glitch, not as weight).
	 */
	--os-ui-ease-spring: cubic-bezier(0.32, 1.5, 0.55, 1);
	/* Decelerating. The default for anything arriving. */
	--os-ui-ease-out: cubic-bezier(0.22, 0.9, 0.28, 1);
	/*
	 * Accelerating — the mirror of `out`, and the default for anything
	 * LEAVING. A surface that eases out on the way in and eases in on
	 * the way out is the difference between "dismissed" and "undone":
	 * decelerating into nothing reads as the thing hesitating, which
	 * is the wrong note when the user has already moved on.
	 */
	--os-ui-ease-in: cubic-bezier(0.4, 0, 1, 1);
	/* Symmetric, for a loop that has to come back where it started. */
	--os-ui-ease-loop: cubic-bezier(0.45, 0, 0.55, 1);

	/*
	 * Status. Derived, not quoted — see the header note. Info takes
	 * Sirius and success takes Holomesh's mint because the brand
	 * already has a colour that means those things; danger and warning
	 * are the shell's own hues lifted until they carry on Obsidian.
	 */
	--os-ui-danger: #ff5a5a;
	--os-ui-danger-hover: #ff8a8a;
	--os-ui-success-fg: #93f0c6;
	--os-ui-warning-fg: #f8f2b6;
	--os-ui-warning: #f8f2b6;
	--os-ui-warning-bg: rgba(248, 242, 182, 0.1);
	--os-ui-warning-border: rgba(248, 242, 182, 0.24);
	--os-ui-info-fg: #c2f1f1;
	--os-ui-info-bg: rgba(194, 241, 241, 0.1);

	/*
	 * ---- Component tokens the palette cannot reach --------------
	 *
	 * Each of these is read with a literal of its own rather than a
	 * `var( --os-ui-<palette> )` chain, so the palette above does not
	 * carry to it. Everything else in the kit is left alone and
	 * inherits.
	 */

	/*
	 * Dialogs, menus and flyouts.
	 *
	 * The `--os-ui-modal-*` names below are the escape hatch
	 * `<os-modal>` needs. Its dialog surface is dark whatever the
	 * admin colour scheme says, so it re-points `--os-ui-fg`,
	 * `--os-ui-fg-muted`, `--os-ui-border`, `--os-window-bg` and
	 * `--os-ui-button-bg-hover` on its own `:host` — and a declaration
	 * on the host outranks anything the host inherits, which is how
	 * the palette used to stop at the dialog's edge. It reads these
	 * instead, so the station owns the dialog again and the literals
	 * behind them stay as the no-stylesheet floor.
	 *
	 * The set has to cover a dark context COMPLETELY, and the four
	 * added last are the ones that proved it. A dialog re-pointing
	 * only its text colours is fine while the palette around it is
	 * dark too — every unre-pointed token happens to agree. Put a
	 * LIGHT palette outside it (any theme in the admin's own colours,
	 * Legacy included) and the halves come apart: `--os-ui-surface`
	 * stays white, so `<os-select>` inside the dialog painted a white
	 * trigger and the re-pointed `--os-ui-fg` wrote near-white text
	 * onto it. `--os-ui-hover` was a black wash over a dark row.
	 *
	 * So the rule for anything added here: if a token names a SURFACE
	 * or a wash on one, the dialog owns it. Leaving one out is not
	 * "inherit the theme", it is "disagree with the other five".
	 */
	--os-ui-modal-bg: #1a1721;
	--os-ui-modal-text: #fffbff;
	--os-ui-modal-text-muted: #b3afb5;
	--os-ui-modal-border: #4d4a52;
	--os-ui-modal-field-bg: #33303a;
	--os-ui-modal-button-bg-hover: rgba(255, 251, 255, 0.14);
	--os-ui-modal-surface: #1a1721;
	--os-ui-modal-surface-elevated: #33303a;
	--os-ui-modal-border-strong: #4d4a52;
	--os-ui-modal-hover: rgba(255, 251, 255, 0.06);
	--os-ui-confirm-dialog-bg: #1a1721;
	--os-ui-context-menu-bg: #1a1721;
	--os-ui-context-menu-separator-color: rgba(255, 251, 255, 0.1);
	--os-ui-flyout-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
	--os-ui-card-shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.45);
	--os-ui-ribbon-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);

	/* Tables. */
	--os-ui-table-bg: #1a1721;
	--os-ui-table-header-bg: #33303a;
	--os-ui-table-border: rgba(255, 251, 255, 0.1);
	--os-ui-table-column-border: rgba(255, 251, 255, 0.16);
	--os-ui-table-row-hover: rgba(255, 251, 255, 0.06);
	--os-ui-table-stripe: rgba(255, 251, 255, 0.03);
	--os-ui-table-sticky-edge: 2px solid rgba(255, 251, 255, 0.14);

	/* Notices and badges — tinted washes of their own status colour. */
	--os-ui-notice-error-bg: rgba(255, 90, 90, 0.12);
	--os-ui-notice-error-border: rgba(255, 90, 90, 0.28);
	--os-ui-notice-success-bg: rgba(147, 240, 198, 0.12);
	--os-ui-notice-success-border: rgba(147, 240, 198, 0.28);
	--os-ui-notice-warning-bg: rgba(248, 242, 182, 0.12);
	--os-ui-notice-warning-border: rgba(248, 242, 182, 0.28);
	--os-ui-notice-info-bg: rgba(194, 241, 241, 0.12);
	--os-ui-notice-info-border: rgba(194, 241, 241, 0.28);
	--os-ui-notice-neutral-bg: rgba(255, 251, 255, 0.06);
	--os-ui-notice-neutral-border: rgba(255, 251, 255, 0.14);
	/*
	 * A link inside a notice reads Nebula, the same value `--os-link`
	 * gives every other shell link — NOT the accent.
	 *
	 * Undeclared, the base fell through to `--wp-admin-theme-color`,
	 * which OpenStation Preferences → Appearance writes inline on `<html>`
	 * from the accent picker. That made the one piece of text a notice
	 * most needs the user to read take whichever hue they chose for
	 * focus rings. An accent is an identity colour; legibility on a dark
	 * surface is not something it can promise, so the link stops asking
	 * it to. Hover lifts to Starlight, and both ends stay in the palette.
	 *
	 * These are consumed from a DOCUMENT-tree rule in `desktop.css`, not
	 * only from the component's own `::slotted( a )`. See the comment
	 * there: a slotted link is a document-tree element, so wp-admin's
	 * bare `a` rule outranks anything the shadow root says about it.
	 */
	--os-ui-notice-link: #ec9bff;
	--os-ui-notice-link-hover: #fffbff;
	--os-ui-badge-danger-bg: rgba(255, 90, 90, 0.16);
	--os-ui-badge-success-bg: rgba(147, 240, 198, 0.16);
	--os-ui-badge-warning-bg: rgba(248, 242, 182, 0.16);
	--os-ui-badge-info-bg: rgba(194, 241, 241, 0.16);
	--os-ui-badge-neutral-bg: rgba(255, 251, 255, 0.08);

	/*
	 * Fills that must stay fills. Each of these paints a solid shape —
	 * a ribbon, a chip, a focused input's edge — and each was reading
	 * a token the palette turned into a 10%-alpha wash or into the
	 * body colour itself. A ribbon the same colour as the card behind
	 * it is not a subtle ribbon, it is a missing one.
	 */
	/*
	 * Text on the bright status fills. These are the surfaces the
	 * `--os-ui-fg-on-accent` note points at: a ribbon filled with Pulse,
	 * Sirius or Holomesh mint is a LIGHT surface, and Starlight on it
	 * disappears.
	 */
	--os-ui-ribbon-fg: #0c0b0f;
	--os-ui-step-chip-fg: #0c0b0f;
	/*
	 * The step chip is the right size and shape for the mesh: a small
	 * round fill, one per row, already carrying dark text. Its
	 * `--os-ui-step-chip-fg` above is Void, which is what the mesh
	 * needs anyway — the two decisions were made independently and
	 * agree, which is usually the sign a palette is holding together.
	 */
	--os-ui-step-chip-bg: var(--os-mesh-holo);

	/*
	 * The default ribbon. One per tile, small, angled, and already
	 * paired with Void ink above — which is what a mesh needs anyway.
	 * The status ribbons below keep their flat colours: a ribbon that
	 * says "deprecated" should say it in warning yellow, not in brand.
	 */
	--os-ui-ribbon-bg: var(--os-mesh-holo);

	--os-ui-ribbon-info: #c2f1f1;
	--os-ui-ribbon-warning: #f8f2b6;
	--os-ui-ribbon-neutral: #4d4a52;
	--os-ui-crumb-bg: #33303a;
	--os-ui-tag-input-input-border: #f252fc;

	/*
	 * Pairs that have to stay distinguishable. Hover and pressed, or
	 * base and shimmer, both resolve through `--os-ui-hover` — one wash
	 * for two states reads as a dead control, so the second step of
	 * each pair is named.
	 */
	--os-ui-btn-bg-active: rgba(255, 251, 255, 0.12);
	/*
	 * `<os-button>` ghost + secondary. Their resting wash and their
	 * hover wash BOTH fall through to `--os-ui-hover` — in two different
	 * rules, with two different literals behind them (6% and 10%), so
	 * declaring the palette collapsed them onto one value and the
	 * buttons stopped reacting to the pointer entirely. Primary is
	 * unaffected: it hovers with `filter: brightness()` over its
	 * accent fill rather than through this token.
	 */
	--os-ui-button-bg-hover: rgba(255, 251, 255, 0.14);
	--os-ui-key-bg-hover: rgba(255, 251, 255, 0.1);
	--os-ui-key-bg-pressed: rgba(255, 251, 255, 0.16);
	--os-ui-table-skeleton-highlight: rgba(255, 251, 255, 0.14);

	/* Code, logs, progress, ratings, avatars. */
	--os-ui-code-border: 1px solid rgba(255, 251, 255, 0.1);
	--os-ui-log-border: 1px solid rgba(255, 251, 255, 0.08);
	--os-ui-log-row-border: 1px solid rgba(255, 251, 255, 0.06);
	--os-ui-progress-track-bg: rgba(255, 251, 255, 0.1);
	/*
	 * The filled part of a progress bar is the one surface in the kit
	 * that is *already* a hero: bounded, watched, and the only thing
	 * moving on screen. So it takes Holomesh, and the bar stretches
	 * the mesh as it grows — the hue travelling from lavender toward
	 * cyan IS the progress, which a flat accent could only report.
	 *
	 * Retinted here rather than in `<os-progress-bar>` because the
	 * component's own literal has to stay the pre-brand WordPress blue
	 * (it is the no-stylesheet floor, and it is what Legacy collected).
	 * The tone modifiers — success, warning, danger — still declare
	 * flat colours and still win: a failing job should read as red, not
	 * as brand.
	 */
	--os-ui-progress-fill: var(--os-mesh-holo);
	--os-ui-rating-star: #f8f2b6;
	--os-ui-rating-fill: linear-gradient(90deg, #f8f2b6 0%, #fffbff 100%);
	--os-ui-avatar-dot-ring: #1a1721;
	/* The avatar's hover halo. It was a fixed indigo — a hue no
	   palette in the station names — so it lit itself in a colour
	   nothing around it used. Pulse. */
	--os-ui-avatar-halo: #f252fc;
	/* Search-match highlight inside a label. A WASH, not a fill: the
	   text keeps its own colour, so the wash has to stay light enough
	   for Starlight to carry over it. */
	--os-ui-search-highlight-bg: rgba(248, 242, 182, 0.3);
	--os-ui-color-accent: #f252fc;
	/*
	 * The content graph's cluster hue is NOT retinted. Graph node
	 * colours are composed artwork that encodes meaning — the docs list
	 * them under "what stays fixed" — and moving this one to Nebula put
	 * white cluster labels on a pale lilac chip at 1.9:1. It keeps its
	 * own colour, and the label keeps carrying on it.
	 *
	 * The graph's toolbar is chrome, though, and it does follow. The
	 * count pill on an ACTIVE filter chip is a 70%-white fill, and it
	 * was carrying `--os-ui-accent-strong` numerals — Nebula on white,
	 * ~1.7:1. It is one more bright fill naming its own dark text, the
	 * same escape hatch `--os-ui-ribbon-fg` and `--os-ui-step-chip-fg`
	 * take, and for the same reason.
	 */
	--os-ui-cg-chip-count-fg-active: #0c0b0f;

	/*
	 * ---- Typography --------------------------------------------
	 *
	 * Geist for everything human — headlines, UI copy, body text — and
	 * Geist Mono for the instrument voice: labels, metadata, statuses,
	 * code. Both faces are declared above; a stack without them still
	 * reads correctly, which is why every entry ends in a generic
	 * family.
	 *
	 * The chrome / body split is the one real decision here, and it is
	 * the one real desktop environments make. Here both sides run
	 * Geist: the station is one surface, and the title bar is not a
	 * different product from the window under it.
	 *
	 * Note that iframe windows render a real wp-admin document in a
	 * separate browsing context that this stylesheet does not reach —
	 * these tokens style the shell and native windows.
	 */
	--os-font: 'Geist', system-ui, sans-serif;
	--os-titlebar-font: 'Geist', system-ui, sans-serif;
	--os-ui-font: 'Geist', system-ui, sans-serif;
	--os-ui-font-mono: 'Geist Mono', ui-monospace, monospace;

	/*
	 * ---- Desktop-theme texture family -------------------------
	 *
	 * These are DELIBERATELY not declared. Every rule that consumes
	 * one reads it as `var( --name, <initial> )`, so an unset variable
	 * resolves to the CSS initial value and costs exactly nothing.
	 * Declaring them with `none` up here would be equivalent but would
	 * imply they are part of the default palette — they are not. They
	 * exist only while an uploaded or code-registered desktop theme is
	 * active, and only the compiled theme stylesheet ever sets them.
	 *
	 *   TITLEBAR          --os-titlebar-image
	 *                     --os-titlebar-image-repeat
	 *                     --os-titlebar-image-size
	 *   TITLEBAR_FOCUSED  --os-titlebar-image-focused
	 *                       (falls back to the unfocused image)
	 *   WINDOW_FRAME      --os-window-border-image-source
	 *                     --os-window-border-image-slice
	 *                     --os-window-border-image-width
	 *                     --os-window-border-image-repeat
	 *   WINDOW_CORNER_*   --os-window-corner-{ne,nw,se,sw}-image
	 *                     --os-window-corner-size (shared)
	 *                     --os-window-corner-inset (shared;
	 *                       how far inside the rounded corner the
	 *                       ornament sits. Defaults to the resize
	 *                       handle's 3px overhang plus a share of
	 *                       --os-window-radius, because the
	 *                       window clips its descendants)
	 *   WINDOW_FRAME_FOCUSED
	 *                     --os-window-border-image-focused-*
	 *                       (same four sub-properties as WINDOW_FRAME)
	 *   WINDOW_BODY       --os-window-body-image
	 *   TABBAR            --os-tabs-image
	 *   TITLEBAR_CONTROLS --os-titlebar-controls-image
	 *   TITLEBAR_BUTTON   --os-ui-btn-bg-image
	 *   DOCK              --os-dock-bg-image
	 *                     --os-dock-bg-image-repeat
	 *                     --os-dock-bg-image-size
	 *   DOCK_ITEM         --os-dock-item-image
	 *   DESKTOP           --os-desktop-image
	 *                     --os-desktop-image-repeat
	 *                     --os-desktop-image-size
	 *   ICON_TILE         --os-tile-image
	 *   WIDGET            --os-widget-image
	 *   MENU              --os-ui-menu-bg-image
	 *   DIALOG            --os-ui-dialog-bg-image
	 *   SCRIM             --os-ui-scrim-image
	 *   PANEL             --os-ui-panel-bg-image
	 *   TOAST             --os-ui-toast-bg-image
	 *   TABLE_HEADER      --os-ui-table-header-bg-image
	 *   BUTTON            --os-ui-button-bg-image
	 *
	 * Every `image` slot also gets `<prop>-repeat` and `<prop>-size`
	 * companions (the corners share one `-size`, and
	 * TITLEBAR_FOCUSED inherits TITLEBAR's).
	 *
	 * Consumers: window-chrome.css (titlebar, frame, body, tab bar),
	 * window-states.css (corners), dock.css, desktop.css (wallpaper,
	 * icons, widgets), and the `<os-*>` component stylesheets under
	 * `src/ui/components/` for the last seven.
	 * Authoring reference: docs/desktop-themes.md.
	 */

	/*
	 * ---- Badges ----------------------------------------------
	 *
	 * Sizes stay undeclared and DERIVED: every badge sizes itself off
	 * the icon it decorates — the dock badge off
	 * `--os-dock-icon-size`, the wallpaper badges off
	 * `--os-icon-image-size` — so a user who picks a large
	 * dock, or a theme that scales its icons, doesn't end up with a
	 * speck stuck to an oversized tile.
	 *
	 *   Dock          --os-dock-badge-size (overrides the
	 *                   whole derivation; font + padding follow it)
	 *                 --os-dock-badge-font-size / -padding
	 *                 --os-dock-badge-offset / -bg / -fg
	 *                 --os-dock-badge-font-weight
	 *   Desktop icon  --os-icon-badge-size / -font-size
	 *                 --os-icon-badge-padding / -bg / -fg
	 *   Recycle bin   --os-recycle-badge-size / -font-size
	 *                   / -bg / -fg. Falls through to the generic
	 *                   icon-badge token, so "badges are 22px" reaches
	 *                   the bin too.
	 *
	 * The count badges wear Pulse, and wear it as a LITERAL gradient:
	 * both stops used to resolve through `--os-ui-danger`, which the
	 * palette moved, flattening the badge to one flat fill and turning
	 * its numerals Void by accident. Naming the stops here keeps it a
	 * gradient on purpose. The bin's neutral badge is deliberately not
	 * part of this: a count of things in the trash is ambient state,
	 * not something asking for you.
	 *
	 * **The numerals are Void, not Starlight, and that is a
	 * requirement rather than a preference.** Starlight on Pulse is
	 * 2.9:1, which fails AA for text this small; Void on Pulse is
	 * 6.8:1. Any future retint of these has to keep the pair legible,
	 * so check the ratio before swapping the ink back to white.
	 */
	--os-dock-badge-bg: linear-gradient(180deg, #f97dff 0%, #f252fc 100%);
	--os-dock-badge-fg: #0c0b0f;
	--os-icon-badge-bg: linear-gradient(180deg, #f97dff 0%, #f252fc 100%);
	--os-icon-badge-fg: #0c0b0f;
	--os-recycle-badge-bg: rgba(26, 23, 33, 0.85);
	--os-recycle-badge-fg: rgba(255, 251, 255, 0.96);
	--os-dock-recycle-badge-bg: rgba(26, 23, 33, 0.85);
	--os-dock-recycle-badge-fg: rgba(255, 251, 255, 0.92);

	/*
	 * ---- Tooltips ---------------------------------------------
	 *
	 * Two tokens own every tooltip CHIP in the shell — the dock tile
	 * tooltip and the content-graph satellite tooltip.
	 *
	 * They are worth naming rather than leaving derived. Without them
	 * tooltips borrow their colours from unrelated families —
	 * `--os-ui-scrim` (an overlay BACKDROP) or `--os-ui-surface-elevated`
	 * for the surface, `--os-ui-fg-on-accent` for the text — and those
	 * pairings come apart the moment the palette moves. On this
	 * palette `--os-ui-fg-on-accent` is Void, which would have rendered
	 * near-black text on a near-black chip.
	 *
	 * A chip is deliberately the SAME dark lozenge on every desktop
	 * theme, which is why these two are flat values and not derived
	 * from anything. That is right for one line of text pinned to a
	 * control, and wrong for a rich card: the My WordPress entity
	 * hover card used to read them and showed up Obsidian over a white
	 * Legacy window. It has its own derived family now —
	 * `--os-my-wordpress-card-*`, further down — and it still names
	 * these two as its last fallback.
	 */
	--os-tooltip-bg: #33303a;
	--os-tooltip-fg: #fffbff;

	/*
	 * ---- Dock --------------------------------------------------
	 *
	 * Void glass over the desk, with a Starlight hairline. The tile
	 * hover wash and the focus ring are the dock's identity moment:
	 * Pulse, at the two alphas that read as "warm" and "here".
	 *
	 * `--os-dock-icon-color` is a COLOUR, not a fill, and
	 * that is load-bearing: every image glyph in the dock is painted
	 * as a mask filled with this colour — a theme's own iconset via
	 * `"iconColor": "currentColor"`, and plugin / CPT artwork via
	 * `currentColor` on the tile. One token drives dashicons, theme
	 * art, plugin art, and the hover transition between them.
	 *
	 * Consumers: dock.css, dock-peek.css (which re-states the hover
	 * rule and so must read the same two tokens).
	 */
	--os-dock-width: 56px;
	--os-dock-icon-size: 20px;
	--os-dock-bg: rgba(12, 11, 15, 0.72);
	--os-dock-icon-color: rgba(255, 251, 255, 0.72);
	--os-dock-icon-color-hover: #fffbff;
	--os-dock-item-bg-hover: rgba(242, 82, 252, 0.18);
	/*
	 * The status indicator and the focus ring, and both say "this
	 * one" about a tile the user chose — so they read the accent
	 * rather than a fixed Pulse. Pulse is still what an untouched
	 * install shows, because that is what the picker starts on.
	 */
	--os-dock-item-outline: var(--os-ui-accent, #f252fc);
	/*
	 * Hairline between the dock and the desktop. Reaches the side
	 * placements (as a single inline-edge border) AND the floating
	 * bottom pill (as its full outline). Set `transparent` for a
	 * borderless dock.
	 *
	 * The bottom pill's own chrome is separately overridable, because
	 * it is deliberately a different piece of glass — a warmer tint, a
	 * brighter top edge, an inset highlight, a drop shadow. Each falls
	 * back to the shared value above it.
	 */
	--os-dock-border: rgba(255, 251, 255, 0.1);

	/*
	 * The dividers INSIDE the rail, which are a different job from the
	 * outline around it: they separate tiles that belong to different
	 * groups — core menus, plugin apps, and OpenStation's own controls.
	 *
	 * One treatment for all of them. An earlier pass gave the
	 * WordPress-to-OpenStation boundary Pulse and dropped the
	 * core-to-plugin one to a hairline, on the theory that only one
	 * boundary was worth reading. Two weights in one short rail read as
	 * two unrelated ideas instead of one system, so both are now the
	 * same line: Pulse at the waist, falling away to nothing at both
	 * ends, with a soft glow to hold it against the dock tint.
	 *
	 * Resolved one step back from the accent through
	 * `--os-ui-accent-dim`, where every ambient use of Pulse resolves,
	 * so "tone the station down" stays one edit.
	 */
	--os-dock-divider: color-mix(in srgb, var(--os-ui-accent-dim) 70%, transparent);
	--os-dock-floating-bg: rgba(12, 11, 15, 0.62);
	--os-dock-floating-border: rgba(255, 251, 255, 0.12);
	--os-dock-floating-border-top: rgba(255, 251, 255, 0.18);
	--os-dock-floating-highlight: rgba(255, 251, 255, 0.1);
	--os-dock-floating-shadow: rgba(0, 0, 0, 0.55);

	/*
	 * ---- The OpenStation layout ---------------------------------
	 *
	 * The constellation: the flyout that fans a menu's submenu out of
	 * its tile on hover, on every rail in every layout. The rail's
	 * divider used to live here too; it is shared chrome now and reads
	 * through `--os-dock-divider`.
	 *
	 * The constellation's SURFACE is Obsidian, deliberately. The mesh
	 * is spent on the row under the pointer and on the head's icon
	 * halo — the two moments where the panel is answering the user —
	 * and nowhere else. A flyout that was iridescent edge to edge
	 * would have nothing left to say when you actually pointed at
	 * something in it.
	 */
	--os-cn-surface: rgba(26, 23, 33, 0.82);
	--os-cn-border: rgba(255, 251, 255, 0.14);
	--os-cn-shadow: 0 24px 64px rgba(0, 0, 0, 0.62),
		0 2px 8px rgba(0, 0, 0, 0.4);
	--os-cn-fg: #fffbff;
	--os-cn-fg-muted: rgba(255, 251, 255, 0.62);
	--os-cn-legend: rgba(255, 251, 255, 0.4);
	--os-cn-divider: rgba(255, 251, 255, 0.1);
	/* The row under the pointer — the panel's identity moment. */
	--os-cn-row-fill: var(--os-ui-holo-fill);
	--os-cn-row-ink: var(--os-ui-holo-ink);
	/* Beam: the thread from the panel's underside down to the tile. */
	--os-cn-beam: color-mix(in srgb, var(--os-ui-accent-dim) 80%, transparent);
	--os-cn-radius: 14px;
	/*
	 * Above every window and above the dock. Two panels coexist while
	 * the pointer moves along the rail — one dismissing, one arriving
	 * — and the retiring one is painted one step below so it can
	 * never fade out on top of the menu being read.
	 */
	--os-cn-z: 2147483000;

	/*
	 * ---- Shell surfaces ----------------------------------------
	 *
	 * The panels, tab strips and washes that belong to the shell
	 * rather than to the component kit. Same Obsidian-on-Void
	 * composition; the selection wash is Pulse.
	 */
	--os-accent: #f252fc;
	--os-link: #ec9bff;
	--os-danger: #ff5a5a;
	--os-border: rgba(255, 251, 255, 0.1);
	--os-hover: rgba(255, 251, 255, 0.06);
	--os-selected: rgba(242, 82, 252, 0.18);
	--os-fg-muted: rgba(255, 251, 255, 0.7);
	/*
	 * Shell text. Undeclared before the brand and it had to be: it read
	 * light on the desk (tile labels over the wallpaper) and dark
	 * inside a window, and one value broke one of the two. The station
	 * settles the argument — every surface it owns is dark now, so
	 * Starlight is right in both places.
	 */
	--os-fg: #fffbff;
	/*
	 * "…on light" is a name from before the station, and it is now a
	 * misnomer worth keeping: every surface that reads it — the folder
	 * window, its preview pane, the upload HUD — was a light card and
	 * is Obsidian today. The token means "text on the surfaces this one
	 * names", and those surfaces are dark, so the text is Starlight.
	 * Renaming it is a public-API change for the sake of tidiness; the
	 * comment is cheaper and the behaviour is what matters.
	 */
	--os-fg-on-light: #fffbff;
	--os-panel-bg: #1a1721;
	--os-panel-header-bg: rgba(255, 251, 255, 0.04);
	--os-folder-window-bg: #1a1721;
	--os-my-wordpress-bg: #1a1721;
	--os-my-wordpress-fg: #fffbff;
	--os-my-wordpress-surface: rgba(255, 251, 255, 0.06);
	/*
	 * The entity hover card — the "thumb" that floats over a tile with
	 * a title, a featured image and an excerpt.
	 *
	 * It is NOT a dock chip, and it used to be painted as one.
	 * `--os-tooltip-bg` is a fixed dark lozenge on every theme by
	 * design — one line of text, always the same object, no reason to
	 * follow anything. The card borrowed it, so a light desktop theme
	 * summoned an Obsidian card over a white window, with a border in
	 * `--os-ui-border` and an excerpt in `--os-ui-fg-muted` that both
	 * followed the WINDOW instead. Three families, one surface.
	 *
	 * The card is window furniture: it belongs to the surface it was
	 * summoned from, so it follows the theme that paints that surface.
	 * Every value below is DERIVED rather than declared flat, which is
	 * what makes that true — a theme that names only
	 * `--os-my-wordpress-bg` (Legacy names exactly that, `#fff`) moves
	 * the card with the window for free, and a theme that wants the
	 * card alone names a `-card-` token.
	 *
	 * A card lands on the same colour as the window under it, so the
	 * border and the shadow are the whole reason it reads as a
	 * separate object. Neither is decoration here and neither may
	 * collapse into the background — which is exactly what the border
	 * did: `--os-ui-border` is #33303a on this palette, the SAME value
	 * `--os-tooltip-bg` had, so the card had no edge at all.
	 *
	 * The shadow is the one flat value in the family. A drop shadow is
	 * cast light, dark on a light theme and dark on a dark one, so
	 * there is no window token for it to follow; it is named so a
	 * theme can still reach it.
	 */
	--os-my-wordpress-card-bg: var(--os-my-wordpress-bg, #1a1721);
	--os-my-wordpress-card-fg: var(--os-my-wordpress-fg, #fffbff);
	--os-my-wordpress-card-fg-muted: var(--os-ui-fg-muted, #b3afb5);
	--os-my-wordpress-card-border: var(--os-ui-border, #33303a);
	--os-my-wordpress-card-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
	--os-my-wordpress-card-thumb-bg: var(
		--os-media-tile-bg,
		rgba(255, 251, 255, 0.05)
	);
	--os-my-wordpress-card-lock-bg: var(
		--os-ui-badge-danger-bg,
		rgba(255, 90, 90, 0.16)
	);
	--os-ai-panel-bg: rgba(26, 23, 33, 0.97);
	/*
	 * ---- Window tab strip ---------------------------------------
	 *
	 * A window with sub-pages reads as three surfaces stacked in
	 * depth, and the values below are that ramp: Astro on the focused
	 * title bar, VOID for the track the tabs sit in, then the page
	 * itself. The track is the darkest of the three on purpose — it
	 * has to stay a distinct band under BOTH title-bar states, and
	 * Obsidian would collapse into the unfocused bar (also Obsidian)
	 * exactly when a window has the least going on to distinguish it.
	 *
	 * The active tab is not a colour, it is the page arriving early.
	 * `chromeless.css` paints every admin page inside a window `#fff`
	 * whatever the admin colour scheme says, so the tab can name that
	 * white and the joint between the two is seamless. Retinting the
	 * tab means retinting the page it belongs to, and these two move
	 * together or the seam comes back.
	 *
	 * `-color-muted` is the second tone anything nested in an active
	 * tab needs — the external tab's detach and close chips. It is
	 * the only reason a "muted on light" value exists this far down a
	 * dark palette.
	 */
	--os-tabs-bg: #1a1721;
	/*
	 * The track on an UNFOCUSED window, which follows the title bar
	 * down to Void rather than holding Obsidian.
	 *
	 * A focused window already reads as two surfaces: title bar and
	 * track are the same Obsidian, and the tab lifts out of them. Let
	 * the track keep that Obsidian while the bar above it dims and
	 * the window reads as THREE, with the strip belonging to neither
	 * the chrome above nor the page below. Following the bar down
	 * keeps the count at two in both states.
	 *
	 * Both tracks read this: the shell's strip on an iframe window,
	 * and `<os-tabs variant="window">` inside a native one. The track
	 * is chrome in both cases, so it dims with the chrome; what keeps
	 * the tab attached to the content while that happens is the
	 * plate's fill, which wears the body's own colour rather than the
	 * track's.
	 *
	 * Note the asymmetry with the title bar's pair, which is
	 * deliberate: there, `--os-titlebar-bg` is the unfocused base and
	 * `-focused` is the modifier. Here the base is the LIT value,
	 * so a theme naming one track colour lands on the state where the
	 * track is doing the most work.
	 *
	 * A theme that sets only `--os-tabs-bg` (Legacy, and anything
	 * written before this token existed) resolves through it in both
	 * states and keeps its single strip colour.
	 */
	--os-tabs-bg-unfocused: #0c0b0f;
	--os-tabs-color: #b3afb5;
	--os-tabs-active-bg: #fff;
	--os-tabs-active-color: #0c0b0f;
	--os-tabs-active-color-muted: rgba(12, 11, 15, 0.6);
	/*
	 * The plate's own body: a frosted crown resolving to flat page
	 * white well before the joint.
	 *
	 * Every stop is OPAQUE, and that is a requirement rather than a
	 * preference. The face used to be translucent over a
	 * `backdrop-filter`, which promotes it to its own compositor layer
	 * and leaves a faint grey hairline where that layer is clipped —
	 * down the plate's sides and across its bottom, which is precisely
	 * where this design has a bright rail and needs everything else to
	 * be invisible.
	 *
	 * The last stop must also land above the joint (the bottom
	 * `--os-tabs-radius` of the plate), because the joint is painted
	 * in flat `--os-tabs-active-bg` and a body still tinted where the
	 * two meet draws a seam of its own. If you retune these stops,
	 * check the bottom edge first.
	 */
	--os-tabs-active-frost: linear-gradient(
		180deg,
		#f4eff9 0%,
		#ffffff 58%
	);
	/*
	 * The crown. Holomesh, masked away before the joint for the same
	 * reason. It is on exactly ONE tab at a time — that is what keeps
	 * the mesh an identity moment instead of wallpaper, and it is the
	 * rule the rest of the kit follows. Set it to `none` to get the
	 * plain frosted plate back.
	 */
	--os-tabs-active-crown: var(--os-mesh-holo);
	--os-tabs-active-crown-opacity: 0.3;
	/*
	 * The rail: one continuous line around the silhouette of
	 * page-plus-tab.
	 *
	 * This is the one place the accent is spent on the tab strip, and
	 * spending it here is what lets the tab itself stay uncoloured —
	 * so it is the accent the user picked, flat, and not Pulsemesh.
	 * A mesh here would be the accent everywhere else on the tab
	 * strip disagreeing with the one line that carries it. Written as
	 * a gradient rather than a colour because the rail is painted as
	 * a background-image through a mask.
	 *
	 * Set it to `none` for the plain frosted plate with no line.
	 */
	--os-tabs-rail: linear-gradient(
		var(--os-ui-accent, #f252fc),
		var(--os-ui-accent, #f252fc)
	);
	--os-tabs-rail-width: 2px;
	--os-media-tile-bg: rgba(255, 251, 255, 0.05);
	--os-media-visual-bg: rgba(255, 251, 255, 0.04);
	--os-skeleton-low: rgba(255, 251, 255, 0.05);
	--os-skeleton-high: rgba(255, 251, 255, 0.13);
	--os-drop-preview-bg: rgba(242, 82, 252, 0.1);
	--os-drop-preview-border: rgba(242, 82, 252, 0.55);

	/*
	 * File tiles — anywhere a `.os-file-tile` (or sibling
	 * canvas tile) renders. The defaults match the desk: Starlight
	 * labels over a dark sky, with a Pulse selection wash.
	 *
	 * Surfaces that are light in their own right override these in
	 * their own scope, e.g.:
	 *
	 *   .desktop-mode-folder-window {
	 *       --os-tile-fg: #1d2327;
	 *       --os-tile-hover-bg: rgba(0, 0, 0, 0.06);
	 *   }
	 *
	 * Plugin authors can override any of these in their own theme
	 * to retint tiles globally without touching the rules below.
	 */
	--os-tile-fg: var(--os-fg, #fffbff);
	--os-tile-fg-muted: rgba(255, 251, 255, 0.7);
	--os-tile-hover-bg: rgba(255, 251, 255, 0.12);
	--os-tile-selected-bg: rgba(242, 82, 252, 0.24);
	--os-tile-focus-ring: var(--wp-admin-theme-color, #f252fc);
	--os-tile-shortcut-bg: rgba(12, 11, 15, 0.7);
	--os-tile-shortcut-fg: #fffbff;
	--os-tile-shortcut-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);

	/*
	 * Label rendering tokens. The desk is dark at every wallpaper, so
	 * a soft drop-shadow boosts legibility against the artwork,
	 * regular weight, default antialiasing.
	 *
	 * Light-context surfaces (folder windows, My WordPress, any
	 * future window with a light body) rebind these in their own
	 * scope so tile labels render with the same crispness as the
	 * surrounding chrome — no per-surface duplicate rules.
	 */
	--os-tile-label-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
	--os-tile-label-weight: 400;
	--os-tile-label-smoothing: auto;
	--os-tile-label-color: var(--os-tile-fg);

	/*
	 * Admin bar — how many pixels of the bar stay on screen while it
	 * is parked off the top edge in `dynamic` mode. This is the
	 * VISIBLE seam only: small enough to read as "nothing is there".
	 * Themes may retune it; `0` makes the bar invisible until hovered.
	 */
	--os-admin-bar-peek: 4px;

	/*
	 * How far BELOW the parked bar the invisible reveal zone reaches.
	 * Decoupled from the peek on purpose — the seam wants to be a
	 * hairline, the pointer target wants to be forgiving, and tying
	 * them together forces one of the two to be wrong. The band the
	 * user actually has to hit is `peek + this`.
	 *
	 * CEILING: keep `peek + reveal-zone` comfortably under the admin
	 * bar's own height (32px, 46px below 783px). Past that, leaving
	 * the revealed bar drops the pointer straight back into the zone,
	 * which re-reveals it — an oscillation loop, not a wider target.
	 */
	--os-admin-bar-reveal-zone: 16px;

	/* Z-index layers */
	--os-z-base: 100;
	--os-z-dock: 200;
	--os-z-adminbar: 9991;
	--os-z-fullscreen: 99999;
	/* Window-link layer — above widgets (z 1), behind windows (z 100+). */
	--os-z-window-links: 50;
	/*
	 * Mio layer — above every window (windows run 100 + stack
	 * index) so the companion can perch on top of them, but below
	 * the dock (200) so it never covers navigation.
	 */
	--os-z-mio: 190;
	/*
	 * The desktop-name caption. Same reasoning as Mio, one step up:
	 * above every window so a maximized Dashboard cannot hide it, and
	 * above the companion, since a momentary message should not be
	 * the thing that gets perched on. Still under the dock (200).
	 */
	--os-z-desktop-name: 195;
	/*
	 * The notch — on the desk, under the windows (100 + stack index),
	 * above the window-link wires (50) so a tie never draws across the
	 * pill. It hangs over the strip a window's title bar occupies, and
	 * a pill floating above someone's window is the shell talking over
	 * the thing they are working in.
	 */
	--os-z-notch: 60;

	/*
	 * Window-link ties (relation splines between windows). The accent
	 * follows the admin color scheme. Solid colors only — the resting
	 * dimming is done with stroke-opacity/fill-opacity in
	 * window-links.css, NOT color-mix(): an unresolvable color-mix()
	 * inside a custom property makes the consuming declaration invalid
	 * at computed-value time, which turns SVG strokes invisible and
	 * marker fills black (the written rgba() fallback declaration is
	 * already gone from the cascade by then). Overridable per scheme
	 * or by plugins/themes without touching the renderer.
	 */
	--os-window-link-color: var(--wp-admin-theme-color, #f252fc);
	--os-window-link-color-active: var(--wp-admin-theme-color, #f252fc);
	--os-window-link-width: 1.5px;
	--os-window-link-accent: var(--wp-admin-theme-color, #f252fc);
	/*
	 * Halo behind related windows (`.os-window--linked`).
	 * A literal rgba of Pulse rather than a color-mix() of the accent —
	 * same no-color-mix rationale as above: if this token failed to
	 * resolve, the whole box-shadow declaration (elevation shadow
	 * included) would go invalid. Schemes/plugins that retint the
	 * accent should override this too.
	 */
	--os-window-link-glow: rgba(242, 82, 252, 0.45);
}

/*
 * ---- Iframe windows keep a light body -----------------------------
 *
 * A window hosting a real `wp-admin` page is not a station surface — it
 * is a frame around someone else's document, and that document is
 * light. The reveal surface is what covers such a window while it
 * loads, so a dark one wiping back to a white page is a flash in the
 * wrong direction.
 *
 * The window's own background is deliberately NOT overridden here, and
 * that was learned the hard way: `chromeless.css` paints the iframe's
 * canvas, so nothing of the window shows through the page anyway —
 * while the tab strip above it carries a 16px `mask-image` fade at
 * each end (the "scroll for more tabs" affordance). A mask makes the
 * element's own background transparent, so a white window behind a
 * dark strip turned that invisible cosmetic into a white gradient
 * bleeding into the chrome. The strip needs the station's colour
 * behind it; the page needs its own; those are two different surfaces
 * and only one of them belongs to the window element.
 *
 * Native windows — the ones the station draws itself — are excluded
 * and keep the Obsidian reveal from the palette above.
 */
.os-window:not( .os-window--native ) {
	--os-window-reveal-surface: #fff;
}

/*
 * ---- The WordPress admin colour scheme no longer tints the shell ----
 *
 * There used to be nine blocks here, one per admin colour scheme, each
 * setting `--wp-admin-theme-color` on
 * `.os-shell[data-os-scheme="…"]`. They are gone,
 * and their removal is a bug fix as much as a rebrand.
 *
 * OS Settings → Appearance writes the user's chosen accent as an inline
 * style on `<html>` — deliberately, so it also reaches the WordPress
 * admin bar, which is a SIBLING of `#os-shell`. But those
 * blocks matched the shell root, a descendant of `<html>`, and for
 * custom properties the nearest ancestor with a value wins no matter
 * what the specificity of the rule that set it was. So inside the shell
 * the scheme always beat the picker: choosing Rose, or Pulse, changed
 * the admin bar and nothing else. Every focus ring, tab underline, sort
 * arrow and selection wash kept the colour of the admin scheme.
 *
 * With the accent picker defaulting to Pulse there is nothing left for
 * these blocks to express: the station has one identity colour, and a
 * user who wants their admin scheme's hue picks it in the same panel.
 * The `data-os-scheme` attribute is still stamped on the
 * shell root for plugins and themes that key off it.
 */

```
