PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.13
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.13
1.1.13 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 All 37 releases
← All changes | assets/css/window-states.css +365 -63 0.9.2 → 1.1.13 View file →
@@ -1,6 +1,6 @@
1 1 /**
2 - * Desktop Mode — Window state visuals.
2 + * OpenStation — Window state visuals.
3 3 *
4 4 * Every rule that keys off a `--state` modifier class: the resize
5 5 * handle + its overlay shield (drag/resize iframe-interception),
6 6 * maximize, fullscreen, the opening animation, minimize + closing
@@ -20,21 +20,108 @@
20 20 * visible even without hover; the other three remain transparent
21 21 * (discoverable by cursor change only) to keep the window chrome
22 22 * clean.
23 23 *
24 - * Hit-area is `--desktop-mode-resize-size` (20 px by default). The
24 + * Hit-area is `--os-resize-size` (20 px by default). The
25 25 * handles extend a few pixels OUTSIDE the window edge so a cursor
26 26 * landing just past the border still activates them — matches how
27 27 * native OS windows forgive edge-grabs.
28 28 */
29 -.desktop-mode-window__resize-handle {
29 +.os-window__resize-handle {
30 30 position: absolute;
31 - width: var(--desktop-mode-resize-size);
32 - height: var(--desktop-mode-resize-size);
31 + width: var(--os-resize-size);
32 + height: var(--os-resize-size);
33 33 z-index: 999;
34 34 }
35 35
36 -.desktop-mode-window__resize-handle--se {
36 +/*
37 + * Desktop-theme corner ornaments (WINDOW_CORNER_{NE,NW,SE,SW}
38 + * texture slots).
39 + *
40 + * ## Why they are INSET rather than centred on the handle
41 + *
42 + * The obvious placement — centre the ornament on the resize handle —
43 + * is wrong, and looks it. `.os-window` sets
44 + * `overflow: hidden` (it clips iframe content to the rounded
45 + * corners), and the handles deliberately sit 3px OUTSIDE the window
46 + * box so an edge-grab still lands. So a handle-centred ornament gets
47 + * clipped twice: the part past the window edge is cut, and then
48 + * `border-radius` shaves what remains. The result reads as if the
49 + * artwork were painted behind the window.
50 + *
51 + * Escaping the clip is not an option worth its cost: the ornaments
52 + * would have to leave `.os-window` entirely — a wrapper
53 + * element, or an overlay in `.os-area` whose geometry is
54 + * re-tracked on every drag/resize frame. A theme that genuinely
55 + * wants artwork OUTSIDE the frame already has the right tool in the
56 + * `WINDOW_FRAME` slot: `border-image` paints in the border area,
57 + * which `overflow` does not clip.
58 + *
59 + * So the ornament is anchored inside the window's rounded corner
60 + * instead. The default inset clears the handle's 3px overhang plus a
61 + * share of the corner radius, which is what keeps the arc from
62 + * cutting it at larger radii. Themes that want it tighter or looser
63 + * override `--os-window-corner-inset`.
64 + *
65 + * Physical `top`/`left`/`right`/`bottom` rather than logical
66 + * properties here, deliberately: these track the resize handles,
67 + * which are physical corners by nature (the NW handle is top-left in
68 + * both LTR and RTL) and are themselves positioned physically.
69 + *
70 + * With no theme active every `--os-window-corner-*-image`
71 + * is unset, the shorthand resolves to `none`, and the pseudo-element
72 + * paints nothing — zero visual change and no image request.
73 + *
74 + * NOTE the BEM modifiers are `--ne` / `--nw` / `--se` / `--sw` on
75 + * `.os-window__resize-handle`, not bare `.--ne`.
76 + */
77 +.os-window__resize-handle--ne::after,
78 +.os-window__resize-handle--nw::after,
79 +.os-window__resize-handle--se::after,
80 +.os-window__resize-handle--sw::after {
81 + content: "";
82 + position: absolute;
83 + /* 3px = the handle's overhang past the window edge (see the
84 + * `--nw` / `--ne` / `--sw` / `--se` rules above). The radius
85 + * share keeps the ornament clear of the corner arc. */
86 + --_dm-corner-inset: var(
87 + --os-window-corner-inset,
88 + calc( 3px + var( --os-window-radius, 8px ) * 0.4 )
89 + );
90 + width: var( --os-window-corner-size, 16px );
91 + height: var( --os-window-corner-size, 16px );
92 + /* Ornaments are decoration; the handle keeps the drag affordance. */
93 + pointer-events: none;
94 + background-repeat: no-repeat;
95 + background-position: center;
96 + background-size: contain;
97 +}
98 +
99 +.os-window__resize-handle--nw::after {
100 + top: var( --_dm-corner-inset );
101 + left: var( --_dm-corner-inset );
102 + background-image: var( --os-window-corner-nw-image, none );
103 +}
104 +
105 +.os-window__resize-handle--ne::after {
106 + top: var( --_dm-corner-inset );
107 + right: var( --_dm-corner-inset );
108 + background-image: var( --os-window-corner-ne-image, none );
109 +}
110 +
111 +.os-window__resize-handle--sw::after {
112 + bottom: var( --_dm-corner-inset );
113 + left: var( --_dm-corner-inset );
114 + background-image: var( --os-window-corner-sw-image, none );
115 +}
116 +
117 +.os-window__resize-handle--se::after {
118 + bottom: var( --_dm-corner-inset );
119 + right: var( --_dm-corner-inset );
120 + background-image: var( --os-window-corner-se-image, none );
121 +}
122 +
123 +.os-window__resize-handle--se {
37 124 right: -3px;
38 125 bottom: -3px;
39 126 cursor: nwse-resize;
40 127 background: linear-gradient(
@@ -39,33 +126,33 @@
39 126 cursor: nwse-resize;
40 127 background: linear-gradient(
41 128 135deg,
42 129 transparent 30%,
43 - var(--desktop-mode-resize-color) 30%,
44 - var(--desktop-mode-resize-color) 40%,
130 + var(--os-resize-color) 30%,
131 + var(--os-resize-color) 40%,
45 132 transparent 40%,
46 133 transparent 55%,
47 - var(--desktop-mode-resize-color) 55%,
48 - var(--desktop-mode-resize-color) 65%,
134 + var(--os-resize-color) 55%,
135 + var(--os-resize-color) 65%,
49 136 transparent 65%,
50 137 transparent 80%,
51 - var(--desktop-mode-resize-color) 80%
138 + var(--os-resize-color) 80%
52 139 );
53 140 }
54 141
55 -.desktop-mode-window__resize-handle--sw {
142 +.os-window__resize-handle--sw {
56 143 left: -3px;
57 144 bottom: -3px;
58 145 cursor: nesw-resize;
59 146 }
60 147
61 -.desktop-mode-window__resize-handle--ne {
148 +.os-window__resize-handle--ne {
62 149 right: -3px;
63 150 top: -3px;
64 151 cursor: nesw-resize;
65 152 }
66 153
67 -.desktop-mode-window__resize-handle--nw {
154 +.os-window__resize-handle--nw {
68 155 left: -3px;
69 156 top: -3px;
70 157 cursor: nwse-resize;
71 158 }
@@ -70,13 +157,25 @@
70 157 cursor: nwse-resize;
71 158 }
72 159
73 160 /*
161 + * The top corners sit over the title bar, above its controls. A theme
162 + * whose title bar puts a control in a corner (a close button flush
163 + * with the frame) shrinks them so that control stays clickable; the
164 + * bottom corners keep their size.
165 + */
166 +.os-window__resize-handle--ne,
167 +.os-window__resize-handle--nw {
168 + width: var(--os-resize-top-size, var(--os-resize-size));
169 + height: var(--os-resize-top-size, var(--os-resize-size));
170 +}
171 +
172 +/*
74 173 * During drag/resize, overlay the iframe to prevent it from
75 174 * capturing pointer events and breaking the drag.
76 175 */
77 -.desktop-mode-window--dragging .desktop-mode-window__body::after,
78 -.desktop-mode-window--resizing .desktop-mode-window__body::after {
176 +.os-window--dragging .os-window__body::after,
177 +.os-window--resizing .os-window__body::after {
79 178 content: "";
80 179 position: absolute;
81 180 inset: 0;
82 181 z-index: 10;
@@ -86,13 +185,13 @@
86 185 * Maximized state: the pixel geometry is applied inline from JS
87 186 * (matching the desktop area) so left/top/width/height can animate.
88 187 * Only cosmetic overrides here.
89 188 */
90 -.desktop-mode-window--maximized {
189 +.os-window--maximized {
91 190 border-radius: 0;
92 191 }
93 192
94 -.desktop-mode-window--maximized .desktop-mode-window__resize-handle {
193 +.os-window--maximized .os-window__resize-handle {
95 194 display: none;
96 195 }
97 196
98 197 /*
@@ -98,24 +197,24 @@
98 197 /*
99 198 * Fullscreen ("focus") state — the window escapes the desktop area and
100 199 * covers the entire viewport, hiding the admin bar and dock behind
101 200 * it. Uses position: fixed so no ancestor overflow clips it, and
102 - * a z-index above the admin bar (--desktop-mode-z-adminbar). Inline
201 + * a z-index above the admin bar (--os-z-adminbar). Inline
103 202 * geometry set by toggleMaximize() is overridden with !important because
104 203 * we never want fullscreen to respect a saved x/y/width/height.
105 204 */
106 -.desktop-mode-window--fullscreen {
205 +.os-window--fullscreen {
107 206 position: fixed !important;
108 207 left: 0 !important;
109 208 top: 0 !important;
110 209 width: 100vw !important;
111 210 height: 100vh !important;
112 - z-index: var(--desktop-mode-z-fullscreen) !important;
211 + z-index: var(--os-z-fullscreen) !important;
113 212 border-radius: 0 !important;
114 213 border: none;
115 214 }
116 215
117 -.desktop-mode-window--fullscreen .desktop-mode-window__resize-handle {
216 +.os-window--fullscreen .os-window__resize-handle {
118 217 display: none;
119 218 }
120 219
121 220 /*
@@ -121,9 +220,9 @@
121 220 /*
122 221 * Opening animation — subtle scale-up + fade-in.
123 222 * The class is added on creation and removed after the animation ends.
124 223 */
125 -@keyframes desktop-mode-window-open {
224 +@keyframes os-window-open {
126 225 from {
127 226 opacity: 0;
128 227 transform: scale(0.92);
129 228 }
@@ -132,10 +231,10 @@
132 231 transform: scale(1);
133 232 }
134 233 }
135 234
136 -.desktop-mode-window--opening {
137 - animation: desktop-mode-window-open 0.2s ease forwards;
235 +.os-window--opening {
236 + animation: os-window-open 0.2s ease forwards;
138 237 }
139 238
140 239 /*
141 240 * Shake animation — used by `Window.shake()` for nudge / attention-
@@ -147,9 +246,9 @@
147 246 * Reduced-motion fallback: a static accent ring for the same
148 247 * duration so users with prefers-reduced-motion still see the
149 248 * affordance without movement.
150 249 */
151 -@keyframes desktop-mode-window-shake {
250 +@keyframes os-window-shake {
152 251 0%, 100% { transform: translateX( 0 ); }
153 252 8% { transform: translateX( -10px ); }
154 253 18% { transform: translateX( 9px ); }
155 254 28% { transform: translateX( -8px ); }
@@ -159,14 +258,14 @@
159 258 72% { transform: translateX( -3px ); }
160 259 84% { transform: translateX( 2px ); }
161 260 }
162 261
163 -.desktop-mode-window--shaking {
164 - animation: desktop-mode-window-shake 0.6s cubic-bezier( 0.36, 0.07, 0.19, 0.97 ) both;
262 +.os-window--shaking {
263 + animation: os-window-shake 0.6s cubic-bezier( 0.36, 0.07, 0.19, 0.97 ) both;
165 264 }
166 265
167 266 @media ( prefers-reduced-motion: reduce ) {
168 - .desktop-mode-window--shaking {
267 + .os-window--shaking {
169 268 animation: none;
170 269 outline: 3px solid var( --wp-admin-theme-color, #2271b1 );
171 270 outline-offset: 2px;
172 271 transition: outline 0.6s ease;
@@ -173,12 +272,12 @@
173 272 }
174 273 }
175 274
176 275 /*
177 - * Desktop-switch slide — applied to `.desktop-mode-area` (the windows
276 + * Desktop-switch slide — applied to `.os-area` (the windows
178 277 * + widgets container) when the user moves between virtual desktops
179 278 * via arrow keys or any other directional caller. The wallpaper lives
180 - * OUTSIDE this element (sibling under `.desktop-mode-shell`), so the
279 + * OUTSIDE this element (sibling under `.os-shell`), so the
181 280 * slide reveals the wallpaper as a backdrop — no black flash, no
182 281 * awkward gap on the leading edge.
183 282 *
184 283 * `--sliding-from-right` plays when moving to the next desktop (the
@@ -195,9 +294,9 @@
195 294 *
196 295 * `forwards` isn't needed — the JS strips the class on `animationend`
197 296 * so the element returns to its natural transform / opacity state.
198 297 */
199 -@keyframes desktop-mode-area-slide-from-right {
298 +@keyframes os-area-slide-from-right {
200 299 from {
201 300 transform: translateX(64px);
202 301 opacity: 0.35;
203 302 }
@@ -206,9 +305,9 @@
206 305 opacity: 1;
207 306 }
208 307 }
209 308
210 -@keyframes desktop-mode-area-slide-from-left {
309 +@keyframes os-area-slide-from-left {
211 310 from {
212 311 transform: translateX(-64px);
213 312 opacity: 0.35;
214 313 }
@@ -217,39 +316,93 @@
217 316 opacity: 1;
218 317 }
219 318 }
220 319
221 -.desktop-mode-area--sliding-from-right {
222 - animation: desktop-mode-area-slide-from-right 0.28s cubic-bezier( 0.2, 0.7, 0.2, 1 );
320 +.os-area--sliding-from-right {
321 + animation: os-area-slide-from-right 0.28s cubic-bezier( 0.2, 0.7, 0.2, 1 );
223 322 }
224 323
225 -.desktop-mode-area--sliding-from-left {
226 - animation: desktop-mode-area-slide-from-left 0.28s cubic-bezier( 0.2, 0.7, 0.2, 1 );
324 +.os-area--sliding-from-left {
325 + animation: os-area-slide-from-left 0.28s cubic-bezier( 0.2, 0.7, 0.2, 1 );
227 326 }
228 327
229 328 @media ( prefers-reduced-motion: reduce ) {
230 - .desktop-mode-area--sliding-from-right,
231 - .desktop-mode-area--sliding-from-left {
329 + .os-area--sliding-from-right,
330 + .os-area--sliding-from-left {
232 331 animation: none;
233 332 }
234 333 }
235 334
236 335 /*
237 - * Minimized state — fade + scale down.
238 - * Uses opacity + pointer-events instead of display: none so the
239 - * CSS transition can animate the change. The window is invisible
240 - * and non-interactive but stays in the DOM for a smooth restore.
336 + * Desktop-name HUD. `pointer-events: none` is required, not cosmetic:
337 + * it sits over the wallpaper's click surface, which minimizes every
338 + * window.
241 339 */
242 -.desktop-mode-window--minimized {
340 +.os-desktop-name-hud {
341 + position: absolute;
342 + top: 50%;
343 + left: 50%;
344 + transform: translate( -50%, -50% );
345 + z-index: var( --os-z-desktop-name, 195 );
346 + max-width: min( 70%, 520px );
347 + padding: 14px 28px;
348 + border-radius: 16px;
349 + font-size: 20px;
350 + font-weight: 600;
351 + letter-spacing: 0.01em;
352 + text-align: center;
353 + white-space: nowrap;
354 + overflow: hidden;
355 + text-overflow: ellipsis;
356 + color: var( --os-ui-fg-on-accent, #fff );
357 + background-color: var( --os-ui-scrim, rgba( 0, 0, 0, 0.32 ) );
358 + backdrop-filter: blur( 18px ) saturate( 140% );
359 + -webkit-backdrop-filter: blur( 18px ) saturate( 140% );
360 + box-shadow:
361 + 0 8px 28px rgba( 0, 0, 0, 0.35 ),
362 + inset 0 0 0 1px rgba( 255, 255, 255, 0.08 );
363 + pointer-events: none;
364 + opacity: 1;
365 + transition: opacity 0.26s ease;
366 +}
367 +
368 +.os-desktop-name-hud--out {
243 369 opacity: 0;
244 - transform: scale(0.92) translateY(16px);
370 +}
371 +
372 +@media ( prefers-reduced-motion: reduce ) {
373 + .os-desktop-name-hud {
374 + transition: none;
375 + }
376 +}
377 +
378 +/*
379 + * Minimized state — window is hidden and non-interactive at the dock.
380 + * Uses opacity + pointer-events + transition: none so neither state
381 + * changes nor WAAPI animation teardown trigger accidental transitions.
382 + */
383 +.os-window--minimized {
384 + opacity: 0;
245 385 pointer-events: none;
246 - will-change: transform, opacity;
386 + transition: none !important;
247 387 }
248 388
249 389 /*
390 + * Genie helpers — active only during the WAAPI flight to/from the
391 + * dock. Disable the base `left/top/width/height/transform/opacity`
392 + * transition so the CSS transition doesn't fight the WAAPI
393 + * `element.animate()` that drives the scale-to-dock motion. The
394 + * minimized class is applied AFTER the minimizing flight lands, and
395 + * the restoring class is removed AFTER the restoring flight lands.
396 + */
397 +.os-window--minimizing,
398 +.os-window--restoring {
399 + transition: none !important;
400 +}
401 +
402 +/*
250 403 * `pointer-events: none` on the window root above is not enough on
251 - * its own: a number of descendants (most notably `.desktop-mode-
404 + * its own: a number of descendants (most notably `.os-
252 405 * file-tile`, but also other tile renderers) explicitly set
253 406 * `pointer-events: auto` so the wallpaper / folder layer doesn't
254 407 * eat their clicks. That `auto` overrides the parent's `none`, and
255 408 * after "Show Desktop" minimizes every window, hovering the empty
@@ -259,10 +412,10 @@
259 412 * The universal selector is scoped INSIDE the minimized window so
260 413 * it doesn't touch anything else, and `!important` is required to
261 414 * win over the per-tile `pointer-events: auto`.
262 415 */
263 -.desktop-mode-window--minimized *,
264 -.desktop-mode-window--closing * {
416 +.os-window--minimized *,
417 +.os-window--closing * {
265 418 pointer-events: none !important;
266 419 }
267 420
268 421 /*
@@ -268,9 +421,9 @@
268 421 /*
269 422 * Closing animation — fade out + slight scale down.
270 423 * The JS waits for transitionend then removes the element.
271 424 */
272 -.desktop-mode-window--closing {
425 +.os-window--closing {
273 426 opacity: 0;
274 427 transform: scale(0.95);
275 428 pointer-events: none;
276 429 will-change: transform, opacity;
@@ -281,14 +434,19 @@
281 434 * so state changes are instant but still technically transition
282 435 * (avoids layout jumps from display: none).
283 436 */
284 437 @media ( prefers-reduced-motion: reduce ) {
285 - .desktop-mode-window {
438 + .os-window {
286 439 transition-duration: 0.01ms !important;
287 440 }
288 - .desktop-mode-window--opening {
441 + .os-window--opening {
289 442 animation-duration: 0.01ms !important;
290 443 }
444 + .os-window--minimizing,
445 + .os-window--restoring {
446 + animation: none !important;
447 + transition: none !important;
448 + }
291 449 }
292 450
293 451 /*
294 452 * RTL note: resize handles use physical `left` / `right` anchors
@@ -306,9 +464,9 @@
306 464 * of the left/right edge (see window-manager/snap-zones.ts). It's a
307 465 * translucent accent-colored rectangle covering the target half, with
308 466 * a bright border so the user sees exactly where the window will land.
309 467 * --------------------------------------------------------------- */
310 -.desktop-mode-snap-preview {
468 +.os-snap-preview {
311 469 position: absolute;
312 470 background: color-mix(
313 471 in srgb,
314 472 var( --wp-admin-theme-color, #2271b1 ) 14%,
@@ -330,13 +488,157 @@
330 488 width 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
331 489 height 0.18s cubic-bezier( 0.2, 0, 0.2, 1 );
332 490 }
333 491
334 -.desktop-mode-snap-preview--visible {
492 +.os-snap-preview--visible {
335 493 opacity: 1;
336 494 }
337 495
496 +/* ---------------------------------------------------------------
497 + * Grid snap — the 6×6 desk shown while Option / Alt is held during a
498 + * drag (see window-manager/grid-snap.ts).
499 + *
500 + * The overlay is the work area; the lines are one repeating gradient
501 + * sized to a cell, and a cell is a fraction of the overlay — so the
502 + * same rule draws the grid at any dimensions, on any display, and
503 + * follows the work area if the dock folds away mid-drag. The target
504 + * is the span the window will land on, in the same accent as the
505 + * edge-snap preview so both previews read as one vocabulary.
506 + * --------------------------------------------------------------- */
507 +.os-grid-snap {
508 + position: absolute;
509 + /* Same layer as the edge-snap preview: under the windows, so the
510 + one being dragged stays on top of its own landing zone. */
511 + z-index: 99;
512 + pointer-events: none;
513 + opacity: 0;
514 + transition: opacity 0.2s ease;
515 + --os-grid-snap-cols: 6;
516 + --os-grid-snap-rows: 6;
517 + --os-grid-snap-line: color-mix(
518 + in srgb,
519 + var( --wp-admin-theme-color, #2271b1 ) 55%,
520 + transparent
521 + );
522 + background-image:
523 + linear-gradient( to right, var( --os-grid-snap-line ) 1px, transparent 1px ),
524 + linear-gradient( to bottom, var( --os-grid-snap-line ) 1px, transparent 1px );
525 + background-size:
526 + calc( 100% / var( --os-grid-snap-cols ) ) 100%,
527 + 100% calc( 100% / var( --os-grid-snap-rows ) );
528 + /* The gradient draws a line at the START of every cell; the
529 + inset ring supplies the far edges so the grid is closed. */
530 + box-shadow: inset 0 0 0 1px var( --os-grid-snap-line );
531 +}
532 +
533 +.os-grid-snap--visible {
534 + opacity: 1;
535 +}
536 +
537 +.os-grid-snap__target {
538 + position: absolute;
539 + background: color-mix(
540 + in srgb,
541 + var( --wp-admin-theme-color, #2271b1 ) 16%,
542 + transparent
543 + );
544 + border: 2px solid var( --wp-admin-theme-color, #2271b1 );
545 + border-radius: 10px;
546 + box-shadow:
547 + 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset,
548 + 0 12px 36px rgba( 0, 0, 0, 0.22 );
549 + /* Geometry animates so a cell-to-cell change slides rather than
550 + jumps; short, so it never lags the pointer by a cell. */
551 + transition:
552 + left 0.12s cubic-bezier( 0.2, 0, 0.2, 1 ),
553 + top 0.12s cubic-bezier( 0.2, 0, 0.2, 1 ),
554 + width 0.12s cubic-bezier( 0.2, 0, 0.2, 1 ),
555 + height 0.12s cubic-bezier( 0.2, 0, 0.2, 1 );
556 +}
557 +
338 558 /*
559 + * The span, as "cols × rows", in the MIDDLE of the target. A user
560 + * picking a 3×2 wants to know it is 3×2 before letting go; the count
561 + * is what makes the grid a tool rather than a decoration, but only
562 + * if it is read, and in the corner it was not. The pointer is
563 + * somewhere inside the target by definition, so the centre is the
564 + * shortest average trip for the eye; on a 2×5 the corner could be
565 + * several hundred pixels from the hand.
566 + *
567 + * Centred rather than pinned to the pointer on purpose: a badge that
568 + * tracked the cursor would rewrite its position every move, and a
569 + * number that slides while you read it is harder to read than one
570 + * that waits. It only moves when the span does.
571 + */
572 +.os-grid-snap__target::after {
573 + content: attr( data-cols ) ' × ' attr( data-rows );
574 + position: absolute;
575 + top: 50%;
576 + left: 50%;
577 + transform: translate( -50%, -50% );
578 + padding: 2px 7px;
579 + border-radius: 6px;
580 + background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.55 ) );
581 + color: var( --os-ui-fg-on-accent, #fff );
582 + font-size: 11px;
583 + font-weight: 600;
584 + letter-spacing: 0.03em;
585 + font-variant-numeric: tabular-nums;
586 +}
587 +
588 +/*
589 + * The desk in grid mode. Every OTHER window recedes so the grid and
590 + * the landing zone read through them; the one in hand stays solid —
591 + * it is the thing being placed, and the user needs to see it, not
592 + * through it. Translucent enough that the grid shows, opaque enough
593 + * that the other windows are still recognisably where they are,
594 + * because "will this land on top of Posts?" is the question the grid
595 + * is there to answer.
596 + *
597 + * Opacity only: a blur or a desaturate on a live iframe is a
598 + * per-frame filter on a large surface, and the drag has to stay
599 + * pixel-accurate at 120Hz. Minimized windows are already hidden and
600 + * need no rule.
601 + */
602 +.os-area--grid-snapping .os-window:not( .os-window--grid-snapping ) {
603 + opacity: 0.45;
604 + transition: opacity 0.16s ease;
605 +}
606 +
607 +/* A shake moved the anchor: one pulse so the reset is seen, not inferred. */
608 +.os-grid-snap__target--reset {
609 + animation: os-grid-snap-reset 0.32s ease;
610 +}
611 +
612 +@keyframes os-grid-snap-reset {
613 + 0% {
614 + box-shadow:
615 + 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset,
616 + 0 0 0 0 var( --wp-admin-theme-color, #2271b1 );
617 + }
618 + 60% {
619 + box-shadow:
620 + 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset,
621 + 0 0 0 10px transparent;
622 + }
623 + 100% {
624 + box-shadow:
625 + 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset,
626 + 0 12px 36px rgba( 0, 0, 0, 0.22 );
627 + }
628 +}
629 +
630 +@media ( prefers-reduced-motion: reduce ) {
631 + .os-grid-snap,
632 + .os-grid-snap__target {
633 + transition: none;
634 + }
635 + .os-grid-snap__target--reset {
636 + animation: none;
637 + }
638 +}
639 +
640 +/*
339 641 * Snapped window state — `--snapped-left` / `--snapped-right` carry
340 642 * cosmetic tweaks only. Resize handles stay visible and functional:
341 643 * the user can grab any corner to shrink the window back to floating
342 644 * (the pointer layer drops the snapped-* class on first resize so
@@ -351,10 +653,10 @@
351 653 * ghost line at the seam — and compete visually with the partner
352 654 * window. Flat edges + no shadow gives the two-up feel of a native
353 655 * split view: windows adjacent, no separator.
354 656 */
355 -.desktop-mode-window--snapped-left,
356 -.desktop-mode-window--snapped-right {
657 +.os-window--snapped-left,
658 +.os-window--snapped-right {
357 659 box-shadow: none;
358 660 }
359 661
360 662 /* Override the elevated shadow applied to `--focused` windows too —
@@ -360,20 +662,20 @@
360 662 /* Override the elevated shadow applied to `--focused` windows too —
361 663 the focused selector runs at equal specificity, so without this
362 664 rule the focus shadow would re-introduce the seam once the snapped
363 665 window is active. */
364 -.desktop-mode-window--focused.desktop-mode-window--snapped-left,
365 -.desktop-mode-window--focused.desktop-mode-window--snapped-right {
666 +.os-window--focused.os-window--snapped-left,
667 +.os-window--focused.os-window--snapped-right {
366 668 box-shadow: none;
367 669 }
368 670
369 -.desktop-mode-window--snapped-left {
671 +.os-window--snapped-left {
370 672 /* Inner (right) edge flush; outer corners keep the radius. */
371 673 border-top-right-radius: 0;
372 674 border-bottom-right-radius: 0;
373 675 }
374 676
375 -.desktop-mode-window--snapped-right {
677 +.os-window--snapped-right {
376 678 border-top-left-radius: 0;
377 679 border-bottom-left-radius: 0;
378 680 }
379 681
@@ -383,12 +685,12 @@
383 685 * thumbnail visuals as the main overview; only the backdrop tint is
384 686 * lighter (the anchor window still takes the other half, so we want
385 687 * the picker to feel "peripheral" not "modal").
386 688 */
387 -.desktop-mode-area--split-overview {
689 +.os-area--split-overview {
388 690 background-color: rgba( 0, 0, 0, 0.28 );
389 691 transition: background-color 0.24s ease;
390 692 }
391 693
392 -/* Toast styling moved into `<wpd-toast-container>` and `<wpd-toast>`
393 - * (src/ui/components/wpd-toast/). The components own their shadow
694 +/* Toast styling moved into `<os-toast-container>` and `<os-toast>`
695 + * (src/ui/components/os-toast/). The components own their shadow
394 696 * DOM styling; nothing at host level needs to reach inside. */