# desktop-mode/1.0.1/assets/css/desktop.css

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

- Page: https://pluginprobe.com/plugins/desktop-mode/1.0.1/code/assets/css/desktop.css
- Raw: https://pluginprobe.com/plugins/desktop-mode/1.0.1/raw/assets/css/desktop.css
- Modified: 2026-08-07T20:39:04+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.0.1/code/assets/css/desktop.css#L10-L20`.

```css
/**
 * OpenStation — Shell Layout.
 *
 * When OpenStation is active, the classic admin chrome still renders
 * server-side (for hook compatibility) but is hidden with CSS. The
 * plugin-injected shell floats above it, positioned just below the
 * fixed admin bar, covering the rest of the viewport.
 *
 * @since 0.1.0
 */

/*
 * Solid backdrop behind the shell. Reads the backstop token rather
 * than a literal so it moves with the desk: this is the colour a
 * dropped frame shows, and it has to be the same one the shell paints
 * or the seam is visible at exactly the wrong moment.
 */
body.os-active {
	background: var( --os-backstop, #1d2327 );
	overflow: hidden;
}

/*
 * Text selection.
 *
 * The shell had no ::selection rule at all, so every selection in it
 * was the browser default — a colour chosen for a white page, landing
 * on a near-black one. In Chrome that is a dark translucent blue over
 * Obsidian, which is roughly 1.3:1 against the surface and leaves the
 * selected text at a contrast the UA never checked, because the UA
 * assumed a light background. Dragging across a paragraph produced no
 * visible change at all on some displays.
 *
 * Both halves are set on purpose. `background` alone is the common
 * half-fix: the UA then keeps its own selected-text colour, which is
 * frequently forced to black and lands on this violet at ~2:1.
 *
 * Scoped to the shell document. `variables.css` is a dependency of
 * `chromeless.css` and so loads inside every iframe window, but this
 * file does not — a wp-admin page in a window keeps the selection
 * colour it has outside one, which is the same promise the palette
 * makes about everything else.
 *
 * `::-moz-selection` is a separate rule rather than a second selector
 * on this one: an unrecognised pseudo-element invalidates the entire
 * selector list, so pairing them would leave Chrome unstyled too.
 */
body.os-active ::selection {
	background: var( --os-ui-selection-bg, rgba( 159, 152, 255, 0.6 ) );
	color: var( --os-ui-selection-fg, #fffbff );
}

body.os-active ::-moz-selection {
	background: var( --os-ui-selection-bg, rgba( 159, 152, 255, 0.6 ) );
	color: var( --os-ui-selection-fg, #fffbff );
}

/*
 * Cursor policy — default arrow everywhere in OpenStation.
 *
 * Per user preference (since 0.20.0): no hand/pointer/grab/grabbing/
 * copy/no-drop cursors on icons, tiles, dock items, dock peek,
 * window chrome, or any other clickable surface. The default arrow
 * is the only "neutral" cursor we render.
 *
 * Exceptions:
 *   - Window + widget resize handles keep their direction-specific
 *     cursors (ns-resize, ew-resize, nesw-resize, nwse-resize) —
 *     they're a functional cue, not a hover affordance, and removing
 *     them would make corner-grabs guesswork.
 *   - Text inputs / textareas / contenteditable surfaces keep the
 *     browser-default I-beam.
 *   - The AI assistant's inline "AI Settings" recovery link keeps a
 *     pointer so it reads as a genuine link (rule lives in
 *     ai-assistant.css).
 *
 * `!important` is needed because the specific `cursor: pointer` and
 * drag-state declarations elsewhere in the codebase have the same or
 * higher specificity. The resize-handle exceptions use `!important`
 * for the same reason.
 *
 * Shadow-DOM web components (`os-*`) define their own cursors
 * inside their shadow trees; those internal rules win regardless of
 * what we set on the host. Anything visible on the desktop shell
 * (light DOM) is covered by this policy.
 */
body.os-active,
body.os-active * {
	cursor: default !important;
}

body.os-active input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]),
body.os-active textarea,
body.os-active [contenteditable=""],
body.os-active [contenteditable="true"] {
	cursor: text !important;
}

body.os-active .os-window__resize-handle--se,
body.os-active .os-window__resize-handle--nw,
body.os-active .os-widgets__resize--se,
body.os-active .os-widgets__resize--nw {
	cursor: nwse-resize !important;
}

body.os-active .os-window__resize-handle--sw,
body.os-active .os-window__resize-handle--ne,
body.os-active .os-widgets__resize--sw,
body.os-active .os-widgets__resize--ne {
	cursor: nesw-resize !important;
}

body.os-active .os-widgets__resize--n,
body.os-active .os-widgets__resize--s {
	cursor: ns-resize !important;
}

body.os-active .os-widgets__resize--e,
body.os-active .os-widgets__resize--w {
	cursor: ew-resize !important;
}

/*
 * Hide the classic admin chrome while the shell is active.
 *
 * The markup still exists in the DOM so server-side action hooks that
 * target these regions (admin_notices, in_admin_footer, etc.) continue
 * to fire — we just take them out of the visual flow.
 */
body.os-active #adminmenuwrap,
body.os-active #adminmenuback,
body.os-active #wpbody,
body.os-active #wpfooter,
body.os-active .wp-responsive-toggle,
body.os-active #collapse-menu {
	display: none !important;
}

/*
 * The admin bar is the ONLY escape hatch in OpenStation — it hosts the
 * "Switch to Classic Admin" toggle. Any plugin CSS, Gutenberg state,
 * user preference, or stray `display:none` that hides it would strand
 * the user inside the shell with no way out. Hard-pin it visible.
 *
 * The exception: while a window is in immersive fullscreen, we *want*
 * the bar hidden (macOS convention). That rule lives below and wins on
 * specificity because it also targets #wpadminbar under a body class.
 */
body.os-active #wpadminbar {
	display: block !important;
	visibility: visible !important;
	opacity: 1 !important;
	position: fixed !important;
	inset-block-start: 0 !important;
	inset-inline-start: 0 !important;
	inset-inline-end: 0 !important;
}

/*
 * Admin-bar presentation modes — OS Settings → Appearance → Admin bar,
 * persisted as `adminBarMode` and emitted as a
 * `os-admin-bar-<mode>` body class by PHP on first paint and
 * re-written by the shell's apply pass on every change.
 *
 * `static` is the absence of rules: the pin above stands and the shell
 * starts below the bar. The two modes below both take the bar out of
 * the shell's way, so the shell reclaims the full viewport in each.
 *
 * Every selector here carries two body classes, so it outranks the
 * one-class pin rule above without needing to escalate anything.
 */

/*
 * Dynamic — the bar parks off the top edge leaving a peek strip, and
 * slides back in when the pointer reaches the top of the viewport or
 * something inside it takes keyboard focus. The classic Windows
 * auto-hide taskbar, and the reason `transform` is the right tool: a
 * transformed element keeps hit-testing at its PAINTED position, so
 * the parked remainder stops catching the pointer the moment it goes
 * off-screen. What DOES catch it is the peek strip plus the reveal
 * zone added below.
 */
body.os-active.os-admin-bar-dynamic #wpadminbar {
	transform: translateY(
		calc(-100% + var(--os-admin-bar-peek, 4px))
	);
	transition: transform 180ms ease;
}

body.os-active.os-admin-bar-dynamic #wpadminbar:hover,
body.os-active.os-admin-bar-dynamic #wpadminbar:focus-within,
body.os-active.os-admin-bar-dynamic #wpadminbar:active {
	transform: translateY(0);
}

/*
 * Reveal zone — an invisible extension of the bar's hit area, hanging
 * below the parked peek strip.
 *
 * Hovering a pseudo-element counts as hovering its originating
 * element, so this widens what `:hover` above responds to WITHOUT
 * widening the visible seam. The two wants are genuinely different:
 * the seam should be a hairline, the target should be forgiving, and
 * `--os-admin-bar-peek` alone can't be both.
 *
 * Core ships no bare `#wpadminbar::before/::after` rule, so this
 * claims the slot rather than fighting one.
 */
