PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.3
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.3
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
desktop-mode / assets / css / window-states.css

window-states.css in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.3, at assets/css/window-states.css

525 lines 16.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * OpenStation — Window state visuals.
3 *
4 * Every rule that keys off a `--state` modifier class: the resize
5 * handle + its overlay shield (drag/resize iframe-interception),
6 * maximize, fullscreen, the opening animation, minimize + closing
7 * fade-outs, reduced-motion scoped overrides, and the RTL cursor
8 * flip on the resize corner.
9 *
10 * Base chrome lives in window-chrome.css; the zoom-out grid lives
11 * in window-overview.css.
12 *
13 * @since 6.9.0
14 */
15
16 /*
17 * Resize handles — one per corner (NE / NW / SE / SW). Each is an
18 * invisible square hit-target whose cursor telegraphs the resize
19 * axis. SE keeps the legacy grip-lines glyph so the affordance is
20 * visible even without hover; the other three remain transparent
21 * (discoverable by cursor change only) to keep the window chrome
22 * clean.
23 *
24 * Hit-area is `--os-resize-size` (20 px by default). The
25 * handles extend a few pixels OUTSIDE the window edge so a cursor
26 * landing just past the border still activates them — matches how
27 * native OS windows forgive edge-grabs.
28 */
29 .os-window__resize-handle {
30 position: absolute;
31 width: var(--os-resize-size);
32 height: var(--os-resize-size);
33 z-index: 999;
34 }
35
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 {
124 right: -3px;
125 bottom: -3px;
126 cursor: nwse-resize;
127 background: linear-gradient(
128 135deg,
129 transparent 30%,
130 var(--os-resize-color) 30%,
131 var(--os-resize-color) 40%,
132 transparent 40%,
133 transparent 55%,
134 var(--os-resize-color) 55%,
135 var(--os-resize-color) 65%,
136 transparent 65%,
137 transparent 80%,
138 var(--os-resize-color) 80%
139 );
140 }
141
142 .os-window__resize-handle--sw {
143 left: -3px;
144 bottom: -3px;
145 cursor: nesw-resize;
146 }
147
148 .os-window__resize-handle--ne {
149 right: -3px;
150 top: -3px;
151 cursor: nesw-resize;
152 }
153
154 .os-window__resize-handle--nw {
155 left: -3px;
156 top: -3px;
157 cursor: nwse-resize;
158 }
159
160 /*
161 * During drag/resize, overlay the iframe to prevent it from
162 * capturing pointer events and breaking the drag.
163 */
164 .os-window--dragging .os-window__body::after,
165 .os-window--resizing .os-window__body::after {
166 content: "";
167 position: absolute;
168 inset: 0;
169 z-index: 10;
170 }
171
172 /*
173 * Maximized state: the pixel geometry is applied inline from JS
174 * (matching the desktop area) so left/top/width/height can animate.
175 * Only cosmetic overrides here.
176 */
177 .os-window--maximized {
178 border-radius: 0;
179 }
180
181 .os-window--maximized .os-window__resize-handle {
182 display: none;
183 }
184
185 /*
186 * Fullscreen ("focus") state — the window escapes the desktop area and
187 * covers the entire viewport, hiding the admin bar and dock behind
188 * it. Uses position: fixed so no ancestor overflow clips it, and
189 * a z-index above the admin bar (--os-z-adminbar). Inline
190 * geometry set by toggleMaximize() is overridden with !important because
191 * we never want fullscreen to respect a saved x/y/width/height.
192 */
193 .os-window--fullscreen {
194 position: fixed !important;
195 left: 0 !important;
196 top: 0 !important;
197 width: 100vw !important;
198 height: 100vh !important;
199 z-index: var(--os-z-fullscreen) !important;
200 border-radius: 0 !important;
201 border: none;
202 }
203
204 .os-window--fullscreen .os-window__resize-handle {
205 display: none;
206 }
207
208 /*
209 * Opening animation — subtle scale-up + fade-in.
210 * The class is added on creation and removed after the animation ends.
211 */
212 @keyframes os-window-open {
213 from {
214 opacity: 0;
215 transform: scale(0.92);
216 }
217 to {
218 opacity: 1;
219 transform: scale(1);
220 }
221 }
222
223 .os-window--opening {
224 animation: os-window-open 0.2s ease forwards;
225 }
226
227 /*
228 * Shake animation — used by `Window.shake()` for nudge / attention-
229 * grab affordances. The window briefly jiggles horizontally while
230 * keeping its position. Composes via CSS variable so the inline
231 * `left`/`top` written by the window manager keeps wining; the
232 * shake is a translate ON TOP of that.
233 *
234 * Reduced-motion fallback: a static accent ring for the same
235 * duration so users with prefers-reduced-motion still see the
236 * affordance without movement.
237 */
238 @keyframes os-window-shake {
239 0%, 100% { transform: translateX( 0 ); }
240 8% { transform: translateX( -10px ); }
241 18% { transform: translateX( 9px ); }
242 28% { transform: translateX( -8px ); }
243 38% { transform: translateX( 7px ); }
244 48% { transform: translateX( -5px ); }
245 60% { transform: translateX( 4px ); }
246 72% { transform: translateX( -3px ); }
247 84% { transform: translateX( 2px ); }
248 }
249
250 .os-window--shaking {
251 animation: os-window-shake 0.6s cubic-bezier( 0.36, 0.07, 0.19, 0.97 ) both;
252 }
253
254 @media ( prefers-reduced-motion: reduce ) {
255 .os-window--shaking {
256 animation: none;
257 outline: 3px solid var( --wp-admin-theme-color, #2271b1 );
258 outline-offset: 2px;
259 transition: outline 0.6s ease;
260 }
261 }
262
263 /*
264 * Desktop-switch slide — applied to `.os-area` (the windows
265 * + widgets container) when the user moves between virtual desktops
266 * via arrow keys or any other directional caller. The wallpaper lives
267 * OUTSIDE this element (sibling under `.os-shell`), so the
268 * slide reveals the wallpaper as a backdrop — no black flash, no
269 * awkward gap on the leading edge.
270 *
271 * `--sliding-from-right` plays when moving to the next desktop (the
272 * rightward arrow press): the new desktop's content rushes in from
273 * the right and settles to centre. `--sliding-from-left` mirrors it
274 * for the previous-desktop direction. Wrap-around presses (rightmost
275 * → first via ArrowRight) keep the direction the user perceived,
276 * not the index delta.
277 *
278 * Opacity tapers from 0.35 → 1 so the moving content visibly arrives
279 * rather than just sliding in at full strength, but the wallpaper
280 * stays at full opacity behind it for an anchored sense of place.
281 * Cubic-bezier ease-out lands cleanly without overshoot.
282 *
283 * `forwards` isn't needed — the JS strips the class on `animationend`
284 * so the element returns to its natural transform / opacity state.
285 */
286 @keyframes os-area-slide-from-right {
287 from {
288 transform: translateX(64px);
289 opacity: 0.35;
290 }
291 to {
292 transform: translateX(0);
293 opacity: 1;
294 }
295 }
296
297 @keyframes os-area-slide-from-left {
298 from {
299 transform: translateX(-64px);
300 opacity: 0.35;
301 }
302 to {
303 transform: translateX(0);
304 opacity: 1;
305 }
306 }
307
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 }
311
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 }
315
316 @media ( prefers-reduced-motion: reduce ) {
317 .os-area--sliding-from-right,
318 .os-area--sliding-from-left {
319 animation: none;
320 }
321 }
322
323 /*
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.
327 */
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 {
357 opacity: 0;
358 }
359
360 @media ( prefers-reduced-motion: reduce ) {
361 .os-desktop-name-hud {
362 transition: none;
363 }
364 }
365
366 /*
367 * Minimized state — fade + scale down.
368 * Uses opacity + pointer-events instead of display: none so the
369 * CSS transition can animate the change. The window is invisible
370 * and non-interactive but stays in the DOM for a smooth restore.
371 */
372 .os-window--minimized {
373 opacity: 0;
374 transform: scale(0.92) translateY(16px);
375 pointer-events: none;
376 will-change: transform, opacity;
377 }
378
379 /*
380 * `pointer-events: none` on the window root above is not enough on
381 * its own: a number of descendants (most notably `.os-
382 * file-tile`, but also other tile renderers) explicitly set
383 * `pointer-events: auto` so the wallpaper / folder layer doesn't
384 * eat their clicks. That `auto` overrides the parent's `none`, and
385 * after "Show Desktop" minimizes every window, hovering the empty
386 * space where a tile used to sit would still trigger its tooltip
387 * / dock peek — the invisible-but-still-interactive bug.
388 *
389 * The universal selector is scoped INSIDE the minimized window so
390 * it doesn't touch anything else, and `!important` is required to
391 * win over the per-tile `pointer-events: auto`.
392 */
393 .os-window--minimized *,
394 .os-window--closing * {
395 pointer-events: none !important;
396 }
397
398 /*
399 * Closing animation — fade out + slight scale down.
400 * The JS waits for transitionend then removes the element.
401 */
402 .os-window--closing {
403 opacity: 0;
404 transform: scale(0.95);
405 pointer-events: none;
406 will-change: transform, opacity;
407 }
408
409 /*
410 * Respect prefers-reduced-motion: collapse all durations to near-zero
411 * so state changes are instant but still technically transition
412 * (avoids layout jumps from display: none).
413 */
414 @media ( prefers-reduced-motion: reduce ) {
415 .os-window {
416 transition-duration: 0.01ms !important;
417 }
418 .os-window--opening {
419 animation-duration: 0.01ms !important;
420 }
421 }
422
423 /*
424 * RTL note: resize handles use physical `left` / `right` anchors
425 * (not logical `inset-inline-*`) so the four corners stay in the
426 * same physical positions under RTL. Each handle already has the
427 * correct cursor for its physical diagonal (SE + NW use
428 * `nwse-resize`, NE + SW use `nesw-resize`); no RTL override
429 * needed.
430 */
431
432 /* ---------------------------------------------------------------
433 * Snap zones — edge-snap preview rectangle + snapped-state chrome.
434 *
435 * Preview appears while the drag cursor is within SNAP_EDGE_THRESHOLD
436 * of the left/right edge (see window-manager/snap-zones.ts). It's a
437 * translucent accent-colored rectangle covering the target half, with
438 * a bright border so the user sees exactly where the window will land.
439 * --------------------------------------------------------------- */
440 .os-snap-preview {
441 position: absolute;
442 background: color-mix(
443 in srgb,
444 var( --wp-admin-theme-color, #2271b1 ) 14%,
445 transparent
446 );
447 border: 2px solid var( --wp-admin-theme-color, #2271b1 );
448 border-radius: 12px;
449 box-shadow:
450 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset,
451 0 12px 36px rgba( 0, 0, 0, 0.22 );
452 pointer-events: none;
453 z-index: 99; /* Just under windows (which start at 100) so a */
454 /* still-dragging window renders on top. */
455 opacity: 0;
456 transition:
457 opacity 0.15s ease,
458 left 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
459 top 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
460 width 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
461 height 0.18s cubic-bezier( 0.2, 0, 0.2, 1 );
462 }
463
464 .os-snap-preview--visible {
465 opacity: 1;
466 }
467
468 /*
469 * Snapped window state — `--snapped-left` / `--snapped-right` carry
470 * cosmetic tweaks only. Resize handles stay visible and functional:
471 * the user can grab any corner to shrink the window back to floating
472 * (the pointer layer drops the snapped-* class on first resize so
473 * geometry stops pretending to be half-screen).
474 *
475 * Rounded-corner profile flattens the INNER edges (the ones that
476 * butt against the partner window) to match macOS split-view
477 * aesthetic.
478 *
479 * Shadows are dropped on both snapped variants. The default window
480 * drop shadow would render inside the opposite half — a dark vertical
481 * ghost line at the seam — and compete visually with the partner
482 * window. Flat edges + no shadow gives the two-up feel of a native
483 * split view: windows adjacent, no separator.
484 */
485 .os-window--snapped-left,
486 .os-window--snapped-right {
487 box-shadow: none;
488 }
489
490 /* Override the elevated shadow applied to `--focused` windows too —
491 the focused selector runs at equal specificity, so without this
492 rule the focus shadow would re-introduce the seam once the snapped
493 window is active. */
494 .os-window--focused.os-window--snapped-left,
495 .os-window--focused.os-window--snapped-right {
496 box-shadow: none;
497 }
498
499 .os-window--snapped-left {
500 /* Inner (right) edge flush; outer corners keep the radius. */
501 border-top-right-radius: 0;
502 border-bottom-right-radius: 0;
503 }
504
505 .os-window--snapped-right {
506 border-top-left-radius: 0;
507 border-bottom-left-radius: 0;
508 }
509
510 /*
511 * Split overview — a constrained variant of the full overview,
512 * scoped to the half opposite a just-snapped window. Uses the same
513 * thumbnail visuals as the main overview; only the backdrop tint is
514 * lighter (the anchor window still takes the other half, so we want
515 * the picker to feel "peripheral" not "modal").
516 */
517 .os-area--split-overview {
518 background-color: rgba( 0, 0, 0, 0.28 );
519 transition: background-color 0.24s ease;
520 }
521
522 /* Toast styling moved into `<os-toast-container>` and `<os-toast>`
523 * (src/ui/components/os-toast/). The components own their shadow
524 * DOM styling; nothing at host level needs to reach inside. */
525