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

482 lines 15.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 * 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.
328 */
329 .os-window--minimized {
330 opacity: 0;
331 transform: scale(0.92) translateY(16px);
332 pointer-events: none;
333 will-change: transform, opacity;
334 }
335
336 /*
337 * `pointer-events: none` on the window root above is not enough on
338 * its own: a number of descendants (most notably `.os-
339 * file-tile`, but also other tile renderers) explicitly set
340 * `pointer-events: auto` so the wallpaper / folder layer doesn't
341 * eat their clicks. That `auto` overrides the parent's `none`, and
342 * after "Show Desktop" minimizes every window, hovering the empty
343 * space where a tile used to sit would still trigger its tooltip
344 * / dock peek — the invisible-but-still-interactive bug.
345 *
346 * The universal selector is scoped INSIDE the minimized window so
347 * it doesn't touch anything else, and `!important` is required to
348 * win over the per-tile `pointer-events: auto`.
349 */
350 .os-window--minimized *,
351 .os-window--closing * {
352 pointer-events: none !important;
353 }
354
355 /*
356 * Closing animation — fade out + slight scale down.
357 * The JS waits for transitionend then removes the element.
358 */
359 .os-window--closing {
360 opacity: 0;
361 transform: scale(0.95);
362 pointer-events: none;
363 will-change: transform, opacity;
364 }
365
366 /*
367 * Respect prefers-reduced-motion: collapse all durations to near-zero
368 * so state changes are instant but still technically transition
369 * (avoids layout jumps from display: none).
370 */
371 @media ( prefers-reduced-motion: reduce ) {
372 .os-window {
373 transition-duration: 0.01ms !important;
374 }
375 .os-window--opening {
376 animation-duration: 0.01ms !important;
377 }
378 }
379
380 /*
381 * RTL note: resize handles use physical `left` / `right` anchors
382 * (not logical `inset-inline-*`) so the four corners stay in the
383 * same physical positions under RTL. Each handle already has the
384 * correct cursor for its physical diagonal (SE + NW use
385 * `nwse-resize`, NE + SW use `nesw-resize`); no RTL override
386 * needed.
387 */
388
389 /* ---------------------------------------------------------------
390 * Snap zones — edge-snap preview rectangle + snapped-state chrome.
391 *
392 * Preview appears while the drag cursor is within SNAP_EDGE_THRESHOLD
393 * of the left/right edge (see window-manager/snap-zones.ts). It's a
394 * translucent accent-colored rectangle covering the target half, with
395 * a bright border so the user sees exactly where the window will land.
396 * --------------------------------------------------------------- */
397 .os-snap-preview {
398 position: absolute;
399 background: color-mix(
400 in srgb,
401 var( --wp-admin-theme-color, #2271b1 ) 14%,
402 transparent
403 );
404 border: 2px solid var( --wp-admin-theme-color, #2271b1 );
405 border-radius: 12px;
406 box-shadow:
407 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset,
408 0 12px 36px rgba( 0, 0, 0, 0.22 );
409 pointer-events: none;
410 z-index: 99; /* Just under windows (which start at 100) so a */
411 /* still-dragging window renders on top. */
412 opacity: 0;
413 transition:
414 opacity 0.15s ease,
415 left 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
416 top 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
417 width 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
418 height 0.18s cubic-bezier( 0.2, 0, 0.2, 1 );
419 }
420
421 .os-snap-preview--visible {
422 opacity: 1;
423 }
424
425 /*
426 * Snapped window state — `--snapped-left` / `--snapped-right` carry
427 * cosmetic tweaks only. Resize handles stay visible and functional:
428 * the user can grab any corner to shrink the window back to floating
429 * (the pointer layer drops the snapped-* class on first resize so
430 * geometry stops pretending to be half-screen).
431 *
432 * Rounded-corner profile flattens the INNER edges (the ones that
433 * butt against the partner window) to match macOS split-view
434 * aesthetic.
435 *
436 * Shadows are dropped on both snapped variants. The default window
437 * drop shadow would render inside the opposite half — a dark vertical
438 * ghost line at the seam — and compete visually with the partner
439 * window. Flat edges + no shadow gives the two-up feel of a native
440 * split view: windows adjacent, no separator.
441 */
442 .os-window--snapped-left,
443 .os-window--snapped-right {
444 box-shadow: none;
445 }
446
447 /* Override the elevated shadow applied to `--focused` windows too —
448 the focused selector runs at equal specificity, so without this
449 rule the focus shadow would re-introduce the seam once the snapped
450 window is active. */
451 .os-window--focused.os-window--snapped-left,
452 .os-window--focused.os-window--snapped-right {
453 box-shadow: none;
454 }
455
456 .os-window--snapped-left {
457 /* Inner (right) edge flush; outer corners keep the radius. */
458 border-top-right-radius: 0;
459 border-bottom-right-radius: 0;
460 }
461
462 .os-window--snapped-right {
463 border-top-left-radius: 0;
464 border-bottom-left-radius: 0;
465 }
466
467 /*
468 * Split overview — a constrained variant of the full overview,
469 * scoped to the half opposite a just-snapped window. Uses the same
470 * thumbnail visuals as the main overview; only the backdrop tint is
471 * lighter (the anchor window still takes the other half, so we want
472 * the picker to feel "peripheral" not "modal").
473 */
474 .os-area--split-overview {
475 background-color: rgba( 0, 0, 0, 0.28 );
476 transition: background-color 0.24s ease;
477 }
478
479 /* Toast styling moved into `<os-toast-container>` and `<os-toast>`
480 * (src/ui/components/os-toast/). The components own their shadow
481 * DOM styling; nothing at host level needs to reach inside. */
482