body.os-active.os-admin-bar-dynamic #wpadminbar::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-start: 100%;
	height: var(--os-admin-bar-reveal-zone, 16px);
}

/*
 * ...but only while the bar is parked. Left standing once the bar is
 * out, the zone would hang over the top of the desktop and swallow
 * clicks on whatever window is up there.
 *
 * Collapsing it is also what keeps the reveal stable: the pointer
 * that tripped the zone is, post-reveal, inside the bar's real box,
 * so `:hover` holds without the zone's help.
 */
body.os-active.os-admin-bar-dynamic #wpadminbar:hover::after,
body.os-active.os-admin-bar-dynamic #wpadminbar:focus-within::after,
body.os-active.os-admin-bar-dynamic #wpadminbar:active::after {
	height: 0;
}

/*
 * Coarse pointers have no hover state and a much fatter contact patch,
 * so a 4px seam is neither discoverable nor tappable. Widen both the
 * seam and the zone — `:active` above is what actually reveals the bar
 * there. Kept under the 32px bar height, same ceiling as the default.
 */
@media (hover: none) {
	body.os-active.os-admin-bar-dynamic #wpadminbar {
		--os-admin-bar-peek: 10px;
		--os-admin-bar-reveal-zone: 20px;
	}
}

@media (prefers-reduced-motion: reduce) {
	body.os-active.os-admin-bar-dynamic #wpadminbar {
		transition: none;
	}
}

/*
 * Hidden — the bar is gone. This removes the "Switch to Classic Admin"
 * toggle, which is why it is safe: the dock's core rail always carries
 * an "Exit OpenStation" tile (src/exit-os.ts) hitting the
 * same endpoint. Do not add a mode that removes both.
 */
body.os-active.os-admin-bar-hidden #wpadminbar {
	display: none !important;
}

/*
 * Both non-static modes: the shell owns the whole viewport. Two body
 * classes beat the `.os-shell` base rule AND its <783px
 * media-query variant on specificity, so source order doesn't matter.
 */
body.os-active.os-admin-bar-dynamic .os-shell,
body.os-active.os-admin-bar-hidden .os-shell {
	inset-block-start: 0;
}

/*
 * Core reserves the bar's height as `padding-top` on <html>. In both
 * non-static modes the bar is out of flow (or absent) and the shell
 * covers the viewport anyway, so the reserve is dead space — release
 * it so any classic content behind the shell isn't pushed down by a
 * bar that isn't sitting there. Same idiom as chromeless.css.
 */
html.wp-toolbar:has(body.os-admin-bar-dynamic),
html.wp-toolbar:has(body.os-admin-bar-hidden) {
	padding-top: 0 !important;
}

/*
 * Align the admin-bar W logo with the dock below it.
 *
 * Without this rule the admin bar's leftmost item (the WordPress W)
 * renders at its Core-dictated ~44 px width, while the dock below it
 * can be 48, 56, or 72 px wide depending on the user's OS-Settings
 * preference. The misalignment reads as "two disconnected left
 * regions" — visually scruffy on an otherwise tight shell.
 *
 * We size the logo's <li> to match the dock's width and center the
 * W inside, so the two columns share an axis. `--os-dock-width`
 * is now set on :root (see settings.ts → apply()) so it reaches
 * siblings of #os-shell like the admin bar.
 */
body.os-active #wpadminbar #wp-admin-bar-wp-logo {
	width: var( --os-dock-width, 56px );
}

body.os-active #wpadminbar #wp-admin-bar-wp-logo > .ab-item {
	display: flex !important;
	align-items: center;
	justify-content: center;
	padding-inline: 0 !important;
}

/*
 * The W glyph is painted via a ::before pseudo on `.ab-icon`. Center
 * it within the flex cell so varying dock widths don't leave it
 * visually offset.
 */
body.os-active #wpadminbar #wp-admin-bar-wp-logo > .ab-item > .ab-icon {
	margin: 0;
}

/*
 * Classic wpcontent carries a `margin-left: 160px` for the sidebar. With
 * the sidebar hidden we let wpcontent fall back to flush — the shell is
 * fixed-positioned anyway so this doesn't change the shell's layout, it
 * just prevents a phantom sidebar gutter in any stray visible content.
 */
body.os-active #wpcontent {
	margin-inline-start: 0;
	padding-inline-start: 0;
}

/*
 * Desktop shell — fills the viewport below the admin bar.
 * Uses position: fixed so it's unaffected by any containing-block
 * quirks in #wpwrap/#wpcontent.
 */
