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

1,415 lines 42.5 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 * - The AI assistant's inline "AI Settings" recovery link keeps a
34 * pointer so it reads as a genuine link (rule lives in
35 * ai-assistant.css).
36 *
37 * `!important` is needed because the specific `cursor: pointer` and
38 * drag-state declarations elsewhere in the codebase have the same or
39 * higher specificity. The resize-handle exceptions use `!important`
40 * for the same reason.
41 *
42 * Shadow-DOM web components (`wpd-*`) define their own cursors
43 * inside their shadow trees; those internal rules win regardless of
44 * what we set on the host. Anything visible on the desktop shell
45 * (light DOM) is covered by this policy.
46 */
47 body.desktop-mode-active,
48 body.desktop-mode-active * {
49 cursor: default !important;
50 }
51
52 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"]),
53 body.desktop-mode-active textarea,
54 body.desktop-mode-active [contenteditable=""],
55 body.desktop-mode-active [contenteditable="true"] {
56 cursor: text !important;
57 }
58
59 body.desktop-mode-active .desktop-mode-window__resize-handle--se,
60 body.desktop-mode-active .desktop-mode-window__resize-handle--nw,
61 body.desktop-mode-active .desktop-mode-widgets__resize--se,
62 body.desktop-mode-active .desktop-mode-widgets__resize--nw {
63 cursor: nwse-resize !important;
64 }
65
66 body.desktop-mode-active .desktop-mode-window__resize-handle--sw,
67 body.desktop-mode-active .desktop-mode-window__resize-handle--ne,
68 body.desktop-mode-active .desktop-mode-widgets__resize--sw,
69 body.desktop-mode-active .desktop-mode-widgets__resize--ne {
70 cursor: nesw-resize !important;
71 }
72
73 body.desktop-mode-active .desktop-mode-widgets__resize--n,
74 body.desktop-mode-active .desktop-mode-widgets__resize--s {
75 cursor: ns-resize !important;
76 }
77
78 body.desktop-mode-active .desktop-mode-widgets__resize--e,
79 body.desktop-mode-active .desktop-mode-widgets__resize--w {
80 cursor: ew-resize !important;
81 }
82
83 /*
84 * Hide the classic admin chrome while the shell is active.
85 *
86 * The markup still exists in the DOM so server-side action hooks that
87 * target these regions (admin_notices, in_admin_footer, etc.) continue
88 * to fire — we just take them out of the visual flow.
89 */
90 body.desktop-mode-active #adminmenuwrap,
91 body.desktop-mode-active #adminmenuback,
92 body.desktop-mode-active #wpbody,
93 body.desktop-mode-active #wpfooter,
94 body.desktop-mode-active .wp-responsive-toggle,
95 body.desktop-mode-active #collapse-menu {
96 display: none !important;
97 }
98
99 /*
100 * The admin bar is the ONLY escape hatch in desktop mode — it hosts the
101 * "Switch to Classic Admin" toggle. Any plugin CSS, Gutenberg state,
102 * user preference, or stray `display:none` that hides it would strand
103 * the user inside the shell with no way out. Hard-pin it visible.
104 *
105 * The exception: while a window is in immersive fullscreen, we *want*
106 * the bar hidden (macOS convention). That rule lives below and wins on
107 * specificity because it also targets #wpadminbar under a body class.
108 */
109 body.desktop-mode-active #wpadminbar {
110 display: block !important;
111 visibility: visible !important;
112 opacity: 1 !important;
113 position: fixed !important;
114 inset-block-start: 0 !important;
115 inset-inline-start: 0 !important;
116 inset-inline-end: 0 !important;
117 }
118
119 /*
120 * Admin-bar presentation modes — OS Settings → Appearance → Admin bar,
121 * persisted as `adminBarMode` and emitted as a
122 * `desktop-mode-admin-bar-<mode>` body class by PHP on first paint and
123 * re-written by the shell's apply pass on every change.
124 *
125 * `static` is the absence of rules: the pin above stands and the shell
126 * starts below the bar. The two modes below both take the bar out of
127 * the shell's way, so the shell reclaims the full viewport in each.
128 *
129 * Every selector here carries two body classes, so it outranks the
130 * one-class pin rule above without needing to escalate anything.
131 */
132
133 /*
134 * Dynamic — the bar parks off the top edge leaving a peek strip, and
135 * slides back in when the pointer reaches the top of the viewport or
136 * something inside it takes keyboard focus. The classic Windows
137 * auto-hide taskbar, and the reason `transform` is the right tool: a
138 * transformed element keeps hit-testing at its PAINTED position, so
139 * the parked remainder stops catching the pointer the moment it goes
140 * off-screen. What DOES catch it is the peek strip plus the reveal
141 * zone added below.
142 */
143 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar {
144 transform: translateY(
145 calc(-100% + var(--desktop-mode-admin-bar-peek, 4px))
146 );
147 transition: transform 180ms ease;
148 }
149
150 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar:hover,
151 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar:focus-within,
152 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar:active {
153 transform: translateY(0);
154 }
155
156 /*
157 * Reveal zone — an invisible extension of the bar's hit area, hanging
158 * below the parked peek strip.
159 *
160 * Hovering a pseudo-element counts as hovering its originating
161 * element, so this widens what `:hover` above responds to WITHOUT
162 * widening the visible seam. The two wants are genuinely different:
163 * the seam should be a hairline, the target should be forgiving, and
164 * `--desktop-mode-admin-bar-peek` alone can't be both.
165 *
166 * Core ships no bare `#wpadminbar::before/::after` rule, so this
167 * claims the slot rather than fighting one.
168 */
169 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar::after {
170 content: "";
171 position: absolute;
172 inset-inline: 0;
173 inset-block-start: 100%;
174 height: var(--desktop-mode-admin-bar-reveal-zone, 16px);
175 }
176
177 /*
178 * ...but only while the bar is parked. Left standing once the bar is
179 * out, the zone would hang over the top of the desktop and swallow
180 * clicks on whatever window is up there.
181 *
182 * Collapsing it is also what keeps the reveal stable: the pointer
183 * that tripped the zone is, post-reveal, inside the bar's real box,
184 * so `:hover` holds without the zone's help.
185 */
186 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar:hover::after,
187 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar:focus-within::after,
188 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar:active::after {
189 height: 0;
190 }
191
192 /*
193 * Coarse pointers have no hover state and a much fatter contact patch,
194 * so a 4px seam is neither discoverable nor tappable. Widen both the
195 * seam and the zone — `:active` above is what actually reveals the bar
196 * there. Kept under the 32px bar height, same ceiling as the default.
197 */
198 @media (hover: none) {
199 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar {
200 --desktop-mode-admin-bar-peek: 10px;
201 --desktop-mode-admin-bar-reveal-zone: 20px;
202 }
203 }
204
205 @media (prefers-reduced-motion: reduce) {
206 body.desktop-mode-active.desktop-mode-admin-bar-dynamic #wpadminbar {
207 transition: none;
208 }
209 }
210
211 /*
212 * Hidden — the bar is gone. This removes the "Switch to Classic Admin"
213 * toggle, which is why it is safe: the dock's core rail always carries
214 * an "Exit Desktop Mode" tile (src/exit-desktop-mode.ts) hitting the
215 * same endpoint. Do not add a mode that removes both.
216 */
217 body.desktop-mode-active.desktop-mode-admin-bar-hidden #wpadminbar {
218 display: none !important;
219 }
220
221 /*
222 * Both non-static modes: the shell owns the whole viewport. Two body
223 * classes beat the `.desktop-mode-shell` base rule AND its <783px
224 * media-query variant on specificity, so source order doesn't matter.
225 */
226 body.desktop-mode-active.desktop-mode-admin-bar-dynamic .desktop-mode-shell,
227 body.desktop-mode-active.desktop-mode-admin-bar-hidden .desktop-mode-shell {
228 inset-block-start: 0;
229 }
230
231 /*
232 * Core reserves the bar's height as `padding-top` on <html>. In both
233 * non-static modes the bar is out of flow (or absent) and the shell
234 * covers the viewport anyway, so the reserve is dead space — release
235 * it so any classic content behind the shell isn't pushed down by a
236 * bar that isn't sitting there. Same idiom as chromeless.css.
237 */
238 html.wp-toolbar:has(body.desktop-mode-admin-bar-dynamic),
239 html.wp-toolbar:has(body.desktop-mode-admin-bar-hidden) {
240 padding-top: 0 !important;
241 }
242
243 /*
244 * Align the admin-bar W logo with the dock below it.
245 *
246 * Without this rule the admin bar's leftmost item (the WordPress W)
247 * renders at its Core-dictated ~44 px width, while the dock below it
248 * can be 48, 56, or 72 px wide depending on the user's OS-Settings
249 * preference. The misalignment reads as "two disconnected left
250 * regions" — visually scruffy on an otherwise tight shell.
251 *
252 * We size the logo's <li> to match the dock's width and center the
253 * W inside, so the two columns share an axis. `--desktop-mode-dock-width`
254 * is now set on :root (see settings.ts → apply()) so it reaches
255 * siblings of #desktop-mode-shell like the admin bar.
256 */
257 body.desktop-mode-active #wpadminbar #wp-admin-bar-wp-logo {
258 width: var( --desktop-mode-dock-width, 56px );
259 }
260
261 body.desktop-mode-active #wpadminbar #wp-admin-bar-wp-logo > .ab-item {
262 display: flex !important;
263 align-items: center;
264 justify-content: center;
265 padding-inline: 0 !important;
266 }
267
268 /*
269 * The W glyph is painted via a ::before pseudo on `.ab-icon`. Center
270 * it within the flex cell so varying dock widths don't leave it
271 * visually offset.
272 */
273 body.desktop-mode-active #wpadminbar #wp-admin-bar-wp-logo > .ab-item > .ab-icon {
274 margin: 0;
275 }
276
277 /*
278 * Classic wpcontent carries a `margin-left: 160px` for the sidebar. With
279 * the sidebar hidden we let wpcontent fall back to flush — the shell is
280 * fixed-positioned anyway so this doesn't change the shell's layout, it
281 * just prevents a phantom sidebar gutter in any stray visible content.
282 */
283 body.desktop-mode-active #wpcontent {
284 margin-inline-start: 0;
285 padding-inline-start: 0;
286 }
287
288 /*
289 * Desktop shell — fills the viewport below the admin bar.
290 * Uses position: fixed so it's unaffected by any containing-block
291 * quirks in #wpwrap/#wpcontent.
292 */
293 .desktop-mode-shell {
294 position: fixed;
295 inset-block-start: var(--wp-admin--admin-bar--height, 32px);
296 inset-inline-start: 0;
297 inset-inline-end: 0;
298 inset-block-end: 0;
299 display: flex;
300 flex-direction: column;
301 overflow: hidden;
302 z-index: var(--desktop-mode-z-base);
303 /*
304 * Shell typeface. Every piece of chrome that doesn't set its own
305 * face inherits from here — dock labels, desktop icon labels,
306 * widgets, the overview. Windows override with `--wpd-font` on
307 * their body, so a theme can run a display face on the desk and a
308 * text face inside windows. `inherit` is the no-theme value, i.e.
309 * identical to not declaring the property at all.
310 */
311 font-family: var(--desktop-mode-font, inherit);
312 }
313
314 /*
315 * Wallpaper layer — first child of the shell, sits behind the dock
316 * and desktop area so a translucent dock shows through to the
317 * background (macOS pattern). CSS wallpapers paint via the
318 * `--desktop-mode-bg` custom property; canvas wallpapers mount their
319 * own DOM (typically a <canvas>) into this element via JS.
320 *
321 * z-index: 0 keeps it below the shell body flex row which has no
322 * explicit z-index of its own but establishes its own local
323 * stacking context via `position: relative`, so the dock + windows
324 * always render above the wallpaper regardless of DOM order.
325 */
326 .desktop-mode-wallpaper {
327 position: absolute;
328 inset: 0;
329 z-index: 0;
330 background: var(--desktop-mode-bg);
331 overflow: hidden;
332 pointer-events: none;
333 }
334
335 /*
336 * Desktop-theme wallpaper texture (DESKTOP slot).
337 *
338 * On a `::before` rather than this element's own background, because
339 * `--desktop-mode-bg` is set by the wallpaper picker and can be a
340 * gradient — i.e. a background-IMAGE. Overriding `background-image`
341 * here would silently erase every gradient wallpaper the moment a
342 * theme was active. The pseudo-element layers instead of replacing.
343 *
344 * Resulting precedence, bottom to top:
345 * CSS wallpaper (`--desktop-mode-bg`)
346 * → theme DESKTOP texture (this rule)
347 * → canvas wallpapers, which mount as CHILDREN of the layer and
348 * therefore paint above both.
349 *
350 * A theme that wants the user's wallpaper to remain visible ships a
351 * texture with transparency; an opaque one takes the desk over.
352 * With no theme active the image is `none` and this paints nothing.
353 */
354 .desktop-mode-wallpaper::before {
355 content: "";
356 position: absolute;
357 inset: 0;
358 pointer-events: none;
359 background-image: var(--desktop-mode-desktop-image, none);
360 background-repeat: var(--desktop-mode-desktop-image-repeat, repeat);
361 background-size: var(--desktop-mode-desktop-image-size, auto);
362 background-position: var(--desktop-mode-desktop-image-position, center);
363 }
364
365 /*
366 * Canvas children fill the layer and ignore pointer events by
367 * default — a wallpaper is presentational chrome, not an interactive
368 * element. Plugins that genuinely need a clickable wallpaper can
369 * override `pointer-events` on their own mounted node.
370 */
371 .desktop-mode-wallpaper > canvas,
372 .desktop-mode-wallpaper > * {
373 width: 100%;
374 height: 100%;
375 display: block;
376 }
377
378 /* Below 783px WP collapses the admin bar to 46px. */
379 @media screen and (max-width: 782px) {
380 .desktop-mode-shell {
381 inset-block-start: var(--wp-admin--admin-bar--height, 46px);
382 }
383 }
384
385 /*
386 * Immersive fullscreen: when any window is in fullscreen state, hide the
387 * admin bar and let the shell cover the full viewport. This keeps the
388 * fullscreen window visually above all chrome without fighting the shell's
389 * stacking context (a fullscreen window lives inside the shell, so its
390 * z-index is always bounded by the shell's — moving it out of the shell
391 * to raise it would break focus, drag, and session snapshotting).
392 *
393 * The window's title bar remains visible because the window itself is
394 * position: fixed at top: 0, so the focus/exit-fullscreen button is
395 * still reachable.
396 */
397 body.desktop-mode-has-fullscreen-window #wpadminbar {
398 display: none !important;
399 }
400
401 body.desktop-mode-has-fullscreen-window .desktop-mode-shell {
402 inset-block-start: 0;
403 }
404
405 /* Shell body: contains dock + desktop area side by side. */
406 .desktop-mode-shell__body {
407 flex: 1;
408 display: flex;
409 overflow: hidden;
410 position: relative;
411 }
412
413 /*
414 * Desktop area — where windows float. Background is now transparent
415 * because the wallpaper renders in the shell-level layer behind
416 * both this area and the dock. Stacking context via position: relative
417 * ensures children (windows) sit above the wallpaper.
418 *
419 * `padding-bottom` reserves room for the floating bottom dock pill
420 * that every layout (Classic, Unified, Spatial) puts at the bottom
421 * — 40px icons + 16px padding + 12px gap below + 8px breathing room.
422 */
423 .desktop-mode-area {
424 flex: 1;
425 position: relative;
426 overflow: hidden;
427 background: transparent;
428 padding-bottom: 80px;
429 transition: opacity 180ms ease;
430 }
431
432 /*
433 * Boot reveal gate.
434 *
435 * On F5 the shell paints in stages: the dispatcher's
436 * `repaintIcons()` lands the server-rendered wallpaper icons
437 * synchronously from `desktopModeConfig.desktopIcons`, then the
438 * files-layer mounts and momentarily renders an empty bucket while
439 * REST `listPlacements(0)` is in flight, then folders/posts/links
440 * slot in on the next paint. The user reads that as "plugins show
441 * first, then everything blinks and the rest arrives."
442 *
443 * The `--booting` modifier is applied BY PHP (see
444 * `includes/render/shell.php`) on the initial HTML so the area
445 * starts invisible BEFORE any JS runs — otherwise the dispatcher
446 * gets to paint the wallpaper icons into a visible area first and
447 * the user sees them flash before our gate kicks in.
448 *
449 * `desktop.ts` removes the class inside a `requestAnimationFrame`
450 * after the root files-layer's `hydrated` promise resolves (with a
451 * 2 s JS safety timeout). The opacity transition declared on
452 * `.desktop-mode-area` smooths the reveal.
453 *
454 * The fallback animation is a resilience layer for the case where
455 * the shell JS never runs (network error mid-load, ad-blocker
456 * tearing down a bundle, …). The animation holds the area
457 * invisible for 2.7 s then flips it to opacity 1; the
458 * `animation-fill-mode: forwards` keeps the revealed state held
459 * after the animation ends. In the normal case JS removes the
460 * class long before 2.7 s and the animation is discarded.
461 *
462 * `pointer-events: none` while booting prevents a stray click on
463 * the still-hidden surface from triggering Show-Desktop or opening
464 * a tile that's about to reposition once REST returns.
465 *
466 * @since 0.18.x
467 */
468 .desktop-mode-area--booting {
469 opacity: 0;
470 pointer-events: none;
471 animation: desktop-mode-area-boot-fallback 3s forwards;
472 }
473
474 @keyframes desktop-mode-area-boot-fallback {
475 0%,
476 90% {
477 opacity: 0;
478 pointer-events: none;
479 }
480 100% {
481 opacity: 1;
482 pointer-events: auto;
483 }
484 }
485
486 /* ---------------------------------------------------------------
487 * Desktop icons — wallpaper shortcut tiles registered via the
488 * `wp_register_desktop_icon()` PHP API. Rendered inside the
489 * desktop area as the first child after the wallpaper, so they
490 * paint above the wallpaper but beneath any window.
491 *
492 * Grid layout is fixed-width columns; tiles wrap onto multiple
493 * rows when the desktop is wide enough to host more than one
494 * column's worth. `pointer-events: none` on the container keeps
495 * the background clickable (for "minimize all" on wallpaper
496 * click) while individual tiles re-enable pointer events so they
497 * remain clickable.
498 * --------------------------------------------------------------- */
499
500 .desktop-mode-icons {
501 position: absolute;
502 inset-block-start: 16px;
503 inset-block-end: 16px;
504 inset-inline-start: 16px;
505 inset-inline-end: 16px;
506 display: grid;
507 /*
508 * Column-major flow: icons fill the first column top-to-bottom,
509 * then start the next column. Matches the macOS / Finder
510 * convention. `grid-auto-flow: column` plus `auto-fill` rows
511 * (sized to the container height) yields exactly that layout.
512 *
513 * All four insets are pinned so the container has a definite
514 * height — `repeat(auto-fill, 96px)` needs that to compute the
515 * row count. 96px is one icon button (48px image + 6px gap +
516 * up-to-two-line label + 8px vertical padding); a slightly
517 * generous figure leaves room for descenders without clipping.
518 */
519 grid-auto-flow: column;
520 grid-template-rows: repeat(auto-fill, 96px);
521 grid-auto-columns: 88px;
522 align-content: start;
523 justify-content: start;
524 gap: 12px;
525 z-index: 1;
526 pointer-events: none;
527 /*
528 * Spill protection: if a user ever pins more icons than fit
529 * vertically, the grid scrolls horizontally so the unreachable
530 * tiles can be reached without clipping the visible ones.
531 * Individual tiles re-enable pointer events so they stay
532 * clickable inside the scroll surface.
533 */
534 overflow: auto;
535 }
536
537 .desktop-mode-icon {
538 pointer-events: auto;
539 display: flex;
540 flex-direction: column;
541 align-items: center;
542 justify-content: flex-start;
543 gap: 6px;
544 width: 88px;
545 padding: 8px 4px;
546 border: 0;
547 background-color: transparent;
548 /*
549 * ICON_TILE texture slot — a plate behind each wallpaper icon.
550 * Painted under the icon image and its label, so a theme can give
551 * desktop shortcuts a physical tile the way a phone home screen
552 * does. Unset = `none` = the transparent tile we always had.
553 */
554 background-image: var(--desktop-mode-tile-image, none);
555 background-repeat: var(--desktop-mode-tile-image-repeat, no-repeat);
556 background-size: var(--desktop-mode-tile-image-size, auto);
557 background-position: var(--desktop-mode-tile-image-position, center);
558 color: var(--desktop-mode-fg, #fff);
559 cursor: pointer;
560 border-radius: 8px;
561 transition: background-color 0.15s ease;
562 /* Positioning context for `.desktop-mode-icon__badge`. */
563 position: relative;
564 }
565
566 /*
567 * Icon badge — symmetric to `.desktop-mode-dock__badge` but anchored
568 * to the wallpaper-icon image. Same gradient, same ring, slightly
569 * larger so it reads at the longer wallpaper viewing distance.
570 * Painted by `wp.desktop.icons.setBadge( id, count )` — see
571 * `src/desktop-icons.ts` for the imperative API and
572 * `src/recycle-bin/badge.ts` for the canonical multi-rail
573 * consumer. The class name is part of the stable contract; the
574 * positioning, color, and shadow are not — themes or future
575 * shell builds may restyle freely.
576 */
577 .desktop-mode-icon__badge {
578 position: absolute;
579 top: 4px;
580 inset-inline-end: 14px;
581 /* Same derivation as the dock badge, sized off the wallpaper icon
582 * rather than the dock icon. The default resolves to 18px / 11px —
583 * exactly the old values. */
584 --desktop-mode-icon-badge-computed-size: var(
585 --desktop-mode-icon-badge-size,
586 calc( var( --desktop-mode-icon-image-size, 48px ) * 0.375 )
587 );
588 min-width: var( --desktop-mode-icon-badge-computed-size );
589 height: var( --desktop-mode-icon-badge-computed-size );
590 padding: 0 var(
591 --desktop-mode-icon-badge-padding,
592 calc( var( --desktop-mode-icon-badge-computed-size ) * 0.28 )
593 );
594 box-sizing: border-box;
595 border-radius: 999px;
596 background: var(
597 --desktop-mode-icon-badge-bg,
598 linear-gradient( 180deg, #ff5a5a 0%, var( --wpd-danger, #d63638 ) 100% )
599 );
600 color: var( --desktop-mode-icon-badge-fg, var( --wpd-fg-on-accent, #fff ) );
601 display: inline-flex;
602 align-items: center;
603 justify-content: center;
604 font-size: var(
605 --desktop-mode-icon-badge-font-size,
606 calc( var( --desktop-mode-icon-badge-computed-size ) * 0.611 )
607 );
608 font-weight: var( --desktop-mode-icon-badge-font-weight, 700 );
609 line-height: 1;
610 font-variant-numeric: tabular-nums;
611 box-shadow:
612 0 0 0 1.5px rgba( 0, 0, 0, 0.4 ),
613 0 1px 3px rgba( 0, 0, 0, 0.35 );
614 pointer-events: none;
615 }
616
617 .desktop-mode-icon[ data-icon-id="desktop-mode-recycle-bin" ] > .desktop-mode-icon__badge {
618 /*
619 * The bin's count is tucked ONTO the lid rather than floating off
620 * the corner, so it gets its own smaller geometry. Every value
621 * derives from the icon image size, which keeps the badge on the
622 * lid when a theme scales wallpaper icons — hardcoded offsets slid
623 * off the artwork entirely.
624 *
625 * Override chain: bin-specific token → the generic icon-badge
626 * token (so a theme that says "badges are 22px" gets the bin too)
627 * → the derivation. All three resolve to the historical 15px / 9px
628 * at the default 48px icon.
629 */
630 --desktop-mode-recycle-badge-computed-size: var(
631 --desktop-mode-recycle-badge-size,
632 var(
633 --desktop-mode-icon-badge-size,
634 calc( var( --desktop-mode-icon-image-size, 48px ) * 0.3125 )
635 )
636 );
637 top: calc( var( --desktop-mode-icon-image-size, 48px ) * 0.8125 );
638 inset-inline-end: calc( var( --desktop-mode-icon-image-size, 48px ) * 0.4167 );
639 min-width: var( --desktop-mode-recycle-badge-computed-size );
640 height: var( --desktop-mode-recycle-badge-computed-size );
641 padding: 0 calc( var( --desktop-mode-recycle-badge-computed-size ) * 0.267 );
642 border-radius: 999px;
643 background: var( --desktop-mode-recycle-badge-bg, rgba( 29, 35, 39, 0.74 ) );
644 color: var( --desktop-mode-recycle-badge-fg, rgba( 255, 255, 255, 0.96 ) );
645 font-size: var(
646 --desktop-mode-recycle-badge-font-size,
647 var(
648 --desktop-mode-icon-badge-font-size,
649 calc( var( --desktop-mode-recycle-badge-computed-size ) * 0.6 )
650 )
651 );
652 font-weight: 700;
653 letter-spacing: 0;
654 box-shadow:
655 inset 0 0 0 1px rgba( 255, 255, 255, 0.28 ),
656 0 1px 2px rgba( 0, 0, 0, 0.28 );
657 }
658
659 .desktop-mode-icon:hover,
660 .desktop-mode-icon:focus-visible {
661 /* background-COLOR, not the shorthand: the shorthand would reset
662 * the ICON_TILE texture declared above, so a themed tile would
663 * vanish on hover. */
664 background-color: rgba(255, 255, 255, 0.12);
665 outline: none;
666 }
667
668 .desktop-mode-icon:focus-visible {
669 box-shadow: 0 0 0 2px var(--wp-admin-theme-color, #2271b1);
670 }
671
672 .desktop-mode-icon__image {
673 display: inline-flex;
674 align-items: center;
675 justify-content: center;
676 /* Tokenized so a theme can scale wallpaper icons — and so the
677 * badge above has something to derive its own size from. */
678 width: var( --desktop-mode-icon-image-size, 48px );
679 height: var( --desktop-mode-icon-image-size, 48px );
680 font-size: calc( var( --desktop-mode-icon-image-size, 48px ) * 0.667 );
681 line-height: 1;
682 }
683
684 .desktop-mode-icon__image img {
685 max-width: 48px;
686 max-height: 48px;
687 object-fit: contain;
688 }
689
690 .desktop-mode-icon__label {
691 font-size: 12px;
692 text-align: center;
693 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
694 line-height: 1.2;
695 word-break: break-word;
696 }
697
698 /* ---------------------------------------------------------------
699 * Sticky notes — Gutenberg `wp_guideline` artifacts tagged with the
700 * sticky guideline type. Notes sit above wallpaper icons/widgets and
701 * below windows, so they behave like desktop objects rather than app
702 * chrome.
703 * --------------------------------------------------------------- */
704
705 .desktop-mode-sticky-notes {
706 position: absolute;
707 inset: 0;
708 z-index: 2;
709 pointer-events: none;
710 transition:
711 opacity 0.22s ease,
712 transform 0.28s cubic-bezier( 0.2, 0, 0.2, 1 );
713 }
714
715 .desktop-mode-area--overview .desktop-mode-sticky-notes {
716 opacity: 0;
717 transform: translateY( 12px );
718 pointer-events: none;
719 }
720
721 .desktop-mode-sticky-note {
722 position: absolute;
723 display: flex;
724 flex-direction: column;
725 pointer-events: auto;
726 background: #fff09a;
727 color: #2a2513;
728 border: 1px solid rgba( 84, 67, 0, 0.24 );
729 border-radius: 4px;
730 box-shadow:
731 0 12px 28px rgba( 0, 0, 0, 0.22 ),
732 0 2px 5px rgba( 0, 0, 0, 0.16 ),
733 inset 0 1px 0 rgba( 255, 255, 255, 0.55 );
734 overflow: hidden;
735 resize: both;
736 }
737
738 .desktop-mode-sticky-note--dragging {
739 opacity: 0.92;
740 resize: none;
741 user-select: none;
742 }
743
744 .desktop-mode-sticky-note__header {
745 display: flex;
746 align-items: center;
747 gap: 5px;
748 min-height: 30px;
749 padding: 4px 6px 4px 8px;
750 background: rgba( 82, 64, 0, 0.06 );
751 border-bottom: 1px solid rgba( 82, 64, 0, 0.14 );
752 cursor: grab;
753 touch-action: none;
754 user-select: none;
755 }
756
757 .desktop-mode-sticky-note--dragging .desktop-mode-sticky-note__header {
758 cursor: grabbing;
759 }
760
761 .desktop-mode-sticky-note__grip {
762 flex: 0 0 auto;
763 width: 8px;
764 height: 14px;
765 background-image: radial-gradient(
766 circle,
767 rgba( 60, 48, 0, 0.5 ) 1.2px,
768 transparent 1.5px
769 );
770 background-size: 4px 4px;
771 background-position: 0 1px;
772 background-repeat: space;
773 opacity: 0.42;
774 }
775
776 .desktop-mode-sticky-note__title {
777 flex: 1;
778 min-width: 0;
779 font-size: 11px;
780 font-weight: 650;
781 overflow: hidden;
782 text-overflow: ellipsis;
783 white-space: nowrap;
784 }
785
786 .desktop-mode-sticky-note__status {
787 flex: 0 0 auto;
788 }
789
790 .desktop-mode-sticky-note wpd-window-button {
791 --wpd-btn-color: rgba( 42, 37, 19, 0.7 );
792 --wpd-btn-color-hover: #2a2513;
793 --wpd-btn-bg-hover: rgba( 42, 37, 19, 0.1 );
794 --wpd-btn-bg-active: rgba( 42, 37, 19, 0.16 );
795 --wpd-btn-danger-hover: rgba( 214, 54, 56, 0.18 );
796 --wpd-btn-outline: var( --wp-admin-theme-color, #2271b1 );
797 }
798
799 .desktop-mode-sticky-note__open.is-disabled {
800 opacity: 0.34;
801 pointer-events: none;
802 }
803
804 .desktop-mode-sticky-note__editor {
805 flex: 1;
806 min-height: 0;
807 --desktop-mode-window-bg: transparent;
808 --wpd-border: transparent;
809 --wpd-fg: #2a2513;
810 --wpd-fg-muted: rgba( 42, 37, 19, 0.62 );
811 font-family:
812 -apple-system,
813 BlinkMacSystemFont,
814 "Segoe UI",
815 sans-serif;
816 }
817
818 .desktop-mode-sticky-note__editor::part(textarea) {
819 height: 100%;
820 min-height: 100%;
821 padding: 10px 12px 12px;
822 background: transparent;
823 border: 0;
824 border-radius: 0;
825 box-shadow: none;
826 color: #2a2513;
827 font-size: 13px;
828 line-height: 1.4;
829 resize: none;
830 }
831
832 .desktop-mode-sticky-note__editor::part(textarea):hover,
833 .desktop-mode-sticky-note__editor::part(textarea):focus-visible {
834 border-color: transparent;
835 box-shadow: none;
836 }
837
838 /* ---------------------------------------------------------------
839 * Widgets column — right-edge glass strip that paints above the
840 * wallpaper but beneath windows. Hosts stacked widget cards and a
841 * trailing "Add widget" tile. Interactive only on its own cards +
842 * the add tile; every other pixel is pointer-transparent so window
843 * drag / resize / click-through behaviour is unaffected.
844 * --------------------------------------------------------------- */
845
846 .desktop-mode-widgets {
847 border-radius: 14px;
848 position: absolute;
849 top: 16px;
850 bottom: 16px;
851 inset-inline-end: 16px;
852 width: 320px;
853 display: flex;
854 flex-direction: column;
855 gap: 12px;
856 z-index: 1;
857 pointer-events: none;
858 overflow-y: auto;
859 /* Hide the scrollbar chrome unless the user actually overflows —
860 * most setups never will. */
861 scrollbar-width: thin;
862 /* Fade alongside the overview backdrop. The class flip that
863 * drives this is on the parent area, set at t=0 of enter and
864 * removed at t=0 of exit, so widgets fade in parallel with the
865 * dock collapse / return — never sequential. */
866 opacity: 1;
867 transition:
868 opacity 0.22s ease,
869 transform 0.28s cubic-bezier( 0.2, 0, 0.2, 1 );
870 }
871
872 .desktop-mode-area--overview .desktop-mode-widgets {
873 opacity: 0;
874 /* Slight slide-off-right so the fade reads as "the column is
875 * stepping back out of the way" rather than flat-dimming. */
876 transform: translateX( 16px );
877 pointer-events: none;
878 }
879
880 .desktop-mode-widgets__list {
881 display: flex;
882 flex-direction: column;
883 gap: 12px;
884 padding: 4px;
885 }
886
887 /*
888 * Individual widget card — glass backdrop matching the dock /
889 * overview top bar. Card root is pointer-events: auto so clicks on
890 * content + the remove X land; the column itself stays
891 * pointer-transparent so a drag that grazes the column's margin
892 * falls through to the window beneath.
893 */
894 .desktop-mode-widgets__card {
895 position: relative;
896 padding: 0;
897 /* Column layout so the body can flex-shrink and scroll when the
898 * frame writes a fixed inline height (floating / resized cards).
899 * Without this the body keeps its natural height and content
900 * spills past the card's rounded bottom edge. */
901 display: flex;
902 flex-direction: column;
903 pointer-events: auto;
904 background-color: rgba( 20, 20, 22, 0.55 );
905 /*
906 * WIDGET texture slot — layered over the frosted card colour, so a
907 * translucent texture still gets the blur underneath.
908 */
909 background-image: var( --desktop-mode-widget-image, none );
910 background-repeat: var( --desktop-mode-widget-image-repeat, repeat );
911 background-size: var( --desktop-mode-widget-image-size, auto );
912 background-position: var( --desktop-mode-widget-image-position, center );
913 backdrop-filter: blur( 18px ) saturate( 140% );
914 -webkit-backdrop-filter: blur( 18px ) saturate( 140% );
915 border: 1px solid rgba( 255, 255, 255, 0.08 );
916 border-radius: 14px;
917 color: var( --wpd-fg-on-accent, #fff );
918 box-shadow:
919 0 8px 28px rgba( 0, 0, 0, 0.35 ),
920 inset 0 0 0 1px rgba( 255, 255, 255, 0.04 );
921 transition:
922 box-shadow 0.18s ease,
923 opacity 0.18s ease;
924 }
925
926 /* Hover feedback is shadow-only — no transform. Cards must feel
927 * anchored; a hover nudge reads as the widget drifting under the
928 * pointer (and fought the user's chosen position on floating cards). */
929 .desktop-mode-widgets__card:hover {
930 box-shadow:
931 0 12px 36px rgba( 0, 0, 0, 0.45 ),
932 inset 0 0 0 1px rgba( 255, 255, 255, 0.08 );
933 }
934
935 .desktop-mode-widgets__card-body {
936 padding: 16px;
937 min-height: 48px;
938 flex: 1 1 auto;
939 overflow-x: hidden;
940 overflow-y: auto;
941 scrollbar-width: thin;
942 }
943
944 .desktop-mode-widgets__card-close {
945 position: absolute;
946 top: 6px;
947 inset-inline-end: 6px;
948 width: 22px;
949 height: 22px;
950 display: flex;
951 align-items: center;
952 justify-content: center;
953 padding: 0;
954 border: 0;
955 background: var( --wpd-scrim, rgba( 0, 0, 0, 0.55 ) );
956 color: rgba( 255, 255, 255, 0.75 );
957 border-radius: 50%;
958 cursor: pointer;
959 opacity: 0;
960 transition:
961 opacity 0.15s ease,
962 background-color 0.15s ease,
963 color 0.15s ease;
964 }
965
966 .desktop-mode-widgets__card:hover .desktop-mode-widgets__card-close,
967 .desktop-mode-widgets__card-close:focus-visible {
968 opacity: 1;
969 }
970
971 .desktop-mode-widgets__card-close:hover {
972 background: var( --wpd-danger, #d63638 );
973 color: var( --wpd-fg-on-accent, #fff );
974 }
975
976 /*
977 * Movable widgets — a thin chrome header at the top of the card holds
978 * the drag grip, the widget's own label, and the × button. The whole
979 * header is the drag handle (except for interactive children — those
980 * remain clickable as normal). Non-movable widgets keep today's
981 * corner-× layout unchanged.
982 */
983 .desktop-mode-widgets__chrome {
984 display: flex;
985 flex-shrink: 0;
986 align-items: center;
987 gap: 8px;
988 padding: 6px 8px;
989 border-bottom: 1px solid rgba( 255, 255, 255, 0.06 );
990 cursor: grab;
991 user-select: none;
992 -webkit-user-select: none;
993 touch-action: none;
994 }
995
996 .desktop-mode-widgets__card--dragging .desktop-mode-widgets__chrome {
997 cursor: grabbing;
998 }
999
1000 .desktop-mode-widgets__grip {
1001 flex-shrink: 0;
1002 width: 10px;
1003 height: 16px;
1004 background-image: radial-gradient(
1005 circle,
1006 rgba( 255, 255, 255, 0.55 ) 1.2px,
1007 transparent 1.5px
1008 );
1009 background-size: 5px 5px;
1010 background-position: 0 1px;
1011 background-repeat: space;
1012 opacity: 0.45;
1013 transition: opacity 0.12s ease;
1014 }
1015
1016 .desktop-mode-widgets__card:hover .desktop-mode-widgets__grip {
1017 opacity: 0.85;
1018 }
1019
1020 .desktop-mode-widgets__title {
1021 flex: 1;
1022 font-size: 12px;
1023 font-weight: 500;
1024 letter-spacing: 0.01em;
1025 color: rgba( 255, 255, 255, 0.85 );
1026 overflow: hidden;
1027 text-overflow: ellipsis;
1028 white-space: nowrap;
1029 }
1030
1031 /* When the close button sits inside the chrome it's inline, not
1032 corner-absolute — reset position + always-visible opacity. */
1033 .desktop-mode-widgets__chrome .desktop-mode-widgets__card-close {
1034 position: static;
1035 opacity: 0.7;
1036 width: 20px;
1037 height: 20px;
1038 }
1039
1040 .desktop-mode-widgets__chrome .desktop-mode-widgets__card-close:hover,
1041 .desktop-mode-widgets__chrome .desktop-mode-widgets__card-close:focus-visible {
1042 opacity: 1;
1043 }
1044
1045 /*
1046 * Re-dock button — sits in the chrome next to the close button and
1047 * returns a floating widget to the right column. Visibility is
1048 * gated on the `--floating` modifier class so docked widgets never
1049 * expose it (tapping it on a docked card would be a confusing
1050 * no-op). The DOM node stays mounted either way so state flips
1051 * don't rebuild chrome.
1052 *
1053 * Styling mirrors the inline close button: 20×20 round tile,
1054 * dimmed by default, full-opacity on hover / focus. Distinct hover
1055 * color (theme accent, not red) signals "put back" vs. "remove."
1056 */
1057 .desktop-mode-widgets__card-redock {
1058 display: none;
1059 align-items: center;
1060 justify-content: center;
1061 padding: 0;
1062 border: 0;
1063 background: var( --wpd-scrim, rgba( 0, 0, 0, 0.55 ) );
1064 color: rgba( 255, 255, 255, 0.75 );
1065 border-radius: 50%;
1066 width: 20px;
1067 height: 20px;
1068 cursor: pointer;
1069 opacity: 0.7;
1070 transition:
1071 opacity 0.15s ease,
1072 background-color 0.15s ease,
1073 color 0.15s ease;
1074 }
1075
1076 .desktop-mode-widgets__card--floating .desktop-mode-widgets__card-redock {
1077 display: flex;
1078 }
1079
1080 .desktop-mode-widgets__card-redock:hover,
1081 .desktop-mode-widgets__card-redock:focus-visible {
1082 opacity: 1;
1083 background: var( --wp-admin-theme-color, #2271b1 );
1084 color: var( --wpd-fg-on-accent, #fff );
1085 }
1086
1087 /*
1088 * Floating widgets — absolute-positioned, inline top/left/width/height
1089 * written by the frame.
1090 */
1091 .desktop-mode-widgets__card--floating {
1092 position: absolute;
1093 margin: 0;
1094 }
1095
1096 .desktop-mode-widgets__card--dragging,
1097 .desktop-mode-widgets__card--resizing {
1098 transition: none;
1099 z-index: 2;
1100 }
1101
1102 /*
1103 * Resize handles — 8 in all, with the 4 edges as hair-line strips and
1104 * the 4 corners as small squares. Invisible by default; cursor change
1105 * telegraphs them on hover. The frame attaches pointer listeners to
1106 * every handle; a non-movable widget ignores everything except the
1107 * `s` (bottom-edge, height-only) handle at the pointer layer, so we
1108 * visually hide the mismatched handles via the modifier class.
1109 */
1110 .desktop-mode-widgets__resize {
1111 position: absolute;
1112 z-index: 1;
1113 }
1114
1115 .desktop-mode-widgets__resize--n {
1116 top: -3px;
1117 left: 8px;
1118 right: 8px;
1119 height: 6px;
1120 cursor: ns-resize;
1121 }
1122
1123 .desktop-mode-widgets__resize--s {
1124 bottom: -3px;
1125 left: 8px;
1126 right: 8px;
1127 height: 6px;
1128 cursor: ns-resize;
1129 }
1130
1131 .desktop-mode-widgets__resize--e {
1132 top: 8px;
1133 bottom: 8px;
1134 right: -3px;
1135 width: 6px;
1136 cursor: ew-resize;
1137 }
1138
1139 .desktop-mode-widgets__resize--w {
1140 top: 8px;
1141 bottom: 8px;
1142 left: -3px;
1143 width: 6px;
1144 cursor: ew-resize;
1145 }
1146
1147 .desktop-mode-widgets__resize--ne {
1148 top: -4px;
1149 right: -4px;
1150 width: 12px;
1151 height: 12px;
1152 cursor: nesw-resize;
1153 }
1154
1155 .desktop-mode-widgets__resize--nw {
1156 top: -4px;
1157 left: -4px;
1158 width: 12px;
1159 height: 12px;
1160 cursor: nwse-resize;
1161 }
1162
1163 .desktop-mode-widgets__resize--se {
1164 bottom: -4px;
1165 right: -4px;
1166 width: 12px;
1167 height: 12px;
1168 cursor: nwse-resize;
1169 }
1170
1171 .desktop-mode-widgets__resize--sw {
1172 bottom: -4px;
1173 left: -4px;
1174 width: 12px;
1175 height: 12px;
1176 cursor: nesw-resize;
1177 }
1178
1179 /*
1180 * Only floating cards expose the full 8-direction handle set. A
1181 * column-docked card — non-movable OR movable-but-not-yet-liberated —
1182 * resizes on the height axis alone (the frame ignores every other
1183 * direction while docked), so hide all handles except the bottom
1184 * edge and the cursor never lies about what's interactive.
1185 */
1186 .desktop-mode-widgets__card--resizable:not( .desktop-mode-widgets__card--floating ) .desktop-mode-widgets__resize:not( .desktop-mode-widgets__resize--s ) {
1187 display: none;
1188 }
1189
1190 /*
1191 * Add-widget tile — matches the "+" tile in the overview top bar.
1192 * Always visible at the bottom of the column; promoted to center
1193 * when the column is otherwise empty so first-run users see a
1194 * friendly target rather than an empty strip.
1195 */
1196 .desktop-mode-widgets__add {
1197 margin-top: auto;
1198 padding: 14px 12px;
1199 display: flex;
1200 align-items: center;
1201 justify-content: center;
1202 gap: 8px;
1203 pointer-events: auto;
1204 background: transparent;
1205 border: 2px dashed rgba( 255, 255, 255, 0.22 );
1206 border-radius: 14px;
1207 color: rgba( 255, 255, 255, 0.72 );
1208 font: inherit;
1209 font-weight: 500;
1210 cursor: pointer;
1211 transition:
1212 border-color 0.15s ease,
1213 background-color 0.15s ease,
1214 color 0.15s ease;
1215 }
1216
1217 .desktop-mode-widgets__add:hover {
1218 border-color: rgba( 255, 255, 255, 0.4 );
1219 background: rgba( 255, 255, 255, 0.06 );
1220 color: var( --wpd-fg-on-accent, #fff );
1221 }
1222
1223 .desktop-mode-widgets__add:focus-visible {
1224 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
1225 outline-offset: 2px;
1226 }
1227
1228 .desktop-mode-widgets__add-plus {
1229 font-size: 20px;
1230 line-height: 1;
1231 font-weight: 200;
1232 }
1233
1234 .desktop-mode-widgets__add-label {
1235 font-size: 13px;
1236 letter-spacing: 0.02em;
1237 }
1238
1239 /* Built-in clock widget */
1240 .desktop-mode-widget-clock {
1241 text-align: center;
1242 /* The time + date are passive glyphs, not content users would
1243 * ever reasonably select — turning it off avoids the accidental-
1244 * highlight mess that happens when a drag starts on the clock. */
1245 user-select: none;
1246 -webkit-user-select: none;
1247 }
1248
1249 .desktop-mode-widget-clock__time {
1250 font-size: 34px;
1251 font-weight: 600;
1252 line-height: 1.1;
1253 font-variant-numeric: tabular-nums;
1254 letter-spacing: 0.01em;
1255 }
1256
1257 .desktop-mode-widget-clock__date {
1258 margin-top: 4px;
1259 font-size: 13px;
1260 color: rgba( 255, 255, 255, 0.75 );
1261 letter-spacing: 0.02em;
1262 }
1263
1264 /* ---------------------------------------------------------------
1265 * Widget picker popover — opens from the add tile, lists every
1266 * registered widget. Positioned fixed so it can extend outside
1267 * the desktop-area clip.
1268 * --------------------------------------------------------------- */
1269
1270 .desktop-mode-widget-picker {
1271 position: fixed;
1272 width: 320px;
1273 max-height: 60vh;
1274 overflow-y: auto;
1275 padding: 10px;
1276 border-radius: 14px;
1277 background: rgba( 20, 20, 22, 0.88 );
1278 backdrop-filter: blur( 22px ) saturate( 160% );
1279 -webkit-backdrop-filter: blur( 22px ) saturate( 160% );
1280 border: 1px solid rgba( 255, 255, 255, 0.1 );
1281 box-shadow: 0 18px 54px rgba( 0, 0, 0, 0.55 );
1282 color: var( --wpd-fg-on-accent, #fff );
1283 z-index: 9999;
1284 animation: desktop-mode-widget-picker-in 0.14s ease-out;
1285 }
1286
1287 @keyframes desktop-mode-widget-picker-in {
1288 from {
1289 opacity: 0;
1290 transform: translateY( 4px );
1291 }
1292 }
1293
1294 .desktop-mode-widget-picker__title {
1295 padding: 4px 8px 8px;
1296 font-size: 11px;
1297 font-weight: 600;
1298 letter-spacing: 0.08em;
1299 text-transform: uppercase;
1300 color: rgba( 255, 255, 255, 0.55 );
1301 }
1302
1303 .desktop-mode-widget-picker__list {
1304 display: flex;
1305 flex-direction: column;
1306 gap: 4px;
1307 }
1308
1309 .desktop-mode-widget-picker__entry {
1310 display: flex;
1311 align-items: center;
1312 gap: 12px;
1313 padding: 10px;
1314 background: transparent;
1315 border: 0;
1316 border-radius: 10px;
1317 color: var( --wpd-fg-on-accent, #fff );
1318 cursor: pointer;
1319 text-align: start;
1320 font: inherit;
1321 transition: background-color 0.12s ease;
1322 }
1323
1324 .desktop-mode-widget-picker__entry:hover:not( :disabled ) {
1325 background: rgba( 255, 255, 255, 0.08 );
1326 }
1327
1328 .desktop-mode-widget-picker__entry:focus-visible {
1329 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
1330 outline-offset: 1px;
1331 }
1332
1333 .desktop-mode-widget-picker__entry--added {
1334 opacity: 0.55;
1335 cursor: default;
1336 }
1337
1338 .desktop-mode-widget-picker__entry-icon {
1339 flex-shrink: 0;
1340 font-size: 20px;
1341 width: 20px;
1342 height: 20px;
1343 line-height: 1;
1344 }
1345
1346 .desktop-mode-widget-picker__entry-text {
1347 display: flex;
1348 flex-direction: column;
1349 gap: 2px;
1350 flex: 1;
1351 min-width: 0;
1352 }
1353
1354 .desktop-mode-widget-picker__entry-label {
1355 font-size: 13px;
1356 font-weight: 500;
1357 white-space: nowrap;
1358 overflow: hidden;
1359 text-overflow: ellipsis;
1360 }
1361
1362 .desktop-mode-widget-picker__entry-description {
1363 font-size: 11px;
1364 color: rgba( 255, 255, 255, 0.6 );
1365 line-height: 1.35;
1366 white-space: normal;
1367 }
1368
1369 .desktop-mode-widget-picker__entry-status {
1370 flex-shrink: 0;
1371 font-size: 11px;
1372 color: rgba( 255, 255, 255, 0.5 );
1373 text-transform: uppercase;
1374 letter-spacing: 0.08em;
1375 }
1376
1377 .desktop-mode-widget-picker__empty {
1378 padding: 12px 8px;
1379 font-size: 12px;
1380 color: rgba( 255, 255, 255, 0.6 );
1381 line-height: 1.4;
1382 }
1383
1384 /**
1385 * Chromeless mode: pages loaded inside iframes.
1386 * Remove all shell chrome and let content fill the frame.
1387 */
1388 .desktop-mode-chromeless {
1389 margin: 0;
1390 padding: 0;
1391 background: var(--desktop-mode-window-bg);
1392 }
1393
1394 /*
1395 * Chromeless #wpbody-content, #adminmenuwrap, #wpfooter overrides are in
1396 * chromeless.css to keep all legacy page tweaks in one place.
1397 */
1398
1399 /*
1400 * Hide WordPress core's built-in command palette (`@wordpress/commands`).
1401 * Desktop mode force-enqueues `wp_enqueue_command_palette_assets()` so
1402 * the `core/commands` data store is populated for our shell harvester
1403 * (see `src/commands/shell-harvester.ts`); the side effect is that WP
1404 * also mounts its own `<CommandPalette>` to the document body. We
1405 * already block its Cmd+K handler in `src/palette-registry.ts`, but if
1406 * a third-party script opens it programmatically (`wp.data.dispatch(
1407 * 'core/commands' ).open()`), the dialog would float over the desktop
1408 * with admin-menu navigation callbacks whose `document.location = url`
1409 * would unload the shell. Keep it permanently hidden.
1410 */
1411 .commands-command-menu,
1412 .commands-command-menu__overlay {
1413 display: none !important;
1414 }
1415