PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 All 36 releases
← All changes | assets/css/dock.css +640 -283 0.9.8 → 1.1.12 View file →
@@ -1,10 +1,10 @@
1 1 /**
2 - * Desktop Mode — Dock.
2 + * OpenStation — Dock.
3 3 *
4 4 * A rail that hosts admin menu tiles plus shell-level system tiles.
5 5 * Placement (left / right / bottom) is reflected on each dock element
6 - * itself as `data-desktop-mode-dock-placement`; this stylesheet keys
6 + * itself as `data-os-dock-placement`; this stylesheet keys
7 7 * off that attribute for layout, tooltip anchor, and indicator
8 8 * position. Two instances can coexist (Classic layout: a left side
9 9 * bar with core menus + a bottom dock with plugin menus); each
10 10 * carries its own placement attribute, so the two rails are styled
@@ -16,13 +16,13 @@
16 16 /* ------------------------------------------------------------------
17 17 * Shared dock chrome — applies to every placement.
18 18 * ------------------------------------------------------------------ */
19 19
20 -.desktop-mode-dock {
20 +.os-dock {
21 21 display: flex;
22 22 align-items: center;
23 - z-index: var( --desktop-mode-z-dock );
24 - background-color: var( --desktop-mode-dock-bg );
23 + z-index: var( --os-z-dock );
24 + background-color: var( --os-dock-bg );
25 25 /*
26 26 * Desktop-theme dock texture (DOCK slot). Layered OVER the dock
27 27 * background colour, so a theme can ship a semi-transparent
28 28 * texture and still get the translucent wash underneath. `none`
@@ -27,12 +27,12 @@
27 27 * background colour, so a theme can ship a semi-transparent
28 28 * texture and still get the translucent wash underneath. `none`
29 29 * when unset — no theme, no cost.
30 30 */
31 - background-image: var( --desktop-mode-dock-bg-image, none );
32 - background-repeat: var( --desktop-mode-dock-bg-image-repeat, repeat );
33 - background-size: var( --desktop-mode-dock-bg-image-size, auto );
34 - background-position: var( --desktop-mode-dock-bg-image-position, center );
31 + background-image: var( --os-dock-bg-image, none );
32 + background-repeat: var( --os-dock-bg-image-repeat, repeat );
33 + background-size: var( --os-dock-bg-image-size, auto );
34 + background-position: var( --os-dock-bg-image-position, center );
35 35 backdrop-filter: blur( 20px );
36 36 -webkit-backdrop-filter: blur( 20px );
37 37 flex-shrink: 0;
38 38 /*
@@ -45,9 +45,9 @@
45 45 overflow: visible;
46 46 }
47 47
48 48 /* A single dock tile — icon button (+ optional instance rail below). */
49 -.desktop-mode-dock__item {
49 +.os-dock__item {
50 50 position: relative;
51 51 display: flex;
52 52 flex-direction: column;
53 53 align-items: center;
@@ -55,9 +55,9 @@
55 55 flex-shrink: 0;
56 56 }
57 57
58 58 /* Primary icon button — opens or focuses the page. */
59 -.desktop-mode-dock__item-primary {
59 +.os-dock__item-primary {
60 60 position: relative;
61 61 display: flex;
62 62 align-items: center;
63 63 justify-content: center;
@@ -74,40 +74,51 @@
74 74 * relying on the dock strip's texture showing through. Painted
75 75 * under the icon; the hover wash below is a background-COLOR so
76 76 * it composes with the texture rather than erasing it.
77 77 */
78 - background-image: var( --desktop-mode-dock-item-image, none );
79 - background-repeat: var( --desktop-mode-dock-item-image-repeat, no-repeat );
80 - background-size: var( --desktop-mode-dock-item-image-size, auto );
81 - background-position: var( --desktop-mode-dock-item-image-position, center );
78 + background-image: var( --os-dock-item-image, none );
79 + background-repeat: var( --os-dock-item-image-repeat, no-repeat );
80 + background-size: var( --os-dock-item-image-size, auto );
81 + background-position: var( --os-dock-item-image-position, center );
82 82 /*
83 83 * Dock glyph colour. The literal is what this rule always said;
84 84 * the token is the name a theme aims at when its dock strip is
85 85 * not dark. See the "Dock glyphs" block in variables.css.
86 86 */
87 - color: var( --desktop-mode-dock-icon-color, rgba( 255, 255, 255, 0.7 ) );
87 + color: var( --os-dock-icon-color, rgba( 255, 255, 255, 0.7 ) );
88 88 transition: background-color 0.15s ease, transform 0.15s ease, color 0.15s ease;
89 89 }
90 90
91 -.desktop-mode-dock__item-primary:hover {
91 +/*
92 + * The tile whose window is in front sits on a lit plate. Declared
93 + * before the hover rule at the same specificity so the pointer still
94 + * wins while it is over the tile. Falls back to nothing, which is
95 + * what every placement drew before the token existed.
96 + */
97 +.os-dock__item--focused .os-dock__item-primary {
98 + background-color: var( --os-dock-item-bg-focused, transparent );
99 + color: var( --os-dock-icon-color-hover, var( --os-ui-fg-on-accent, #fff ) );
100 +}
101 +
102 +.os-dock__item-primary:hover {
92 103 background-color: var(
93 - --desktop-mode-dock-item-bg-hover,
104 + --os-dock-item-bg-hover,
94 105 rgba( 255, 255, 255, 0.15 )
95 106 );
96 107 color: var(
97 - --desktop-mode-dock-icon-color-hover,
98 - var( --wpd-fg-on-accent, #fff )
108 + --os-dock-icon-color-hover,
109 + var( --os-ui-fg-on-accent, #fff )
99 110 );
100 111 transform: scale( 1.1 );
101 112 }
102 113
103 -.desktop-mode-dock__item-primary:active {
114 +.os-dock__item-primary:active {
104 115 transform: scale( 0.95 );
105 116 }
106 117
107 -.desktop-mode-dock__item-primary:focus-visible {
118 +.os-dock__item-primary:focus-visible {
108 119 outline: 2px solid var(
109 - --desktop-mode-dock-item-outline,
120 + --os-dock-item-outline,
110 121 rgba( 255, 255, 255, 0.7 )
111 122 );
112 123 outline-offset: 2px;
113 124 }
@@ -113,29 +124,37 @@
113 124 }
114 125
115 126 /* Stacked card hint on hover — a second card peeks from behind the
116 127 icon when the tile has multiple open windows. Only visible on hover so
117 - it never conflicts with focused tiles. */
118 -.desktop-mode-dock__item--stacked:hover .desktop-mode-dock__item-primary::before,
119 -.desktop-mode-dock__item--stacked[data-peek-active] .desktop-mode-dock__item-primary::before {
128 + it never conflicts with focused tiles.
129 + *
130 + * Painted on the TILE, not on the button it hints at. The hover that
131 + * arms this also scales the button, a transform makes a stacking
132 + * context, and a `z-index: -1` child of one cannot escape it: the card
133 + * landed above the button's own hover plate instead of behind the
134 + * tile, reading as a second plate laid over the first. The tile is
135 + * transformed by nothing, and a positioned child (the button) paints
136 + * over a parent's `::before` at the same level, so the card sits where
137 + * its name says without asking for a z-index at all. */
138 +.os-dock__item--stacked:hover::before,
139 +.os-dock__item--stacked[ data-peek-active ]::before {
120 140 content: "";
121 141 position: absolute;
122 142 top: -4px;
123 143 inset-inline-start: -4px;
124 - width: 100%;
125 - height: 100%;
126 - border-radius: inherit;
144 + width: 40px;
145 + height: 40px;
146 + border-radius: 10px;
127 147 background: rgba( 255, 255, 255, 0.06 );
128 148 border: 1px solid rgba( 255, 255, 255, 0.10 );
129 - z-index: -1;
130 149 pointer-events: none;
131 150 }
132 151
133 152 /* Dashicon inside dock item. */
134 -.desktop-mode-dock__item .dashicons {
135 - font-size: var( --desktop-mode-dock-icon-size, 20px );
136 - width: var( --desktop-mode-dock-icon-size, 20px );
137 - height: var( --desktop-mode-dock-icon-size, 20px );
153 +.os-dock__item .dashicons {
154 + font-size: var( --os-dock-icon-size, 20px );
155 + width: var( --os-dock-icon-size, 20px );
156 + height: var( --os-dock-icon-size, 20px );
138 157 line-height: 1;
139 158 }
140 159
141 160 /* SVG icon (for custom post types with data:image/svg+xml icons).
@@ -143,11 +162,11 @@
143 162 * The FALLBACK path now: `_makeSvgIcon()` paints these as a mask
144 163 * filled with `currentColor` (see `__item-mask` below) and only lands
145 164 * here when the mask refuses the URL — one carrying quotes, spaces or
146 165 * parens. The filter is what that fallback still needs. */
147 -.desktop-mode-dock__item-svg {
148 - width: var( --desktop-mode-dock-icon-size, 20px );
149 - height: var( --desktop-mode-dock-icon-size, 20px );
166 +.os-dock__item-svg {
167 + width: var( --os-dock-icon-size, 20px );
168 + height: var( --os-dock-icon-size, 20px );
150 169 display: block;
151 170 fill: currentColor;
152 171 opacity: 0.7;
153 172 /* Some plugin SVGs ship hardcoded `fill="..."` attributes inside
@@ -159,9 +178,9 @@
159 178 filter: brightness(0) invert(1);
160 179 transition: opacity 0.15s ease;
161 180 }
162 181
163 -.desktop-mode-dock__item-primary:hover .desktop-mode-dock__item-svg {
182 +.os-dock__item-primary:hover .os-dock__item-svg {
164 183 opacity: 1;
165 184 }
166 185
167 186 /*
@@ -172,9 +191,9 @@
172 191 *
173 192 * - A desktop theme's tinted iconset, filled with the colour the
174 193 * theme named for that slot.
175 194 * - Every other plugin / CPT image icon, filled with
176 - * `currentColor` — which is `--desktop-mode-dock-icon-color`, the
195 + * `currentColor` — which is `--os-dock-icon-color`, the
177 196 * same token the dashicons follow.
178 197 *
179 198 * Deliberately NOT `__item-svg`: that class force-whitens its content
180 199 * with `filter: brightness(0) invert(1)`. Both mechanisms discard the
@@ -189,11 +208,11 @@
189 208 * would silently mix it with the dock.
190 209 *
191 210 * @since 0.9.8
192 211 */
193 -.desktop-mode-dock__item-mask {
194 - width: var( --desktop-mode-dock-icon-size, 20px );
195 - height: var( --desktop-mode-dock-icon-size, 20px );
212 +.os-dock__item-mask {
213 + width: var( --os-dock-icon-size, 20px );
214 + height: var( --os-dock-icon-size, 20px );
196 215 display: block;
197 216 flex-shrink: 0;
198 217 }
199 218
@@ -201,9 +220,9 @@
201 220 * Letter-badge icon — rendered for items registered without a
202 221 * dashicon / SVG / image URL. Miniature "app placeholder": rounded
203 222 * square tinted with a title-derived hue, first letter centered.
204 223 */
205 -.desktop-mode-dock__item-letter {
224 +.os-dock__item-letter {
206 225 display: flex;
207 226 align-items: center;
208 227 justify-content: center;
209 228 width: 28px;
@@ -208,13 +227,13 @@
208 227 justify-content: center;
209 228 width: 28px;
210 229 height: 28px;
211 230 border-radius: 8px;
212 - color: var( --wpd-fg-on-accent, #fff );
231 + color: var( --os-ui-fg-on-accent, #fff );
213 232 font-size: 15px;
214 233 font-weight: 700;
215 234 font-family: var(
216 - --desktop-mode-font,
235 + --os-font,
217 236 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif
218 237 );
219 238 line-height: 1;
220 239 letter-spacing: 0;
@@ -223,17 +242,17 @@
223 242 inset 0 1px 0 rgba( 255, 255, 255, 0.2 ),
224 243 inset 0 -1px 0 rgba( 0, 0, 0, 0.15 );
225 244 }
226 245
227 -.desktop-mode-dock__item img.desktop-mode-dock__item-img {
228 - width: var( --desktop-mode-dock-icon-size, 20px );
229 - height: var( --desktop-mode-dock-icon-size, 20px );
246 +.os-dock__item img.os-dock__item-img {
247 + width: var( --os-dock-icon-size, 20px );
248 + height: var( --os-dock-icon-size, 20px );
230 249 display: block;
231 250 opacity: 0.7;
232 251 transition: opacity 0.15s ease;
233 252 }
234 253
235 -.desktop-mode-dock__item-primary:hover img.desktop-mode-dock__item-img {
254 +.os-dock__item-primary:hover img.os-dock__item-img {
236 255 opacity: 1;
237 256 }
238 257
239 258 /* "Open another" floating chip — shown on multi-capable tiles that
@@ -238,9 +257,9 @@
238 257
239 258 /* "Open another" floating chip — shown on multi-capable tiles that
240 259 * already have ≥1 open instance. Positioned inside each orientation
241 260 * block so the chip hugs the outward edge of the tile. */
242 -.desktop-mode-dock__item-new {
261 +.os-dock__item-new {
243 262 position: absolute;
244 263 display: flex;
245 264 align-items: center;
246 265 justify-content: center;
@@ -246,27 +265,27 @@
246 265 justify-content: center;
247 266 width: 20px;
248 267 height: 20px;
249 268 padding: 0;
250 - border: 2px solid var( --desktop-mode-dock-bg, rgba( 0, 0, 0, 0.4 ) );
269 + border: 2px solid var( --os-dock-bg, rgba( 0, 0, 0, 0.4 ) );
251 270 border-radius: 50%;
252 271 background: var( --wp-admin-theme-color, #2271b1 );
253 - color: var( --wpd-fg-on-accent, #fff );
272 + color: var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) );
254 273 cursor: pointer;
255 274 transition: background-color 0.15s ease, transform 0.15s ease;
256 275 z-index: 1;
257 276 }
258 277
259 -.desktop-mode-dock__item-new[hidden] {
278 +.os-dock__item-new[hidden] {
260 279 display: none;
261 280 }
262 281
263 -.desktop-mode-dock__item-new:hover {
264 - background: var( --wpd-surface, #fff );
282 +.os-dock__item-new:hover {
283 + background: var( --os-ui-surface, #fff );
265 284 color: var( --wp-admin-theme-color, #2271b1 );
266 285 }
267 286
268 -.desktop-mode-dock__item-new:focus-visible {
287 +.os-dock__item-new:focus-visible {
269 288 outline: 2px solid rgba( 255, 255, 255, 0.85 );
270 289 outline-offset: 2px;
271 290 }
272 291
@@ -272,9 +291,9 @@
272 291
273 292 /* Notification badge — iOS/macOS-style pill straddling the tile
274 293 * corner. Ring matches the dock bg so it reads as "punched out."
275 294 * Badge positioning is placement-aware via the blocks below. */
276 -.desktop-mode-dock__badge {
295 +.os-dock__badge {
277 296 /*
278 297 * Badge geometry DERIVES from the dock's icon size instead of
279 298 * being pinned to 16px.
280 299 *
@@ -284,30 +303,30 @@
284 303 * chosen so the DEFAULT dock (20px icons) resolves to exactly the
285 304 * old numbers — 16px box, 10px text, ~5px padding — so nothing
286 305 * moves for anyone who hasn't changed the setting.
287 306 *
288 - * `--desktop-mode-dock-badge-size` overrides the whole derivation
307 + * `--os-dock-badge-size` overrides the whole derivation
289 308 * for themes that want a specific badge; the font size and padding
290 309 * then follow it, so one token is usually enough.
291 310 */
292 - --desktop-mode-dock-badge-computed-size: var(
293 - --desktop-mode-dock-badge-size,
294 - calc( var( --desktop-mode-dock-icon-size, 20px ) * 0.8 )
311 + --os-dock-badge-computed-size: var(
312 + --os-dock-badge-size,
313 + calc( var( --os-dock-icon-size, 20px ) * 0.8 )
295 314 );
296 315 position: absolute;
297 - min-width: var( --desktop-mode-dock-badge-computed-size );
298 - height: var( --desktop-mode-dock-badge-computed-size );
316 + min-width: var( --os-dock-badge-computed-size );
317 + height: var( --os-dock-badge-computed-size );
299 318 padding: 0 var(
300 - --desktop-mode-dock-badge-padding,
301 - calc( var( --desktop-mode-dock-badge-computed-size ) * 0.3 )
319 + --os-dock-badge-padding,
320 + calc( var( --os-dock-badge-computed-size ) * 0.3 )
302 321 );
303 322 box-sizing: border-box;
304 323 border-radius: 999px;
305 324 background: var(
306 - --desktop-mode-dock-badge-bg,
307 - linear-gradient( 180deg, #ff5a5a 0%, var( --wpd-danger, #d63638 ) 100% )
325 + --os-dock-badge-bg,
326 + linear-gradient( 180deg, #ff5a5a 0%, var( --os-ui-danger, #d63638 ) 100% )
308 327 );
309 - color: var( --desktop-mode-dock-badge-fg, var( --wpd-fg-on-accent, #fff ) );
328 + color: var( --os-dock-badge-fg, var( --os-ui-fg-on-accent, #fff ) );
310 329 display: inline-flex;
311 330 align-items: center;
312 331 justify-content: center;
313 332 /*
@@ -321,26 +340,26 @@
321 340 * ratio below — it sits INSIDE the bin glyph, where the pill reads
322 341 * as part of the artwork and the number has to carry itself.
323 342 */
324 343 font-size: var(
325 - --desktop-mode-dock-badge-font-size,
326 - calc( var( --desktop-mode-dock-badge-computed-size ) * 0.625 )
344 + --os-dock-badge-font-size,
345 + calc( var( --os-dock-badge-computed-size ) * 0.625 )
327 346 );
328 - font-weight: var( --desktop-mode-dock-badge-font-weight, 700 );
347 + font-weight: var( --os-dock-badge-font-weight, 700 );
329 348 line-height: 1;
330 349 letter-spacing: 0.01em;
331 350 text-align: center;
332 351 font-variant-numeric: tabular-nums;
333 352 box-shadow:
334 - 0 0 0 1.5px var( --desktop-mode-dock-bg, rgba( 0, 0, 0, 0.4 ) ),
353 + 0 0 0 1.5px var( --os-dock-bg, rgba( 0, 0, 0, 0.4 ) ),
335 354 0 1px 3px rgba( 0, 0, 0, 0.35 );
336 355 pointer-events: none;
337 356 transition: transform 0.15s ease;
338 - top: var( --desktop-mode-dock-badge-offset, -3px );
339 - inset-inline-end: var( --desktop-mode-dock-badge-offset, -3px );
357 + top: var( --os-dock-badge-offset, -3px );
358 + inset-inline-end: var( --os-dock-badge-offset, -3px );
340 359 }
341 360
342 -.desktop-mode-dock__item:hover .desktop-mode-dock__badge {
361 +.os-dock__item:hover .os-dock__badge {
343 362 transform: scale( 1.05 );
344 363 top: -4px;
345 364 }
346 365
@@ -348,9 +367,9 @@
348 367 * Recycle Bin count — unlike unread/update badges, the trash count
349 368 * is ambient state. Keep it inside the bin glyph as a quiet neutral
350 369 * marker instead of a red alert on the tile corner.
351 370 */
352 -.desktop-mode-dock__item[ data-system-id="desktop-mode-recycle-bin" ] .desktop-mode-dock__badge {
371 +.os-dock__item[ data-system-id="desktop-mode-recycle-bin" ] .os-dock__badge {
353 372 /*
354 373 * Sized off the dock icon like every other badge, so it tracks the
355 374 * dock-size setting instead of sitting at a fixed 13px on an icon
356 375 * that may be 18, 20 or 26px.
@@ -363,34 +382,34 @@
363 382 * Override chain: bin-specific token → the generic dock-badge
364 383 * token → the derivation, so a theme that says "dock badges are
365 384 * 20px" reaches the bin without naming it.
366 385 */
367 - --desktop-mode-dock-recycle-badge-computed-size: var(
368 - --desktop-mode-dock-recycle-badge-size,
386 + --os-dock-recycle-badge-computed-size: var(
387 + --os-dock-recycle-badge-size,
369 388 var(
370 - --desktop-mode-dock-badge-size,
371 - calc( var( --desktop-mode-dock-icon-size, 20px ) * 0.75 )
389 + --os-dock-badge-size,
390 + calc( var( --os-dock-icon-size, 20px ) * 0.75 )
372 391 )
373 392 );
374 - top: calc( var( --desktop-mode-dock-icon-size, 20px ) * 1 );
375 - inset-inline-end: calc( var( --desktop-mode-dock-icon-size, 20px ) * 0.35 );
376 - min-width: var( --desktop-mode-dock-recycle-badge-computed-size );
377 - height: var( --desktop-mode-dock-recycle-badge-computed-size );
378 - padding: 0 calc( var( --desktop-mode-dock-recycle-badge-computed-size ) * 0.25 );
393 + top: calc( var( --os-dock-icon-size, 20px ) * 1 );
394 + inset-inline-end: calc( var( --os-dock-icon-size, 20px ) * 0.35 );
395 + min-width: var( --os-dock-recycle-badge-computed-size );
396 + height: var( --os-dock-recycle-badge-computed-size );
397 + padding: 0 calc( var( --os-dock-recycle-badge-computed-size ) * 0.25 );
379 398 border-radius: 999px;
380 399 background: var(
381 - --desktop-mode-dock-recycle-badge-bg,
400 + --os-dock-recycle-badge-bg,
382 401 rgba( 29, 35, 39, 0.72 )
383 402 );
384 403 color: var(
385 - --desktop-mode-dock-recycle-badge-fg,
404 + --os-dock-recycle-badge-fg,
386 405 rgba( 255, 255, 255, 0.92 )
387 406 );
388 407 font-size: var(
389 - --desktop-mode-dock-recycle-badge-font-size,
408 + --os-dock-recycle-badge-font-size,
390 409 var(
391 - --desktop-mode-dock-badge-font-size,
392 - calc( var( --desktop-mode-dock-recycle-badge-computed-size ) * 0.8 )
410 + --os-dock-badge-font-size,
411 + calc( var( --os-dock-recycle-badge-computed-size ) * 0.8 )
393 412 )
394 413 );
395 414 font-weight: 700;
396 415 letter-spacing: 0;
@@ -398,14 +417,14 @@
398 417 inset 0 0 0 1px rgba( 255, 255, 255, 0.26 ),
399 418 0 1px 2px rgba( 0, 0, 0, 0.24 );
400 419 }
401 420
402 -.desktop-mode-dock__item[ data-system-id="desktop-mode-recycle-bin" ]:hover .desktop-mode-dock__badge {
421 +.os-dock__item[ data-system-id="desktop-mode-recycle-bin" ]:hover .os-dock__badge {
403 422 /* Re-assert the derived offset, not a literal: the base rule above
404 423 * is overridden by the generic `__item:hover .__badge` rule, and
405 424 * hardcoding 20px here would strand the badge the moment the dock
406 425 * icon size changed. */
407 - top: calc( var( --desktop-mode-dock-icon-size, 20px ) * 1 );
426 + top: calc( var( --os-dock-icon-size, 20px ) * 1 );
408 427 transform: scale( 1.03 );
409 428 }
410 429
411 430 /* System tiles — same footprint as menu tiles, no badges, no rails.
@@ -413,10 +432,10 @@
413 432 * Reads the SAME glyph token as a menu tile, with its own literal as
414 433 * the fallback: unthemed, system tiles keep sitting a notch brighter
415 434 * than the rest (0.8 vs 0.7); themed, one colour covers every glyph
416 435 * in the dock rather than leaving these four stranded white. */
417 -.desktop-mode-dock__item--system .desktop-mode-dock__item-primary {
418 - color: var( --desktop-mode-dock-icon-color, rgba( 255, 255, 255, 0.8 ) );
436 +.os-dock__item--system .os-dock__item-primary {
437 + color: var( --os-dock-icon-color, rgba( 255, 255, 255, 0.8 ) );
419 438 }
420 439
421 440 /* -----------------------------------------------------------------------------
422 441 * Attention animations — driven by `Dock.setAttention()` /
@@ -428,9 +447,9 @@
428 447 * shows a static accent ring for the same duration so the affordance
429 448 * still works.
430 449 * ------------------------------------------------------------------------- */
431 450
432 -@keyframes wpd-dock-attention-pulse {
451 +@keyframes os-dock-attention-pulse {
433 452 0%, 100% {
434 453 transform: scale( 1 );
435 454 box-shadow: 0 0 0 0 rgba( 255, 90, 90, 0.55 );
436 455 }
@@ -439,9 +458,9 @@
439 458 box-shadow: 0 0 0 8px rgba( 255, 90, 90, 0 );
440 459 }
441 460 }
442 461
443 -@keyframes wpd-dock-attention-shake {
462 +@keyframes os-dock-attention-shake {
444 463 0%, 100% {
445 464 transform: translateX( 0 );
446 465 }
447 466 20%, 60% {
@@ -451,9 +470,9 @@
451 470 transform: translateX( 3px );
452 471 }
453 472 }
454 473
455 -@keyframes wpd-dock-attention-bounce {
474 +@keyframes os-dock-attention-bounce {
456 475 0%, 100% {
457 476 transform: translateY( 0 );
458 477 }
459 478 30% {
@@ -463,36 +482,36 @@
463 482 transform: translateY( -2px );
464 483 }
465 484 }
466 485
467 -.desktop-mode-dock__item--attention-pulse .desktop-mode-dock__item-primary {
468 - animation: wpd-dock-attention-pulse 1.4s ease-in-out infinite;
486 +.os-dock__item--attention-pulse .os-dock__item-primary {
487 + animation: os-dock-attention-pulse 1.4s ease-in-out infinite;
469 488 }
470 -.desktop-mode-dock__item--attention-shake .desktop-mode-dock__item-primary {
471 - animation: wpd-dock-attention-shake 0.8s ease-in-out infinite;
489 +.os-dock__item--attention-shake .os-dock__item-primary {
490 + animation: os-dock-attention-shake 0.8s ease-in-out infinite;
472 491 }
473 -.desktop-mode-dock__item--attention-bounce .desktop-mode-dock__item-primary {
474 - animation: wpd-dock-attention-bounce 1.2s ease-in-out infinite;
492 +.os-dock__item--attention-bounce .os-dock__item-primary {
493 + animation: os-dock-attention-bounce 1.2s ease-in-out infinite;
475 494 }
476 495
477 496 /* Intensity modulates animation duration — strong = faster, more
478 497 urgent; subtle = slower, calmer. */
479 -.desktop-mode-dock__item--intensity-strong.desktop-mode-dock__item--attention-pulse .desktop-mode-dock__item-primary {
498 +.os-dock__item--intensity-strong.os-dock__item--attention-pulse .os-dock__item-primary {
480 499 animation-duration: 1s;
481 500 }
482 -.desktop-mode-dock__item--intensity-strong.desktop-mode-dock__item--attention-shake .desktop-mode-dock__item-primary {
501 +.os-dock__item--intensity-strong.os-dock__item--attention-shake .os-dock__item-primary {
483 502 animation-duration: 0.6s;
484 503 }
485 -.desktop-mode-dock__item--intensity-strong.desktop-mode-dock__item--attention-bounce .desktop-mode-dock__item-primary {
504 +.os-dock__item--intensity-strong.os-dock__item--attention-bounce .os-dock__item-primary {
486 505 animation-duration: 0.9s;
487 506 }
488 -.desktop-mode-dock__item--intensity-subtle.desktop-mode-dock__item--attention-pulse .desktop-mode-dock__item-primary {
507 +.os-dock__item--intensity-subtle.os-dock__item--attention-pulse .os-dock__item-primary {
489 508 animation-duration: 1.8s;
490 509 }
491 -.desktop-mode-dock__item--intensity-subtle.desktop-mode-dock__item--attention-shake .desktop-mode-dock__item-primary {
510 +.os-dock__item--intensity-subtle.os-dock__item--attention-shake .os-dock__item-primary {
492 511 animation-duration: 1s;
493 512 }
494 -.desktop-mode-dock__item--intensity-subtle.desktop-mode-dock__item--attention-bounce .desktop-mode-dock__item-primary {
513 +.os-dock__item--intensity-subtle.os-dock__item--attention-bounce .os-dock__item-primary {
495 514 animation-duration: 1.6s;
496 515 }
497 516
498 517 /* Reduced-motion: kill the animation, replace with a static accent ring
@@ -497,11 +516,11 @@
497 516
498 517 /* Reduced-motion: kill the animation, replace with a static accent ring
499 518 for the same visual prominence without the movement. */
500 519 @media ( prefers-reduced-motion: reduce ) {
501 - .desktop-mode-dock__item--attention-pulse .desktop-mode-dock__item-primary,
502 - .desktop-mode-dock__item--attention-shake .desktop-mode-dock__item-primary,
503 - .desktop-mode-dock__item--attention-bounce .desktop-mode-dock__item-primary {
520 + .os-dock__item--attention-pulse .os-dock__item-primary,
521 + .os-dock__item--attention-shake .os-dock__item-primary,
522 + .os-dock__item--attention-bounce .os-dock__item-primary {
504 523 animation: none;
505 524 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
506 525 outline-offset: 2px;
507 526 border-radius: 8px;
@@ -508,18 +527,18 @@
508 527 }
509 528 }
510 529
511 530 /* Shared tooltip — placement-specific anchor modifiers applied by JS. */
512 -.desktop-mode-dock__tooltip {
531 +.os-dock__tooltip {
513 532 position: fixed;
514 533 padding: 6px 12px;
515 534 background: var(
516 - --desktop-mode-tooltip-bg,
517 - var( --wpd-scrim, rgba( 0, 0, 0, 0.85 ) )
535 + --os-tooltip-bg,
536 + var( --os-ui-scrim, rgba( 0, 0, 0, 0.85 ) )
518 537 );
519 538 color: var(
520 - --desktop-mode-tooltip-fg,
521 - var( --wpd-fg-on-accent, #fff )
539 + --os-tooltip-fg,
540 + var( --os-ui-fg-on-accent, #fff )
522 541 );
523 542 font-size: 12px;
524 543 font-weight: 400;
525 544 line-height: 1.4;
@@ -525,14 +544,14 @@
525 544 line-height: 1.4;
526 545 white-space: nowrap;
527 546 border-radius: 6px;
528 547 pointer-events: none;
529 - z-index: calc( var( --desktop-mode-z-dock ) + 1 );
548 + z-index: calc( var( --os-z-dock ) + 1 );
530 549 opacity: 0;
531 550 transition: opacity 0.15s ease, transform 0.15s ease;
532 551 }
533 552
534 -.desktop-mode-dock__tooltip--visible {
553 +.os-dock__tooltip--visible {
535 554 opacity: 1;
536 555 }
537 556
538 557 /* ------------------------------------------------------------------
@@ -539,11 +558,11 @@
539 558 * Vertical placement (left + right) — dock runs the full shell body
540 559 * height along one edge, with items stacked vertically.
541 560 * ------------------------------------------------------------------ */
542 561
543 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ],
544 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ] {
545 - width: var( --desktop-mode-dock-width );
562 +.os-dock[ data-os-dock-placement="left" ],
563 +.os-dock[ data-os-dock-placement="right" ] {
564 + width: var( --os-dock-width );
546 565 flex-direction: column;
547 566 padding: 16px 0 12px;
548 567 gap: 6px;
549 568 /*
@@ -575,10 +594,10 @@
575 594 * Firefox via `scrollbar-width: none`, WebKit via the pseudo-element
576 595 * rule below — so the rail keeps its clean macOS look. Mouse wheel,
577 596 * touchpad, touch, and keyboard focus-into-view all still work.
578 597 */
579 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ] .desktop-mode-dock__scroll,
580 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ] .desktop-mode-dock__scroll {
598 +.os-dock[ data-os-dock-placement="left" ] .os-dock__scroll,
599 +.os-dock[ data-os-dock-placement="right" ] .os-dock__scroll {
581 600 display: flex;
582 601 flex-direction: column;
583 602 align-items: center;
584 603 gap: 6px;
@@ -589,10 +608,10 @@
589 608 overflow-x: clip;
590 609 scrollbar-width: none;
591 610 }
592 611
593 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ] .desktop-mode-dock__scroll::-webkit-scrollbar,
594 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ] .desktop-mode-dock__scroll::-webkit-scrollbar {
612 +.os-dock[ data-os-dock-placement="left" ] .os-dock__scroll::-webkit-scrollbar,
613 +.os-dock[ data-os-dock-placement="right" ] .os-dock__scroll::-webkit-scrollbar {
595 614 display: none;
596 615 }
597 616
598 617 /*
@@ -599,10 +618,10 @@
599 618 * Pinned area below `__scroll` — system tiles stay reachable here
600 619 * regardless of how the menu list scrolls. `flex-shrink: 0` so it's
601 620 * never compressed; sized to its content's natural height.
602 621 */
603 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ] .desktop-mode-dock__pinned,
604 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ] .desktop-mode-dock__pinned {
622 +.os-dock[ data-os-dock-placement="left" ] .os-dock__pinned,
623 +.os-dock[ data-os-dock-placement="right" ] .os-dock__pinned {
605 624 display: flex;
606 625 flex-direction: column;
607 626 align-items: center;
608 627 gap: 6px;
@@ -610,22 +629,22 @@
610 629 width: 100%;
611 630 }
612 631
613 632 /* Left: dock is the first flex item in __body (row layout). */
614 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ] {
615 - border-inline-end: 1px solid var( --desktop-mode-dock-border );
633 +.os-dock[ data-os-dock-placement="left" ] {
634 + border-inline-end: 1px solid var( --os-dock-border );
616 635 order: -1;
617 636 }
618 637
619 638 /* Right: push the dock to the end of __body and flip the border edge. */
620 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ] {
621 - border-inline-start: 1px solid var( --desktop-mode-dock-border );
639 +.os-dock[ data-os-dock-placement="right" ] {
640 + border-inline-start: 1px solid var( --os-dock-border );
622 641 order: 1;
623 642 }
624 643
625 644 /* Active-indicator dot — left dock: inside-edge vertical pill. */
626 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ]
627 - .desktop-mode-dock__item--active::before {
645 +.os-dock[ data-os-dock-placement="left" ]
646 + .os-dock__item--active::before {
628 647 content: "";
629 648 position: absolute;
630 649 inset-inline-start: -6px;
631 650 top: 50%;
@@ -632,13 +651,13 @@
632 651 transform: translateY( -50% );
633 652 width: 4px;
634 653 height: 4px;
635 654 border-radius: 50%;
636 - background: var( --wpd-surface, #fff );
655 + background: var( --os-dock-item-outline, #fff );
637 656 }
638 657
639 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ]
640 - .desktop-mode-dock__item--focused::before {
658 +.os-dock[ data-os-dock-placement="left" ]
659 + .os-dock__item--focused::before {
641 660 width: 4px;
642 661 height: 16px;
643 662 border-radius: 2px;
644 663 }
@@ -652,20 +671,20 @@
652 671 *
653 672 * Sized slightly larger than the solid dot so the ring is legible at
654 673 * dock size — a 4×4 outline reads as a dirty pixel.
655 674 */
656 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ]
657 - .desktop-mode-dock__item--all-minimized::before {
675 +.os-dock[ data-os-dock-placement="left" ]
676 + .os-dock__item--all-minimized::before {
658 677 width: 6px;
659 678 height: 6px;
660 679 border-radius: 50%;
661 680 background: transparent;
662 - border: 1px solid rgba( 255, 255, 255, 0.85 );
681 + border: 1px solid var( --os-dock-item-outline, rgba( 255, 255, 255, 0.85 ) );
663 682 }
664 683
665 684 /* Right dock: indicator on the opposite edge (inside-facing). */
666 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ]
667 - .desktop-mode-dock__item--active::before {
685 +.os-dock[ data-os-dock-placement="right" ]
686 + .os-dock__item--active::before {
668 687 content: "";
669 688 position: absolute;
670 689 inset-inline-end: -6px;
671 690 top: 50%;
@@ -672,78 +691,95 @@
672 691 transform: translateY( -50% );
673 692 width: 4px;
674 693 height: 4px;
675 694 border-radius: 50%;
676 - background: var( --wpd-surface, #fff );
695 + background: var( --os-dock-item-outline, #fff );
677 696 }
678 697
679 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ]
680 - .desktop-mode-dock__item--focused::before {
698 +.os-dock[ data-os-dock-placement="right" ]
699 + .os-dock__item--focused::before {
681 700 width: 4px;
682 701 height: 16px;
683 702 border-radius: 2px;
684 703 }
685 704
686 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ]
687 - .desktop-mode-dock__item--all-minimized::before {
705 +.os-dock[ data-os-dock-placement="right" ]
706 + .os-dock__item--all-minimized::before {
688 707 width: 6px;
689 708 height: 6px;
690 709 border-radius: 50%;
691 710 background: transparent;
692 - border: 1px solid rgba( 255, 255, 255, 0.85 );
711 + border: 1px solid var( --os-dock-item-outline, rgba( 255, 255, 255, 0.85 ) );
693 712 }
694 713
695 714 /* "Open another" chip — left: right of tile; right: left of tile. */
696 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ]
697 - .desktop-mode-dock__item-new {
715 +.os-dock[ data-os-dock-placement="left" ]
716 + .os-dock__item-new {
698 717 top: 50%;
699 718 inset-inline-end: -13px;
700 719 transform: translateY( -50% );
701 720 }
702 721
703 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ]
704 - .desktop-mode-dock__item-new:hover {
722 +.os-dock[ data-os-dock-placement="left" ]
723 + .os-dock__item-new:hover {
705 724 transform: translateY( -50% ) scale( 1.1 );
706 725 }
707 726
708 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ]
709 - .desktop-mode-dock__item-new {
727 +.os-dock[ data-os-dock-placement="right" ]
728 + .os-dock__item-new {
710 729 top: 50%;
711 730 inset-inline-start: -13px;
712 731 transform: translateY( -50% );
713 732 }
714 733
715 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ]
716 - .desktop-mode-dock__item-new:hover {
734 +.os-dock[ data-os-dock-placement="right" ]
735 + .os-dock__item-new:hover {
717 736 transform: translateY( -50% ) scale( 1.1 );
718 737 }
719 738
720 -/* Separator — horizontal hairline between menu tiles and system
721 - * tiles. With the `__scroll` / `__pinned` split the system separator
722 - * lives at the top of `__pinned`; flex-flow handles the "system
723 - * tiles at the bottom" placement, no margin-top: auto needed. The
724 - * `--group` variant is used inline between core and plugin menu
725 - * tiles in `__scroll`; it keeps the hairline styling and sits
726 - * exactly where it's inserted. */
727 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ]
728 - .desktop-mode-dock__separator,
729 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ]
730 - .desktop-mode-dock__separator {
739 +/* Separator — the line between two groups of tiles. With the
740 + * `__scroll` / `__pinned` split the system separator lives at the top
741 + * of `__pinned`; flex-flow handles the "system tiles at the bottom"
742 + * placement, no margin-top: auto needed. The `--group` variant is used
743 + * inline between core and plugin menu tiles in `__scroll` and sits
744 + * exactly where it's inserted.
745 + *
746 + * ONE treatment for every division in the rail. The selector carries
747 + * only the base class on purpose: `--group` elements carry it too, so
748 + * both boundaries resolve here and cannot drift apart. Two weights in
749 + * one short rail read as two unrelated ideas rather than one system,
750 + * which is why the earlier loud-plus-hairline pairing is gone.
751 + *
752 + * A gradient, not a flat fill: the line is brightest where the eye
753 + * lands and gone by the time it reaches the rail's padding, so it
754 + * never terminates in a visible stub against the glass. The soft glow
755 + * around it is what keeps a 2px line from disappearing into the dock
756 + * tint; nothing is drawn on the line itself. */
757 +.os-dock[ data-os-dock-placement="left" ]
758 + .os-dock__separator,
759 +.os-dock[ data-os-dock-placement="right" ]
760 + .os-dock__separator {
761 + position: relative;
731 762 width: 60%;
732 - height: 1px;
733 - margin: 8px auto 4px;
734 - background: var( --desktop-mode-dock-border );
763 + height: 2px;
764 + margin: 12px auto;
765 + border-radius: 2px;
766 + background: linear-gradient(
767 + to right,
768 + transparent 0%,
769 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 28%,
770 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 72%,
771 + transparent 100%
772 + );
773 + box-shadow: 0 0 10px
774 + color-mix(
775 + in srgb,
776 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 45%,
777 + transparent
778 + );
735 779 flex-shrink: 0;
736 780 }
737 781
738 -.desktop-mode-dock[ data-desktop-mode-dock-placement="left" ]
739 - .desktop-mode-dock__separator--group,
740 -.desktop-mode-dock[ data-desktop-mode-dock-placement="right" ]
741 - .desktop-mode-dock__separator--group {
742 - margin: 10px auto;
743 - background: rgba( 255, 255, 255, 0.22 );
744 -}
745 -
746 782 /*
747 783 * Hide the system separator when nothing precedes it — on a clean
748 784 * install (or a low-cap user with zero menu items) the separator
749 785 * would render as a stray hairline with nothing to divide.
@@ -753,18 +789,19 @@
753 789 * when it's the first such child.
754 790 * 2. Vertical docks — separator lives inside `__pinned`; hide when
755 791 * `__scroll` (its sibling above) is empty of menu tiles.
756 792 */
757 -.desktop-mode-dock > .desktop-mode-dock__separator:first-child {
793 +.os-dock > .os-dock__separator:first-child {
758 794 display: none;
759 795 }
760 796
761 -.desktop-mode-dock__scroll:not(:has( .desktop-mode-dock__item ))
762 - + .desktop-mode-dock__pinned
763 - .desktop-mode-dock__separator {
797 +.os-dock__scroll:not(:has( .os-dock__item ))
798 + + .os-dock__pinned
799 + .os-dock__separator {
764 800 display: none;
765 801 }
766 802
803 +
767 804 /*
768 805 * Drop an empty `__scroll` from the flex flow entirely. When only
769 806 * system tiles are present (e.g. just the Recycle Bin), the empty
770 807 * wrapper would otherwise still occupy the rail: its `flex-grow`
@@ -774,20 +811,20 @@
774 811 * it lets the dock center the lone system tile. As soon as a menu
775 812 * tile is added the `:has()` no longer matches and normal flow
776 813 * resumes.
777 814 *
778 - * The `[data-desktop-mode-dock-placement]` qualifier is load-bearing:
815 + * The `[data-os-dock-placement]` qualifier is load-bearing:
779 816 * the per-placement rules that set `display: flex` on `__scroll` are
780 817 * (0,3,0); this selector must out-specify them, so it carries the
781 818 * placement attribute to reach (0,4,0).
782 819 */
783 -.desktop-mode-dock[ data-desktop-mode-dock-placement ]
784 - .desktop-mode-dock__scroll:not(:has( .desktop-mode-dock__item )) {
820 +.os-dock[ data-os-dock-placement ]
821 + .os-dock__scroll:not(:has( .os-dock__item )) {
785 822 display: none;
786 823 }
787 824
788 825 /* Tooltip — anchor modifiers are applied directly to the tooltip
789 - * element (NOT via a descendant selector on `.desktop-mode-dock`)
826 + * element (NOT via a descendant selector on `.os-dock`)
790 827 * because the tooltip lives in `document.body`. JS writes the
791 828 * absolute `top` / `left` coords; CSS handles the slide-in
792 829 * animation direction per modifier.
793 830 *
@@ -797,17 +834,17 @@
797 834 */
798 835
799 836 /* Left dock — tooltip slides in from the left by 4px. JS sets
800 837 * `left` to `tile.right + 8px`; the transform animates the entry. */
801 -.desktop-mode-dock__tooltip--after {
838 +.os-dock__tooltip--after {
802 839 transform: translateX( -4px );
803 840 }
804 841
805 -.desktop-mode-dock__tooltip--after.desktop-mode-dock__tooltip--visible {
842 +.os-dock__tooltip--after.os-dock__tooltip--visible {
806 843 transform: translateX( 0 );
807 844 }
808 845
809 -.desktop-mode-dock__tooltip--before {
846 +.os-dock__tooltip--before {
810 847 /* Right-placed dock — tooltip sits to the left of the tile. JS
811 848 * writes the left coord to the tile's left edge; this translate
812 849 * pulls the tooltip further left by its own width. */
813 850 transform: translate( calc( -100% - 8px ), 0 );
@@ -812,9 +849,9 @@
812 849 * pulls the tooltip further left by its own width. */
813 850 transform: translate( calc( -100% - 8px ), 0 );
814 851 }
815 852
816 -.desktop-mode-dock__tooltip--before.desktop-mode-dock__tooltip--visible {
853 +.os-dock__tooltip--before.os-dock__tooltip--visible {
817 854 transform: translate( calc( -100% - 8px ), 0 );
818 855 opacity: 1;
819 856 }
820 857
@@ -823,9 +860,9 @@
823 860 * viewport floor. Absolutely positioned so it floats as pure chrome
824 861 * rather than stealing vertical space from the shell body.
825 862 * ------------------------------------------------------------------ */
826 863
827 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ] {
864 +.os-dock[ data-os-dock-placement="bottom" ] {
828 865 position: absolute;
829 866 inset-inline-start: 0;
830 867 inset-inline-end: 0;
831 868 bottom: 12px;
@@ -846,18 +883,18 @@
846 883 /* Warmer, more saturated glass tint than vertical placements so
847 884 * the floating pill reads as a distinct piece of chrome rather
848 885 * than a section of the sidebar.
849 886 *
850 - * background-COLOR, not the shorthand: the base `.desktop-mode-dock`
887 + * background-COLOR, not the shorthand: the base `.os-dock`
851 888 * rule declares the DOCK texture slot's background-image, and a
852 889 * shorthand here would reset it — a themed dock texture would
853 890 * simply never appear in the bottom placement.
854 891 *
855 - * The tint is its own token rather than `--desktop-mode-dock-bg`
892 + * The tint is its own token rather than `--os-dock-bg`
856 893 * precisely because the two are meant to differ; a theme that
857 894 * wants them identical points this at the same value. */
858 895 background-color: var(
859 - --desktop-mode-dock-floating-bg,
896 + --os-dock-floating-bg,
860 897 rgba( 22, 22, 26, 0.42 )
861 898 );
862 899 backdrop-filter: blur( 28px ) saturate( 170% );
863 900 -webkit-backdrop-filter: blur( 28px ) saturate( 170% );
@@ -868,38 +905,38 @@
868 905 * one and had no way to remove the outline it saw floating over
869 906 * an empty desktop.
870 907 *
871 908 * The chain matters: the floating-specific token wins, then the
872 - * general `--desktop-mode-dock-border` (so a theme that tokenized
909 + * general `--os-dock-border` (so a theme that tokenized
873 910 * its side docks gets the pill for free, which is what anyone
874 911 * setting that token expects), then the literal. Set either to
875 912 * `transparent` for a borderless pill.
876 913 */
877 914 border: 1px solid var(
878 - --desktop-mode-dock-floating-border,
879 - var( --desktop-mode-dock-border, rgba( 255, 255, 255, 0.12 ) )
915 + --os-dock-floating-border,
916 + var( --os-dock-border, rgba( 255, 255, 255, 0.12 ) )
880 917 );
881 918 border-block-start: 1px solid var(
882 - --desktop-mode-dock-floating-border-top,
919 + --os-dock-floating-border-top,
883 920 var(
884 - --desktop-mode-dock-floating-border,
885 - var( --desktop-mode-dock-border, rgba( 255, 255, 255, 0.18 ) )
921 + --os-dock-floating-border,
922 + var( --os-dock-border, rgba( 255, 255, 255, 0.18 ) )
886 923 )
887 924 );
888 925 box-shadow:
889 926 inset 0 1px 0 var(
890 - --desktop-mode-dock-floating-highlight,
927 + --os-dock-floating-highlight,
891 928 rgba( 255, 255, 255, 0.08 )
892 929 ),
893 930 0 10px 32px var(
894 - --desktop-mode-dock-floating-shadow,
931 + --os-dock-floating-shadow,
895 932 rgba( 0, 0, 0, 0.45 )
896 933 );
897 934 }
898 935
899 936 /* Active-indicator dot — below the tile, macOS-style. */
900 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ]
901 - .desktop-mode-dock__item--active::before {
937 +.os-dock[ data-os-dock-placement="bottom" ]
938 + .os-dock__item--active::before {
902 939 content: "";
903 940 position: absolute;
904 941 inset-inline-start: 50%;
905 942 top: auto;
@@ -907,13 +944,13 @@
907 944 transform: translateX( -50% );
908 945 width: 4px;
909 946 height: 4px;
910 947 border-radius: 50%;
911 - background: var( --wpd-surface, #fff );
948 + background: var( --os-dock-item-outline, #fff );
912 949 }
913 950
914 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ]
915 - .desktop-mode-dock__item--focused::before {
951 +.os-dock[ data-os-dock-placement="bottom" ]
952 + .os-dock__item--focused::before {
916 953 width: 16px;
917 954 height: 4px;
918 955 border-radius: 2px;
919 956 }
@@ -922,53 +959,50 @@
922 959 * "All instances minimized" on the bottom dock — solid dot becomes
923 960 * a hollow ring. Matches the vertical-dock treatment so the cue is
924 961 * consistent across placements.
925 962 */
926 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ]
927 - .desktop-mode-dock__item--all-minimized::before {
963 +.os-dock[ data-os-dock-placement="bottom" ]
964 + .os-dock__item--all-minimized::before {
928 965 width: 6px;
929 966 height: 6px;
930 967 border-radius: 50%;
931 968 background: transparent;
932 - border: 1px solid rgba( 255, 255, 255, 0.85 );
969 + border: 1px solid var( --os-dock-item-outline, rgba( 255, 255, 255, 0.85 ) );
933 970 }
934 971
935 972 /*
936 - * Global "Show Desktop" indicator — set on `<body>` by the dock when
937 - * every live window on the active desktop is in the minimized state.
938 - * The bottom-dock pill picks up a soft theme-color ring so the user
939 - * has a global cue that the windows haven't disappeared, they're just
940 - * minimized. ArrowUp / a wallpaper click / clicking any dock tile
941 - * brings them back; without this the empty wallpaper looks identical
942 - * to a freshly-loaded desktop with no windows open.
973 + * "Show Desktop" — every live window on the active desktop minimized.
974 + *
975 + * The dock does not change when this happens. It used to: the bottom
976 + * pill and the vertical rails each picked up a 1px inset ring, on the
977 + * reasoning that an empty wallpaper otherwise looks identical to a
978 + * desktop that never had anything on it.
979 + *
980 + * The reasoning was sound and the ring was still the wrong answer. It
981 + * outlined the whole dock — the one surface on screen that is always
982 + * present and never the subject — to say something about windows that
983 + * are not on the dock at all. Every tile that has minimized windows
984 + * already says so, precisely and locally, by swapping its solid
985 + * indicator dot for a hollow ring. That is the cue: it points at the
986 + * tiles the windows are under, in the place the user will click to get
987 + * them back.
988 + *
989 + * `body.os-show-desktop-active` is still set (see `src/dock.ts`) and
990 + * still public, so a theme or plugin that wants a global cue can paint
991 + * one. Core does not.
943 992 */
944 -body.desktop-mode-show-desktop-active
945 - .desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ] {
946 - box-shadow:
947 - inset 0 0 0 1px var( --wp-admin-theme-color, rgba( 255, 255, 255, 0.45 ) ),
948 - inset 0 1px 0 rgba( 255, 255, 255, 0.08 ),
949 - 0 10px 32px rgba( 0, 0, 0, 0.45 );
950 -}
951 993
952 -/* Vertical docks get the same treatment along their inner edge. */
953 -body.desktop-mode-show-desktop-active
954 - .desktop-mode-dock[ data-desktop-mode-dock-placement="left" ],
955 -body.desktop-mode-show-desktop-active
956 - .desktop-mode-dock[ data-desktop-mode-dock-placement="right" ] {
957 - box-shadow: inset 0 0 0 1px var( --wp-admin-theme-color, rgba( 255, 255, 255, 0.45 ) );
958 -}
959 -
960 994 /* "Open another" chip — floats above the tile's top-right corner so
961 995 * it doesn't collide with neighbouring tiles in the horizontal row. */
962 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ]
963 - .desktop-mode-dock__item-new {
996 +.os-dock[ data-os-dock-placement="bottom" ]
997 + .os-dock__item-new {
964 998 top: -6px;
965 999 inset-inline-end: -6px;
966 1000 transform: none;
967 1001 }
968 1002
969 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ]
970 - .desktop-mode-dock__item-new:hover {
1003 +.os-dock[ data-os-dock-placement="bottom" ]
1004 + .os-dock__item-new:hover {
971 1005 transform: scale( 1.1 );
972 1006 }
973 1007
974 1008 /*
@@ -973,37 +1007,63 @@
973 1007
974 1008 /*
975 1009 * Bottom-dock inner wrappers — `__scroll` carries the menu tiles and
976 1010 * absorbs horizontal overflow; `__pinned` carries the system tiles and
977 - * stays anchored to the trailing edge. Padding-top on `__scroll` gives
978 - * the badge (top: -3px on the tile) room so `overflow-y: hidden`
979 - * doesn't crop it. `min-width: 0` lets the wrapper shrink below its
1011 + * stays anchored to the trailing edge.
1012 + *
1013 + * **The padding around `__scroll` is what keeps badges visible, and it
1014 + * has to be on both axes.** `overflow-x: auto` below cannot coexist
1015 + * with `overflow-y: visible`: per spec the visible axis computes to
1016 + * `auto`, so this wrapper is a scroll container in BOTH directions
1017 + * however it is authored, and anything a child hangs outside its
1018 + * padding box is clipped. A badge sits at `top: -3px; right: -3px` on
1019 + * its tile, and the LAST tile's edge is the wrapper's edge once the
1020 + * content is wide enough to scroll — which is why this showed up as
1021 + * "the badge on the last plugin looks cut off" rather than as a
1022 + * general problem. The inline padding is symmetric so the tile cluster
1023 + * stays centred in the pill. `min-width: 0` lets the wrapper shrink below its
980 1024 * content's natural width, so the dock pill's `max-width` is what
981 1025 * decides when scroll kicks in instead of the content forcing the
982 - * pill wider. `justify-content: center` keeps tiles balanced inside
983 - * the pill when content fits; when it overflows, scroll engages and
984 - * items extend past the visible area.
1026 + * pill wider. `justify-content: safe center` keeps tiles balanced inside
1027 + * the pill when content fits; when it overflows, the `safe` keyword
1028 + * ensures alignment falls back to `start` so `scrollLeft = 0` reveals the
1029 + * first item and all items remain scrollable without clipping. Bare
1030 + * `center` put the leading tiles at a negative offset, and a scroll
1031 + * container clamps `scrollLeft` at 0, so Dashboard and its neighbours
1032 + * were clipped with no way to scroll back to them. **The plain `start`
1033 + * above it is the fallback, not a leftover** — a browser that cannot
1034 + * parse the overflow-alignment keyword drops the `safe center`
1035 + * declaration and keeps that one, which left-aligns the tiles rather
1036 + * than hiding them. Deleting it restores the bug on those browsers.
985 1037 */
986 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ] .desktop-mode-dock__scroll {
1038 +.os-dock[ data-os-dock-placement="bottom" ] .os-dock__scroll {
987 1039 display: flex;
988 1040 flex-direction: row;
989 1041 align-items: center;
990 - justify-content: center;
1042 + justify-content: start;
1043 + justify-content: safe center;
991 1044 gap: 6px;
992 1045 flex: 1 1 auto;
993 1046 min-width: 0;
994 1047 padding-top: 4px;
995 1048 padding-bottom: 6px;
1049 + padding-inline: 4px;
996 1050 overflow-x: auto;
1051 + /*
1052 + * Authored `visible`, computed `auto` — see the note above. Kept as
1053 + * the honest statement of intent: nothing here wants a vertical
1054 + * scrollbar, and `scrollbar-width: none` plus the padding is what
1055 + * makes that true in practice.
1056 + */
997 1057 overflow-y: visible;
998 1058 scrollbar-width: none;
999 1059 }
1000 1060
1001 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ] .desktop-mode-dock__scroll::-webkit-scrollbar {
1061 +.os-dock[ data-os-dock-placement="bottom" ] .os-dock__scroll::-webkit-scrollbar {
1002 1062 display: none;
1003 1063 }
1004 1064
1005 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ] .desktop-mode-dock__pinned {
1065 +.os-dock[ data-os-dock-placement="bottom" ] .os-dock__pinned {
1006 1066 display: flex;
1007 1067 flex-direction: row;
1008 1068 align-items: center;
1009 1069 gap: 6px;
@@ -1013,58 +1073,55 @@
1013 1073 padding-top: 4px;
1014 1074 padding-bottom: 6px;
1015 1075 }
1016 1076
1017 -/* Separator — vertical hairline between menu tiles and system tiles.
1018 - * Lives at the leading edge of `__pinned`; flex flow handles the
1077 +/* Separator — the line between two groups of tiles. The system one
1078 + * lives at the leading edge of `__pinned` (flex flow handles the
1019 1079 * "pinned at the trailing edge" placement, no margin-inline-start:
1020 - * auto needed. The `--group` variant keeps the hairline styling for
1021 - * the inline core→plugin boundary inside `__scroll` and sits where
1022 - * inserted. */
1023 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ]
1024 - .desktop-mode-dock__separator {
1025 - width: 1px;
1080 + * auto needed); the `--group` one sits inline inside `__scroll` where
1081 + * it was inserted. Both resolve through this single rule — see the
1082 + * note on the vertical placements above for why there is only one. */
1083 +.os-dock[ data-os-dock-placement="bottom" ]
1084 + .os-dock__separator {
1085 + position: relative;
1086 + width: 2px;
1026 1087 /* Fixed height — `60%` collapses against the floating pill's
1027 - intrinsic height, leaving the hairline effectively invisible.
1028 - 28px matches the dock tile inner glyph area. */
1029 - height: 28px;
1088 + intrinsic height, leaving the line effectively invisible. */
1089 + height: 34px;
1030 1090 /* Symmetric horizontal margin so the divider sits centered in the
1031 1091 * inter-tile gap: the 6px flex gap on each side (`__scroll`→`__pinned`
1032 - * on the left, `__pinned`'s own gap on the right) plus an equal 6px
1092 + * on the left, `__pinned`'s own gap on the right) plus an equal
1033 1093 * margin per side balances out. An asymmetric margin here nudges the
1034 1094 * divider — and the whole menu cluster with it — off the pill's
1035 - * center. */
1036 - margin: auto 6px;
1037 - background: var( --desktop-mode-dock-border );
1095 + * center. Wider than the tile gap on purpose: the clusters need to
1096 + * read as groups before the line between them means anything. */
1097 + margin: auto 14px;
1098 + border-radius: 2px;
1099 + background: linear-gradient(
1100 + to bottom,
1101 + transparent 0%,
1102 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 28%,
1103 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 72%,
1104 + transparent 100%
1105 + );
1106 + box-shadow: 0 0 10px
1107 + color-mix(
1108 + in srgb,
1109 + var( --os-dock-divider, rgba( 217, 46, 227, 0.7 ) ) 45%,
1110 + transparent
1111 + );
1038 1112 flex-shrink: 0;
1039 1113 }
1040 1114
1041 -/*
1042 - * Group separator — sits inline between the core cluster and the
1043 - * plugin cluster. Brighter than the menu-vs-system separator because
1044 - * it carries user meaning ("this is where your installed apps
1045 - * begin"), and flanked by extra gap so the boundary reads at a
1046 - * glance rather than disappearing into the dock tint.
1047 - */
1048 -.desktop-mode-dock[ data-desktop-mode-dock-placement="bottom" ]
1049 - .desktop-mode-dock__separator--group {
1050 - width: 1px;
1051 - height: 60%;
1052 - margin: auto 10px;
1053 - margin-inline-start: 10px;
1054 - background: rgba( 255, 255, 255, 0.22 );
1055 - flex-shrink: 0;
1056 -}
1057 -
1058 1115 /* Tooltip — above the hovered tile. JS writes the horizontal center
1059 1116 * + a top near the tile's top edge; CSS translates the tooltip up by
1060 1117 * its own height so it clears the tile. */
1061 -.desktop-mode-dock__tooltip--above {
1118 +.os-dock__tooltip--above {
1062 1119 inset-inline-start: auto;
1063 1120 transform: translate( -50%, calc( -100% - 4px ) );
1064 1121 }
1065 1122
1066 -.desktop-mode-dock__tooltip--above.desktop-mode-dock__tooltip--visible {
1123 +.os-dock__tooltip--above.os-dock__tooltip--visible {
1067 1124 transform: translate( -50%, -100% );
1068 1125 }
1069 1126
1070 1127
@@ -1078,9 +1135,9 @@
1078 1135 * underneath the cursor. Sibling tiles animate via FLIP — the JS
1079 1136 * writes inline `transform` + `transition` per leg, so no CSS
1080 1137 * transition is declared here (it would race with the per-leg
1081 1138 * inline transition). */
1082 -.desktop-mode-dock__item--dragging {
1139 +.os-dock__item--dragging {
1083 1140 pointer-events: none;
1084 1141 z-index: 50;
1085 1142 opacity: 0.92;
1086 1143 cursor: grabbing;
@@ -1089,11 +1146,311 @@
1089 1146 * the dock surface it floats above. */
1090 1147 filter: drop-shadow( 0 6px 12px rgba( 0, 0, 0, 0.35 ) );
1091 1148 }
1092 1149
1093 -.desktop-mode-dock__item:not( .desktop-mode-dock__item--system ) {
1150 +.os-dock__item:not( .os-dock__item--system ) {
1094 1151 cursor: grab;
1095 1152 }
1096 1153
1097 -.desktop-mode-dock__item:not( .desktop-mode-dock__item--system ):active {
1154 +.os-dock__item:not( .os-dock__item--system ):active {
1098 1155 cursor: grabbing;
1156 +}
1157 +
1158 +/* ------------------------------------------------------------------
1159 + * The way out — `Exit OpenStation`
1160 + *
1161 + * Every other system tile opens something you can close again. This
1162 + * one leaves the desktop, and with the admin bar hidden by default it
1163 + * is the only route back to classic admin. Drawn like its neighbours
1164 + * it read as one more launcher, which is the same "some tiles do a
1165 + * different kind of thing" confusion the single dock set out to fix,
1166 + * just moved to the other end of the rail.
1167 + *
1168 + * Three signals, none of them colour. Danger red would overstate it
1169 + * (nothing is destroyed, the session is saved and the desktop is one
1170 + * click away) and Pulse is already spent on the seam:
1171 + *
1172 + * 1. **Last, always.** `order: 1` rather than registration order —
1173 + * native-window tiles from plugins sync in after boot and would
1174 + * otherwise land behind it.
1175 + * 2. **Its own gap**, wider than the rail's 6px, so it reads as
1176 + * sitting outside the set rather than at the end of it. A second
1177 + * divider would have said this too, and would have cost the rail
1178 + * the one-structural-line rule it just earned.
1179 + * 3. **A different silhouette** — a ring rather than a plate. Shape
1180 + * survives every theme, every dock texture, greyscale and a
1181 + * colour-blind reading of the rail; a tint survives none of them
1182 + * reliably.
1183 + *
1184 + * And it leans toward the edge it leads to on hover instead of lifting
1185 + * toward the pointer, because every other tile's lift means "I will
1186 + * come to you" and this one means the opposite.
1187 + *
1188 + * Keyed on the tile id, the same idiom the Recycle Bin badge uses
1189 + * above. `os-exit` is frozen in `src/exit-openstation.ts`
1190 + * (`EXIT_OPENSTATION_TILE_ID`) and `dock-exit-tile.test.ts` pins the
1191 + * attribute this selector depends on.
1192 + * ------------------------------------------------------------------ */
1193 +
1194 +.os-dock__item[ data-system-id="os-exit" ] {
1195 + order: 1;
1196 +}
1197 +
1198 +.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary {
1199 + border-radius: 50%;
1200 + background-color: transparent;
1201 + box-shadow: inset 0 0 0 1px var(
1202 + --os-dock-exit-ring,
1203 + var( --os-dock-border, rgba( 255, 255, 255, 0.18 ) )
1204 + );
1205 + color: var( --os-dock-exit-icon, rgba( 255, 255, 255, 0.55 ) );
1206 +}
1207 +
1208 +/*
1209 + * The gap runs along the rail's own axis, so it follows the placement:
1210 + * inline for the horizontal pill, block for the vertical pillars.
1211 + */
1212 +.os-dock[ data-os-dock-placement="bottom" ]
1213 + .os-dock__item[ data-system-id="os-exit" ] {
1214 + margin-inline-start: 10px;
1215 +}
1216 +
1217 +.os-dock[ data-os-dock-placement="left" ]
1218 + .os-dock__item[ data-system-id="os-exit" ],
1219 +.os-dock[ data-os-dock-placement="right" ]
1220 + .os-dock__item[ data-system-id="os-exit" ] {
1221 + margin-block-start: 8px;
1222 +}
1223 +
1224 +/*
1225 + * Hover: the ring closes up and the tile moves TOWARD its edge. The
1226 + * generic tile rule scales up by 1.1, so these have to out-specify it
1227 + * rather than sit alongside it.
1228 + */
1229 +.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary:hover,
1230 +.os-dock__item[ data-system-id="os-exit" ]
1231 + .os-dock__item-primary:focus-visible {
1232 + background-color: transparent;
1233 + box-shadow: inset 0 0 0 1px var(
1234 + --os-dock-exit-ring-hover,
1235 + var( --os-dock-item-outline, rgba( 255, 255, 255, 0.7 ) )
1236 + );
1237 + color: var( --os-dock-icon-color-hover, var( --os-ui-fg-on-accent, #fff ) );
1238 +}
1239 +
1240 +.os-dock[ data-os-dock-placement="bottom" ]
1241 + .os-dock__item[ data-system-id="os-exit" ]
1242 + .os-dock__item-primary:hover {
1243 + transform: translateY( 2px );
1244 +}
1245 +
1246 +.os-dock[ data-os-dock-placement="left" ]
1247 + .os-dock__item[ data-system-id="os-exit" ]
1248 + .os-dock__item-primary:hover {
1249 + transform: translateX( -2px );
1250 +}
1251 +
1252 +.os-dock[ data-os-dock-placement="right" ]
1253 + .os-dock__item[ data-system-id="os-exit" ]
1254 + .os-dock__item-primary:hover {
1255 + transform: translateX( 2px );
1256 +}
1257 +
1258 +/* The press keeps the shared cue: every tile in the rail dips on
1259 + * :active, and this one should not feel unresponsive by comparison. */
1260 +.os-dock__item[ data-system-id="os-exit" ] .os-dock__item-primary:active {
1261 + transform: scale( 0.95 );
1262 +}
1263 +
1264 +@media ( prefers-reduced-motion: reduce ) {
1265 + .os-dock[ data-os-dock-placement="bottom" ]
1266 + .os-dock__item[ data-system-id="os-exit" ]
1267 + .os-dock__item-primary:hover,
1268 + .os-dock[ data-os-dock-placement="left" ]
1269 + .os-dock__item[ data-system-id="os-exit" ]
1270 + .os-dock__item-primary:hover,
1271 + .os-dock[ data-os-dock-placement="right" ]
1272 + .os-dock__item[ data-system-id="os-exit" ]
1273 + .os-dock__item-primary:hover {
1274 + transform: none;
1275 + }
1276 +}
1277 +
1278 +/* ------------------------------------------------------------------
1279 + * Dynamic dock behavior — OpenStation Preferences → Appearance →
1280 + * Desktop layout, persisted as `dockBehavior` (the dock) and
1281 + * `sideDockBehavior` (the Split sidebar) and worn by each rail as
1282 + * `data-os-dock-behavior` — an attribute PER RAIL rather than a body
1283 + * class, because Split's two rails answer independently. PHP stamps
1284 + * the dock on first paint; the apply pass and `src/dock-behavior.ts`
1285 + * re-stamp on every change and rebuild. `static` is the absence of
1286 + * rules.
1287 + *
1288 + * A dynamic rail that is not `os-dock--revealed` is PARKED: it
1289 + * collapses into a thin indicator line hugging its edge — the iOS
1290 + * home indicator, one line that says "there is a dock here" — and
1291 + * expands back into the full rail when the pointer comes for it.
1292 + * `src/dock-behavior.ts` owns the flip (a full-width edge zone, the
1293 + * rail's own box, its flyouts, keyboard focus) and runs it through
1294 + * the View Transitions API, so the line morphs into the pill and
1295 + * back; the rules below only describe the two resting states and
1296 + * tune the morph. There is deliberately no `:hover` here — a state
1297 + * CSS flipped on its own would jump instead of morphing.
1298 + *
1299 + * The line is the rail itself, not a separate element: same node,
1300 + * same `view-transition-name`, which is what lets the browser animate
1301 + * one box into the other. Its children are hidden while parked so
1302 + * the line is a line. A side rail leaves the flex row for the
1303 + * duration, so the area grows to the full width and the rail rides
1304 + * over it when summoned; the work area reserves nothing for a
1305 + * dynamic rail (`src/work-area/index.ts`).
1306 + * ------------------------------------------------------------------ */
1307 +
1308 +/*
1309 + * Side rails overlay the area in both states — the flex row must not
1310 + * shrink for a rail that is only sometimes there.
1311 + */
1312 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ],
1313 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] {
1314 + position: absolute;
1315 + inset-block: 0;
1316 +}
1317 +
1318 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ] {
1319 + inset-inline-start: 0;
1320 +}
1321 +
1322 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ] {
1323 + inset-inline-end: 0;
1324 +}
1325 +
1326 +/*
1327 + * Parked — the indicator line. Everything that makes the pill a
1328 + * pill (padding, border, blur, texture, tile gap) comes off, and the
1329 + * box is the line's size. `overflow: hidden` clips whatever a tile
1330 + * paints past its box during the morph.
1331 + */
1332 +.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) {
1333 + min-width: 0;
1334 + min-height: 0;
1335 + max-width: none;
1336 + padding: 0;
1337 + gap: 0;
1338 + border: 0;
1339 + border-radius: 999px;
1340 + overflow: hidden;
1341 + background-color: var(
1342 + --os-dock-indicator-bg,
1343 + var( --os-dock-item-outline, rgba( 255, 255, 255, 0.55 ) )
1344 + );
1345 + background-image: none;
1346 + backdrop-filter: none;
1347 + -webkit-backdrop-filter: none;
1348 + box-shadow: none;
1349 + cursor: pointer;
1350 +}
1351 +
1352 +.os-dock[ data-os-dock-behavior="dynamic" ]:not( .os-dock--revealed ) > * {
1353 + display: none;
1354 +}
1355 +
1356 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="bottom" ]:not( .os-dock--revealed ) {
1357 + width: var( --os-dock-indicator-length, 180px );
1358 + height: var( --os-dock-indicator-thickness, 5px );
1359 + bottom: 8px;
1360 +}
1361 +
1362 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ),
1363 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) {
1364 + width: var( --os-dock-indicator-thickness, 5px );
1365 + height: var( --os-dock-indicator-length, 180px );
1366 + inset-block: calc( 50% - var( --os-dock-indicator-length, 180px ) / 2 );
1367 +}
1368 +
1369 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="left" ]:not( .os-dock--revealed ) {
1370 + inset-inline-start: 8px;
1371 +}
1372 +
1373 +.os-dock[ data-os-dock-behavior="dynamic" ][ data-os-dock-placement="right" ]:not( .os-dock--revealed ) {
1374 + inset-inline-end: 8px;
1375 +}
1376 +
1377 +/*
1378 + * The morph. With the API, `src/dock-behavior.ts` names the rail for
1379 + * the duration of one flip and the browser animates the old box into
1380 + * the new one; the rail's contents cross-fade underneath. The root is
1381 + * opted out of the snapshot so the rest of the desktop keeps
1382 + * running while the line becomes a pill — a frozen page for 260ms
1383 + * on every reveal would be the animation costing more than it
1384 + * gives. Without the API, the properties that can transition do.
1385 + */
1386 +@supports ( view-transition-name: none ) {
1387 + html.os-dock-vt {
1388 + view-transition-name: none;
1389 + }
1390 +
1391 + html.os-dock-vt::view-transition-old( root ),
1392 + html.os-dock-vt::view-transition-new( root ) {
1393 + animation: none;
1394 + }
1395 +
1396 + /*
1397 + * The transition layer must not catch the pointer. By default it
1398 + * does — the whole page reads as inert until the morph settles —
1399 + * and with the root opted out of the snapshot that is worse than
1400 + * pointless: the live desktop is right there under a layer that
1401 + * swallows every mouseup and every pointerenter for 260ms. The
1402 + * behavior module already holds a park back while a button is
1403 + * down; this is the other half, so a hover that lands on a tile
1404 + * while the rail is still morphing out starts its hover-intent
1405 + * timer instead of waiting for the next pointer movement.
1406 + */
1407 + html.os-dock-vt::view-transition {
1408 + pointer-events: none;
1409 + }
1410 +
1411 + ::view-transition-group( os-dock-os-dock ),
1412 + ::view-transition-group( os-dock-os-side-dock ) {
1413 + animation-duration: 260ms;
1414 + animation-timing-function: cubic-bezier( 0.22, 1, 0.36, 1 );
1415 + }
1416 +
1417 + ::view-transition-old( os-dock-os-dock ),
1418 + ::view-transition-old( os-dock-os-side-dock ) {
1419 + animation: os-dock-vt-out 120ms ease-out both;
1420 + }
1421 +
1422 + ::view-transition-new( os-dock-os-dock ),
1423 + ::view-transition-new( os-dock-os-side-dock ) {
1424 + animation: os-dock-vt-in 180ms ease-out 80ms both;
1425 + }
1426 +
1427 + @keyframes os-dock-vt-out {
1428 + to {
1429 + opacity: 0;
1430 + }
1431 + }
1432 +
1433 + @keyframes os-dock-vt-in {
1434 + from {
1435 + opacity: 0;
1436 + }
1437 + }
1438 +}
1439 +
1440 +@supports not ( view-transition-name: none ) {
1441 + .os-dock[ data-os-dock-behavior="dynamic" ] {
1442 + transition:
1443 + width 240ms ease,
1444 + height 240ms ease,
1445 + opacity 240ms ease,
1446 + border-radius 240ms ease,
1447 + bottom 240ms ease,
1448 + inset-block 240ms ease;
1449 + }
1450 +}
1451 +
1452 +@media ( prefers-reduced-motion: reduce ) {
1453 + .os-dock[ data-os-dock-behavior="dynamic" ] {
1454 + transition: none;
1455 + }
1099 1456 }