.os-shell {
	position: fixed;
	inset-block-start: var(--wp-admin--admin-bar--height, 32px);
	inset-inline-start: 0;
	inset-inline-end: 0;
	inset-block-end: 0;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	z-index: var(--os-z-base);
	/*
	 * Opaque floor. The wallpaper paints over this and is what anyone
	 * actually sees — but the wallpaper is one layer among several,
	 * and behind this shell is the classic admin page, which is WHITE.
	 * Without a background here, any frame in which the wallpaper
	 * doesn't paint flashes that white through the whole viewport.
	 * See `--os-backstop`.
	 */
	background: var(--os-backstop, #1d2327);
	/*
	 * Shell typeface. Every piece of chrome that doesn't set its own
	 * face inherits from here — dock labels, desktop icon labels,
	 * widgets, the overview. Windows override with `--os-ui-font` on
	 * their body, so a theme can run a display face on the desk and a
	 * text face inside windows. `inherit` is the no-theme value, i.e.
	 * identical to not declaring the property at all.
	 */
	font-family: var(--os-font, inherit);
}

/*
 * Wallpaper layer — first child of the shell, sits behind the dock
 * and desktop area so a translucent dock shows through to the
 * background (macOS pattern). CSS wallpapers paint via the
 * `--os-bg` custom property; canvas wallpapers mount their
 * own DOM (typically a <canvas>) into this element via JS.
 *
 * z-index: 0 keeps it below the shell body flex row which has no
 * explicit z-index of its own but establishes its own local
 * stacking context via `position: relative`, so the dock + windows
 * always render above the wallpaper regardless of DOM order.
 */
.os-wallpaper {
	position: absolute;
	inset: 0;
	z-index: 0;
	background: var(--os-bg);
	overflow: hidden;
	pointer-events: none;
}

/*
 * Mio layer — the desk companion.
 *
 * A sibling of the wallpaper inside the shell, but at the far end of
 * the stack: Mio paints ABOVE windows (it perches on them)
 * and below the dock (it must never cover navigation). Created on
 * demand by `src/mio/controller.ts`. A shell whose user has never
 * switched Mio on has no extra element at all; switching it off
 * hides this one rather than removing it, because the instance is
 * parked (WebGL context intact) rather than destroyed — see the
 * `parked` field there.
 *
 * The layer and its <canvas> are permanently inert. Making a
 * full-shell canvas interactive — even conditionally, from a
 * per-frame hit test — would swallow clicks meant for the window
 * underneath. Only the small round `__handle` inside it takes
 * pointer events, and it rides on the blob.
 */
.os-mio {
	position: absolute;
	inset: 0;
	z-index: var(--os-z-mio);
	overflow: hidden;
	pointer-events: none;
}

.os-mio__handle {
	position: absolute;
	/*
	 * PHYSICAL `top` / `left`, deliberately — the one place in this
	 * sheet that isn't logical. The handle is positioned every frame
	 * by a `translate3d()` carrying the body's canvas coordinates,
	 * and `transform` is always physical. Anchoring with
	 * `inset-inline-start` would flip the origin to the right edge
	 * under RTL while the translation kept pushing rightwards, and
	 * the handle would walk off screen.
	 */
	top: 0;
	left: 0;
	border-radius: 50%;
	pointer-events: auto;
	cursor: grab;
	/* Promote to its own layer: the handle is re-positioned via
	 * `transform` on every animation frame. */
	will-change: transform;
	touch-action: none;
}

.os-mio__handle.is-dragging {
	cursor: grabbing;
}

/*
 * "Make it yours" panel — a live-preview dialog, not a modal.
 *
 * `<os-modal>` is built for the usual case: dim the page, blur it
 * slightly, put the decision in front of everything. Every one of
 * those defaults is wrong here, because the thing being edited is
 * ON the page and the whole point is watching it change.
 *
 *   - No scrim and no `backdrop-filter`, or the companion the sliders
 *     are driving is a blurred grey smudge behind them.
 *   - `pointer-events: none` on the scrim so the desk stays live: Mio
 *     can still be picked up and thrown while the panel is open, and a
 *     click on the wallpaper doesn't dismiss the panel mid-adjustment.
 *     `::part(dialog)` puts them back for the box itself.
 *   - Parked against the inline end rather than centred, so it isn't
 *     sitting on top of the subject.
 *
 * Mio never treats the panel as an obstacle: the collision set comes
 * from `getWallpaperSurfaces()`, which seeds windows, the shell floor,
 * docks and widget cards — a dialog on `document.body` is none of
 * those, so there is nothing to bump into.
 */
.os-mio-panel {
	background: transparent;
	background-image: none;
	backdrop-filter: none;
	pointer-events: none;
	justify-content: flex-end;
	padding-inline-end: clamp(16px, 4vw, 64px);
}

/*
 * Both halves are required, and the second one is easy to miss.
 *
 * `::part(dialog)` reaches the box in the modal's SHADOW tree — its
 * header, title and close button inherit from there. But the controls
 * are slotted LIGHT-DOM children of `<os-modal>`, and slotted content
 * inherits from its light-DOM parent (the host) rather than from the
 * shadow ancestor it is projected into. So the host's
 * `pointer-events: none` reaches them regardless of what the part
 * says, and without this second rule every slider and button in the
 * panel is inert while looking perfectly normal.
 */
.os-mio-panel::part(dialog),
.os-mio-panel > * {
	pointer-events: auto;
}

/*
 * Overview (Exposé) hides the desk furniture so the window grid
 * reads cleanly — Mio goes with the widgets and the notes.
 * `:has()` rather than a descendant selector because Mio
 * layer is a sibling of `.os-shell__body`, not a child of
 * the desktop area.
 */
.os-mio {
	transition: opacity 160ms ease;
}

.os-shell:has( .os-area--overview ) .os-mio {
	opacity: 0;
}

/*
 * Reduced motion is handled in the simulation, not here: the runtime
 * zeroes the idle float and the hue drift so Mio holds still
 * until the user actually interacts with it. Hiding it would be
 * wrong — the user switched it on deliberately.
 */

/*
 * Desktop-theme wallpaper texture (DESKTOP slot).
 *
 * On a `::before` rather than this element's own background, because
 * `--os-bg` is set by the wallpaper picker and can be a
 * gradient — i.e. a background-IMAGE. Overriding `background-image`
 * here would silently erase every gradient wallpaper the moment a
 * theme was active. The pseudo-element layers instead of replacing.
 *
 * Resulting precedence, bottom to top:
 *   CSS wallpaper (`--os-bg`)
 *   → theme DESKTOP texture (this rule)
 *   → canvas wallpapers, which mount as CHILDREN of the layer and
 *     therefore paint above both.
 *
 * A theme that wants the user's wallpaper to remain visible ships a
 * texture with transparency; an opaque one takes the desk over.
 * With no theme active the image is `none` and this paints nothing.
 */
.os-wallpaper::before {
	content: "";
	position: absolute;
	inset: 0;
	pointer-events: none;
	background-image: var(--os-desktop-image, none);
	background-repeat: var(--os-desktop-image-repeat, repeat);
	background-size: var(--os-desktop-image-size, auto);
	background-position: var(--os-desktop-image-position, center);
}

/*
 * Canvas children fill the layer and ignore pointer events by
 * default — a wallpaper is presentational chrome, not an interactive
 * element. Plugins that genuinely need a clickable wallpaper can
 * override `pointer-events` on their own mounted node.
 */
.os-wallpaper > canvas,
.os-wallpaper > * {
	width: 100%;
	height: 100%;
	display: block;
}

/* Below 783px WP collapses the admin bar to 46px. */
@media screen and (max-width: 782px) {
	.os-shell {
		inset-block-start: var(--wp-admin--admin-bar--height, 46px);
	}
}

/*
 * Immersive fullscreen: when any window is in fullscreen state, hide the
 * admin bar and let the shell cover the full viewport. This keeps the
 * fullscreen window visually above all chrome without fighting the shell's
 * stacking context (a fullscreen window lives inside the shell, so its
 * z-index is always bounded by the shell's — moving it out of the shell
 * to raise it would break focus, drag, and session snapshotting).
 *
 * The window's title bar remains visible because the window itself is
 * position: fixed at top: 0, so the focus/exit-fullscreen button is
 * still reachable.
 */
body.os-has-fullscreen-window #wpadminbar {
	display: none !important;
}

