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