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