PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.8.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.8.7
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 / desktop.css

desktop.css in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.8.7, at assets/css/desktop.css

1,017 lines 28.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Desktop Mode — Shell Layout.
3 *
4 * When desktop mode is active, the classic admin chrome still renders
5 * server-side (for hook compatibility) but is hidden with CSS. The
6 * plugin-injected shell floats above it, positioned just below the
7 * fixed admin bar, covering the rest of the viewport.
8 *
9 * @since 0.1.0
10 */
11
12 /* Solid backdrop behind the shell. */
13 body.desktop-mode-active {
14 background: #1d2327;
15 overflow: hidden;
16 }
17
18 /*
19 * Cursor policy — default arrow everywhere in desktop mode.
20 *
21 * Per user preference (since 0.20.0): no hand/pointer/grab/grabbing/
22 * copy/no-drop cursors on icons, tiles, dock items, dock peek,
23 * window chrome, or any other clickable surface. The default arrow
24 * is the only "neutral" cursor we render.
25 *
26 * Exceptions:
27 * - Window + widget resize handles keep their direction-specific
28 * cursors (ns-resize, ew-resize, nesw-resize, nwse-resize) —
29 * they're a functional cue, not a hover affordance, and removing
30 * them would make corner-grabs guesswork.
31 * - Text inputs / textareas / contenteditable surfaces keep the
32 * browser-default I-beam.
33 *
34 * `!important` is needed because the specific `cursor: pointer` and
35 * drag-state declarations elsewhere in the codebase have the same or
36 * higher specificity. The resize-handle exceptions use `!important`
37 * for the same reason.
38 *
39 * Shadow-DOM web components (`wpd-*`) define their own cursors
40 * inside their shadow trees; those internal rules win regardless of
41 * what we set on the host. Anything visible on the desktop shell
42 * (light DOM) is covered by this policy.
43 */
44 body.desktop-mode-active,
45 body.desktop-mode-active * {
46 cursor: default !important;
47 }
48
49 body.desktop-mode-active input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]),
50 body.desktop-mode-active textarea,
51 body.desktop-mode-active [contenteditable=""],
52 body.desktop-mode-active [contenteditable="true"] {
53 cursor: text !important;
54 }
55
56 body.desktop-mode-active .desktop-mode-window__resize-handle--se,
57 body.desktop-mode-active .desktop-mode-window__resize-handle--nw,
58 body.desktop-mode-active .desktop-mode-widgets__resize--se,
59 body.desktop-mode-active .desktop-mode-widgets__resize--nw {
60 cursor: nwse-resize !important;
61 }
62
63 body.desktop-mode-active .desktop-mode-window__resize-handle--sw,
64 body.desktop-mode-active .desktop-mode-window__resize-handle--ne,
65 body.desktop-mode-active .desktop-mode-widgets__resize--sw,
66 body.desktop-mode-active .desktop-mode-widgets__resize--ne {
67 cursor: nesw-resize !important;
68 }
69
70 body.desktop-mode-active .desktop-mode-widgets__resize--n,
71 body.desktop-mode-active .desktop-mode-widgets__resize--s {
72 cursor: ns-resize !important;
73 }
74
75 body.desktop-mode-active .desktop-mode-widgets__resize--e,
76 body.desktop-mode-active .desktop-mode-widgets__resize--w {
77 cursor: ew-resize !important;
78 }
79
80 /*
81 * Hide the classic admin chrome while the shell is active.
82 *
83 * The markup still exists in the DOM so server-side action hooks that
84 * target these regions (admin_notices, in_admin_footer, etc.) continue
85 * to fire — we just take them out of the visual flow.
86 */
87 body.desktop-mode-active #adminmenuwrap,
88 body.desktop-mode-active #adminmenuback,
89 body.desktop-mode-active #wpbody,
90 body.desktop-mode-active #wpfooter,
91 body.desktop-mode-active .wp-responsive-toggle,
92 body.desktop-mode-active #collapse-menu {
93 display: none !important;
94 }
95
96 /*
97 * The admin bar is the ONLY escape hatch in desktop mode — it hosts the
98 * "Switch to Classic Admin" toggle. Any plugin CSS, Gutenberg state,
99 * user preference, or stray `display:none` that hides it would strand
100 * the user inside the shell with no way out. Hard-pin it visible.
101 *
102 * The exception: while a window is in immersive fullscreen, we *want*
103 * the bar hidden (macOS convention). That rule lives below and wins on
104 * specificity because it also targets #wpadminbar under a body class.
105 */
106 body.desktop-mode-active #wpadminbar {
107 display: block !important;
108 visibility: visible !important;
109 opacity: 1 !important;
110 position: fixed !important;
111 inset-block-start: 0 !important;
112 inset-inline-start: 0 !important;
113 inset-inline-end: 0 !important;
114 }
115
116 /*
117 * Align the admin-bar W logo with the dock below it.
118 *
119 * Without this rule the admin bar's leftmost item (the WordPress W)
120 * renders at its Core-dictated ~44 px width, while the dock below it
121 * can be 48, 56, or 72 px wide depending on the user's OS-Settings
122 * preference. The misalignment reads as "two disconnected left
123 * regions" — visually scruffy on an otherwise tight shell.
124 *
125 * We size the logo's <li> to match the dock's width and center the
126 * W inside, so the two columns share an axis. `--desktop-mode-dock-width`
127 * is now set on :root (see settings.ts → apply()) so it reaches
128 * siblings of #desktop-mode-shell like the admin bar.
129 */
130 body.desktop-mode-active #wpadminbar #wp-admin-bar-wp-logo {
131 width: var( --desktop-mode-dock-width, 56px );
132 }
133
134 body.desktop-mode-active #wpadminbar #wp-admin-bar-wp-logo > .ab-item {
135 display: flex !important;
136 align-items: center;
137 justify-content: center;
138 padding-inline: 0 !important;
139 }
140
141 /*
142 * The W glyph is painted via a ::before pseudo on `.ab-icon`. Center
143 * it within the flex cell so varying dock widths don't leave it
144 * visually offset.
145 */
146 body.desktop-mode-active #wpadminbar #wp-admin-bar-wp-logo > .ab-item > .ab-icon {
147 margin: 0;
148 }
149
150 /*
151 * Classic wpcontent carries a `margin-left: 160px` for the sidebar. With
152 * the sidebar hidden we let wpcontent fall back to flush — the shell is
153 * fixed-positioned anyway so this doesn't change the shell's layout, it
154 * just prevents a phantom sidebar gutter in any stray visible content.
155 */
156 body.desktop-mode-active #wpcontent {
157 margin-inline-start: 0;
158 padding-inline-start: 0;
159 }
160
161 /*
162 * Desktop shell — fills the viewport below the admin bar.
163 * Uses position: fixed so it's unaffected by any containing-block
164 * quirks in #wpwrap/#wpcontent.
165 */
166 .desktop-mode-shell {
167 position: fixed;
168 inset-block-start: var(--wp-admin--admin-bar--height, 32px);
169 inset-inline-start: 0;
170 inset-inline-end: 0;
171 inset-block-end: 0;
172 display: flex;
173 flex-direction: column;
174 overflow: hidden;
175 z-index: var(--desktop-mode-z-base);
176 }
177
178 /*
179 * Wallpaper layer — first child of the shell, sits behind the dock
180 * and desktop area so a translucent dock shows through to the
181 * background (macOS pattern). CSS wallpapers paint via the
182 * `--desktop-mode-bg` custom property; canvas wallpapers mount their
183 * own DOM (typically a <canvas>) into this element via JS.
184 *
185 * z-index: 0 keeps it below the shell body flex row which has no
186 * explicit z-index of its own but establishes its own local
187 * stacking context via `position: relative`, so the dock + windows
188 * always render above the wallpaper regardless of DOM order.
189 */
190 .desktop-mode-wallpaper {
191 position: absolute;
192 inset: 0;
193 z-index: 0;
194 background: var(--desktop-mode-bg);
195 overflow: hidden;
196 pointer-events: none;
197 }
198
199 /*
200 * Canvas children fill the layer and ignore pointer events by
201 * default — a wallpaper is presentational chrome, not an interactive
202 * element. Plugins that genuinely need a clickable wallpaper can
203 * override `pointer-events` on their own mounted node.
204 */
205 .desktop-mode-wallpaper > canvas,
206 .desktop-mode-wallpaper > * {
207 width: 100%;
208 height: 100%;
209 display: block;
210 }
211
212 /* Below 783px WP collapses the admin bar to 46px. */
213 @media screen and (max-width: 782px) {
214 .desktop-mode-shell {
215 inset-block-start: var(--wp-admin--admin-bar--height, 46px);
216 }
217 }
218
219 /*
220 * Immersive fullscreen: when any window is in fullscreen state, hide the
221 * admin bar and let the shell cover the full viewport. This keeps the
222 * fullscreen window visually above all chrome without fighting the shell's
223 * stacking context (a fullscreen window lives inside the shell, so its
224 * z-index is always bounded by the shell's — moving it out of the shell
225 * to raise it would break focus, drag, and session snapshotting).
226 *
227 * The window's title bar remains visible because the window itself is
228 * position: fixed at top: 0, so the focus/exit-fullscreen button is
229 * still reachable.
230 */
231 body.desktop-mode-has-fullscreen-window #wpadminbar {
232 display: none !important;
233 }
234
235 body.desktop-mode-has-fullscreen-window .desktop-mode-shell {
236 inset-block-start: 0;
237 }
238
239 /* Shell body: contains dock + desktop area side by side. */
240 .desktop-mode-shell__body {
241 flex: 1;
242 display: flex;
243 overflow: hidden;
244 position: relative;
245 }
246
247 /*
248 * Desktop area — where windows float. Background is now transparent
249 * because the wallpaper renders in the shell-level layer behind
250 * both this area and the dock. Stacking context via position: relative
251 * ensures children (windows) sit above the wallpaper.
252 *
253 * `padding-bottom` reserves room for the floating bottom dock pill
254 * that every layout (Classic, Unified, Spatial) puts at the bottom
255 * — 40px icons + 16px padding + 12px gap below + 8px breathing room.
256 */
257 .desktop-mode-area {
258 flex: 1;
259 position: relative;
260 overflow: hidden;
261 background: transparent;
262 padding-bottom: 80px;
263 transition: opacity 180ms ease;
264 }
265
266 /*
267 * Boot reveal gate.
268 *
269 * On F5 the shell paints in stages: the dispatcher's
270 * `repaintIcons()` lands the server-rendered wallpaper icons
271 * synchronously from `desktopModeConfig.desktopIcons`, then the
272 * files-layer mounts and momentarily renders an empty bucket while
273 * REST `listPlacements(0)` is in flight, then folders/posts/links
274 * slot in on the next paint. The user reads that as "plugins show
275 * first, then everything blinks and the rest arrives."
276 *
277 * The `--booting` modifier is applied BY PHP (see
278 * `includes/render/shell.php`) on the initial HTML so the area
279 * starts invisible BEFORE any JS runs — otherwise the dispatcher
280 * gets to paint the wallpaper icons into a visible area first and
281 * the user sees them flash before our gate kicks in.
282 *
283 * `desktop.ts` removes the class inside a `requestAnimationFrame`
284 * after the root files-layer's `hydrated` promise resolves (with a
285 * 2 s JS safety timeout). The opacity transition declared on
286 * `.desktop-mode-area` smooths the reveal.
287 *
288 * The fallback animation is a resilience layer for the case where
289 * the shell JS never runs (network error mid-load, ad-blocker
290 * tearing down a bundle, …). The animation holds the area
291 * invisible for 2.7 s then flips it to opacity 1; the
292 * `animation-fill-mode: forwards` keeps the revealed state held
293 * after the animation ends. In the normal case JS removes the
294 * class long before 2.7 s and the animation is discarded.
295 *
296 * `pointer-events: none` while booting prevents a stray click on
297 * the still-hidden surface from triggering Show-Desktop or opening
298 * a tile that's about to reposition once REST returns.
299 *
300 * @since 0.18.x
301 */
302 .desktop-mode-area--booting {
303 opacity: 0;
304 pointer-events: none;
305 animation: desktop-mode-area-boot-fallback 3s forwards;
306 }
307
308 @keyframes desktop-mode-area-boot-fallback {
309 0%,
310 90% {
311 opacity: 0;
312 pointer-events: none;
313 }
314 100% {
315 opacity: 1;
316 pointer-events: auto;
317 }
318 }
319
320 /* ---------------------------------------------------------------
321 * Desktop icons — wallpaper shortcut tiles registered via the
322 * `wp_register_desktop_icon()` PHP API. Rendered inside the
323 * desktop area as the first child after the wallpaper, so they
324 * paint above the wallpaper but beneath any window.
325 *
326 * Grid layout is fixed-width columns; tiles wrap onto multiple
327 * rows when the desktop is wide enough to host more than one
328 * column's worth. `pointer-events: none` on the container keeps
329 * the background clickable (for "minimize all" on wallpaper
330 * click) while individual tiles re-enable pointer events so they
331 * remain clickable.
332 * --------------------------------------------------------------- */
333
334 .desktop-mode-icons {
335 position: absolute;
336 inset-block-start: 16px;
337 inset-block-end: 16px;
338 inset-inline-start: 16px;
339 inset-inline-end: 16px;
340 display: grid;
341 /*
342 * Column-major flow: icons fill the first column top-to-bottom,
343 * then start the next column. Matches the macOS / Finder
344 * convention. `grid-auto-flow: column` plus `auto-fill` rows
345 * (sized to the container height) yields exactly that layout.
346 *
347 * All four insets are pinned so the container has a definite
348 * height — `repeat(auto-fill, 96px)` needs that to compute the
349 * row count. 96px is one icon button (48px image + 6px gap +
350 * up-to-two-line label + 8px vertical padding); a slightly
351 * generous figure leaves room for descenders without clipping.
352 */
353 grid-auto-flow: column;
354 grid-template-rows: repeat(auto-fill, 96px);
355 grid-auto-columns: 88px;
356 align-content: start;
357 justify-content: start;
358 gap: 12px;
359 z-index: 1;
360 pointer-events: none;
361 /*
362 * Spill protection: if a user ever pins more icons than fit
363 * vertically, the grid scrolls horizontally so the unreachable
364 * tiles can be reached without clipping the visible ones.
365 * Individual tiles re-enable pointer events so they stay
366 * clickable inside the scroll surface.
367 */
368 overflow: auto;
369 }
370
371 .desktop-mode-icon {
372 pointer-events: auto;
373 display: flex;
374 flex-direction: column;
375 align-items: center;
376 justify-content: flex-start;
377 gap: 6px;
378 width: 88px;
379 padding: 8px 4px;
380 border: 0;
381 background: transparent;
382 color: var(--desktop-mode-fg, #fff);
383 cursor: pointer;
384 border-radius: 8px;
385 transition: background-color 0.15s ease;
386 /* Positioning context for `.desktop-mode-icon__badge`. */
387 position: relative;
388 }
389
390 /*
391 * Icon badge — symmetric to `.desktop-mode-dock__badge` but anchored
392 * to the wallpaper-icon image. Same gradient, same ring, slightly
393 * larger so it reads at the longer wallpaper viewing distance.
394 * Painted by `wp.desktop.icons.setBadge( id, count )` — see
395 * `src/desktop-icons.ts` for the imperative API and
396 * `src/recycle-bin/badge.ts` for the canonical multi-rail
397 * consumer. The class name is part of the stable contract; the
398 * positioning, color, and shadow are not — themes or future
399 * shell builds may restyle freely.
400 */
401 .desktop-mode-icon__badge {
402 position: absolute;
403 top: 4px;
404 inset-inline-end: 14px;
405 min-width: 18px;
406 height: 18px;
407 padding: 0 5px;
408 box-sizing: border-box;
409 border-radius: 999px;
410 background: linear-gradient( 180deg, #ff5a5a 0%, #d63638 100% );
411 color: #fff;
412 display: inline-flex;
413 align-items: center;
414 justify-content: center;
415 font-size: 11px;
416 font-weight: 700;
417 line-height: 1;
418 font-variant-numeric: tabular-nums;
419 box-shadow:
420 0 0 0 1.5px rgba( 0, 0, 0, 0.4 ),
421 0 1px 3px rgba( 0, 0, 0, 0.35 );
422 pointer-events: none;
423 }
424
425 .desktop-mode-icon:hover,
426 .desktop-mode-icon:focus-visible {
427 background: rgba(255, 255, 255, 0.12);
428 outline: none;
429 }
430
431 .desktop-mode-icon:focus-visible {
432 box-shadow: 0 0 0 2px var(--wp-admin-theme-color, #2271b1);
433 }
434
435 .desktop-mode-icon__image {
436 display: inline-flex;
437 align-items: center;
438 justify-content: center;
439 width: 48px;
440 height: 48px;
441 font-size: 32px;
442 line-height: 1;
443 }
444
445 .desktop-mode-icon__image img {
446 max-width: 48px;
447 max-height: 48px;
448 object-fit: contain;
449 }
450
451 .desktop-mode-icon__label {
452 font-size: 12px;
453 text-align: center;
454 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
455 line-height: 1.2;
456 word-break: break-word;
457 }
458
459 /* ---------------------------------------------------------------
460 * Widgets column — right-edge glass strip that paints above the
461 * wallpaper but beneath windows. Hosts stacked widget cards and a
462 * trailing "Add widget" tile. Interactive only on its own cards +
463 * the add tile; every other pixel is pointer-transparent so window
464 * drag / resize / click-through behaviour is unaffected.
465 * --------------------------------------------------------------- */
466
467 .desktop-mode-widgets {
468 position: absolute;
469 top: 16px;
470 bottom: 16px;
471 inset-inline-end: 16px;
472 width: 320px;
473 display: flex;
474 flex-direction: column;
475 gap: 12px;
476 z-index: 1;
477 pointer-events: none;
478 overflow-y: auto;
479 /* Hide the scrollbar chrome unless the user actually overflows —
480 * most setups never will. */
481 scrollbar-width: thin;
482 /* Fade alongside the overview backdrop. The class flip that
483 * drives this is on the parent area, set at t=0 of enter and
484 * removed at t=0 of exit, so widgets fade in parallel with the
485 * dock collapse / return — never sequential. */
486 opacity: 1;
487 transition:
488 opacity 0.22s ease,
489 transform 0.28s cubic-bezier( 0.2, 0, 0.2, 1 );
490 }
491
492 .desktop-mode-area--overview .desktop-mode-widgets {
493 opacity: 0;
494 /* Slight slide-off-right so the fade reads as "the column is
495 * stepping back out of the way" rather than flat-dimming. */
496 transform: translateX( 16px );
497 pointer-events: none;
498 }
499
500 .desktop-mode-widgets__list {
501 display: flex;
502 flex-direction: column;
503 gap: 12px;
504 }
505
506 /*
507 * Individual widget card — glass backdrop matching the dock /
508 * overview top bar. Card root is pointer-events: auto so clicks on
509 * content + the remove X land; the column itself stays
510 * pointer-transparent so a drag that grazes the column's margin
511 * falls through to the window beneath.
512 */
513 .desktop-mode-widgets__card {
514 position: relative;
515 padding: 0;
516 pointer-events: auto;
517 background: rgba( 20, 20, 22, 0.55 );
518 backdrop-filter: blur( 18px ) saturate( 140% );
519 -webkit-backdrop-filter: blur( 18px ) saturate( 140% );
520 border: 1px solid rgba( 255, 255, 255, 0.08 );
521 border-radius: 14px;
522 color: #fff;
523 box-shadow:
524 0 8px 28px rgba( 0, 0, 0, 0.35 ),
525 inset 0 0 0 1px rgba( 255, 255, 255, 0.04 );
526 transition:
527 transform 0.18s ease,
528 box-shadow 0.18s ease,
529 opacity 0.18s ease;
530 }
531
532 .desktop-mode-widgets__card:hover {
533 transform: translateX( -2px );
534 box-shadow:
535 0 12px 36px rgba( 0, 0, 0, 0.45 ),
536 inset 0 0 0 1px rgba( 255, 255, 255, 0.08 );
537 }
538
539 .desktop-mode-widgets__card-body {
540 padding: 16px;
541 min-height: 48px;
542 }
543
544 .desktop-mode-widgets__card-close {
545 position: absolute;
546 top: 6px;
547 inset-inline-end: 6px;
548 width: 22px;
549 height: 22px;
550 display: flex;
551 align-items: center;
552 justify-content: center;
553 padding: 0;
554 border: 0;
555 background: rgba( 0, 0, 0, 0.55 );
556 color: rgba( 255, 255, 255, 0.75 );
557 border-radius: 50%;
558 cursor: pointer;
559 opacity: 0;
560 transition:
561 opacity 0.15s ease,
562 background-color 0.15s ease,
563 color 0.15s ease;
564 }
565
566 .desktop-mode-widgets__card:hover .desktop-mode-widgets__card-close,
567 .desktop-mode-widgets__card-close:focus-visible {
568 opacity: 1;
569 }
570
571 .desktop-mode-widgets__card-close:hover {
572 background: #d63638;
573 color: #fff;
574 }
575
576 /*
577 * Movable widgets — a thin chrome header at the top of the card holds
578 * the drag grip, the widget's own label, and the × button. The whole
579 * header is the drag handle (except for interactive children — those
580 * remain clickable as normal). Non-movable widgets keep today's
581 * corner-× layout unchanged.
582 */
583 .desktop-mode-widgets__chrome {
584 display: flex;
585 align-items: center;
586 gap: 8px;
587 padding: 6px 8px;
588 border-bottom: 1px solid rgba( 255, 255, 255, 0.06 );
589 cursor: grab;
590 user-select: none;
591 -webkit-user-select: none;
592 touch-action: none;
593 }
594
595 .desktop-mode-widgets__card--dragging .desktop-mode-widgets__chrome {
596 cursor: grabbing;
597 }
598
599 .desktop-mode-widgets__grip {
600 flex-shrink: 0;
601 width: 10px;
602 height: 16px;
603 background-image: radial-gradient(
604 circle,
605 rgba( 255, 255, 255, 0.55 ) 1.2px,
606 transparent 1.5px
607 );
608 background-size: 5px 5px;
609 background-position: 0 1px;
610 background-repeat: space;
611 opacity: 0.45;
612 transition: opacity 0.12s ease;
613 }
614
615 .desktop-mode-widgets__card:hover .desktop-mode-widgets__grip {
616 opacity: 0.85;
617 }
618
619 .desktop-mode-widgets__title {
620 flex: 1;
621 font-size: 12px;
622 font-weight: 500;
623 letter-spacing: 0.01em;
624 color: rgba( 255, 255, 255, 0.85 );
625 overflow: hidden;
626 text-overflow: ellipsis;
627 white-space: nowrap;
628 }
629
630 /* When the close button sits inside the chrome it's inline, not
631 corner-absolute — reset position + always-visible opacity. */
632 .desktop-mode-widgets__chrome .desktop-mode-widgets__card-close {
633 position: static;
634 opacity: 0.7;
635 width: 20px;
636 height: 20px;
637 }
638
639 .desktop-mode-widgets__chrome .desktop-mode-widgets__card-close:hover,
640 .desktop-mode-widgets__chrome .desktop-mode-widgets__card-close:focus-visible {
641 opacity: 1;
642 }
643
644 /*
645 * Re-dock button — sits in the chrome next to the close button and
646 * returns a floating widget to the right column. Visibility is
647 * gated on the `--floating` modifier class so docked widgets never
648 * expose it (tapping it on a docked card would be a confusing
649 * no-op). The DOM node stays mounted either way so state flips
650 * don't rebuild chrome.
651 *
652 * Styling mirrors the inline close button: 20×20 round tile,
653 * dimmed by default, full-opacity on hover / focus. Distinct hover
654 * color (theme accent, not red) signals "put back" vs. "remove."
655 */
656 .desktop-mode-widgets__card-redock {
657 display: none;
658 align-items: center;
659 justify-content: center;
660 padding: 0;
661 border: 0;
662 background: rgba( 0, 0, 0, 0.55 );
663 color: rgba( 255, 255, 255, 0.75 );
664 border-radius: 50%;
665 width: 20px;
666 height: 20px;
667 cursor: pointer;
668 opacity: 0.7;
669 transition:
670 opacity 0.15s ease,
671 background-color 0.15s ease,
672 color 0.15s ease;
673 }
674
675 .desktop-mode-widgets__card--floating .desktop-mode-widgets__card-redock {
676 display: flex;
677 }
678
679 .desktop-mode-widgets__card-redock:hover,
680 .desktop-mode-widgets__card-redock:focus-visible {
681 opacity: 1;
682 background: var( --wp-admin-theme-color, #2271b1 );
683 color: #fff;
684 }
685
686 /*
687 * Floating widgets — absolute-positioned, inline top/left/width/height
688 * written by the frame. The hover translate-X effect would fight the
689 * user's chosen position, so disable it.
690 */
691 .desktop-mode-widgets__card--floating {
692 position: absolute;
693 margin: 0;
694 }
695
696 .desktop-mode-widgets__card--floating:hover {
697 transform: none;
698 }
699
700 .desktop-mode-widgets__card--dragging,
701 .desktop-mode-widgets__card--resizing {
702 transition: none;
703 z-index: 2;
704 }
705
706 /*
707 * Resize handles — 8 in all, with the 4 edges as hair-line strips and
708 * the 4 corners as small squares. Invisible by default; cursor change
709 * telegraphs them on hover. The frame attaches pointer listeners to
710 * every handle; a non-movable widget ignores everything except the
711 * `s` (bottom-edge, height-only) handle at the pointer layer, so we
712 * visually hide the mismatched handles via the modifier class.
713 */
714 .desktop-mode-widgets__resize {
715 position: absolute;
716 z-index: 1;
717 }
718
719 .desktop-mode-widgets__resize--n {
720 top: -3px;
721 left: 8px;
722 right: 8px;
723 height: 6px;
724 cursor: ns-resize;
725 }
726
727 .desktop-mode-widgets__resize--s {
728 bottom: -3px;
729 left: 8px;
730 right: 8px;
731 height: 6px;
732 cursor: ns-resize;
733 }
734
735 .desktop-mode-widgets__resize--e {
736 top: 8px;
737 bottom: 8px;
738 right: -3px;
739 width: 6px;
740 cursor: ew-resize;
741 }
742
743 .desktop-mode-widgets__resize--w {
744 top: 8px;
745 bottom: 8px;
746 left: -3px;
747 width: 6px;
748 cursor: ew-resize;
749 }
750
751 .desktop-mode-widgets__resize--ne {
752 top: -4px;
753 right: -4px;
754 width: 12px;
755 height: 12px;
756 cursor: nesw-resize;
757 }
758
759 .desktop-mode-widgets__resize--nw {
760 top: -4px;
761 left: -4px;
762 width: 12px;
763 height: 12px;
764 cursor: nwse-resize;
765 }
766
767 .desktop-mode-widgets__resize--se {
768 bottom: -4px;
769 right: -4px;
770 width: 12px;
771 height: 12px;
772 cursor: nwse-resize;
773 }
774
775 .desktop-mode-widgets__resize--sw {
776 bottom: -4px;
777 left: -4px;
778 width: 12px;
779 height: 12px;
780 cursor: nesw-resize;
781 }
782
783 /*
784 * Non-movable resizable widgets only expose the bottom-edge handle —
785 * width stays locked to the column width. Hide the other seven so the
786 * cursor doesn't lie about what's interactive.
787 */
788 .desktop-mode-widgets__card--resizable:not( .desktop-mode-widgets__card--movable ) .desktop-mode-widgets__resize:not( .desktop-mode-widgets__resize--s ) {
789 display: none;
790 }
791
792 /*
793 * Add-widget tile — matches the "+" tile in the overview top bar.
794 * Always visible at the bottom of the column; promoted to center
795 * when the column is otherwise empty so first-run users see a
796 * friendly target rather than an empty strip.
797 */
798 .desktop-mode-widgets__add {
799 margin-top: auto;
800 padding: 14px 12px;
801 display: flex;
802 align-items: center;
803 justify-content: center;
804 gap: 8px;
805 pointer-events: auto;
806 background: transparent;
807 border: 2px dashed rgba( 255, 255, 255, 0.22 );
808 border-radius: 14px;
809 color: rgba( 255, 255, 255, 0.72 );
810 font: inherit;
811 font-weight: 500;
812 cursor: pointer;
813 transition:
814 border-color 0.15s ease,
815 background-color 0.15s ease,
816 color 0.15s ease;
817 }
818
819 .desktop-mode-widgets__add:hover {
820 border-color: rgba( 255, 255, 255, 0.4 );
821 background: rgba( 255, 255, 255, 0.06 );
822 color: #fff;
823 }
824
825 .desktop-mode-widgets__add:focus-visible {
826 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
827 outline-offset: 2px;
828 }
829
830 .desktop-mode-widgets__add-plus {
831 font-size: 20px;
832 line-height: 1;
833 font-weight: 200;
834 }
835
836 .desktop-mode-widgets__add-label {
837 font-size: 13px;
838 letter-spacing: 0.02em;
839 }
840
841 /* Built-in clock widget */
842 .desktop-mode-widget-clock {
843 text-align: center;
844 /* The time + date are passive glyphs, not content users would
845 * ever reasonably select — turning it off avoids the accidental-
846 * highlight mess that happens when a drag starts on the clock. */
847 user-select: none;
848 -webkit-user-select: none;
849 }
850
851 .desktop-mode-widget-clock__time {
852 font-size: 34px;
853 font-weight: 600;
854 line-height: 1.1;
855 font-variant-numeric: tabular-nums;
856 letter-spacing: 0.01em;
857 }
858
859 .desktop-mode-widget-clock__date {
860 margin-top: 4px;
861 font-size: 13px;
862 color: rgba( 255, 255, 255, 0.75 );
863 letter-spacing: 0.02em;
864 }
865
866 /* ---------------------------------------------------------------
867 * Widget picker popover — opens from the add tile, lists every
868 * registered widget. Positioned fixed so it can extend outside
869 * the desktop-area clip.
870 * --------------------------------------------------------------- */
871
872 .desktop-mode-widget-picker {
873 position: fixed;
874 width: 320px;
875 max-height: 60vh;
876 overflow-y: auto;
877 padding: 10px;
878 border-radius: 14px;
879 background: rgba( 20, 20, 22, 0.88 );
880 backdrop-filter: blur( 22px ) saturate( 160% );
881 -webkit-backdrop-filter: blur( 22px ) saturate( 160% );
882 border: 1px solid rgba( 255, 255, 255, 0.1 );
883 box-shadow: 0 18px 54px rgba( 0, 0, 0, 0.55 );
884 color: #fff;
885 z-index: 9999;
886 animation: desktop-mode-widget-picker-in 0.14s ease-out;
887 }
888
889 @keyframes desktop-mode-widget-picker-in {
890 from {
891 opacity: 0;
892 transform: translateY( 4px );
893 }
894 }
895
896 .desktop-mode-widget-picker__title {
897 padding: 4px 8px 8px;
898 font-size: 11px;
899 font-weight: 600;
900 letter-spacing: 0.08em;
901 text-transform: uppercase;
902 color: rgba( 255, 255, 255, 0.55 );
903 }
904
905 .desktop-mode-widget-picker__list {
906 display: flex;
907 flex-direction: column;
908 gap: 4px;
909 }
910
911 .desktop-mode-widget-picker__entry {
912 display: flex;
913 align-items: center;
914 gap: 12px;
915 padding: 10px;
916 background: transparent;
917 border: 0;
918 border-radius: 10px;
919 color: #fff;
920 cursor: pointer;
921 text-align: start;
922 font: inherit;
923 transition: background-color 0.12s ease;
924 }
925
926 .desktop-mode-widget-picker__entry:hover:not( :disabled ) {
927 background: rgba( 255, 255, 255, 0.08 );
928 }
929
930 .desktop-mode-widget-picker__entry:focus-visible {
931 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
932 outline-offset: 1px;
933 }
934
935 .desktop-mode-widget-picker__entry--added {
936 opacity: 0.55;
937 cursor: default;
938 }
939
940 .desktop-mode-widget-picker__entry-icon {
941 flex-shrink: 0;
942 font-size: 20px;
943 width: 20px;
944 height: 20px;
945 line-height: 1;
946 }
947
948 .desktop-mode-widget-picker__entry-text {
949 display: flex;
950 flex-direction: column;
951 gap: 2px;
952 flex: 1;
953 min-width: 0;
954 }
955
956 .desktop-mode-widget-picker__entry-label {
957 font-size: 13px;
958 font-weight: 500;
959 white-space: nowrap;
960 overflow: hidden;
961 text-overflow: ellipsis;
962 }
963
964 .desktop-mode-widget-picker__entry-description {
965 font-size: 11px;
966 color: rgba( 255, 255, 255, 0.6 );
967 line-height: 1.35;
968 white-space: normal;
969 }
970
971 .desktop-mode-widget-picker__entry-status {
972 flex-shrink: 0;
973 font-size: 11px;
974 color: rgba( 255, 255, 255, 0.5 );
975 text-transform: uppercase;
976 letter-spacing: 0.08em;
977 }
978
979 .desktop-mode-widget-picker__empty {
980 padding: 12px 8px;
981 font-size: 12px;
982 color: rgba( 255, 255, 255, 0.6 );
983 line-height: 1.4;
984 }
985
986 /**
987 * Chromeless mode: pages loaded inside iframes.
988 * Remove all shell chrome and let content fill the frame.
989 */
990 .desktop-mode-chromeless {
991 margin: 0;
992 padding: 0;
993 background: var(--desktop-mode-window-bg);
994 }
995
996 /*
997 * Chromeless #wpbody-content, #adminmenuwrap, #wpfooter overrides are in
998 * chromeless.css to keep all legacy page tweaks in one place.
999 */
1000
1001 /*
1002 * Hide WordPress core's built-in command palette (`@wordpress/commands`).
1003 * Desktop mode force-enqueues `wp_enqueue_command_palette_assets()` so
1004 * the `core/commands` data store is populated for our shell harvester
1005 * (see `src/commands/shell-harvester.ts`); the side effect is that WP
1006 * also mounts its own `<CommandPalette>` to the document body. We
1007 * already block its Cmd+K handler in `src/palette-registry.ts`, but if
1008 * a third-party script opens it programmatically (`wp.data.dispatch(
1009 * 'core/commands' ).open()`), the dialog would float over the desktop
1010 * with admin-menu navigation callbacks whose `document.location = url`
1011 * would unload the shell. Keep it permanently hidden.
1012 */
1013 .commands-command-menu,
1014 .commands-command-menu__overlay {
1015 display: none !important;
1016 }
1017