# desktop-mode/trunk/assets/css/dock.css

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

- Page: https://pluginprobe.com/plugins/desktop-mode/trunk/code/assets/css/dock.css
- Raw: https://pluginprobe.com/plugins/desktop-mode/trunk/raw/assets/css/dock.css
- Modified: 2026-09-04T15:30:56+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/trunk/code/assets/css/dock.css#L10-L20`.

```css
/**
 * OpenStation — Dock.
 *
 * A rail that hosts admin menu tiles plus shell-level system tiles.
 * Placement (left / right / bottom) is reflected on each dock element
 * itself as `data-os-dock-placement`; this stylesheet keys
 * off that attribute for layout, tooltip anchor, and indicator
 * position. Two instances can coexist (Classic layout: a left side
 * bar with core menus + a bottom dock with plugin menus); each
 * carries its own placement attribute, so the two rails are styled
 * independently without leaking selector state to one another.
 *
 * @since 6.9.0
 */

/* ------------------------------------------------------------------
 * Shared dock chrome — applies to every placement.
 * ------------------------------------------------------------------ */

.os-dock {
	display: flex;
	align-items: center;
	z-index: var( --os-z-dock );
	background-color: var( --os-dock-bg );
	/*
	 * Desktop-theme dock texture (DOCK slot). Layered OVER the dock
	 * background colour, so a theme can ship a semi-transparent
	 * texture and still get the translucent wash underneath. `none`
	 * when unset — no theme, no cost.
	 */
	background-image: var( --os-dock-bg-image, none );
	background-repeat: var( --os-dock-bg-image-repeat, repeat );
	background-size: var( --os-dock-bg-image-size, auto );
	background-position: var( --os-dock-bg-image-position, center );
	backdrop-filter: blur( 20px );
	-webkit-backdrop-filter: blur( 20px );
	flex-shrink: 0;
	/*
	 * overflow: visible so the per-tile "Open another" chip can float
	 * past the dock's outer edge. Real-world bottom-dock menus fit
	 * the viewport without a scrollbar; vertical placements route
	 * their overflow through the inner `__scroll` wrapper, leaving
	 * the dock itself non-scrolling.
	 */
	overflow: visible;
}

/* A single dock tile — icon button (+ optional instance rail below). */
.os-dock__item {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	width: 40px;
	flex-shrink: 0;
}

/* Primary icon button — opens or focuses the page. */
.os-dock__item-primary {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	border: none;
	border-radius: 10px;
	cursor: pointer;
	padding: 0;
	background-color: transparent;
	/*
	 * DOCK_ITEM texture slot — the face of a single dock tile, so a
	 * theme can give every launcher a physical key/plate instead of
	 * relying on the dock strip's texture showing through. Painted
	 * under the icon; the hover wash below is a background-COLOR so
	 * it composes with the texture rather than erasing it.
	 */
	background-image: var( --os-dock-item-image, none );
	background-repeat: var( --os-dock-item-image-repeat, no-repeat );
	background-size: var( --os-dock-item-image-size, auto );
	background-position: var( --os-dock-item-image-position, center );
	/*
	 * Dock glyph colour. The literal is what this rule always said;
	 * the token is the name a theme aims at when its dock strip is
	 * not dark. See the "Dock glyphs" block in variables.css.
	 */
	color: var( --os-dock-icon-color, rgba( 255, 255, 255, 0.7 ) );
	transition: background-color 0.15s ease, transform 0.15s ease, color 0.15s ease;
}

.os-dock__item-primary:hover {
	background-color: var(
		--os-dock-item-bg-hover,
		rgba( 255, 255, 255, 0.15 )
	);
	color: var(
		--os-dock-icon-color-hover,
		var( --os-ui-fg-on-accent, #fff )
	);
	transform: scale( 1.1 );
}

.os-dock__item-primary:active {
	transform: scale( 0.95 );
}

.os-dock__item-primary:focus-visible {
	outline: 2px solid var(
		--os-dock-item-outline,
		rgba( 255, 255, 255, 0.7 )
	);
	outline-offset: 2px;
}

/* Stacked card hint on hover — a second card peeks from behind the
   icon when the tile has multiple open windows. Only visible on hover so
   it never conflicts with focused tiles. */
.os-dock__item--stacked:hover .os-dock__item-primary::before,
.os-dock__item--stacked[data-peek-active] .os-dock__item-primary::before {
	content: "";
	position: absolute;
	top: -4px;
	inset-inline-start: -4px;
	width: 100%;
	height: 100%;
	border-radius: inherit;
	background: rgba( 255, 255, 255, 0.06 );
	border: 1px solid rgba( 255, 255, 255, 0.10 );
	z-index: -1;
	pointer-events: none;
}

/* Dashicon inside dock item. */
.os-dock__item .dashicons {
	font-size: var( --os-dock-icon-size, 20px );
	width: var( --os-dock-icon-size, 20px );
	height: var( --os-dock-icon-size, 20px );
	line-height: 1;
}

/* SVG icon (for custom post types with data:image/svg+xml icons).
 *
 * The FALLBACK path now: `_makeSvgIcon()` paints these as a mask
 * filled with `currentColor` (see `__item-mask` below) and only lands
 * here when the mask refuses the URL — one carrying quotes, spaces or
 * parens. The filter is what that fallback still needs. */
.os-dock__item-svg {
	width: var( --os-dock-icon-size, 20px );
	height: var( --os-dock-icon-size, 20px );
	display: block;
	fill: currentColor;
	opacity: 0.7;
	/* Some plugin SVGs ship hardcoded `fill="..."` attributes inside
	   the markup, which override `fill: currentColor` and make the
	   icon keep its brand color. Force the monochrome white palette
	   to match dashicons. Flattens to WHITE specifically, which is why
	   the mask path is preferred: a filter has no colour to name, so a
	   theme cannot reach it. */
	filter: brightness(0) invert(1);
	transition: opacity 0.15s ease;
}

.os-dock__item-primary:hover .os-dock__item-svg {
	opacity: 1;
}

/*
 * MASKED icon — the glyph is painted as a CSS mask filled with a
 * colour, so only the source artwork's alpha is used.
 *
 * Two callers, one shape:
 *
 *   - A desktop theme's tinted iconset, filled with the colour the
 *     theme named for that slot.
 *   - Every other plugin / CPT image icon, filled with
 *     `currentColor` — which is `--os-dock-icon-color`, the
 *     same token the dashicons follow.
 *
 * Deliberately NOT `__item-svg`: that class force-whitens its content
 * with `filter: brightness(0) invert(1)`. Both mechanisms discard the
 * artwork's colours and keep its alpha, but only one of them has a
 * colour a theme can name.
 *
 * Full opacity, unlike `__item-svg`'s 0.7: the fill carries its own
 * alpha. Unthemed that is `rgba( 255, 255, 255, 0.7 )` at rest and
 * `#fff` on hover — exactly what the filter plus the opacity pair
 * used to compute — and a theme that names a colour has already
 * decided how prominent its glyphs should be, so dimming it again
 * would silently mix it with the dock.
 *
 * @since 0.9.8
 */