body.os-has-fullscreen-window .os-shell {
	inset-block-start: 0;
}

/* Shell body: contains dock + desktop area side by side. */
.os-shell__body {
	flex: 1;
	display: flex;
	overflow: hidden;
	position: relative;
}

/*
 * Desktop area — where windows float. Background is now transparent
 * because the wallpaper renders in the shell-level layer behind
 * both this area and the dock. Stacking context via position: relative
 * ensures children (windows) sit above the wallpaper.
 *
 * `padding-bottom` reserves room for the floating bottom dock pill
 * that every layout (Classic, Unified, Spatial) puts at the bottom
 * — 40px icons + 16px padding + 12px gap below + 8px breathing room.
 */
.os-area {
	flex: 1;
	position: relative;
	overflow: hidden;
	background: transparent;
	padding-bottom: 80px;
	transition: opacity 180ms ease;
}

/*
 * Boot reveal gate.
 *
 * On F5 the shell paints in stages: the dispatcher's
 * `repaintIcons()` lands the server-rendered wallpaper icons
 * synchronously from `openStationConfig.desktopIcons`, then the
 * files-layer mounts and momentarily renders an empty bucket while
 * REST `listPlacements(0)` is in flight, then folders/posts/links
 * slot in on the next paint. The user reads that as "plugins show
 * first, then everything blinks and the rest arrives."
 *
 * The `--booting` modifier is applied BY PHP (see
 * `includes/render/shell.php`) on the initial HTML so the area
 * starts invisible BEFORE any JS runs — otherwise the dispatcher
 * gets to paint the wallpaper icons into a visible area first and
 * the user sees them flash before our gate kicks in.
 *
 * `desktop.ts` removes the class inside a `requestAnimationFrame`
 * after the root files-layer's `hydrated` promise resolves (with a
 * 2 s JS safety timeout). The opacity transition declared on
 * `.os-area` smooths the reveal.
 *
 * The fallback animation is a resilience layer for the case where
 * the shell JS never runs (network error mid-load, ad-blocker
 * tearing down a bundle, …). The animation holds the area
 * invisible for 2.7 s then flips it to opacity 1; the
 * `animation-fill-mode: forwards` keeps the revealed state held
 * after the animation ends. In the normal case JS removes the
 * class long before 2.7 s and the animation is discarded.
 *
 * `pointer-events: none` while booting prevents a stray click on
 * the still-hidden surface from triggering Show-Desktop or opening
 * a tile that's about to reposition once REST returns.
 *
 * @since 0.18.x
 */
.os-area--booting {
	opacity: 0;
	pointer-events: none;
	animation: os-area-boot-fallback 3s forwards;
}

@keyframes os-area-boot-fallback {
	0%,
	90% {
		opacity: 0;
		pointer-events: none;
	}
	100% {
		opacity: 1;
		pointer-events: auto;
	}
}

/* ---------------------------------------------------------------
 * Desktop icons — wallpaper shortcut tiles registered via the
 * `wp_register_desktop_icon()` PHP API. Rendered inside the
 * desktop area as the first child after the wallpaper, so they
 * paint above the wallpaper but beneath any window.
 *
 * Grid layout is fixed-width columns; tiles wrap onto multiple
 * rows when the desktop is wide enough to host more than one
 * column's worth. `pointer-events: none` on the container keeps
 * the background clickable (for "minimize all" on wallpaper
 * click) while individual tiles re-enable pointer events so they
 * remain clickable.
 * --------------------------------------------------------------- */

.os-icons {
	position: absolute;
	inset-block-start: 16px;
	inset-block-end: 16px;
	inset-inline-start: 16px;
	inset-inline-end: 16px;
	display: grid;
	/*
	 * Column-major flow: icons fill the first column top-to-bottom,
	 * then start the next column. Matches the macOS / Finder
	 * convention. `grid-auto-flow: column` plus `auto-fill` rows
	 * (sized to the container height) yields exactly that layout.
	 *
	 * All four insets are pinned so the container has a definite
	 * height — `repeat(auto-fill, 96px)` needs that to compute the
	 * row count. 96px is one icon button (48px image + 6px gap +
	 * up-to-two-line label + 8px vertical padding); a slightly
	 * generous figure leaves room for descenders without clipping.
	 */
	grid-auto-flow: column;
	grid-template-rows: repeat(auto-fill, 96px);
	grid-auto-columns: 88px;
	align-content: start;
	justify-content: start;
	gap: 12px;
	z-index: 1;
	pointer-events: none;
	/*
	 * Spill protection: if a user ever pins more icons than fit
	 * vertically, the grid scrolls horizontally so the unreachable
	 * tiles can be reached without clipping the visible ones.
	 * Individual tiles re-enable pointer events so they stay
	 * clickable inside the scroll surface.
	 */
	overflow: auto;
}

