# desktop-mode/1.1.3/assets/css/openstation-layout.css

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

- Page: https://pluginprobe.com/plugins/desktop-mode/1.1.3/code/assets/css/openstation-layout.css
- Raw: https://pluginprobe.com/plugins/desktop-mode/1.1.3/raw/assets/css/openstation-layout.css
- Modified: 2026-08-21T13:22:46+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/openstation-layout.css#L10-L20`.

```css
/**
 * OpenStation — the constellation.
 *
 * The flyout that fans a menu's submenu out of its tile on hover, on
 * every rail in every layout. Body-attached (it has to escape the
 * dock's stacking context and its `overflow`), so its selectors are
 * rooted on `.os-constellation` and reach it wherever the dock is
 * parked.
 *
 * Colour discipline, per the palette rule: every declaration here
 * reads `var( --token, <literal> )`, and every literal is a plain
 * value that stands on its own if `variables.css` never loads. The
 * mesh appears in exactly two places — the hovered/focused row and
 * the head's icon halo — because those are the moments the panel is
 * answering the user. Everywhere else is Obsidian.
 */

/*
 * The dock tooltip stands down while a flyout is open. The panel's
 * head carries the same label, larger and attached to the thing it
 * names; the tooltip on top of it is a second hover surface saying
 * the same word twice.
 */
body.os-constellation-open .os-dock__tooltip {
	opacity: 0;
	pointer-events: none;
}

.os-constellation {
	position: fixed;
	z-index: var( --os-cn-z, 2147483000 );
	/*
	 * `--os-cn-shift` is written by the clamp when the panel would
	 * have overflowed a viewport edge; the beam compensates with
	 * `--os-cn-beam-x` so it stays pointed at the tile. Both default
	 * to 0, which is the un-clamped case.
	 */
	transform: translate(
			calc( -50% + var( --os-cn-shift, 0px ) ),
			-100%
		)
		scale( 0.94 );
	transform-origin: bottom center;
	opacity: 0;
	pointer-events: none;
	transition:
		transform var( --os-ui-motion-slow, 340ms )
			var( --os-ui-ease-spring, cubic-bezier( 0.32, 1.5, 0.55, 1 ) ),
		opacity var( --os-ui-motion-fast, 140ms ) ease-out;
}

.os-constellation.os-constellation--open {
	transform: translate( calc( -50% + var( --os-cn-shift, 0px ) ), -100% )
		scale( 1 );
	opacity: 1;
	pointer-events: auto;
}

/*
 * The exit, and it is NOT the entrance in reverse.
 *
 * Arriving is an event and gets a spring that overshoots. Leaving is
 * the user having already moved on: it eases IN (accelerating away
 * rather than settling), runs in under half the time, and — because
 * `transform-origin` is `bottom center`, where the beam meets the
 * tile — the shrink plus the few pixels of downward travel read as
 * the panel dropping back into the rail it came out of.
 *
 * `pointer-events: none` from the first frame. A panel that is
 * visibly leaving must not still be clickable, or a fast pointer
 * lands a row the user has already dismissed.
 */
.os-constellation.os-constellation--closing {
	transform: translate(
			calc( -50% + var( --os-cn-shift, 0px ) ),
			calc( -100% + 8px )
		)
		scale( 0.96 );
	opacity: 0;
	pointer-events: none;
	transition:
		transform 160ms var( --os-ui-ease-in, cubic-bezier( 0.4, 0, 1, 1 ) ),
		opacity 140ms var( --os-ui-ease-in, cubic-bezier( 0.4, 0, 1, 1 ) );
}

/*
 * The panel leaves as ONE object. Without this the rows revert to
 * their pre-entrance state and each plays its own staggered exit
 * inside a panel that is itself shrinking — two motions at two
 * speeds, which reads as the menu coming apart rather than closing.
 */
.os-constellation--closing .os-constellation__row {
	opacity: 1;
	transform: none;
	transition: none;
}

/*
 * The beam goes first, and faster. It is the thread to the tile, so
 * cutting it a beat before the panel lands sells the panel as
 * falling rather than fading.
 */
.os-constellation--closing .os-constellation__beam {
	opacity: 0;
	transition: opacity 90ms ease-in;
}

/*
 * A retiring panel never paints over a live one.
 *
 * Moving along the rail leaves two panels on screen at once — the one
 * you left finishing its dismissal above its own tile, the one you
 * arrived at rising above its. Adjacent dock tiles are ~46px apart
 * and a panel is 230px+ wide, so they overlap heavily, and without an
 * explicit order the outgoing one wins on source order and fades out
 * ON TOP of the menu the user is actually looking at.
 */
.os-constellation--closing {
	z-index: calc( var( --os-cn-z, 2147483000 ) - 1 );
}

/* ---- Fanning sideways -------------------------------------------- */

/*
 * Everything above assumes the rail is along the bottom: the panel
 * hangs off the top of its tile, is centred on it horizontally, and
 * drops back down into the rail as it leaves. A dock on the left or
 * the right needs the same three statements rotated a quarter turn.
 *
 * `data-os-cn-side` is written by `position()` in
 * `src/dock-constellation/index.ts` and names where the PANEL is
 * relative to its tile, not where the rail is: a left-hand rail fans
 * its panels out to the `right`.
 *
 * Two things change per side and nothing else does. The panel is
 * pinned by its facing edge instead of its bottom one, so the
 * translate loses its vertical half entirely: `top` is an absolute
 * position the JS already clamped, not an offset from the tile. And
 * `transform-origin` swaps which axis is pinned and which is centred.
 *
 * ## The cross axis is CENTRED, and that is the whole animation
 *
 * The entrance is a 0.94 → 1 scale and nothing else, so what the eye
 * reads is entirely down to where the origin is. Above a bottom rail
 * it is `bottom center`: the growth along the pinned axis all goes
 * one way (the panel's top edge rises out of the dock) and the growth
 * across it is symmetrical, ±7px that cancel. The panel does not
 * appear to travel at all — it opens.
 *
 * Anchoring the cross axis to `--os-cn-beam-y` instead — where the
 * beam meets the panel, which sounds like the right place for a menu
 * to grow out of — breaks exactly that. On the first tile of a rail
 * the beam sits 20px down a 400px panel, which is a corner in all
 * but name, and growth off a corner all goes one way: measured, the
 * top edge does not move at all while the bottom and right edges
 * both travel. It expands down-and-right where the bottom rail
 * opens evenly.
 *
 * So the cross axis stays `center` here too. The beam still lands on
 * the tile — it is positioned from `--os-cn-beam-y` independently —
 * but it does not get to drag the origin with it.
 */
/*
 * The properties below are PHYSICAL (`left` / `right`, not
 * `inset-inline-*`), because the thing they follow is physical: the
 * screen edge the dock is parked on. `dockPlacement` is `left` or
 * `right` in both directions of text.
 */
.os-constellation[ data-os-cn-side='right' ] {
	transform: translate( 0, 0 ) scale( 0.94 );
	transform-origin: left center;
}
.os-constellation[ data-os-cn-side='right' ].os-constellation--open {
	transform: translate( 0, 0 ) scale( 1 );
}
.os-constellation[ data-os-cn-side='right' ].os-constellation--closing {
	transform: translate( -8px, 0 ) scale( 0.96 );
}

.os-constellation[ data-os-cn-side='left' ] {
	transform: translate( -100%, 0 ) scale( 0.94 );
	transform-origin: right center;
}
.os-constellation[ data-os-cn-side='left' ].os-constellation--open {
	transform: translate( -100%, 0 ) scale( 1 );
}
.os-constellation[ data-os-cn-side='left' ].os-constellation--closing {
	transform: translate( calc( -100% + 8px ), 0 ) scale( 0.96 );
}

/* The panel body. */
.os-constellation__surface {
	position: relative;
	display: flex;
	flex-direction: column;
	min-width: 232px;
	max-width: min( 340px, calc( 100vw - 24px ) );
	/*
	 * The vertical clamp. `--os-cn-max-h` is written by the JS on
	 * every placement: the distance from this panel's bottom edge up
	 * to the top of the viewport. The panel cannot be nudged
	 * downwards to fit — that would push it over the dock — so it is
	 * capped instead, and the group below takes the scroll.
	 */
	max-height: var( --os-cn-max-h, min( 78vh, 620px ) );
	/*
	 * The surface itself never scrolls. A long menu (WooCommerce's
	 * fifteen children, a CPT with every taxonomy under it) puts the
	 * scroll on its own group instead, which keeps the head and the
	 * new-window row pinned AND keeps this element a stable
	 * containing block for the spotlight + edge overlays — an
	 * absolutely-positioned child of a scroll container scrolls with
	 * the content, so the hairline would slide off the top.
	 */
	padding: 6px;
	border-radius: var( --os-cn-radius, 14px );
	background-color: var( --os-cn-surface, rgba( 26, 23, 33, 0.82 ) );
	backdrop-filter: blur( 28px ) saturate( 170% );
	-webkit-backdrop-filter: blur( 28px ) saturate( 170% );
	box-shadow: var(
		--os-cn-shadow,
		0 24px 64px rgba( 0, 0, 0, 0.62 ),
		0 2px 8px rgba( 0, 0, 0, 0.4 )
	);
	color: var( --os-cn-fg, #fffbff );
}

/*
 * Cursor spotlight. The JS writes `--os-cn-x` / `--os-cn-y` on
 * pointermove; this paints a soft bloom there so the panel lights up
 * under the pointer instead of sitting inert. Additive (`screen`) so
 * it brightens the surface without washing the text.
 *
 * `::before`, not `::after`, so it sits BELOW the rows in paint
 * order — the rows are positioned, and a spotlight painted over the
 * mesh of a hovered row would flatten exactly the moment it is
 * meant to reward.
 */
.os-constellation__surface::before {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	background: radial-gradient(
		220px circle at var( --os-cn-x, 50% ) var( --os-cn-y, 0% ),
		hsl( var( --os-cn-hue, 300 ) 90% 70% / 0.14 ) 0%,
		transparent 70%
	);
	mix-blend-mode: screen;
	pointer-events: none;
}

/*
 * The iridescent hairline. Painted as a gradient on a padding-box
 * mask rather than as a `border-color`, because a border cannot hold
 * a gradient and `border-image` loses the corner radius — the same
 * trick `holoEdge` uses inside the component kit.
 */
.os-constellation__surface::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	padding: 1px;
	background: var(
		--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%
		)
	);
	-webkit-mask:
		linear-gradient( #000 0 0 ) content-box,
		linear-gradient( #000 0 0 );
	mask:
		linear-gradient( #000 0 0 ) content-box,
		linear-gradient( #000 0 0 );
	-webkit-mask-composite: xor;
	mask-composite: exclude;
	pointer-events: none;
}

/*
 * The sheen. One diagonal band of light crossing the panel, once, as
 * it opens.
 *
 * An element rather than a pseudo, and a child of the ROOT rather
 * than of the surface: the surface has already spent `::before` on
 * the spotlight and `::after` on the edge mask (the pseudo budget is
 * exactly two), and hanging it off the root is what lets it sweep
 * over the rows instead of under them.
 */
.os-constellation__sheen {
	position: absolute;
	inset: 0;
	border-radius: var( --os-cn-radius, 14px );
	overflow: hidden;
	pointer-events: none;
	opacity: 0;
}

.os-constellation--open .os-constellation__sheen {
	animation: os-cn-sheen 900ms var( --os-ui-ease-out, ease-out ) 60ms 1;
}

.os-constellation__sheen::before {
	content: "";
	position: absolute;
	inset: -40% -120%;
	background: linear-gradient(
		104deg,
		transparent 42%,
		rgba( 255, 253, 255, 0.42 ) 50%,
		transparent 58%
	);
	transform: translateX( -60% );
}

@keyframes os-cn-sheen {
	0% {
		opacity: 0;
	}
	18% {
		opacity: 1;
	}
	100% {
		opacity: 0;
		transform: translateX( 120% );
	}
}

/* ---- The beam ---------------------------------------------------- */

/*
 * The thread from the panel's underside down to the tile it belongs
 * to. Its only real job is the clamped case: once the panel has been
 * nudged sideways to stay on screen, the beam is the only thing left
 * saying which tile it came out of.
 */
.os-constellation__beam {
	position: absolute;
	inset-inline-start: 50%;
	top: 100%;
	width: 2px;
	height: 14px;
	transform: translateX(
		calc( -50% + var( --os-cn-beam-x, 0px ) )
	);
	background: linear-gradient(
		to bottom,
		var( --os-cn-beam, rgba( 217, 46, 227, 0.8 ) ) 0%,
		transparent 100%
	);
	pointer-events: none;
	opacity: 0;
	transition: opacity var( --os-ui-motion-fast, 140ms ) ease-out 80ms;
}

.os-constellation--open .os-constellation__beam {
	opacity: 1;
}

/*
 * The same thread, turned. It leaves the panel by its facing edge and
 * runs back to the tile, so beside a rail it is 14px wide and 2px
 * tall rather than the other way round, and the fade runs toward the
 * tile in both cases.
 *
 * `--os-cn-beam-y` is where the tile is, measured down from the top
 * of the panel — not a correction applied to a centred beam, which is
 * what the bottom rail's `--os-cn-beam-x` is. Beside a rail the panel
 * is top-aligned with its tile rather than centred on it, so the
 * beam's position IS the anchoring, and a panel that had to be
 * clamped away from a viewport edge still has a thread landing on the
 * tile it belongs to.
 */
.os-constellation[ data-os-cn-side='right' ] .os-constellation__beam,
.os-constellation[ data-os-cn-side='left' ] .os-constellation__beam {
	top: var( --os-cn-beam-y, 50% );
	width: 14px;
	height: 2px;
	transform: translateY( -50% );
}
.os-constellation[ data-os-cn-side='right' ] .os-constellation__beam {
	inset-inline-start: auto;
	left: auto;
	right: 100%;
	background: linear-gradient(
		to left,
		var( --os-cn-beam, rgba( 217, 46, 227, 0.8 ) ) 0%,
		transparent 100%
	);
}
.os-constellation[ data-os-cn-side='left' ] .os-constellation__beam {
	inset-inline-start: auto;
	right: auto;
	left: 100%;
	background: linear-gradient(
		to right,
		var( --os-cn-beam, rgba( 217, 46, 227, 0.8 ) ) 0%,
		transparent 100%
	);
}

/* ---- Rows -------------------------------------------------------- */

.os-constellation__row {
	position: relative;
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	/* The head and the new-window row are direct flex children of a
	 * height-capped surface; without this they would be squashed
	 * before the scrollable group in the middle gave way. */
	flex: 0 0 auto;
	padding: 8px 10px;
	border: none;
	border-radius: 9px;
	background: transparent;
	color: inherit;
	font: inherit;
	font-size: 13px;
	line-height: 1.3;
	text-align: start;
	cursor: pointer;
	/*
	 * Staggered entrance. `--os-cn-row` is the row's index, written by
	 * the JS, so the list unfurls top-down instead of appearing all at
	 * once. Capped at 8 legs of delay: a 30-item submenu that took
	 * 30 × 26ms to finish would read as sluggish, not as choreography.
	 */
	opacity: 0;
	transform: translateY( 6px );
	transition:
		background-color var( --os-ui-motion-fast, 140ms ) ease-out,
		color var( --os-ui-motion-fast, 140ms ) ease-out,
		opacity 220ms ease-out,
		transform 220ms
			var( --os-ui-ease-spring, cubic-bezier( 0.32, 1.5, 0.55, 1 ) );
	transition-delay: 0s, 0s,
		calc( min( var( --os-cn-row, 0 ), 8 ) * 26ms ),
		calc( min( var( --os-cn-row, 0 ), 8 ) * 26ms );
}

.os-constellation--open .os-constellation__row {
	opacity: 1;
	transform: translateY( 0 );
}

.os-constellation__row:hover,
.os-constellation__row:focus-visible {
	outline: none;
	/*
	 * The identity moment. The row under the pointer is "selected",
	 * which is exactly the state the brand reserves the mesh for — so
	 * this is where the panel spends it, and the ink flips to Void
	 * because every mesh in the brand is a LIGHT surface.
	 */
	background-image: var(
		--os-cn-row-fill,
		var( --os-ui-holo-fill, linear-gradient( 124deg, #afa2e8, #c3b8ef ) )
	);
	color: var( --os-cn-row-ink, var( --os-ui-holo-ink, #0c0b0f ) );
}

.os-constellation__row:focus-visible {
	box-shadow: var(
		--os-ui-focus-ring,
		0 0 0 2px rgba( 12, 11, 15, 0.9 ),
		0 0 0 4px #f252fc
	);
}

.os-constellation__row:active {
	transform: scale( 0.985 );
}

.os-constellation__row-label {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.os-constellation__row-meta {
	flex: 0 0 auto;
	font-size: 11px;
	opacity: 0.6;
}

.os-constellation__row-offsite {
	flex: 0 0 auto;
	font-size: 14px;
	width: 14px;
	height: 14px;
	opacity: 0.55;
}

/*
 * Reaches screen readers, never the eye. It rides beside the row's own
 * label so the accessible name reads "Docs (opens in a new tab)"; an
 * `aria-label` on the row would have replaced that name rather than
 * extended it, taking the label and any plugin-appended meta with it.
 */
.os-constellation__row-note {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	white-space: nowrap;
	clip-path: inset( 50% );
}

/* ---- Head -------------------------------------------------------- */

.os-constellation__head {
	gap: 12px;
	padding: 10px;
	margin-bottom: 4px;
}

.os-constellation__head-icon {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 34px;
	height: 34px;
	border-radius: 10px;
	color: var( --os-ui-holo-ink, #0c0b0f );
	/*
	 * The head's icon is the panel's second identity moment — the
	 * menu, stated. It wears the mesh permanently (unlike the rows,
	 * which earn it under the pointer) because there is exactly one
	 * head per panel and it is the thing the panel is about.
	 */
	background-image: var(
		--os-cn-row-fill,
		var( --os-ui-holo-fill, linear-gradient( 124deg, #afa2e8, #c3b8ef ) )
	);
	box-shadow: var(
		--os-ui-holo-glow,
		0 0 0 1px rgba( 217, 46, 227, 0.2 ),
		0 2px 10px rgba( 217, 46, 227, 0.15 )
	);
}

.os-constellation__head-icon .dashicons {
	width: 20px;
	height: 20px;
	font-size: 20px;
	line-height: 20px;
}

/*
 * Drawn art — a data URI or a URL rather than a named dashicon, which
 * is what every shell-owned tile wears. Painted as a mask filled with
 * `currentColor` (the head's ink over the mesh), so only the artwork's
 * alpha is used and a black-stroked glyph stays legible.
 */
.os-constellation__head-art {
	width: 20px;
	height: 20px;
}

/*
 * The head's own hover state deliberately does NOT take the row mesh
 * — the icon beside it is already wearing it, and two meshes touching
 * is where iridescence stops reading as emphasis. It gets a plain
 * raised wash instead.
 */
.os-constellation__head:hover,
.os-constellation__head:focus-visible {
	background-image: none;
	background-color: var(
		--os-ui-surface-raised,
		rgba( 255, 251, 255, 0.06 )
	);
	color: var( --os-cn-fg, #fffbff );
}

.os-constellation__head-text {
	display: flex;
	flex: 1 1 auto;
	min-width: 0;
	flex-direction: column;
	gap: 1px;
}

.os-constellation__head-title {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: 14px;
	font-weight: 600;
}


/* ---- Groups ------------------------------------------------------ */

.os-constellation__group {
	display: flex;
	flex-direction: column;
	padding-top: 4px;
	border-top: 1px solid var( --os-cn-divider, rgba( 255, 251, 255, 0.1 ) );
	/*
	 * The scroll lives here rather than on the surface, so the head
	 * and the new-window row stay pinned while a long submenu rolls
	 * under them.
	 *
	 * `min-height: 0` is what lets the surface's `max-height` reach
	 * this: a flex item's default `min-height: auto` refuses to
	 * shrink below its content, so without it a tall submenu would
	 * simply overflow the capped surface instead of scrolling inside
	 * it. `flex: 0 1 auto` says this is the part that gives — the
	 * head and the new-window row do not.
	 */
	flex: 0 1 auto;
	min-height: 0;
	max-height: min( 46vh, 360px );
	overflow-y: auto;
	overflow-x: hidden;
	scrollbar-width: thin;
}

.os-constellation__legend {
	padding: 4px 10px 3px;
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var( --os-cn-legend, rgba( 255, 251, 255, 0.4 ) );
}

/* ---- Submenu rows ------------------------------------------------ */

/*
 * The orbit dot. Each row's hue comes from its own title, so a long
 * submenu reads as a spectrum rather than as fifteen identical grey
 * bullets — and "Menus" is the same colour on every site, every
 * session, because the hue is hashed from the string.
 */
.os-constellation__orbit {
	position: relative;
	flex: 0 0 auto;
	width: 8px;
	height: 8px;
	margin-inline-start: 3px;
	border-radius: 50%;
	background: hsl( var( --os-cn-row-hue, 300 ) 80% 70% );
	box-shadow: 0 0 0 3px hsl( var( --os-cn-row-hue, 300 ) 80% 70% / 0.16 );
	transition: box-shadow var( --os-ui-motion-fast, 140ms ) ease-out;
}

.os-constellation__row--sub:hover .os-constellation__orbit,
.os-constellation__row--sub:focus-visible .os-constellation__orbit {
	/* On the mesh the halo would disappear; a Void ring reads instead. */
	box-shadow: 0 0 0 3px rgba( 12, 11, 15, 0.25 );
}

/* ---- Live-window rows -------------------------------------------- */

.os-constellation__pip {
	flex: 0 0 auto;
	width: 8px;
	height: 8px;
	margin-inline-start: 3px;
	border-radius: 50%;
	background: var( --os-ui-accent, #f252fc );
	box-shadow: 0 0 8px var( --os-ui-accent-dim, #d92ee3 );
}

/* A minimized window's pip is hollow — same cue the dock's own
 * indicator uses for "open, but put away". */
.os-constellation__row--live[ data-state="minimized" ] .os-constellation__pip {
	background: transparent;
	border: 1px solid var( --os-ui-accent, #f252fc );
	box-shadow: none;
}

/* ==================================================================
 * 3. Motion budget
 * ================================================================== */

/*
 * Reduced motion stops the travel, never the surface. The panel still
 * appears, the seam node still glows, the hovered row still wears the
 * mesh — losing those would lose STATE, not animation. What goes is
 * the flight: the spring, the stagger, the sheen sweep, the breathing.
 */
@media ( prefers-reduced-motion: reduce ) {
	.os-constellation,
	.os-constellation__row {
		transition-duration: 1ms;
		transition-delay: 0s;
	}

	/*
	 * No travel, in either direction. The exit is also removed from
	 * the document immediately under this preference (the JS reads
	 * the same media query), so `--closing` is listed here only to
	 * cover the frame between the class landing and the node going.
	 */
	.os-constellation,
	.os-constellation--open,
	.os-constellation--closing {
		transform: translate(
			calc( -50% + var( --os-cn-shift, 0px ) ),
			-100%
		);
	}

	.os-constellation__row {
		transform: none;
	}

	.os-constellation--open .os-constellation__sheen {
		animation: none;
	}

}

```
