PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / trunk
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin vtrunk
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 0.8.6 All 33 releases
desktop-mode / assets / css / window-states.css

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

685 lines 21.2 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 — 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;
373 pointer-events: none;
374 transition: none !important;
375 }
376
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 /*
391 * `pointer-events: none` on the window root above is not enough on
392 * its own: a number of descendants (most notably `.os-
393 * file-tile`, but also other tile renderers) explicitly set
394 * `pointer-events: auto` so the wallpaper / folder layer doesn't
395 * eat their clicks. That `auto` overrides the parent's `none`, and
396 * after "Show Desktop" minimizes every window, hovering the empty
397 * space where a tile used to sit would still trigger its tooltip
398 * / dock peek — the invisible-but-still-interactive bug.
399 *
400 * The universal selector is scoped INSIDE the minimized window so
401 * it doesn't touch anything else, and `!important` is required to
402 * win over the per-tile `pointer-events: auto`.
403 */
404 .os-window--minimized *,
405 .os-window--closing * {
406 pointer-events: none !important;
407 }
408
409 /*
410 * Closing animation — fade out + slight scale down.
411 * The JS waits for transitionend then removes the element.
412 */
413 .os-window--closing {
414 opacity: 0;
415 transform: scale(0.95);
416 pointer-events: none;
417 will-change: transform, opacity;
418 }
419
420 /*
421 * Respect prefers-reduced-motion: collapse all durations to near-zero
422 * so state changes are instant but still technically transition
423 * (avoids layout jumps from display: none).
424 */
425 @media ( prefers-reduced-motion: reduce ) {
426 .os-window {
427 transition-duration: 0.01ms !important;
428 }
429 .os-window--opening {
430 animation-duration: 0.01ms !important;
431 }
432 .os-window--minimizing,
433 .os-window--restoring {
434 animation: none !important;
435 transition: none !important;
436 }
437 }
438
439 /*
440 * RTL note: resize handles use physical `left` / `right` anchors
441 * (not logical `inset-inline-*`) so the four corners stay in the
442 * same physical positions under RTL. Each handle already has the
443 * correct cursor for its physical diagonal (SE + NW use
444 * `nwse-resize`, NE + SW use `nesw-resize`); no RTL override
445 * needed.
446 */
447
448 /* ---------------------------------------------------------------
449 * Snap zones — edge-snap preview rectangle + snapped-state chrome.
450 *
451 * Preview appears while the drag cursor is within SNAP_EDGE_THRESHOLD
452 * of the left/right edge (see window-manager/snap-zones.ts). It's a
453 * translucent accent-colored rectangle covering the target half, with
454 * a bright border so the user sees exactly where the window will land.
455 * --------------------------------------------------------------- */
456 .os-snap-preview {
457 position: absolute;
458 background: color-mix(
459 in srgb,
460 var( --wp-admin-theme-color, #2271b1 ) 14%,
461 transparent
462 );
463 border: 2px solid var( --wp-admin-theme-color, #2271b1 );
464 border-radius: 12px;
465 box-shadow:
466 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset,
467 0 12px 36px rgba( 0, 0, 0, 0.22 );
468 pointer-events: none;
469 z-index: 99; /* Just under windows (which start at 100) so a */
470 /* still-dragging window renders on top. */
471 opacity: 0;
472 transition:
473 opacity 0.15s ease,
474 left 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
475 top 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
476 width 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ),
477 height 0.18s cubic-bezier( 0.2, 0, 0.2, 1 );
478 }
479
480 .os-snap-preview--visible {
481 opacity: 1;
482 }
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
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 /*
629 * Snapped window state — `--snapped-left` / `--snapped-right` carry
630 * cosmetic tweaks only. Resize handles stay visible and functional:
631 * the user can grab any corner to shrink the window back to floating
632 * (the pointer layer drops the snapped-* class on first resize so
633 * geometry stops pretending to be half-screen).
634 *
635 * Rounded-corner profile flattens the INNER edges (the ones that
636 * butt against the partner window) to match macOS split-view
637 * aesthetic.
638 *
639 * Shadows are dropped on both snapped variants. The default window
640 * drop shadow would render inside the opposite half — a dark vertical
641 * ghost line at the seam — and compete visually with the partner
642 * window. Flat edges + no shadow gives the two-up feel of a native
643 * split view: windows adjacent, no separator.
644 */
645 .os-window--snapped-left,
646 .os-window--snapped-right {
647 box-shadow: none;
648 }
649
650 /* Override the elevated shadow applied to `--focused` windows too —
651 the focused selector runs at equal specificity, so without this
652 rule the focus shadow would re-introduce the seam once the snapped
653 window is active. */
654 .os-window--focused.os-window--snapped-left,
655 .os-window--focused.os-window--snapped-right {
656 box-shadow: none;
657 }
658
659 .os-window--snapped-left {
660 /* Inner (right) edge flush; outer corners keep the radius. */
661 border-top-right-radius: 0;
662 border-bottom-right-radius: 0;
663 }
664
665 .os-window--snapped-right {
666 border-top-left-radius: 0;
667 border-bottom-left-radius: 0;
668 }
669
670 /*
671 * Split overview — a constrained variant of the full overview,
672 * scoped to the half opposite a just-snapped window. Uses the same
673 * thumbnail visuals as the main overview; only the backdrop tint is
674 * lighter (the anchor window still takes the other half, so we want
675 * the picker to feel "peripheral" not "modal").
676 */
677 .os-area--split-overview {
678 background-color: rgba( 0, 0, 0, 0.28 );
679 transition: background-color 0.24s ease;
680 }
681
682 /* Toast styling moved into `<os-toast-container>` and `<os-toast>`
683 * (src/ui/components/os-toast/). The components own their shadow
684 * DOM styling; nothing at host level needs to reach inside. */
685