/** * Desktop Mode — Window state visuals. * * Every rule that keys off a `--state` modifier class: the resize * handle + its overlay shield (drag/resize iframe-interception), * maximize, fullscreen, the opening animation, minimize + closing * fade-outs, reduced-motion scoped overrides, and the RTL cursor * flip on the resize corner. * * Base chrome lives in window-chrome.css; the zoom-out grid lives * in window-overview.css. * * @since 6.9.0 */ /* * Resize handles — one per corner (NE / NW / SE / SW). Each is an * invisible square hit-target whose cursor telegraphs the resize * axis. SE keeps the legacy grip-lines glyph so the affordance is * visible even without hover; the other three remain transparent * (discoverable by cursor change only) to keep the window chrome * clean. * * Hit-area is `--desktop-mode-resize-size` (20 px by default). The * handles extend a few pixels OUTSIDE the window edge so a cursor * landing just past the border still activates them — matches how * native OS windows forgive edge-grabs. */ .desktop-mode-window__resize-handle { position: absolute; width: var(--desktop-mode-resize-size); height: var(--desktop-mode-resize-size); z-index: 999; } .desktop-mode-window__resize-handle--se { right: -3px; bottom: -3px; cursor: nwse-resize; background: linear-gradient( 135deg, transparent 30%, var(--desktop-mode-resize-color) 30%, var(--desktop-mode-resize-color) 40%, transparent 40%, transparent 55%, var(--desktop-mode-resize-color) 55%, var(--desktop-mode-resize-color) 65%, transparent 65%, transparent 80%, var(--desktop-mode-resize-color) 80% ); } .desktop-mode-window__resize-handle--sw { left: -3px; bottom: -3px; cursor: nesw-resize; } .desktop-mode-window__resize-handle--ne { right: -3px; top: -3px; cursor: nesw-resize; } .desktop-mode-window__resize-handle--nw { left: -3px; top: -3px; cursor: nwse-resize; } /* * During drag/resize, overlay the iframe to prevent it from * capturing pointer events and breaking the drag. */ .desktop-mode-window--dragging .desktop-mode-window__body::after, .desktop-mode-window--resizing .desktop-mode-window__body::after { content: ""; position: absolute; inset: 0; z-index: 10; } /* * Maximized state: the pixel geometry is applied inline from JS * (matching the desktop area) so left/top/width/height can animate. * Only cosmetic overrides here. */ .desktop-mode-window--maximized { border-radius: 0; } .desktop-mode-window--maximized .desktop-mode-window__resize-handle { display: none; } /* * Fullscreen ("focus") state — the window escapes the desktop area and * covers the entire viewport, hiding the admin bar and dock behind * it. Uses position: fixed so no ancestor overflow clips it, and * a z-index above the admin bar (--desktop-mode-z-adminbar). Inline * geometry set by toggleMaximize() is overridden with !important because * we never want fullscreen to respect a saved x/y/width/height. */ .desktop-mode-window--fullscreen { position: fixed !important; left: 0 !important; top: 0 !important; width: 100vw !important; height: 100vh !important; z-index: var(--desktop-mode-z-fullscreen) !important; border-radius: 0 !important; border: none; } .desktop-mode-window--fullscreen .desktop-mode-window__resize-handle { display: none; } /* * Opening animation — subtle scale-up + fade-in. * The class is added on creation and removed after the animation ends. */ @keyframes desktop-mode-window-open { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } } .desktop-mode-window--opening { animation: desktop-mode-window-open 0.2s ease forwards; } /* * Shake animation — used by `Window.shake()` for nudge / attention- * grab affordances. The window briefly jiggles horizontally while * keeping its position. Composes via CSS variable so the inline * `left`/`top` written by the window manager keeps wining; the * shake is a translate ON TOP of that. * * Reduced-motion fallback: a static accent ring for the same * duration so users with prefers-reduced-motion still see the * affordance without movement. */ @keyframes desktop-mode-window-shake { 0%, 100% { transform: translateX( 0 ); } 8% { transform: translateX( -10px ); } 18% { transform: translateX( 9px ); } 28% { transform: translateX( -8px ); } 38% { transform: translateX( 7px ); } 48% { transform: translateX( -5px ); } 60% { transform: translateX( 4px ); } 72% { transform: translateX( -3px ); } 84% { transform: translateX( 2px ); } } .desktop-mode-window--shaking { animation: desktop-mode-window-shake 0.6s cubic-bezier( 0.36, 0.07, 0.19, 0.97 ) both; } @media ( prefers-reduced-motion: reduce ) { .desktop-mode-window--shaking { animation: none; outline: 3px solid var( --wp-admin-theme-color, #2271b1 ); outline-offset: 2px; transition: outline 0.6s ease; } } /* * Desktop-switch slide — applied to `.desktop-mode-area` (the windows * + widgets container) when the user moves between virtual desktops * via arrow keys or any other directional caller. The wallpaper lives * OUTSIDE this element (sibling under `.desktop-mode-shell`), so the * slide reveals the wallpaper as a backdrop — no black flash, no * awkward gap on the leading edge. * * `--sliding-from-right` plays when moving to the next desktop (the * rightward arrow press): the new desktop's content rushes in from * the right and settles to centre. `--sliding-from-left` mirrors it * for the previous-desktop direction. Wrap-around presses (rightmost * → first via ArrowRight) keep the direction the user perceived, * not the index delta. * * Opacity tapers from 0.35 → 1 so the moving content visibly arrives * rather than just sliding in at full strength, but the wallpaper * stays at full opacity behind it for an anchored sense of place. * Cubic-bezier ease-out lands cleanly without overshoot. * * `forwards` isn't needed — the JS strips the class on `animationend` * so the element returns to its natural transform / opacity state. */ @keyframes desktop-mode-area-slide-from-right { from { transform: translateX(64px); opacity: 0.35; } to { transform: translateX(0); opacity: 1; } } @keyframes desktop-mode-area-slide-from-left { from { transform: translateX(-64px); opacity: 0.35; } to { transform: translateX(0); opacity: 1; } } .desktop-mode-area--sliding-from-right { animation: desktop-mode-area-slide-from-right 0.28s cubic-bezier( 0.2, 0.7, 0.2, 1 ); } .desktop-mode-area--sliding-from-left { animation: desktop-mode-area-slide-from-left 0.28s cubic-bezier( 0.2, 0.7, 0.2, 1 ); } @media ( prefers-reduced-motion: reduce ) { .desktop-mode-area--sliding-from-right, .desktop-mode-area--sliding-from-left { animation: none; } } /* * Minimized state — fade + scale down. * Uses opacity + pointer-events instead of display: none so the * CSS transition can animate the change. The window is invisible * and non-interactive but stays in the DOM for a smooth restore. */ .desktop-mode-window--minimized { opacity: 0; transform: scale(0.92) translateY(16px); pointer-events: none; will-change: transform, opacity; } /* * `pointer-events: none` on the window root above is not enough on * its own: a number of descendants (most notably `.desktop-mode- * file-tile`, but also other tile renderers) explicitly set * `pointer-events: auto` so the wallpaper / folder layer doesn't * eat their clicks. That `auto` overrides the parent's `none`, and * after "Show Desktop" minimizes every window, hovering the empty * space where a tile used to sit would still trigger its tooltip * / dock peek — the invisible-but-still-interactive bug. * * The universal selector is scoped INSIDE the minimized window so * it doesn't touch anything else, and `!important` is required to * win over the per-tile `pointer-events: auto`. */ .desktop-mode-window--minimized *, .desktop-mode-window--closing * { pointer-events: none !important; } /* * Closing animation — fade out + slight scale down. * The JS waits for transitionend then removes the element. */ .desktop-mode-window--closing { opacity: 0; transform: scale(0.95); pointer-events: none; will-change: transform, opacity; } /* * Respect prefers-reduced-motion: collapse all durations to near-zero * so state changes are instant but still technically transition * (avoids layout jumps from display: none). */ @media ( prefers-reduced-motion: reduce ) { .desktop-mode-window { transition-duration: 0.01ms !important; } .desktop-mode-window--opening { animation-duration: 0.01ms !important; } } /* * RTL note: resize handles use physical `left` / `right` anchors * (not logical `inset-inline-*`) so the four corners stay in the * same physical positions under RTL. Each handle already has the * correct cursor for its physical diagonal (SE + NW use * `nwse-resize`, NE + SW use `nesw-resize`); no RTL override * needed. */ /* --------------------------------------------------------------- * Snap zones — edge-snap preview rectangle + snapped-state chrome. * * Preview appears while the drag cursor is within SNAP_EDGE_THRESHOLD * of the left/right edge (see window-manager/snap-zones.ts). It's a * translucent accent-colored rectangle covering the target half, with * a bright border so the user sees exactly where the window will land. * --------------------------------------------------------------- */ .desktop-mode-snap-preview { position: absolute; background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 14%, transparent ); border: 2px solid var( --wp-admin-theme-color, #2271b1 ); border-radius: 12px; box-shadow: 0 0 0 1px rgba( 255, 255, 255, 0.15 ) inset, 0 12px 36px rgba( 0, 0, 0, 0.22 ); pointer-events: none; z-index: 99; /* Just under windows (which start at 100) so a */ /* still-dragging window renders on top. */ opacity: 0; transition: opacity 0.15s ease, left 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ), top 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ), width 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ), height 0.18s cubic-bezier( 0.2, 0, 0.2, 1 ); } .desktop-mode-snap-preview--visible { opacity: 1; } /* * Snapped window state — `--snapped-left` / `--snapped-right` carry * cosmetic tweaks only. Resize handles stay visible and functional: * the user can grab any corner to shrink the window back to floating * (the pointer layer drops the snapped-* class on first resize so * geometry stops pretending to be half-screen). * * Rounded-corner profile flattens the INNER edges (the ones that * butt against the partner window) to match macOS split-view * aesthetic. * * Shadows are dropped on both snapped variants. The default window * drop shadow would render inside the opposite half — a dark vertical * ghost line at the seam — and compete visually with the partner * window. Flat edges + no shadow gives the two-up feel of a native * split view: windows adjacent, no separator. */ .desktop-mode-window--snapped-left, .desktop-mode-window--snapped-right { box-shadow: none; } /* Override the elevated shadow applied to `--focused` windows too — the focused selector runs at equal specificity, so without this rule the focus shadow would re-introduce the seam once the snapped window is active. */ .desktop-mode-window--focused.desktop-mode-window--snapped-left, .desktop-mode-window--focused.desktop-mode-window--snapped-right { box-shadow: none; } .desktop-mode-window--snapped-left { /* Inner (right) edge flush; outer corners keep the radius. */ border-top-right-radius: 0; border-bottom-right-radius: 0; } .desktop-mode-window--snapped-right { border-top-left-radius: 0; border-bottom-left-radius: 0; } /* * Split overview — a constrained variant of the full overview, * scoped to the half opposite a just-snapped window. Uses the same * thumbnail visuals as the main overview; only the backdrop tint is * lighter (the anchor window still takes the other half, so we want * the picker to feel "peripheral" not "modal"). */ .desktop-mode-area--split-overview { background-color: rgba( 0, 0, 0, 0.28 ); transition: background-color 0.24s ease; } /* Toast styling moved into `` and `` * (src/ui/components/wpd-toast/). The components own their shadow * DOM styling; nothing at host level needs to reach inside. */