.os-icon {
	pointer-events: auto;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: flex-start;
	gap: 6px;
	width: 88px;
	padding: 8px 4px;
	border: 0;
	background-color: transparent;
	/*
	 * ICON_TILE texture slot — a plate behind each wallpaper icon.
	 * Painted under the icon image and its label, so a theme can give
	 * desktop shortcuts a physical tile the way a phone home screen
	 * does. Unset = `none` = the transparent tile we always had.
	 */
	background-image: var(--os-tile-image, none);
	background-repeat: var(--os-tile-image-repeat, no-repeat);
	background-size: var(--os-tile-image-size, auto);
	background-position: var(--os-tile-image-position, center);
	color: var(--os-fg, #fff);
	cursor: pointer;
	border-radius: 8px;
	transition: background-color 0.15s ease;
	/* Positioning context for `.os-icon__badge`. */
	position: relative;
}

/*
 * Icon badge — symmetric to `.os-dock__badge` but anchored
 * to the wallpaper-icon image. Same gradient, same ring, slightly
 * larger so it reads at the longer wallpaper viewing distance.
 * Painted by `wp.os.icons.setBadge( id, count )` — see
 * `src/desktop-icons.ts` for the imperative API and
 * `src/recycle-bin/badge.ts` for the canonical multi-rail
 * consumer. The class name is part of the stable contract; the
 * positioning, color, and shadow are not — themes or future
 * shell builds may restyle freely.
 */
.os-icon__badge {
	position: absolute;
	top: 4px;
	inset-inline-end: 14px;
	/* Same derivation as the dock badge, sized off the wallpaper icon
	 * rather than the dock icon. The default resolves to 18px / 11px —
	 * exactly the old values. */
	--os-icon-badge-computed-size: var(
		--os-icon-badge-size,
		calc( var( --os-icon-image-size, 48px ) * 0.375 )
	);
	min-width: var( --os-icon-badge-computed-size );
	height: var( --os-icon-badge-computed-size );
	padding: 0 var(
		--os-icon-badge-padding,
		calc( var( --os-icon-badge-computed-size ) * 0.28 )
	);
	box-sizing: border-box;
	border-radius: 999px;
	background: var(
		--os-icon-badge-bg,
		linear-gradient( 180deg, #ff5a5a 0%, var( --os-ui-danger, #d63638 ) 100% )
	);
	color: var( --os-icon-badge-fg, var( --os-ui-fg-on-accent, #fff ) );
	display: inline-flex;
	align-items: center;
	justify-content: center;
	font-size: var(
		--os-icon-badge-font-size,
		calc( var( --os-icon-badge-computed-size ) * 0.611 )
	);
	font-weight: var( --os-icon-badge-font-weight, 700 );
	line-height: 1;
	font-variant-numeric: tabular-nums;
	box-shadow:
		0 0 0 1.5px rgba( 0, 0, 0, 0.4 ),
		0 1px 3px rgba( 0, 0, 0, 0.35 );
	pointer-events: none;
}

.os-icon:hover,
.os-icon:focus-visible {
	/* background-COLOR, not the shorthand: the shorthand would reset
	 * the ICON_TILE texture declared above, so a themed tile would
	 * vanish on hover. */
	background-color: rgba(255, 255, 255, 0.12);
	outline: none;
}

.os-icon:focus-visible {
	box-shadow: 0 0 0 2px var(--wp-admin-theme-color, #2271b1);
}

.os-icon__image {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* Tokenized so a theme can scale wallpaper icons — and so the
	 * badge above has something to derive its own size from. */
	width: var( --os-icon-image-size, 48px );
	height: var( --os-icon-image-size, 48px );
	font-size: calc( var( --os-icon-image-size, 48px ) * 0.667 );
	line-height: 1;
}

.os-icon__image img {
	max-width: 48px;
	max-height: 48px;
	object-fit: contain;
}

.os-icon__label {
	font-size: 12px;
	text-align: center;
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
	line-height: 1.2;
	word-break: break-word;
}

/* ---------------------------------------------------------------
 * Sticky notes — Gutenberg `wp_guideline` artifacts tagged with the
 * sticky guideline type. Notes sit above wallpaper icons/widgets and
 * below windows, so they behave like desktop objects rather than app
 * chrome.
 * --------------------------------------------------------------- */

.os-sticky-notes {
	position: absolute;
	inset: 0;
	z-index: 2;
	pointer-events: none;
	transition:
		opacity 0.22s ease,
		transform 0.28s cubic-bezier( 0.2, 0, 0.2, 1 );
}

.os-area--overview .os-sticky-notes {
	opacity: 0;
	transform: translateY( 12px );
	pointer-events: none;
}

.os-sticky-note {
	position: absolute;
	display: flex;
	flex-direction: column;
	pointer-events: auto;
	background: #fff09a;
	color: #2a2513;
	border: 1px solid rgba( 84, 67, 0, 0.24 );
	border-radius: 4px;
	box-shadow:
		0 12px 28px rgba( 0, 0, 0, 0.22 ),
		0 2px 5px rgba( 0, 0, 0, 0.16 ),
		inset 0 1px 0 rgba( 255, 255, 255, 0.55 );
	overflow: hidden;
	resize: both;
}

.os-sticky-note--dragging {
	opacity: 0.92;
	resize: none;
	user-select: none;
}

.os-sticky-note__header {
	display: flex;
	align-items: center;
	gap: 5px;
	min-height: 30px;
	padding: 4px 6px 4px 8px;
	background: rgba( 82, 64, 0, 0.06 );
	border-bottom: 1px solid rgba( 82, 64, 0, 0.14 );
	cursor: grab;
	touch-action: none;
	user-select: none;
}

.os-sticky-note--dragging .os-sticky-note__header {
	cursor: grabbing;
}

.os-sticky-note__grip {
	flex: 0 0 auto;
	width: 8px;
	height: 14px;
	background-image: radial-gradient(
		circle,
		rgba( 60, 48, 0, 0.5 ) 1.2px,
		transparent 1.5px
	);
	background-size: 4px 4px;
	background-position: 0 1px;
	background-repeat: space;
	opacity: 0.42;
}

.os-sticky-note__title {
	flex: 1;
	min-width: 0;
	font-size: 11px;
	font-weight: 650;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.os-sticky-note__status {
	flex: 0 0 auto;
}

.os-sticky-note os-window-button {
	--os-ui-btn-color: rgba( 42, 37, 19, 0.7 );
	--os-ui-btn-color-hover: #2a2513;
	--os-ui-btn-bg-hover: rgba( 42, 37, 19, 0.1 );
	--os-ui-btn-bg-active: rgba( 42, 37, 19, 0.16 );
	--os-ui-btn-danger-hover: rgba( 214, 54, 56, 0.18 );
	--os-ui-btn-outline: var( --wp-admin-theme-color, #2271b1 );
}

.os-sticky-note__open.is-disabled {
	opacity: 0.34;
	pointer-events: none;
}

.os-sticky-note__editor {
	flex: 1;
	min-height: 0;
	--os-window-bg: transparent;
	--os-ui-border: transparent;
	--os-ui-fg: #2a2513;
	--os-ui-fg-muted: rgba( 42, 37, 19, 0.62 );
	font-family:
		-apple-system,
		BlinkMacSystemFont,
		"Segoe UI",
		sans-serif;
}

.os-sticky-note__editor::part(textarea) {
	height: 100%;
	min-height: 100%;
	padding: 10px 12px 12px;
	background: transparent;
	border: 0;
	border-radius: 0;
	box-shadow: none;
	color: #2a2513;
	font-size: 13px;
	line-height: 1.4;
	resize: none;
}

.os-sticky-note__editor::part(textarea):hover,
.os-sticky-note__editor::part(textarea):focus-visible {
	border-color: transparent;
	box-shadow: none;
}

/* ---------------------------------------------------------------
 * Widgets column — right-edge glass strip that paints above the
 * wallpaper but beneath windows. Hosts stacked widget cards and a
 * trailing "Add widget" tile. Interactive only on its own cards +
 * the add tile; every other pixel is pointer-transparent so window
 * drag / resize / click-through behaviour is unaffected.
 * --------------------------------------------------------------- */

.os-widgets {
	border-radius: 14px;
	position: absolute;
	top: 16px;
	bottom: 16px;
	inset-inline-end: 16px;
	width: 320px;
	display: flex;
	flex-direction: column;
	gap: 12px;
	z-index: 1;
	pointer-events: none;
	overflow-y: auto;
	/* Hide the scrollbar chrome unless the user actually overflows —
	 * most setups never will. */
	scrollbar-width: thin;
	/* Fade alongside the overview backdrop. The class flip that
	 * drives this is on the parent area, set at t=0 of enter and
	 * removed at t=0 of exit, so widgets fade in parallel with the
	 * dock collapse / return — never sequential. */
	opacity: 1;
	transition:
		opacity 0.22s ease,
		transform 0.28s cubic-bezier( 0.2, 0, 0.2, 1 );
}

.os-area--overview .os-widgets {
	opacity: 0;
	/* Slight slide-off-right so the fade reads as "the column is
	 * stepping back out of the way" rather than flat-dimming. */
	transform: translateX( 16px );
	pointer-events: none;
}

.os-widgets__list {
	display: flex;
	flex-direction: column;
	gap: 12px;
	padding: 4px;
}

/*
 * Individual widget card — glass backdrop matching the dock /
 * overview top bar. Card root is pointer-events: auto so clicks on
 * content + the remove X land; the column itself stays
 * pointer-transparent so a drag that grazes the column's margin
 * falls through to the window beneath.
 */
.os-widgets__card {
	position: relative;
	padding: 0;
	/* Column layout so the body can flex-shrink and scroll when the
	 * frame writes a fixed inline height (floating / resized cards).
	 * Without this the body keeps its natural height and content
	 * spills past the card's rounded bottom edge. */
	display: flex;
	flex-direction: column;
	pointer-events: auto;
	background-color: rgba( 20, 20, 22, 0.55 );
	/*
	 * WIDGET texture slot — layered over the frosted card colour, so a
	 * translucent texture still gets the blur underneath.
	 */
	background-image: var( --os-widget-image, none );
	background-repeat: var( --os-widget-image-repeat, repeat );
	background-size: var( --os-widget-image-size, auto );
	background-position: var( --os-widget-image-position, center );
	backdrop-filter: blur( 18px ) saturate( 140% );
	-webkit-backdrop-filter: blur( 18px ) saturate( 140% );
	border: 1px solid rgba( 255, 255, 255, 0.08 );
	border-radius: 14px;
	color: var( --os-ui-fg-on-accent, #fff );
	box-shadow:
		0 8px 28px rgba( 0, 0, 0, 0.35 ),
		inset 0 0 0 1px rgba( 255, 255, 255, 0.04 );
	transition:
		box-shadow 0.18s ease,
		opacity 0.18s ease;
}

/* Hover feedback is shadow-only — no transform. Cards must feel
 * anchored; a hover nudge reads as the widget drifting under the
 * pointer (and fought the user's chosen position on floating cards). */
.os-widgets__card:hover {
	box-shadow:
		0 12px 36px rgba( 0, 0, 0, 0.45 ),
		inset 0 0 0 1px rgba( 255, 255, 255, 0.08 );
}

.os-widgets__card-body {
	padding: 16px;
	min-height: 48px;
	flex: 1 1 auto;
	overflow-x: hidden;
	overflow-y: auto;
	scrollbar-width: thin;
}

.os-widgets__card-close {
	position: absolute;
	top: 6px;
	inset-inline-end: 6px;
	width: 22px;
	height: 22px;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.55 ) );
	color: rgba( 255, 255, 255, 0.75 );
	border-radius: 50%;
	cursor: pointer;
	opacity: 0;
	transition:
		opacity 0.15s ease,
		background-color 0.15s ease,
		color 0.15s ease;
}

.os-widgets__card:hover .os-widgets__card-close,
.os-widgets__card-close:focus-visible {
	opacity: 1;
}

.os-widgets__card-close:hover {
	background: var( --os-ui-danger, #d63638 );
	color: var( --os-ui-fg-on-accent, #fff );
}

/*
 * Movable widgets — a thin chrome header at the top of the card holds
 * the drag grip, the widget's own label, and the × button. The whole
 * header is the drag handle (except for interactive children — those
 * remain clickable as normal). Non-movable widgets keep today's
 * corner-× layout unchanged.
 */
.os-widgets__chrome {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	gap: 8px;
	padding: 6px 8px;
	border-bottom: 1px solid rgba( 255, 255, 255, 0.06 );
	cursor: grab;
	user-select: none;
	-webkit-user-select: none;
	touch-action: none;
}

.os-widgets__card--dragging .os-widgets__chrome {
	cursor: grabbing;
}

.os-widgets__grip {
	flex-shrink: 0;
	width: 10px;
	height: 16px;
	background-image: radial-gradient(
		circle,
		rgba( 255, 255, 255, 0.55 ) 1.2px,
		transparent 1.5px
	);
	background-size: 5px 5px;
	background-position: 0 1px;
	background-repeat: space;
	opacity: 0.45;
	transition: opacity 0.12s ease;
}

.os-widgets__card:hover .os-widgets__grip {
	opacity: 0.85;
}

.os-widgets__title {
	flex: 1;
	font-size: 12px;
	font-weight: 500;
	letter-spacing: 0.01em;
	color: rgba( 255, 255, 255, 0.85 );
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* When the close button sits inside the chrome it's inline, not
   corner-absolute — reset position + always-visible opacity. */
.os-widgets__chrome .os-widgets__card-close {
	position: static;
	opacity: 0.7;
	width: 20px;
	height: 20px;
}

.os-widgets__chrome .os-widgets__card-close:hover,
.os-widgets__chrome .os-widgets__card-close:focus-visible {
	opacity: 1;
}

/*
 * Re-dock button — sits in the chrome next to the close button and
 * returns a floating widget to the right column. Visibility is
 * gated on the `--floating` modifier class so docked widgets never
 * expose it (tapping it on a docked card would be a confusing
 * no-op). The DOM node stays mounted either way so state flips
 * don't rebuild chrome.
 *
 * Styling mirrors the inline close button: 20×20 round tile,
 * dimmed by default, full-opacity on hover / focus. Distinct hover
 * color (theme accent, not red) signals "put back" vs. "remove."
 */
.os-widgets__card-redock {
	display: none;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.55 ) );
	color: rgba( 255, 255, 255, 0.75 );
	border-radius: 50%;
	width: 20px;
	height: 20px;
	cursor: pointer;
	opacity: 0.7;
	transition:
		opacity 0.15s ease,
		background-color 0.15s ease,
		color 0.15s ease;
}

.os-widgets__card--floating .os-widgets__card-redock {
	display: flex;
}

.os-widgets__card-redock:hover,
.os-widgets__card-redock:focus-visible {
	opacity: 1;
	background: var( --wp-admin-theme-color, #2271b1 );
	color: var( --os-ui-fg-on-accent, #fff );
}

/*
 * Floating widgets — absolute-positioned, inline top/left/width/height
 * written by the frame.
 */
.os-widgets__card--floating {
	position: absolute;
	margin: 0;
}

.os-widgets__card--dragging,
.os-widgets__card--resizing {
	transition: none;
	z-index: 2;
}

/*
 * Resize handles — 8 in all, with the 4 edges as hair-line strips and
 * the 4 corners as small squares. Invisible by default; cursor change
 * telegraphs them on hover. The frame attaches pointer listeners to
 * every handle; a non-movable widget ignores everything except the
 * `s` (bottom-edge, height-only) handle at the pointer layer, so we
 * visually hide the mismatched handles via the modifier class.
 */
.os-widgets__resize {
	position: absolute;
	z-index: 1;
}

.os-widgets__resize--n {
	top: -3px;
	left: 8px;
	right: 8px;
	height: 6px;
	cursor: ns-resize;
}

.os-widgets__resize--s {
	bottom: -3px;
	left: 8px;
	right: 8px;
	height: 6px;
	cursor: ns-resize;
}

.os-widgets__resize--e {
	top: 8px;
	bottom: 8px;
	right: -3px;
	width: 6px;
	cursor: ew-resize;
}

.os-widgets__resize--w {
	top: 8px;
	bottom: 8px;
	left: -3px;
	width: 6px;
	cursor: ew-resize;
}

.os-widgets__resize--ne {
	top: -4px;
	right: -4px;
	width: 12px;
	height: 12px;
	cursor: nesw-resize;
}

.os-widgets__resize--nw {
	top: -4px;
	left: -4px;
	width: 12px;
	height: 12px;
	cursor: nwse-resize;
}

.os-widgets__resize--se {
	bottom: -4px;
	right: -4px;
	width: 12px;
	height: 12px;
	cursor: nwse-resize;
}

.os-widgets__resize--sw {
	bottom: -4px;
	left: -4px;
	width: 12px;
	height: 12px;
	cursor: nesw-resize;
}

/*
 * Only floating cards expose the full 8-direction handle set. A
 * column-docked card — non-movable OR movable-but-not-yet-liberated —
 * resizes on the height axis alone (the frame ignores every other
 * direction while docked), so hide all handles except the bottom
 * edge and the cursor never lies about what's interactive.
 */
.os-widgets__card--resizable:not( .os-widgets__card--floating ) .os-widgets__resize:not( .os-widgets__resize--s ) {
	display: none;
}

/*
 * Add-widget tile — matches the "+" tile in the overview top bar.
 * Always visible at the bottom of the column; promoted to center
 * when the column is otherwise empty so first-run users see a
 * friendly target rather than an empty strip.
 */
.os-widgets__add {
	margin-top: auto;
	padding: 14px 12px;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	pointer-events: auto;
	background: transparent;
	border: 2px dashed rgba( 255, 255, 255, 0.22 );
	border-radius: 14px;
	color: rgba( 255, 255, 255, 0.72 );
	font: inherit;
	font-weight: 500;
	cursor: pointer;
	transition:
		border-color 0.15s ease,
		background-color 0.15s ease,
		color 0.15s ease;
}

.os-widgets__add:hover {
	border-color: rgba( 255, 255, 255, 0.4 );
	background: rgba( 255, 255, 255, 0.06 );
	color: var( --os-ui-fg-on-accent, #fff );
}

.os-widgets__add:focus-visible {
	outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
	outline-offset: 2px;
}

.os-widgets__add-plus {
	font-size: 20px;
	line-height: 1;
	font-weight: 200;
}

.os-widgets__add-label {
	font-size: 13px;
	letter-spacing: 0.02em;
}

/* Built-in clock widget */
.os-widget-clock {
	text-align: center;
	/* The time + date are passive glyphs, not content users would
	 * ever reasonably select — turning it off avoids the accidental-
	 * highlight mess that happens when a drag starts on the clock. */
	user-select: none;
	-webkit-user-select: none;
}

.os-widget-clock__time {
	font-size: 34px;
	font-weight: 600;
	line-height: 1.1;
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.01em;
}

.os-widget-clock__date {
	margin-top: 4px;
	font-size: 13px;
	color: rgba( 255, 255, 255, 0.75 );
	letter-spacing: 0.02em;
}

/* ---------------------------------------------------------------
 * Widget picker popover — opens from the add tile, lists every
 * registered widget. Positioned fixed so it can extend outside
 * the desktop-area clip.
 * --------------------------------------------------------------- */

.os-widget-picker {
	position: fixed;
	width: 320px;
	max-height: 60vh;
	overflow-y: auto;
	padding: 10px;
	border-radius: 14px;
	background: rgba( 20, 20, 22, 0.88 );
	backdrop-filter: blur( 22px ) saturate( 160% );
	-webkit-backdrop-filter: blur( 22px ) saturate( 160% );
	border: 1px solid rgba( 255, 255, 255, 0.1 );
	box-shadow: 0 18px 54px rgba( 0, 0, 0, 0.55 );
	color: var( --os-ui-fg-on-accent, #fff );
	z-index: 9999;
	animation: os-widget-picker-in 0.14s ease-out;
}

@keyframes os-widget-picker-in {
	from {
		opacity: 0;
		transform: translateY( 4px );
	}
}

.os-widget-picker__title {
	padding: 4px 8px 8px;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: rgba( 255, 255, 255, 0.55 );
}

.os-widget-picker__list {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.os-widget-picker__entry {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 10px;
	background: transparent;
	border: 0;
	border-radius: 10px;
	color: var( --os-ui-fg-on-accent, #fff );
	cursor: pointer;
	text-align: start;
	font: inherit;
	transition: background-color 0.12s ease;
}

.os-widget-picker__entry:hover:not( :disabled ) {
	background: rgba( 255, 255, 255, 0.08 );
}

.os-widget-picker__entry:focus-visible {
	outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
	outline-offset: 1px;
}

.os-widget-picker__entry--added {
	opacity: 0.55;
	cursor: default;
}

.os-widget-picker__entry-icon {
	flex-shrink: 0;
	font-size: 20px;
	width: 20px;
	height: 20px;
	line-height: 1;
}

.os-widget-picker__entry-text {
	display: flex;
	flex-direction: column;
	gap: 2px;
	flex: 1;
	min-width: 0;
}

.os-widget-picker__entry-label {
	font-size: 13px;
	font-weight: 500;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.os-widget-picker__entry-description {
	font-size: 11px;
	color: rgba( 255, 255, 255, 0.6 );
	line-height: 1.35;
	white-space: normal;
}

.os-widget-picker__entry-status {
	flex-shrink: 0;
	font-size: 11px;
	color: rgba( 255, 255, 255, 0.5 );
	text-transform: uppercase;
	letter-spacing: 0.08em;
}

.os-widget-picker__empty {
	padding: 12px 8px;
	font-size: 12px;
	color: rgba( 255, 255, 255, 0.6 );
	line-height: 1.4;
}

/**
 * Chromeless mode: pages loaded inside iframes.
 * Remove all shell chrome and let content fill the frame.
 */
.os-chromeless {
	margin: 0;
	padding: 0;
	background: var(--os-window-bg);
}

/*
 * Chromeless #wpbody-content, #adminmenuwrap, #wpfooter overrides are in
 * chromeless.css to keep all legacy page tweaks in one place.
 */

/*
 * Hide WordPress core's built-in command palette (`@wordpress/commands`).
 * OpenStation force-enqueues `wp_enqueue_command_palette_assets()` so
 * the `core/commands` data store is populated for our shell harvester
 * (see `src/commands/shell-harvester.ts`); the side effect is that WP
 * also mounts its own `<CommandPalette>` to the document body. We
 * already block its Cmd+K handler in `src/palette-registry.ts`, but if
 * a third-party script opens it programmatically (`wp.data.dispatch(
 * 'core/commands' ).open()`), the dialog would float over the desktop
 * with admin-menu navigation callbacks whose `document.location = url`
 * would unload the shell. Keep it permanently hidden.
 */
.commands-command-menu,
.commands-command-menu__overlay {
	display: none !important;
}

/*
 * ---- Links slotted into <os-notice> ---------------------------
 *
 * `<os-notice>` styles its slotted links from inside its shadow root,
 * through `::slotted( a )` and the `--os-ui-notice-link` tokens. That
 * rule cannot win here, and no amount of specificity on it would help.
 *
 * A slotted element lives in the DOCUMENT tree; the shadow root only
 * borrows it for rendering. When declarations from two trees collide,
 * CSS Scoping resolves normal (non-`!important`) ones in favour of the
 * OUTER tree regardless of specificity — so `wp-admin/css/common.css`,
 * which sets `a { color: #2271b1 }` and `a:hover { color: #135e96 }` on
 * every anchor in the admin, beats `::slotted( a )` every time. The
 * shell renders inside wp-admin, so it always loses: the link painted
 * WordPress Blue at rest and a darker blue on hover, the second of
 * which is close to illegible on a notice's dark wash.
 *
 * Hence a document-tree rule. It reads the same tokens the component
 * documents, so a theme still restyles the link by setting them; this
 * only moves the declaration into a tree that can win. `a:hover` is
 * (0,1,1), so the hover selector has to carry its own pseudo-class to
 * outrank it — matching `os-notice a` alone would lose the hover state
 * while winning the base one, which is exactly the inert-looking link
 * the component's own styles already went out of their way to avoid.
 */
os-notice a {
	color: var(--os-ui-notice-link, #ec9bff);
}

os-notice a:hover,
os-notice a:focus,
os-notice a:active {
	color: var(--os-ui-notice-link-hover, #fffbff);
}

```