.os-dock__item-mask {
	width: var( --os-dock-icon-size, 20px );
	height: var( --os-dock-icon-size, 20px );
	display: block;
	flex-shrink: 0;
}

/*
 * Letter-badge icon — rendered for items registered without a
 * dashicon / SVG / image URL. Miniature "app placeholder": rounded
 * square tinted with a title-derived hue, first letter centered.
 */
.os-dock__item-letter {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	border-radius: 8px;
	color: var( --os-ui-fg-on-accent, #fff );
	font-size: 15px;
	font-weight: 700;
	font-family: var(
		--os-font,
		-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif
	);
	line-height: 1;
	letter-spacing: 0;
	text-shadow: 0 1px 1px rgba( 0, 0, 0, 0.22 );
	box-shadow:
		inset 0 1px 0 rgba( 255, 255, 255, 0.2 ),
		inset 0 -1px 0 rgba( 0, 0, 0, 0.15 );
}

.os-dock__item img.os-dock__item-img {
	width: var( --os-dock-icon-size, 20px );
	height: var( --os-dock-icon-size, 20px );
	display: block;
	opacity: 0.7;
	transition: opacity 0.15s ease;
}

.os-dock__item-primary:hover img.os-dock__item-img {
	opacity: 1;
}

/* "Open another" floating chip — shown on multi-capable tiles that
 * already have ≥1 open instance. Positioned inside each orientation
 * block so the chip hugs the outward edge of the tile. */
.os-dock__item-new {
	position: absolute;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 20px;
	height: 20px;
	padding: 0;
	border: 2px solid var( --os-dock-bg, rgba( 0, 0, 0, 0.4 ) );
	border-radius: 50%;
	background: var( --wp-admin-theme-color, #2271b1 );
	color: var( --os-ui-fg-on-accent, #fff );
	cursor: pointer;
	transition: background-color 0.15s ease, transform 0.15s ease;
	z-index: 1;
}

.os-dock__item-new[hidden] {
	display: none;
}

.os-dock__item-new:hover {
	background: var( --os-ui-surface, #fff );
	color: var( --wp-admin-theme-color, #2271b1 );
}

.os-dock__item-new:focus-visible {
	outline: 2px solid rgba( 255, 255, 255, 0.85 );
	outline-offset: 2px;
}

/* Notification badge — iOS/macOS-style pill straddling the tile
 * corner. Ring matches the dock bg so it reads as "punched out."
 * Badge positioning is placement-aware via the blocks below. */
.os-dock__badge {
	/*
	 * Badge geometry DERIVES from the dock's icon size instead of
	 * being pinned to 16px.
	 *
	 * The dock is user-sizable (18 / 20 / 26px icons via OS Settings →
	 * dock size). A fixed badge tracked none of that: at "Large" it
	 * read as a speck stuck to an oversized tile. The ratios below are
	 * chosen so the DEFAULT dock (20px icons) resolves to exactly the
	 * old numbers — 16px box, 10px text, ~5px padding — so nothing
	 * moves for anyone who hasn't changed the setting.
	 *
	 * `--os-dock-badge-size` overrides the whole derivation
	 * for themes that want a specific badge; the font size and padding
	 * then follow it, so one token is usually enough.
	 */
	--os-dock-badge-computed-size: var(
		--os-dock-badge-size,
		calc( var( --os-dock-icon-size, 20px ) * 0.8 )
	);
	position: absolute;
	min-width: var( --os-dock-badge-computed-size );
	height: var( --os-dock-badge-computed-size );
	padding: 0 var(
		--os-dock-badge-padding,
		calc( var( --os-dock-badge-computed-size ) * 0.3 )
	);
	box-sizing: border-box;
	border-radius: 999px;
	background: var(
		--os-dock-badge-bg,
		linear-gradient( 180deg, #ff5a5a 0%, var( --os-ui-danger, #d63638 ) 100% )
	);
	color: var( --os-dock-badge-fg, var( --os-ui-fg-on-accent, #fff ) );
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/*
	 * 0.625 of the badge box — 10px on the default 16px badge, the
	 * value this rule carried before the geometry was derived. It is
	 * the right weight for a menu tile's alert count: these badges sit
	 * on the tile CORNER where the pill itself carries the signal, so
	 * the digit only has to confirm it.
	 *
	 * The Recycle Bin's badge is a different problem and has its own
	 * ratio below — it sits INSIDE the bin glyph, where the pill reads
	 * as part of the artwork and the number has to carry itself.
	 */
	font-size: var(
		--os-dock-badge-font-size,
		calc( var( --os-dock-badge-computed-size ) * 0.625 )
	);
	font-weight: var( --os-dock-badge-font-weight, 700 );
	line-height: 1;
	letter-spacing: 0.01em;
	text-align: center;
	font-variant-numeric: tabular-nums;
	box-shadow:
		0 0 0 1.5px var( --os-dock-bg, rgba( 0, 0, 0, 0.4 ) ),
		0 1px 3px rgba( 0, 0, 0, 0.35 );
	pointer-events: none;
	transition: transform 0.15s ease;
	top: var( --os-dock-badge-offset, -3px );
	inset-inline-end: var( --os-dock-badge-offset, -3px );
}

.os-dock__item:hover .os-dock__badge {
	transform: scale( 1.05 );
	top: -4px;
}

/*
 * Recycle Bin count — unlike unread/update badges, the trash count
 * is ambient state. Keep it inside the bin glyph as a quiet neutral
 * marker instead of a red alert on the tile corner.
 */
.os-dock__item[ data-system-id="desktop-mode-recycle-bin" ] .os-dock__badge {
	/*
	 * Sized off the dock icon like every other badge, so it tracks the
	 * dock-size setting instead of sitting at a fixed 13px on an icon
	 * that may be 18, 20 or 26px.
	 *
	 * Deliberately quieter than the corner alert badge above — this is
	 * ambient state, not a notification — but it still has to be a
	 * READABLE number. It was 13px with an 8px digit, which is below
	 * the point where a count reads as a count.
	 *
	 * Override chain: bin-specific token → the generic dock-badge
	 * token → the derivation, so a theme that says "dock badges are
	 * 20px" reaches the bin without naming it.
	 */
	--os-dock-recycle-badge-computed-size: var(
		--os-dock-recycle-badge-size,
		var(
			--os-dock-badge-size,
			calc( var( --os-dock-icon-size, 20px ) * 0.75 )
		)
	);
	top: calc( var( --os-dock-icon-size, 20px ) * 1 );
	inset-inline-end: calc( var( --os-dock-icon-size, 20px ) * 0.35 );
	min-width: var( --os-dock-recycle-badge-computed-size );
	height: var( --os-dock-recycle-badge-computed-size );
	padding: 0 calc( var( --os-dock-recycle-badge-computed-size ) * 0.25 );
	border-radius: 999px;
	background: var(
		--os-dock-recycle-badge-bg,
		rgba( 29, 35, 39, 0.72 )
	);
	color: var(
		--os-dock-recycle-badge-fg,
		rgba( 255, 255, 255, 0.92 )
	);
	font-size: var(
		--os-dock-recycle-badge-font-size,
		var(
			--os-dock-badge-font-size,
			calc( var( --os-dock-recycle-badge-computed-size ) * 0.8 )
		)
	);
	font-weight: 700;
	letter-spacing: 0;
	box-shadow:
		inset 0 0 0 1px rgba( 255, 255, 255, 0.26 ),
		0 1px 2px rgba( 0, 0, 0, 0.24 );
}

.os-dock__item[ data-system-id="desktop-mode-recycle-bin" ]:hover .os-dock__badge {
	/* Re-assert the derived offset, not a literal: the base rule above
	 * is overridden by the generic `__item:hover .__badge` rule, and
	 * hardcoding 20px here would strand the badge the moment the dock
	 * icon size changed. */
	top: calc( var( --os-dock-icon-size, 20px ) * 1 );
	transform: scale( 1.03 );
}

/* System tiles — same footprint as menu tiles, no badges, no rails.
 *
 * Reads the SAME glyph token as a menu tile, with its own literal as
 * the fallback: unthemed, system tiles keep sitting a notch brighter
 * than the rest (0.8 vs 0.7); themed, one colour covers every glyph
 * in the dock rather than leaving these four stranded white. */
.os-dock__item--system .os-dock__item-primary {
	color: var( --os-dock-icon-color, rgba( 255, 255, 255, 0.8 ) );
}

/* -----------------------------------------------------------------------------
 * Attention animations — driven by `Dock.setAttention()` /
 * `Window.requestAttention()`. The class lands on the tile root,
 * the animation runs on the primary icon button so the badge stays
 * stable. Three modes (pulse / shake / bounce) and three intensities
 * (subtle / normal / strong). All animations gated on
 * `prefers-reduced-motion: no-preference`; the reduced-motion fallback
 * shows a static accent ring for the same duration so the affordance
 * still works.
 * ------------------------------------------------------------------------- */

@keyframes os-dock-attention-pulse {
	0%, 100% {
		transform: scale( 1 );
		box-shadow: 0 0 0 0 rgba( 255, 90, 90, 0.55 );
	}
	50% {
		transform: scale( 1.06 );
		box-shadow: 0 0 0 8px rgba( 255, 90, 90, 0 );
	}
}

@keyframes os-dock-attention-shake {
	0%, 100% {
		transform: translateX( 0 );
	}
	20%, 60% {
		transform: translateX( -3px );
	}
	40%, 80% {
		transform: translateX( 3px );
	}
}

@keyframes os-dock-attention-bounce {
	0%, 100% {
		transform: translateY( 0 );
	}
	30% {
		transform: translateY( -6px );
	}
	60% {
		transform: translateY( -2px );
	}
}

.os-dock__item--attention-pulse .os-dock__item-primary {
	animation: os-dock-attention-pulse 1.4s ease-in-out infinite;
}
.os-dock__item--attention-shake .os-dock__item-primary {
	animation: os-dock-attention-shake 0.8s ease-in-out infinite;
}
.os-dock__item--attention-bounce .os-dock__item-primary {
	animation: os-dock-attention-bounce 1.2s ease-in-out infinite;
}

/* Intensity modulates animation duration — strong = faster, more
   urgent; subtle = slower, calmer. */
.os-dock__item--intensity-strong.os-dock__item--attention-pulse .os-dock__item-primary {
	animation-duration: 1s;
}
.os-dock__item--intensity-strong.os-dock__item--attention-shake .os-dock__item-primary {
	animation-duration: 0.6s;
}
.os-dock__item--intensity-strong.os-dock__item--attention-bounce .os-dock__item-primary {
	animation-duration: 0.9s;
}
.os-dock__item--intensity-subtle.os-dock__item--attention-pulse .os-dock__item-primary {
	animation-duration: 1.8s;
}
.os-dock__item--intensity-subtle.os-dock__item--attention-shake .os-dock__item-primary {
	animation-duration: 1s;
}
.os-dock__item--intensity-subtle.os-dock__item--attention-bounce .os-dock__item-primary {
	animation-duration: 1.6s;
}

/* Reduced-motion: kill the animation, replace with a static accent ring
   for the same visual prominence without the movement. */
@media ( prefers-reduced-motion: reduce ) {
	.os-dock__item--attention-pulse .os-dock__item-primary,
	.os-dock__item--attention-shake .os-dock__item-primary,
	.os-dock__item--attention-bounce .os-dock__item-primary {
		animation: none;
		outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
		outline-offset: 2px;
		border-radius: 8px;
	}
}

/* Shared tooltip — placement-specific anchor modifiers applied by JS. */
.os-dock__tooltip {
	position: fixed;
	padding: 6px 12px;
	background: var(
		--os-tooltip-bg,
		var( --os-ui-scrim, rgba( 0, 0, 0, 0.85 ) )
	);
	color: var(
		--os-tooltip-fg,
		var( --os-ui-fg-on-accent, #fff )
	);
	font-size: 12px;
	font-weight: 400;
	line-height: 1.4;
	white-space: nowrap;
	border-radius: 6px;
	pointer-events: none;
	z-index: calc( var( --os-z-dock ) + 1 );
	opacity: 0;
	transition: opacity 0.15s ease, transform 0.15s ease;
}

.os-dock__tooltip--visible {
	opacity: 1;
}

/* ------------------------------------------------------------------
 * Vertical placement (left + right) — dock runs the full shell body
 * height along one edge, with items stacked vertically.
 * ------------------------------------------------------------------ */

.os-dock[ data-os-dock-placement="left" ],
.os-dock[ data-os-dock-placement="right" ] {
	width: var( --os-dock-width );
	flex-direction: column;
	padding: 16px 0 12px;
	gap: 6px;
	/*
	 * Vertical docks split into two inner wrappers:
	 *   - `__scroll`  — flex: 1, scrollable, hosts menu tiles + the
	 *                   inline core/plugin separator. When the menu
	 *                   exceeds the dock's height, this is what
	 *                   scrolls; the outer dock stays a fixed pillar.
	 *   - `__pinned`  — sized to content, hosts system tiles
	 *                   (Recycle Bin, OS Settings, …) + their hairline
	 *                   separator. Always visible at the bottom edge
	 *                   regardless of scroll position.
	 *
	 * `min-height: 0` on the dock is defensive: even though `__scroll`
	 * absorbs the overflow, the dock is a flex item inside the shell
	 * body (a flex row), and the default `min-height: auto` would let
	 * a tall `__pinned` push the dock past the parent's height.
	 */
	min-height: 0;
}

/*
 * Scrollable menu-tile area inside vertical docks. `flex: 1 1 0` plus
 * `min-height: 0` lets it shrink below its content's natural height so
 * `overflow-y: auto` actually engages. `overflow-x: clip` keeps the
 * active-dot / focused-pill indicators visible (they sit at x≈2px
 * inside the wrapper's content box) while preventing `overflow-y: auto`
 * from coercing horizontal scroll. Scrollbar is hidden visually —
 * Firefox via `scrollbar-width: none`, WebKit via the pseudo-element
 * rule below — so the rail keeps its clean macOS look. Mouse wheel,
 * touchpad, touch, and keyboard focus-into-view all still work.
 */
.os-dock[ data-os-dock-placement="left" ] .os-dock__scroll,
.os-dock[ data-os-dock-placement="right" ] .os-dock__scroll {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	flex: 1 1 0;
	min-height: 0;
	width: 100%;
	overflow-y: auto;
	overflow-x: clip;
	scrollbar-width: none;
}

.os-dock[ data-os-dock-placement="left" ] .os-dock__scroll::-webkit-scrollbar,
.os-dock[ data-os-dock-placement="right" ] .os-dock__scroll::-webkit-scrollbar {
	display: none;
}

/*
 * Pinned area below `__scroll` — system tiles stay reachable here
 * regardless of how the menu list scrolls. `flex-shrink: 0` so it's
 * never compressed; sized to its content's natural height.
 */
.os-dock[ data-os-dock-placement="left" ] .os-dock__pinned,
.os-dock[ data-os-dock-placement="right" ] .os-dock__pinned {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	flex-shrink: 0;
	width: 100%;
}

/* Left: dock is the first flex item in __body (row layout). */
.os-dock[ data-os-dock-placement="left" ] {
	border-inline-end: 1px solid var( --os-dock-border );
	order: -1;
}

/* Right: push the dock to the end of __body and flip the border edge. */
.os-dock[ data-os-dock-placement="right" ] {
	border-inline-start: 1px solid var( --os-dock-border );
	order: 1;
}

/* Active-indicator dot — left dock: inside-edge vertical pill. */
.os-dock[ data-os-dock-placement="left" ]
	.os-dock__item--active::before {
	content: "";
	position: absolute;
	inset-inline-start: -6px;
	top: 50%;
	transform: translateY( -50% );
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: var( --os-dock-item-outline, #fff );
}

.os-dock[ data-os-dock-placement="left" ]
	.os-dock__item--focused::before {
	width: 4px;
	height: 16px;
	border-radius: 2px;
}

/*
 * "All instances minimized" — the tile still has windows, they're
 * just hidden. Swap the solid dot for a hollow ring so the user sees
 * "I have something here, currently tucked away" instead of "I have
 * a focused / visible window here." `--all-minimized` is layered on
 * top of `--active`; the rule below wins on selector specificity.
 *
 * Sized slightly larger than the solid dot so the ring is legible at
 * dock size — a 4×4 outline reads as a dirty pixel.
 */
.os-dock[ data-os-dock-placement="left" ]
	.os-dock__item--all-minimized::before {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: transparent;
	border: 1px solid var( --os-dock-item-outline, rgba( 255, 255, 255, 0.85 ) );
}

/* Right dock: indicator on the opposite edge (inside-facing). */
.os-dock[ data-os-dock-placement="right" ]
	.os-dock__item--active::before {
	content: "";
	position: absolute;
	inset-inline-end: -6px;
	top: 50%;
	transform: translateY( -50% );
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: var( --os-dock-item-outline, #fff );
}

.os-dock[ data-os-dock-placement="right" ]
	.os-dock__item--focused::before {
	width: 4px;
	height: 16px;
	border-radius: 2px;
}

.os-dock[ data-os-dock-placement="right" ]
	.os-dock__item--all-minimized::before {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: transparent;
	border: 1px solid var( --os-dock-item-outline, rgba( 255, 255, 255, 0.85 ) );
}

/* "Open another" chip — left: right of tile; right: left of tile. */
.os-dock[ data-os-dock-placement="left" ]
	.os-dock__item-new {
	top: 50%;
	inset-inline-end: -13px;
	transform: translateY( -50% );
}

.os-dock[ data-os-dock-placement="left" ]
	.os-dock__item-new:hover {
	transform: translateY( -50% ) scale( 1.1 );
}

.os-dock[ data-os-dock-placement="right" ]
	.os-dock__item-new {
	top: 50%;
	inset-inline-start: -13px;
	transform: translateY( -50% );
}

.os-dock[ data-os-dock-placement="right" ]
	.os-dock__item-new:hover {
	transform: translateY( -50% ) scale( 1.1 );
}

/* Separator — the line between two groups of tiles. With the
 * `__scroll` / `__pinned` split the system separator lives at the top
 * of `__pinned`; flex-flow handles the "system tiles at the bottom"
 * placement, no margin-top: auto needed. The `--group` variant is used
 * inline between core and plugin menu tiles in `__scroll` and sits
 * exactly where it's inserted.
 *
 * ONE treatment for every division in the rail. The selector carries
 * only the base class on purpose: `--group` elements carry it too, so
 * both boundaries resolve here and cannot drift apart. Two weights in
 * one short rail read as two unrelated ideas rather than one system,
 * which is why the earlier loud-plus-hairline pairing is gone.
 *
 * A gradient, not a flat fill: the line is brightest where the eye
 * lands and gone by the time it reaches the rail's padding, so it
 * never terminates in a visible stub against the glass. The soft glow
 * around it is what keeps a 2px line from disappearing into the dock
 * tint; nothing is drawn on the line itself. */
.os-dock[ data-os-dock-placement="left" ]
	.os-dock__separator,
.os-dock[ data-os-dock-placement="right" ]
	.os-dock__separator {
	position: relative;
	width: 60%;
	height: 2px;
	margin: 12px auto;
	border-radius: 2px;
	background: linear-gradient(
		to right,
		transparent 0%,
		var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 28%,
		var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 72%,
		transparent 100%
	);
	box-shadow: 0 0 10px
		color-mix(
			in srgb,
			var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 45%,
			transparent
		);
	flex-shrink: 0;
}

/*
 * Hide the system separator when nothing precedes it — on a clean
 * install (or a low-cap user with zero menu items) the separator
 * would render as a stray hairline with nothing to divide.
 *
 * Two cases:
 *   1. Bottom dock — separator is a direct child of the dock; hide
 *      when it's the first such child.
 *   2. Vertical docks — separator lives inside `__pinned`; hide when
 *      `__scroll` (its sibling above) is empty of menu tiles.
 */
.os-dock > .os-dock__separator:first-child {
	display: none;
}

.os-dock__scroll:not(:has( .os-dock__item ))
	+ .os-dock__pinned
	.os-dock__separator {
	display: none;
}


/*
 * Drop an empty `__scroll` from the flex flow entirely. When only
 * system tiles are present (e.g. just the Recycle Bin), the empty
 * wrapper would otherwise still occupy the rail: its `flex-grow`
 * stretches it — shoving the lone `__pinned` tile to the far edge on
 * vertical docks — and the inter-wrapper `gap` leaves a phantom
 * offset that nudges the tile off-center on the bottom dock. Removing
 * it lets the dock center the lone system tile. As soon as a menu
 * tile is added the `:has()` no longer matches and normal flow
 * resumes.
 *
 * The `[data-os-dock-placement]` qualifier is load-bearing:
 * the per-placement rules that set `display: flex` on `__scroll` are
 * (0,3,0); this selector must out-specify them, so it carries the
 * placement attribute to reach (0,4,0).
 */
.os-dock[ data-os-dock-placement ]
	.os-dock__scroll:not(:has( .os-dock__item )) {
	display: none;
}

/* Tooltip — anchor modifiers are applied directly to the tooltip
 * element (NOT via a descendant selector on `.os-dock`)
 * because the tooltip lives in `document.body`. JS writes the
 * absolute `top` / `left` coords; CSS handles the slide-in
 * animation direction per modifier.
 *
 *   - `--after`   : left-placed dock  (tooltip to the RIGHT of tile)
 *   - `--before`  : right-placed dock (tooltip to the LEFT of tile)
 *   - `--above`   : bottom-placed dock (tooltip ABOVE the tile)
 */

/* Left dock — tooltip slides in from the left by 4px. JS sets
 * `left` to `tile.right + 8px`; the transform animates the entry. */
.os-dock__tooltip--after {
	transform: translateX( -4px );
}

.os-dock__tooltip--after.os-dock__tooltip--visible {
	transform: translateX( 0 );
}

.os-dock__tooltip--before {
	/* Right-placed dock — tooltip sits to the left of the tile. JS
	 * writes the left coord to the tile's left edge; this translate
	 * pulls the tooltip further left by its own width. */
	transform: translate( calc( -100% - 8px ), 0 );
}

.os-dock__tooltip--before.os-dock__tooltip--visible {
	transform: translate( calc( -100% - 8px ), 0 );
	opacity: 1;
}

/* ------------------------------------------------------------------
 * Bottom placement — floating macOS-style pill centered above the
 * viewport floor. Absolutely positioned so it floats as pure chrome
 * rather than stealing vertical space from the shell body.
 * ------------------------------------------------------------------ */

.os-dock[ data-os-dock-placement="bottom" ] {
	position: absolute;
	inset-inline-start: 0;
	inset-inline-end: 0;
	bottom: 12px;
	margin: 0 auto;
	width: fit-content;
	max-width: calc( 100% - 32px );
	flex-direction: row;
	justify-content: center;
	/* Top padding is 4px instead of 8px so that the inner `__scroll`
	 * wrapper can claim its own 4px padding-top for badge clearance
	 * (the tile's badge sits at top: -3px). Net vertical chrome stays
	 * symmetric: 4px pill padding + 4px wrapper padding above the
	 * tile, 8px pill padding below — tile is 8px from both edges. */
	padding: 4px 12px 8px;
	gap: 6px;
	border-radius: 18px;

	/* Warmer, more saturated glass tint than vertical placements so
	 * the floating pill reads as a distinct piece of chrome rather
	 * than a section of the sidebar.
	 *
	 * background-COLOR, not the shorthand: the base `.os-dock`
	 * rule declares the DOCK texture slot's background-image, and a
	 * shorthand here would reset it — a themed dock texture would
	 * simply never appear in the bottom placement.
	 *
	 * The tint is its own token rather than `--os-dock-bg`
	 * precisely because the two are meant to differ; a theme that
	 * wants them identical points this at the same value. */
	background-color: var(
		--os-dock-floating-bg,
		rgba( 22, 22, 26, 0.42 )
	);
	backdrop-filter: blur( 28px ) saturate( 170% );
	-webkit-backdrop-filter: blur( 28px ) saturate( 170% );

	/*
	 * Hairline + inset highlight. These used to be hardcoded, which
	 * meant a theme could restyle every dock placement EXCEPT this
	 * one and had no way to remove the outline it saw floating over
	 * an empty desktop.
	 *
	 * The chain matters: the floating-specific token wins, then the
	 * general `--os-dock-border` (so a theme that tokenized
	 * its side docks gets the pill for free, which is what anyone
	 * setting that token expects), then the literal. Set either to
	 * `transparent` for a borderless pill.
	 */
	border: 1px solid var(
		--os-dock-floating-border,
		var( --os-dock-border, rgba( 255, 255, 255, 0.12 ) )
	);
	border-block-start: 1px solid var(
		--os-dock-floating-border-top,
		var(
			--os-dock-floating-border,
			var( --os-dock-border, rgba( 255, 255, 255, 0.18 ) )
		)
	);
	box-shadow:
		inset 0 1px 0 var(
			--os-dock-floating-highlight,
			rgba( 255, 255, 255, 0.08 )
		),
		0 10px 32px var(
			--os-dock-floating-shadow,
			rgba( 0, 0, 0, 0.45 )
		);
}

/* Active-indicator dot — below the tile, macOS-style. */
.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__item--active::before {
	content: "";
	position: absolute;
	inset-inline-start: 50%;
	top: auto;
	bottom: -3px;
	transform: translateX( -50% );
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: var( --os-dock-item-outline, #fff );
}

.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__item--focused::before {
	width: 16px;
	height: 4px;
	border-radius: 2px;
}

/*
 * "All instances minimized" on the bottom dock — solid dot becomes
 * a hollow ring. Matches the vertical-dock treatment so the cue is
 * consistent across placements.
 */
.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__item--all-minimized::before {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: transparent;
	border: 1px solid var( --os-dock-item-outline, rgba( 255, 255, 255, 0.85 ) );
}

/*
 * "Show Desktop" — every live window on the active desktop minimized.
 *
 * The dock does not change when this happens. It used to: the bottom
 * pill and the vertical rails each picked up a 1px inset ring, on the
 * reasoning that an empty wallpaper otherwise looks identical to a
 * desktop that never had anything on it.
 *
 * The reasoning was sound and the ring was still the wrong answer. It
 * outlined the whole dock — the one surface on screen that is always
 * present and never the subject — to say something about windows that
 * are not on the dock at all. Every tile that has minimized windows
 * already says so, precisely and locally, by swapping its solid
 * indicator dot for a hollow ring. That is the cue: it points at the
 * tiles the windows are under, in the place the user will click to get
 * them back.
 *
 * `body.os-show-desktop-active` is still set (see `src/dock.ts`) and
 * still public, so a theme or plugin that wants a global cue can paint
 * one. Core does not.
 */

/* "Open another" chip — floats above the tile's top-right corner so
 * it doesn't collide with neighbouring tiles in the horizontal row. */
.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__item-new {
	top: -6px;
	inset-inline-end: -6px;
	transform: none;
}

.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__item-new:hover {
	transform: scale( 1.1 );
}

/*
 * Bottom-dock inner wrappers — `__scroll` carries the menu tiles and
 * absorbs horizontal overflow; `__pinned` carries the system tiles and
 * stays anchored to the trailing edge.
 *
 * **The padding around `__scroll` is what keeps badges visible, and it
 * has to be on both axes.** `overflow-x: auto` below cannot coexist
 * with `overflow-y: visible`: per spec the visible axis computes to
 * `auto`, so this wrapper is a scroll container in BOTH directions
 * however it is authored, and anything a child hangs outside its
 * padding box is clipped. A badge sits at `top: -3px; right: -3px` on
 * its tile, and the LAST tile's edge is the wrapper's edge once the
 * content is wide enough to scroll — which is why this showed up as
 * "the badge on the last plugin looks cut off" rather than as a
 * general problem. The inline padding is symmetric so the tile cluster
 * stays centred in the pill. `min-width: 0` lets the wrapper shrink below its
 * content's natural width, so the dock pill's `max-width` is what
 * decides when scroll kicks in instead of the content forcing the
 * pill wider. `justify-content: center` keeps tiles balanced inside
 * the pill when content fits; when it overflows, scroll engages and
 * items extend past the visible area.
 */
.os-dock[ data-os-dock-placement="bottom" ] .os-dock__scroll {
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: center;
	gap: 6px;
	flex: 1 1 auto;
	min-width: 0;
	padding-top: 4px;
	padding-bottom: 6px;
	padding-inline: 4px;
	overflow-x: auto;
	/*
	 * Authored `visible`, computed `auto` — see the note above. Kept as
	 * the honest statement of intent: nothing here wants a vertical
	 * scrollbar, and `scrollbar-width: none` plus the padding is what
	 * makes that true in practice.
	 */
	overflow-y: visible;
	scrollbar-width: none;
}

.os-dock[ data-os-dock-placement="bottom" ] .os-dock__scroll::-webkit-scrollbar {
	display: none;
}

.os-dock[ data-os-dock-placement="bottom" ] .os-dock__pinned {
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: 6px;
	flex-shrink: 0;
	/* Match the scroll wrapper's badge clearance so system tiles sit
	 * on the same vertical baseline as the menu tiles. */
	padding-top: 4px;
	padding-bottom: 6px;
}

/* Separator — the line between two groups of tiles. The system one
 * lives at the leading edge of `__pinned` (flex flow handles the
 * "pinned at the trailing edge" placement, no margin-inline-start:
 * auto needed); the `--group` one sits inline inside `__scroll` where
 * it was inserted. Both resolve through this single rule — see the
 * note on the vertical placements above for why there is only one. */
.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__separator {
	position: relative;
	width: 2px;
	/* Fixed height — `60%` collapses against the floating pill's
	   intrinsic height, leaving the line effectively invisible. */
	height: 34px;
	/* Symmetric horizontal margin so the divider sits centered in the
	 * inter-tile gap: the 6px flex gap on each side (`__scroll`→`__pinned`
	 * on the left, `__pinned`'s own gap on the right) plus an equal
	 * margin per side balances out. An asymmetric margin here nudges the
	 * divider — and the whole menu cluster with it — off the pill's
	 * center. Wider than the tile gap on purpose: the clusters need to
	 * read as groups before the line between them means anything. */
	margin: auto 14px;
	border-radius: 2px;
	background: linear-gradient(
		to bottom,
		transparent 0%,
		var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 28%,
		var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 72%,
		transparent 100%
	);
	box-shadow: 0 0 10px
		color-mix(
			in srgb,
			var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 45%,
			transparent
		);
	flex-shrink: 0;
}

/* Tooltip — above the hovered tile. JS writes the horizontal center
 * + a top near the tile's top edge; CSS translates the tooltip up by
 * its own height so it clears the tile. */
.os-dock__tooltip--above {
	inset-inline-start: auto;
	transform: translate( -50%, calc( -100% - 4px ) );
}

.os-dock__tooltip--above.os-dock__tooltip--visible {
	transform: translate( -50%, -100% );
}



/* Drag-to-reorder (since 0.25.0).
 *
 * The dragged tile follows the cursor via inline `transform:
 * translate()` written from JS. It stays IN flow so flex doesn't
 * collapse the row — the visual lift is a z-index bump and
 * `pointer-events: none` so `elementFromPoint` finds siblings
 * underneath the cursor. Sibling tiles animate via FLIP — the JS
 * writes inline `transform` + `transition` per leg, so no CSS
 * transition is declared here (it would race with the per-leg
 * inline transition). */
.os-dock__item--dragging {
	pointer-events: none;
	z-index: 50;
	opacity: 0.92;
	cursor: grabbing;
	will-change: transform;
	/* Subtle shadow so the dragged tile reads as "lifted" against
	 * the dock surface it floats above. */
	filter: drop-shadow( 0 6px 12px rgba( 0, 0, 0, 0.35 ) );
}

.os-dock__item:not( .os-dock__item--system ) {
	cursor: grab;
}

.os-dock__item:not( .os-dock__item--system ):active {
	cursor: grabbing;
}

/* ------------------------------------------------------------------
 * The way out — `Exit OpenStation`
 *
 * Every other system tile opens something you can close again. This
 * one leaves the desktop, and with the admin bar hidden by default it
 * is the only route back to classic admin. Drawn like its neighbours
 * it read as one more launcher, which is the same "some tiles do a
 * different kind of thing" confusion the single dock set out to fix,
 * just moved to the other end of the rail.
 *
 * Three signals, none of them colour. Danger red would overstate it
 * (nothing is destroyed, the session is saved and the desktop is one
 * click away) and Pulse is already spent on the seam:
 *
 *   1. **Last, always.** `order: 1` rather than registration order —
 *      native-window tiles from plugins sync in after boot and would
 *      otherwise land behind it.
 *   2. **Its own gap**, wider than the rail's 6px, so it reads as
 *      sitting outside the set rather than at the end of it. A second
 *      divider would have said this too, and would have cost the rail
 *      the one-structural-line rule it just earned.
 *   3. **A different silhouette** — a ring rather than a plate. Shape
 *      survives every theme, every dock texture, greyscale and a
 *      colour-blind reading of the rail; a tint survives none of them
 *      reliably.
 *
 * And it leans toward the edge it leads to on hover instead of lifting
 * toward the pointer, because every other tile's lift means "I will
 * come to you" and this one means the opposite.
 *
 * Keyed on the tile id, the same idiom the Recycle Bin badge uses
 * above. `os-exit` is frozen in `src/exit-openstation.ts`
 * (`EXIT_OPENSTATION_TILE_ID`) and `dock-exit-tile.test.ts` pins the
 * attribute this selector depends on.
 * ------------------------------------------------------------------ */

.os-dock__item[ data-system-id="os-exit" ] {
	order: 1;
}

.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary {
	border-radius: 50%;
	background-color: transparent;
	box-shadow: inset 0 0 0 1px var(
		--os-dock-exit-ring,
		var( --os-dock-border, rgba( 255, 255, 255, 0.18 ) )
	);
	color: var( --os-dock-exit-icon, rgba( 255, 255, 255, 0.55 ) );
}

/*
 * The gap runs along the rail's own axis, so it follows the placement:
 * inline for the horizontal pill, block for the vertical pillars.
 */
.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__item[ data-system-id="os-exit" ] {
	margin-inline-start: 10px;
}

.os-dock[ data-os-dock-placement="left" ]
	.os-dock__item[ data-system-id="os-exit" ],
.os-dock[ data-os-dock-placement="right" ]
	.os-dock__item[ data-system-id="os-exit" ] {
	margin-block-start: 8px;
}

/*
 * Hover: the ring closes up and the tile moves TOWARD its edge. The
 * generic tile rule scales up by 1.1, so these have to out-specify it
 * rather than sit alongside it.
 */
.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary:hover,
.os-dock__item[ data-system-id="os-exit" ]
	.os-dock__item-primary:focus-visible {
	background-color: transparent;
	box-shadow: inset 0 0 0 1px var(
		--os-dock-exit-ring-hover,
		var( --os-dock-item-outline, rgba( 255, 255, 255, 0.7 ) )
	);
	color: var( --os-dock-icon-color-hover, var( --os-ui-fg-on-accent, #fff ) );
}

.os-dock[ data-os-dock-placement="bottom" ]
	.os-dock__item[ data-system-id="os-exit" ]
	.os-dock__item-primary:hover {
	transform: translateY( 2px );
}

.os-dock[ data-os-dock-placement="left" ]
	.os-dock__item[ data-system-id="os-exit" ]
	.os-dock__item-primary:hover {
	transform: translateX( -2px );
}

.os-dock[ data-os-dock-placement="right" ]
	.os-dock__item[ data-system-id="os-exit" ]
	.os-dock__item-primary:hover {
	transform: translateX( 2px );
}

/* The press keeps the shared cue: every tile in the rail dips on
 * :active, and this one should not feel unresponsive by comparison. */
.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary:active {
	transform: scale( 0.95 );
}

@media ( prefers-reduced-motion: reduce ) {
	.os-dock[ data-os-dock-placement="bottom" ]
		.os-dock__item[ data-system-id="os-exit" ]
		.os-dock__item-primary:hover,
	.os-dock[ data-os-dock-placement="left" ]
		.os-dock__item[ data-system-id="os-exit" ]
		.os-dock__item-primary:hover,
	.os-dock[ data-os-dock-placement="right" ]
		.os-dock__item[ data-system-id="os-exit" ]
		.os-dock__item-primary:hover {
		transform: none;
	}
}

/* ------------------------------------------------------------------
 * Dynamic dock behavior — OpenStation Preferences → Appearance →
 * Desktop layout, persisted as `dockBehavior` (the dock) and
 * `sideDockBehavior` (the Split sidebar) and worn by each rail as
 * `data-os-dock-behavior` — an attribute PER RAIL rather than a body
 * class, because Split's two rails answer independently. PHP stamps
 * the dock on first paint; the apply pass and `src/dock-behavior.ts`
 * re-stamp on every change and rebuild. `static` is the absence of
 * rules.
 *
 * A dynamic rail that is not `os-dock--revealed` is PARKED: it
 * collapses into a thin indicator line hugging its edge — the iOS
 * home indicator, one line that says "there is a dock here" — and
 * expands back into the full rail when the pointer comes for it.
 * `src/dock-behavior.ts` owns the flip (a full-width edge zone, the
 * rail's own box, its flyouts, keyboard focus) and runs it through
 * the View Transitions API, so the line morphs into the pill and
 * back; the rules below only describe the two resting states and
 * tune the morph. There is deliberately no `:hover` here — a state
 * CSS flipped on its own would jump instead of morphing.
 *
 * The line is the rail itself, not a separate element: same node,
 * same `view-transition-name`, which is what lets the browser animate
 * one box into the other. Its children are hidden while parked so
 * the line is a line. A side rail leaves the flex row for the
 * duration, so the area grows to the full width and the rail rides
 * over it when summoned; the work area reserves nothing for a
 * dynamic rail (`src/work-area/index.ts`).
 * ------------------------------------------------------------------ */

/*
 * Side rails overlay the area in both states — the flex row must not
 * shrink for a rail that is only sometimes there.
 */
.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ],
.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] {
	position: absolute;
	inset-block: 0;
}

.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ] {
	inset-inline-start: 0;
}

.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] {
	inset-inline-end: 0;
}

/*
 * Parked — the indicator line. Everything that makes the pill a
 * pill (padding, border, blur, texture, tile gap) comes off, and the
 * box is the line's size. `overflow: hidden` clips whatever a tile
 * paints past its box during the morph.
 */
.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) {
	min-width: 0;
	min-height: 0;
	max-width: none;
	padding: 0;
	gap: 0;
	border: 0;
	border-radius: 999px;
	overflow: hidden;
	background-color: var(
		--os-dock-indicator-bg,
		var( --os-dock-item-outline, rgba( 255, 255, 255, 0.55 ) )
	);
	background-image: none;
	backdrop-filter: none;
	-webkit-backdrop-filter: none;
	box-shadow: none;
	cursor: pointer;
}

.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) > * {
	display: none;
}

.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="bottom" ]:not( .os-dock--revealed ) {
	width: var( --os-dock-indicator-length, 180px );
	height: var( --os-dock-indicator-thickness, 5px );
	bottom: 8px;
}

.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ),
.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) {
	width: var( --os-dock-indicator-thickness, 5px );
	height: var( --os-dock-indicator-length, 180px );
	inset-block: calc( 50% - var( --os-dock-indicator-length, 180px ) / 2 );
}

.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ) {
	inset-inline-start: 8px;
}

.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) {
	inset-inline-end: 8px;
}

/*
 * The morph. With the API, `src/dock-behavior.ts` names the rail for
 * the duration of one flip and the browser animates the old box into
 * the new one; the rail's contents cross-fade underneath. The root is
 * opted out of the snapshot so the rest of the desktop keeps
 * running while the line becomes a pill — a frozen page for 260ms
 * on every reveal would be the animation costing more than it
 * gives. Without the API, the properties that can transition do.
 */
@supports ( view-transition-name: none ) {
	html.os-dock-vt {
		view-transition-name: none;
	}

	html.os-dock-vt::view-transition-old( root ),
	html.os-dock-vt::view-transition-new( root ) {
		animation: none;
	}

	/*
	 * The transition layer must not catch the pointer. By default it
	 * does — the whole page reads as inert until the morph settles —
	 * and with the root opted out of the snapshot that is worse than
	 * pointless: the live desktop is right there under a layer that
	 * swallows every mouseup and every pointerenter for 260ms. The
	 * behavior module already holds a park back while a button is
	 * down; this is the other half, so a hover that lands on a tile
	 * while the rail is still morphing out starts its hover-intent
	 * timer instead of waiting for the next pointer movement.
	 */
	html.os-dock-vt::view-transition {
		pointer-events: none;
	}

	::view-transition-group( os-dock-os-dock ),
	::view-transition-group( os-dock-os-side-dock ) {
		animation-duration: 260ms;
		animation-timing-function: cubic-bezier( 0.22, 1, 0.36, 1 );
	}

	::view-transition-old( os-dock-os-dock ),
	::view-transition-old( os-dock-os-side-dock ) {
		animation: os-dock-vt-out 120ms ease-out both;
	}

	::view-transition-new( os-dock-os-dock ),
	::view-transition-new( os-dock-os-side-dock ) {
		animation: os-dock-vt-in 180ms ease-out 80ms both;
	}

	@keyframes os-dock-vt-out {
		to {
			opacity: 0;
		}
	}

	@keyframes os-dock-vt-in {
		from {
			opacity: 0;
		}
	}
}

@supports not ( view-transition-name: none ) {
	.os-dock[ data-os-dock-behavior="dynamic" ] {
		transition:
			width 240ms ease,
			height 240ms ease,
			opacity 240ms ease,
			border-radius 240ms ease,
			bottom 240ms ease,
			inset-block 240ms ease;
	}
}

@media ( prefers-reduced-motion: reduce ) {
	.os-dock[ data-os-dock-behavior="dynamic" ] {
		transition: none;
	}
}

```
