PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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
← All changes | assets/css/desktop-files.css +409 -299 0.9.31.1.10 View file →
@@ -1,15 +1,15 @@
1 1 /**
2 - * Desktop Mode — Files-on-the-Desktop tile + layer styles.
2 + * OpenStation — Files-on-the-Desktop tile + layer styles.
3 3 *
4 4 * The layer is an absolutely-positioned container inside
5 - * `#desktop-mode-area`; tiles inside use absolute positioning
5 + * `#os-area`; tiles inside use absolute positioning
6 6 * keyed off `(x, y)` coordinates persisted server-side.
7 7 *
8 - * Tiles are intentionally close to the look of `desktop-mode-icon`
8 + * Tiles are intentionally close to the look of `os-icon`
9 9 * (the plugin-shortcut tiles) so the desktop reads as one
10 10 * coherent surface — but they live in their own class namespace
11 - * (`desktop-mode-file-tile`) so plugins can theme each rail
11 + * (`os-file-tile`) so plugins can theme each rail
12 12 * independently.
13 13 *
14 14 * @since 0.9.0
15 15 */
@@ -16,15 +16,15 @@
16 16
17 17 /*
18 18 * Unified rail (since 0.9.0): plugin shortcuts now ride on the
19 19 * same files layer as folders / posts / etc — they're a
20 - * `'shortcut'` file type. The legacy `.desktop-mode-icons` DIV
20 + * `'shortcut'` file type. The legacy `.os-icons` DIV
21 21 * still renders for backwards compatibility (existing tests and
22 22 * non-files-active code paths), but when a files layer is
23 23 * mounted as a sibling we hide it so we don't end up with two
24 24 * rails competing for the same wallpaper.
25 25 */
26 -#desktop-mode-area:has( > .desktop-mode-files-layer ) > .desktop-mode-icons {
26 +#os-area:has( > .os-files-layer ) > .os-icons {
27 27 display: none;
28 28 }
29 29
30 30 /*
@@ -31,61 +31,22 @@
31 31 * Drop-target highlight — the `data-files-drop-active` attribute
32 32 * is set on the FilesLayer host (desktop area or folder window
33 33 * body) while a shortcut payload is being dragged over it.
34 34 *
35 - * Marching-ants animation makes the affordance unmissable even
36 - * against the busiest wallpaper, and the soft inner glow draws
37 - * the eye when the user is "shopping" for a drop target. Both
38 - * effects respect prefers-reduced-motion below.
39 - *
40 - * @since 0.18.0; visuals strengthened in 0.20.0.
35 + * A quiet inner glow only. This used to be a dashed, animated
36 + * marching-ants frame around the whole wallpaper, and on a full
37 + * desktop that read as a large moving grid behind the tile in hand
38 + * — loud, and design asked for it to go. The ghost's accept /
39 + * reject chip and the per-cell drop preview already say where the
40 + * tile will land; the glow just confirms the surface is listening.
41 41 */
42 -@keyframes desktop-mode-drop-marching-ants {
43 - to {
44 - background-position: 24px 0, -24px 0, 0 24px, 0 -24px;
45 - }
46 -}
47 -
48 42 [ data-files-drop-active ] {
49 43 position: relative;
50 - outline: 3px dashed var( --wp-admin-theme-color, #2271b1 );
51 - outline-offset: -8px;
52 - background-image:
53 - linear-gradient(
54 - 90deg,
55 - rgba( 34, 113, 177, 0.45 ) 50%,
56 - transparent 0
57 - ),
58 - linear-gradient(
59 - 90deg,
60 - rgba( 34, 113, 177, 0.45 ) 50%,
61 - transparent 0
62 - ),
63 - linear-gradient(
64 - 0deg,
65 - rgba( 34, 113, 177, 0.45 ) 50%,
66 - transparent 0
67 - ),
68 - linear-gradient(
69 - 0deg,
70 - rgba( 34, 113, 177, 0.45 ) 50%,
71 - transparent 0
72 - );
73 - background-repeat: repeat-x, repeat-x, repeat-y, repeat-y;
74 - background-size: 12px 3px, 12px 3px, 3px 12px, 3px 12px;
75 - background-position: 0 6px, 0 calc( 100% - 6px ), 6px 0, calc( 100% - 6px ) 0;
76 44 box-shadow: inset 0 0 32px 4px rgba( 34, 113, 177, 0.12 );
77 - animation: desktop-mode-drop-marching-ants 0.6s linear infinite;
78 45 transition: box-shadow 0.18s ease-out;
79 46 }
80 47
81 -@media ( prefers-reduced-motion: reduce ) {
82 - [ data-files-drop-active ] {
83 - animation: none;
84 - }
85 -}
86 -
87 -.desktop-mode-files-layer {
48 +.os-files-layer {
88 49 position: absolute;
89 50 inset: 0;
90 51 z-index: 0;
91 52 /*
@@ -90,9 +51,9 @@
90 51 z-index: 0;
91 52 /*
92 53 * Layer doesn't intercept pointer events itself — tiles and
93 54 * the wallpaper own that. Phase 4's wallpaper context menu
94 - * binds clicks on the parent `#desktop-mode-area`, which is
55 + * binds clicks on the parent `#os-area`, which is
95 56 * unblocked because of this rule.
96 57 */
97 58 pointer-events: none;
98 59 }
@@ -110,9 +71,9 @@
110 71 * than "this tile."
111 72 *
112 73 * @since 0.20.0
113 74 */
114 -.desktop-mode-files-drop-preview {
75 +.os-files-drop-preview {
115 76 position: absolute;
116 77 top: 0;
117 78 left: 0;
118 79 width: 88px;
@@ -120,14 +81,14 @@
120 81 pointer-events: none;
121 82 box-sizing: border-box;
122 83 border-radius: 12px;
123 84 background: var(
124 - --desktop-mode-drop-preview-bg,
85 + --os-drop-preview-bg,
125 86 rgba( 34, 113, 177, 0.08 )
126 87 );
127 88 border: 2px dashed
128 89 var(
129 - --desktop-mode-drop-preview-border,
90 + --os-drop-preview-border,
130 91 rgba( 34, 113, 177, 0.55 )
131 92 );
132 93 transition: transform 90ms ease-out;
133 94 will-change: transform;
@@ -138,14 +99,14 @@
138 99 z-index: 0;
139 100 }
140 101
141 102 @media ( prefers-reduced-motion: reduce ) {
142 - .desktop-mode-files-drop-preview {
103 + .os-files-drop-preview {
143 104 transition: none;
144 105 }
145 106 }
146 107
147 -.desktop-mode-file-tile {
108 +.os-file-tile {
148 109 pointer-events: auto;
149 110 position: absolute;
150 111 display: flex;
151 112 flex-direction: column;
@@ -151,17 +112,35 @@
151 112 flex-direction: column;
152 113 align-items: center;
153 114 justify-content: flex-start;
154 115 gap: 6px;
155 - width: 88px;
116 + /*
117 + * `box-sizing` is load-bearing, not housekeeping. Without it the
118 + * horizontal padding is ADDED to the declared width, so an 88px
119 + * tile occupies 96px — which was exactly the desktop's old cell
120 + * pitch, and why icons sat edge to edge with no gap at all while
121 + * the maths insisted there were 8px between them.
122 + *
123 + * The tile is the size the grid says it is. Padding lives inside.
124 + */
125 + box-sizing: border-box;
126 + width: var( --os-tile-w, 88px );
127 + /*
128 + * Fixed, not minimum. The tile box IS the selection ring, and a
129 + * box that grows with its label gives a row of selected icons a
130 + * ragged top edge — one height per label that happened to wrap
131 + * to two lines. Every tile occupies its cell; short labels leave
132 + * the slack inside the ring, where it reads as padding.
133 + */
134 + height: var( --os-tile-h, 104px );
156 135 padding: 8px 4px;
157 136 border: 0;
158 137 background: transparent;
159 - /* Tile foreground driven by `--desktop-mode-tile-fg` so any
138 + /* Tile foreground driven by `--os-tile-fg` so any
160 139 * light-on-dark surface (folder window, My WordPress preview)
161 140 * can retint tiles by overriding the variable in its scope —
162 141 * no per-surface selectors needed here. */
163 - color: var( --desktop-mode-tile-fg, var( --desktop-mode-fg, #fff ) );
142 + color: var( --os-tile-fg, var( --os-fg, #fff ) );
164 143 cursor: pointer;
165 144 border-radius: 8px;
166 145 transition: background-color 0.15s ease, transform 0.05s linear;
167 146 -webkit-user-select: none;
@@ -177,16 +156,16 @@
177 156 * cascade is honest; the animation runs once, leaves the tile
178 157 * at its static `opacity: 1`, and never re-fires from later
179 158 * state changes (drag, hover, drop-target).
180 159 */
181 - animation: desktop-mode-file-tile-enter
182 - var( --desktop-mode-file-tile-enter-duration, 0.4s )
160 + animation: os-file-tile-enter
161 + var( --os-file-tile-enter-duration, 0.4s )
183 162 ease-out
184 - var( --desktop-mode-file-tile-enter-delay, 0s )
163 + var( --os-file-tile-enter-delay, 0s )
185 164 backwards;
186 165 }
187 166
188 -@keyframes desktop-mode-file-tile-enter {
167 +@keyframes os-file-tile-enter {
189 168 from {
190 169 opacity: 0;
191 170 }
192 171 to {
@@ -194,49 +173,49 @@
194 173 }
195 174 }
196 175
197 176 @media ( prefers-reduced-motion: reduce ) {
198 - .desktop-mode-file-tile {
177 + .os-file-tile {
199 178 animation: none;
200 179 }
201 180 }
202 181
203 -.desktop-mode-file-tile:hover,
204 -.desktop-mode-file-tile:focus-visible {
182 +.os-file-tile:hover,
183 +.os-file-tile:focus-visible {
205 184 background: var(
206 - --desktop-mode-tile-hover-bg,
185 + --os-tile-hover-bg,
207 186 rgba( 255, 255, 255, 0.12 )
208 187 );
209 188 outline: none;
210 189 }
211 190
212 -.desktop-mode-file-tile:focus-visible {
191 +.os-file-tile:focus-visible {
213 192 /* Inset focus ring — outset rings extended past the tile and
214 193 * visually overlapped the colindant icon when the canvas cell
215 194 * pitch is tight (96 px cell, 88 px tile → only 4 px clearance
216 195 * per side). Inset keeps the ring fully inside the tile. */
217 196 box-shadow: inset 0 0 0 2px
218 - var( --desktop-mode-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) );
197 + var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) );
219 198 }
220 199
221 -/* Status ribbon sizing inside tiles. The `<wpd-ribbon>` defaults
200 +/* Status ribbon sizing inside tiles. The `<os-ribbon>` defaults
222 201 * are tuned for cards (~90 px clipping window); 88 px-wide tiles
223 202 * need a tighter footprint or the ribbon dominates the icon. We
224 203 * override the component's CSS custom-property theming surface so
225 204 * the ribbon stays a small accent in the corner. Authors who want
226 205 * different sizing can override per-tile or per-section. */
227 -.desktop-mode-file-tile > wpd-ribbon {
228 - --wpd-ribbon-size: 48px;
229 - --wpd-ribbon-banner-width: 72px;
230 - --wpd-ribbon-banner-offset: 12px;
231 - --wpd-ribbon-banner-pull: -18px;
232 - --wpd-ribbon-padding: 1px 0;
233 - --wpd-ribbon-font: 700 7px/1.4 var( --desktop-mode-font, system-ui );
234 - --wpd-ribbon-tracking: 0.05em;
235 - --wpd-ribbon-shadow: 0 1px 2px rgba( 0, 0, 0, 0.2 );
206 +.os-file-tile > os-ribbon {
207 + --os-ui-ribbon-size: 48px;
208 + --os-ui-ribbon-banner-width: 72px;
209 + --os-ui-ribbon-banner-offset: 12px;
210 + --os-ui-ribbon-banner-pull: -18px;
211 + --os-ui-ribbon-padding: 1px 0;
212 + --os-ui-ribbon-font: 700 7px/1.4 var( --os-font, system-ui );
213 + --os-ui-ribbon-tracking: 0.05em;
214 + --os-ui-ribbon-shadow: 0 1px 2px rgba( 0, 0, 0, 0.2 );
236 215 }
237 216
238 -.desktop-mode-file-tile--dragging {
217 +.os-file-tile--dragging {
239 218 /* The DragManager renders a separate ghost element that follows
240 219 * the pointer; the SOURCE tile stays in place but is dimmed so
241 220 * the user has a stable reference for "this is what I'm
242 221 * dragging."
@@ -252,14 +231,14 @@
252 231 * other tile until the user attempts a drag (which silently fails).
253 232 * No upfront visual cue: feedback now happens at the moment of the
254 233 * gesture, not before, per @since 0.9.0 design feedback.
255 234 *
256 - * Default `cursor: pointer` from the base `.desktop-mode-file-tile`
235 + * Default `cursor: pointer` from the base `.os-file-tile`
257 236 * rule covers click-to-open. Touch interactions stay as
258 237 * `touch-action: auto` so the OS scroll gesture works on top of
259 238 * the tile.
260 239 */
261 -.desktop-mode-file-tile--pinned {
240 +.os-file-tile--pinned {
262 241 touch-action: auto;
263 242 }
264 243
265 244 /* Folder tile is the drop target during a cross-window shortcut
@@ -266,9 +245,9 @@
266 245 * drag — the user dragged a post / page / user tile out of My
267 246 * WordPress and is hovering over a folder. Bright outline, soft
268 247 * pulse, and a slight scale so the folder reads unmistakably as
269 248 * "yes, drop here." @since 0.8.0; pulse added 0.20.0. */
270 -@keyframes desktop-mode-drop-target-pulse {
249 +@keyframes os-drop-target-pulse {
271 250 0% {
272 251 box-shadow:
273 252 0 0 0 0 rgba( 34, 113, 177, 0.5 ),
274 253 inset 0 0 0 2px var( --wp-admin-theme-color, #2271b1 );
@@ -279,20 +258,20 @@
279 258 inset 0 0 0 2px var( --wp-admin-theme-color, #2271b1 );
280 259 }
281 260 }
282 261
283 -.desktop-mode-file-tile--drop-target {
262 +.os-file-tile--drop-target {
284 263 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
285 264 outline-offset: -4px;
286 265 background: rgba( 34, 113, 177, 0.2 );
287 266 transform: scale( 1.08 );
288 267 z-index: 5;
289 - animation: desktop-mode-drop-target-pulse 1s ease-out infinite;
268 + animation: os-drop-target-pulse 1s ease-out infinite;
290 269 border-radius: 12px;
291 270 }
292 271
293 272 @media ( prefers-reduced-motion: reduce ) {
294 - .desktop-mode-file-tile--drop-target {
273 + .os-file-tile--drop-target {
295 274 animation: none;
296 275 }
297 276 }
298 277
@@ -303,9 +282,9 @@
303 282 * appearance per accept/reject/neutral mode.
304 283 *
305 284 * @since 0.20.0
306 285 */
307 -.desktop-mode-drag-hint {
286 +.os-drag-hint {
308 287 position: fixed;
309 288 top: 0;
310 289 left: 0;
311 290 padding: 6px 10px;
@@ -317,9 +296,9 @@
317 296 pointer-events: none;
318 297 user-select: none;
319 298 box-shadow: 0 4px 14px rgba( 0, 0, 0, 0.22 );
320 299 background: rgba( 30, 30, 30, 0.85 );
321 - color: #fff;
300 + color: var( --os-ui-fg-on-accent, #fff );
322 301 z-index: 2147483647;
323 302 transition:
324 303 background-color 0.12s ease,
325 304 color 0.12s ease,
@@ -325,22 +304,22 @@
325 304 color 0.12s ease,
326 305 transform 0.06s ease-out;
327 306 }
328 307
329 -.desktop-mode-drag-hint--neutral {
308 +.os-drag-hint--neutral {
330 309 background: rgba( 30, 30, 30, 0.85 );
331 - color: #fff;
310 + color: var( --os-ui-fg-on-accent, #fff );
332 311 }
333 312
334 -.desktop-mode-drag-hint--accept {
313 +.os-drag-hint--accept {
335 314 background: #1e8449;
336 - color: #fff;
315 + color: var( --os-ui-fg-on-accent, #fff );
337 316 box-shadow:
338 317 0 0 0 2px rgba( 30, 132, 73, 0.25 ),
339 318 0 6px 18px rgba( 30, 132, 73, 0.3 );
340 319 }
341 320
342 -.desktop-mode-drag-hint--accept::before {
321 +.os-drag-hint--accept::before {
343 322 /* Tiny checkmark chevron prefix so "Drop here" reads as an
344 323 * actionable affirmation rather than a passive label. The
345 324 * checkmark is a real character (U+2713) so screen readers
346 325 * skip past it via the `aria-hidden` on the host chip. */
@@ -346,17 +325,17 @@
346 325 * skip past it via the `aria-hidden` on the host chip. */
347 326 content: '✓ ';
348 327 }
349 328
350 -.desktop-mode-drag-hint--reject {
351 - background: #b32d2e;
352 - color: #fff;
329 +.os-drag-hint--reject {
330 + background: var( --os-ui-danger-hover, #b32d2e );
331 + color: var( --os-ui-fg-on-accent, #fff );
353 332 box-shadow:
354 333 0 0 0 2px rgba( 179, 45, 46, 0.25 ),
355 334 0 6px 18px rgba( 179, 45, 46, 0.3 );
356 335 }
357 336
358 -.desktop-mode-drag-hint--reject::before {
337 +.os-drag-hint--reject::before {
359 338 content: '⊘ ';
360 339 }
361 340
362 341 /*
@@ -386,13 +365,13 @@
386 365 * something. Tiles in My WordPress grids (and any future content
387 366 * surface) are the entity, not a shortcut to it, so the arrow
388 367 * would just be noise. `[data-placement-id]` is the canonical
389 368 * placement marker. */
390 -.desktop-mode-file-tile[ data-placement-id ][ data-file-type="post" ]::after,
391 -.desktop-mode-file-tile[ data-placement-id ][ data-file-type="attachment" ]::after,
392 -.desktop-mode-file-tile[ data-placement-id ][ data-file-type="comment" ]::after,
393 -.desktop-mode-file-tile[ data-placement-id ][ data-file-type="user" ]::after,
394 -.desktop-mode-file-tile[ data-placement-id ][ data-file-type="term" ]::after {
369 +.os-file-tile[ data-placement-id ][ data-file-type="post" ]::after,
370 +.os-file-tile[ data-placement-id ][ data-file-type="attachment" ]::after,
371 +.os-file-tile[ data-placement-id ][ data-file-type="comment" ]::after,
372 +.os-file-tile[ data-placement-id ][ data-file-type="user" ]::after,
373 +.os-file-tile[ data-placement-id ][ data-file-type="term" ]::after {
395 374 content: "↗";
396 375 position: absolute;
397 376 right: 14px;
398 377 top: 44px;
@@ -398,15 +377,15 @@
398 377 top: 44px;
399 378 width: 18px;
400 379 height: 18px;
401 380 border-radius: 50%;
402 - background: var( --desktop-mode-tile-shortcut-bg, rgba( 0, 0, 0, 0.65 ) );
403 - color: var( --desktop-mode-tile-shortcut-fg, #fff );
381 + background: var( --os-tile-shortcut-bg, rgba( 0, 0, 0, 0.65 ) );
382 + color: var( --os-tile-shortcut-fg, #fff );
404 383 font-size: 11px;
405 384 line-height: 18px;
406 385 text-align: center;
407 386 box-shadow: var(
408 - --desktop-mode-tile-shortcut-shadow,
387 + --os-tile-shortcut-shadow,
409 388 0 1px 3px rgba( 0, 0, 0, 0.5 )
410 389 );
411 390 pointer-events: none;
412 391 }
@@ -417,9 +396,9 @@
417 396 * can right-click → Remove without confusing it for an active
418 397 * file. Phase 6's per-placement permission re-check uses the
419 398 * same affordance for "you can't see this" placeholders.
420 399 */
421 -.desktop-mode-file-tile--missing {
400 +.os-file-tile--missing {
422 401 opacity: 0.5;
423 402 }
424 403
425 404 /**
@@ -427,21 +406,21 @@
427 406 * shared the folder containing it) but lacks `can_read` on the
428 407 * underlying entity. Dim the tile, kill the pointer affordance,
429 408 * and overlay a lock badge so the state reads at a glance.
430 409 */
431 -.desktop-mode-file-tile--access-gated {
410 +.os-file-tile--access-gated {
432 411 opacity: 0.65;
433 412 cursor: not-allowed;
434 413 }
435 -.desktop-mode-file-tile--access-gated .desktop-mode-file-tile__visual,
436 -.desktop-mode-file-tile--access-gated .desktop-mode-file-tile__preview {
414 +.os-file-tile--access-gated .os-file-tile__visual,
415 +.os-file-tile--access-gated .os-file-tile__preview {
437 416 filter: grayscale( 0.6 );
438 417 }
439 -.desktop-mode-file-tile--access-gated:hover,
440 -.desktop-mode-file-tile--access-gated:focus-visible {
441 - background: rgba( 0, 0, 0, 0.06 );
418 +.os-file-tile--access-gated:hover,
419 +.os-file-tile--access-gated:focus-visible {
420 + background: var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) );
442 421 }
443 -.desktop-mode-file-tile__lock {
422 +.os-file-tile__lock {
444 423 position: absolute;
445 424 top: 4px;
446 425 inset-inline-end: 4px;
447 426 width: 20px;
@@ -446,10 +425,10 @@
446 425 inset-inline-end: 4px;
447 426 width: 20px;
448 427 height: 20px;
449 428 border-radius: 50%;
450 - background: rgba( 0, 0, 0, 0.7 );
451 - color: #fff;
429 + background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.7 ) );
430 + color: var( --os-ui-fg-on-accent, #fff );
452 431 font-size: 14px;
453 432 line-height: 20px;
454 433 text-align: center;
455 434 pointer-events: none;
@@ -460,9 +439,9 @@
460 439 * Preview-pane empty state when a recipient selects a tile they
461 440 * can't open. Big lock ring + clear copy + secondary hint. Designed
462 441 * to read at a glance in the otherwise-busy preview column.
463 442 */
464 -.desktop-mode-files__access-gated {
443 +.os-files__access-gated {
465 444 display: flex;
466 445 flex-direction: column;
467 446 align-items: center;
468 447 justify-content: center;
@@ -469,11 +448,11 @@
469 448 gap: 16px;
470 449 padding: 32px 24px;
471 450 min-height: 240px;
472 451 text-align: center;
473 - color: var( --desktop-mode-fg, inherit );
452 + color: var( --os-fg, inherit );
474 453 }
475 -.desktop-mode-files__access-gated-ring {
454 +.os-files__access-gated-ring {
476 455 display: flex;
477 456 align-items: center;
478 457 justify-content: center;
479 458 width: 96px;
@@ -488,9 +467,9 @@
488 467 box-shadow:
489 468 inset 0 1px 0 rgba( 255, 255, 255, 0.08 ),
490 469 0 8px 24px rgba( 214, 54, 56, 0.18 );
491 470 }
492 -.desktop-mode-files__access-gated-glyph {
471 +.os-files__access-gated-glyph {
493 472 font-size: 44px;
494 473 width: 44px;
495 474 height: 44px;
496 475 color: #ff8080;
@@ -495,15 +474,15 @@
495 474 height: 44px;
496 475 color: #ff8080;
497 476 filter: drop-shadow( 0 1px 2px rgba( 0, 0, 0, 0.4 ) );
498 477 }
499 -.desktop-mode-files__access-gated-title {
478 +.os-files__access-gated-title {
500 479 margin: 0;
501 480 font-size: 17px;
502 481 font-weight: 600;
503 482 letter-spacing: 0.2px;
504 483 }
505 -.desktop-mode-files__access-gated-sub {
484 +.os-files__access-gated-sub {
506 485 margin: 0;
507 486 font-size: 13px;
508 487 line-height: 1.5;
509 488 max-width: 340px;
@@ -508,9 +487,9 @@
508 487 line-height: 1.5;
509 488 max-width: 340px;
510 489 opacity: 0.85;
511 490 }
512 -.desktop-mode-files__access-gated-hint {
491 +.os-files__access-gated-hint {
513 492 margin: 0;
514 493 font-size: 12px;
515 494 line-height: 1.5;
516 495 max-width: 340px;
@@ -516,9 +495,9 @@
516 495 max-width: 340px;
517 496 opacity: 0.6;
518 497 }
519 498
520 -.desktop-mode-file-tile__visual {
499 +.os-file-tile__visual {
521 500 display: inline-flex;
522 501 align-items: center;
523 502 justify-content: center;
524 503 width: 48px;
@@ -526,9 +505,9 @@
526 505 font-size: 32px;
527 506 line-height: 1;
528 507 }
529 508
530 -.desktop-mode-file-tile__icon {
509 +.os-file-tile__icon {
531 510 display: inline-flex;
532 511 align-items: center;
533 512 justify-content: center;
534 513 width: 48px;
@@ -536,9 +515,9 @@
536 515 font-size: 32px;
537 516 line-height: 1;
538 517 }
539 518
540 -.desktop-mode-file-tile__preview {
519 +.os-file-tile__preview {
541 520 width: 48px;
542 521 height: 48px;
543 522 object-fit: cover;
544 523 border-radius: 4px;
@@ -550,24 +529,24 @@
550 529 * and the tile silently refuses to move. The renderer also sets
551 530 * `draggable="false"` on every `<img>` it produces; this CSS rule
552 531 * covers anything injected later (plugin filters, decorators)
553 532 * without having to remember the attribute. */
554 -.desktop-mode-file-tile img {
533 +.os-file-tile img {
555 534 -webkit-user-drag: none;
556 535 user-select: none;
557 536 }
558 537
559 538 /*
560 - * Wallpaper + tile context menus use `<wpd-context-menu>` and
561 - * `<wpd-context-menu-option>` (Phase 4 / 0.9.0). Their host-level
539 + * Wallpaper + tile context menus use `<os-context-menu>` and
540 + * `<os-context-menu-option>` (Phase 4 / 0.9.0). Their host-level
562 541 * styles live inside the component shadow DOM — see
563 - * `src/ui/components/wpd-context-menu/wpd-context-menu.styles.ts`.
564 - * No project CSS rules needed here; the legacy `.desktop-mode-
542 + * `src/ui/components/os-context-menu/os-context-menu.styles.ts`.
543 + * No project CSS rules needed here; the legacy `.os-
565 544 * wallpaper-menu*` rules were removed when the components landed.
566 545 */
567 546
568 547 /*
569 - * Folder native window — the body of `desktop-mode-folder-<id>`
548 + * Folder native window — the body of `os-folder-<id>`
570 549 * contains a `FilesLayer` rendering that folder's contents.
571 550 * We ensure the body has positioning context so absolutely-
572 551 * positioned tiles inside the layer anchor correctly.
573 552 */
@@ -576,12 +555,12 @@
576 555 min-height: 200px;
577 556 /* Same window background recipe as My WordPress — defaults to
578 557 * the standard window bg (#fff) and themable per-window. */
579 558 background: var(
580 - --desktop-mode-folder-window-bg,
581 - var( --desktop-mode-window-bg, #fff )
559 + --os-folder-window-bg,
560 + var( --os-window-bg, #fff )
582 561 );
583 - color: var( --desktop-mode-fg-on-light, #1d2327 );
562 + color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) );
584 563 display: flex;
585 564 flex-direction: column;
586 565 /* Light-on-dark context: the folder window opens inside a
587 566 * standard window chrome whose body is white-ish. Retint the
@@ -587,18 +566,26 @@
587 566 * standard window chrome whose body is white-ish. Retint the
588 567 * tile color tokens here so dashicons + labels read against
589 568 * a light background. Plugins / themes can override these
590 569 * same variables at any scope to retint further. */
591 - --desktop-mode-tile-fg: var( --desktop-mode-fg-on-light, #1d2327 );
592 - --desktop-mode-tile-fg-muted: rgba( 0, 0, 0, 0.55 );
593 - --desktop-mode-tile-hover-bg: rgba( 0, 0, 0, 0.06 );
570 + --os-tile-fg: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) );
571 + /* Chains through the palette for the same reason as
572 + * `--os-tile-fg` beside it: a bare literal here outranks the
573 + * palette's own value and pins every tile's secondary line to
574 + * near-black, which disappears the moment the surface is dark.
575 + * The literal stays as the pre-brand floor. */
576 + --os-tile-fg-muted: var(
577 + --os-folder-window-fg-muted,
578 + var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.55 ) )
579 + );
580 + --os-tile-hover-bg: rgba( 0, 0, 0, 0.06 );
594 581 /* Light-context label rendering — share the same recipe with
595 582 * every other light-bg tile surface (My WordPress, future
596 583 * windows). One set of tokens, no per-surface duplication. */
597 - --desktop-mode-tile-label-shadow: none;
598 - --desktop-mode-tile-label-weight: 500;
599 - --desktop-mode-tile-label-smoothing: antialiased;
600 - --desktop-mode-tile-label-color: var( --desktop-mode-fg-on-light, #1d2327 );
584 + --os-tile-label-shadow: none;
585 + --os-tile-label-weight: 500;
586 + --os-tile-label-smoothing: antialiased;
587 + --os-tile-label-color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) );
601 588 }
602 589
603 590 /* Folder window's bottom status bar — paint with the same light-
604 591 * context treatment My WordPress uses (transparent bg, soft border,
@@ -604,18 +591,18 @@
604 591 * context treatment My WordPress uses (transparent bg, soft border,
605 592 * muted dark text). Without this, the wallpaper-targeted defaults
606 593 * (`background: rgba(0,0,0,0.18); color: white`) would show as a
607 594 * dark band against the white window bg. */
608 -.desktop-mode-folder-window .desktop-mode-folder-status-bar {
609 - border-top: 1px solid var( --desktop-mode-border, #dcdcde );
595 +.desktop-mode-folder-window .os-folder-status-bar {
596 + border-top: 1px solid var( --os-ui-border, #dcdcde );
610 597 background: transparent;
611 - color: var( --desktop-mode-muted, #50575e );
598 + color: var( --os-ui-fg-muted, #50575e );
612 599 }
613 600
614 601 .desktop-mode-folder-window
615 - .desktop-mode-folder-status-bar
616 - button.desktop-mode-folder-status-bar__segment:hover {
617 - background: var( --desktop-mode-hover, rgba( 0, 0, 0, 0.06 ) );
602 + .os-folder-status-bar
603 + button.os-folder-status-bar__segment:hover {
604 + background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) );
618 605 }
619 606
620 607 /* Generic breadcrumb header — any surface that grows a navigation
621 608 * stack (folder window, My WordPress folder, future drill-down
@@ -625,19 +612,19 @@
625 612 * current segment is bold + non-interactive.
626 613 *
627 614 * @since 0.8.0
628 615 */
629 -.desktop-mode-breadcrumbs {
616 +.os-breadcrumbs {
630 617 display: flex;
631 618 align-items: center;
632 619 gap: 8px;
633 620 padding: 6px 10px;
634 - border-bottom: 1px solid var( --desktop-mode-border, #dcdcde );
621 + border-bottom: 1px solid var( --os-ui-border, #dcdcde );
635 622 background: transparent;
636 623 flex: 0 0 auto;
637 624 }
638 625
639 -.desktop-mode-breadcrumbs__back {
626 +.os-breadcrumbs__back {
640 627 display: inline-flex;
641 628 align-items: center;
642 629 justify-content: center;
643 630 width: 26px;
@@ -650,20 +637,20 @@
650 637 color: inherit;
651 638 flex: 0 0 auto;
652 639 }
653 640
654 -.desktop-mode-breadcrumbs__back:hover:not(:disabled),
655 -.desktop-mode-breadcrumbs__back:focus-visible:not(:disabled) {
656 - background: var( --desktop-mode-hover, rgba( 0, 0, 0, 0.06 ) );
641 +.os-breadcrumbs__back:hover:not(:disabled),
642 +.os-breadcrumbs__back:focus-visible:not(:disabled) {
643 + background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) );
657 644 outline: none;
658 645 }
659 646
660 -.desktop-mode-breadcrumbs__back:disabled {
647 +.os-breadcrumbs__back:disabled {
661 648 opacity: 0.35;
662 649 cursor: default;
663 650 }
664 651
665 -.desktop-mode-breadcrumbs__back .dashicons {
652 +.os-breadcrumbs__back .dashicons {
666 653 font-size: 18px;
667 654 width: 18px;
668 655 height: 18px;
669 656 line-height: 1;
@@ -668,9 +655,9 @@
668 655 height: 18px;
669 656 line-height: 1;
670 657 }
671 658
672 -.desktop-mode-breadcrumbs__crumbs {
659 +.os-breadcrumbs__crumbs {
673 660 display: flex;
674 661 align-items: center;
675 662 gap: 6px;
676 663 font-size: 13px;
@@ -679,12 +666,12 @@
679 666 text-overflow: ellipsis;
680 667 white-space: nowrap;
681 668 }
682 669
683 -.desktop-mode-breadcrumbs__crumb {
670 +.os-breadcrumbs__crumb {
684 671 border: 0;
685 672 background: transparent;
686 - color: var( --desktop-mode-link, #2271b1 );
673 + color: var( --os-link, var( --os-ui-accent, #2271b1 ) );
687 674 cursor: pointer;
688 675 padding: 2px 4px;
689 676 border-radius: 4px;
690 677 font: inherit;
@@ -689,27 +676,27 @@
689 676 border-radius: 4px;
690 677 font: inherit;
691 678 }
692 679
693 -.desktop-mode-breadcrumbs__crumb:hover {
694 - background: var( --desktop-mode-hover, rgba( 0, 0, 0, 0.06 ) );
680 +.os-breadcrumbs__crumb:hover {
681 + background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) );
695 682 }
696 683
697 -.desktop-mode-breadcrumbs__crumb--current {
698 - color: var( --desktop-mode-fg-on-light, #1d2327 );
684 +.os-breadcrumbs__crumb--current {
685 + color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) );
699 686 cursor: default;
700 687 font-weight: 600;
701 688 }
702 689
703 -.desktop-mode-breadcrumbs__crumb--current:hover {
690 +.os-breadcrumbs__crumb--current:hover {
704 691 background: transparent;
705 692 }
706 693
707 -.desktop-mode-breadcrumbs__sep {
708 - color: var( --desktop-mode-muted, #787c82 );
694 +.os-breadcrumbs__sep {
695 + color: var( --os-ui-fg-muted, #787c82 );
709 696 }
710 697
711 -.desktop-mode-folder-window__layer {
698 +.os-folder-window__layer {
712 699 position: relative;
713 700 flex: 1;
714 701 min-height: 0;
715 702 overflow: auto;
@@ -719,9 +706,9 @@
719 706 * reflects the currently-selected tile. Same model as the My
720 707 * WordPress two-pane view so the UX is unified.
721 708 *
722 709 * @since 0.8.0 */
723 -.desktop-mode-folder-window__split {
710 +.os-folder-window__split {
724 711 display: grid;
725 712 grid-template-columns: minmax( 240px, 60% ) minmax( 0, 1fr );
726 713 flex: 1 1 auto;
727 714 min-height: 0;
@@ -726,36 +713,89 @@
726 713 flex: 1 1 auto;
727 714 min-height: 0;
728 715 }
729 716
730 -.desktop-mode-folder-window__preview {
717 +.os-folder-window__preview {
731 718 overflow-y: auto;
732 719 min-width: 0;
733 - background: var( --desktop-mode-window-bg, #fff );
734 - border-inline-start: 1px solid var( --desktop-mode-border, #dcdcde );
735 - color: var( --desktop-mode-fg-on-light, #1d2327 );
720 + background: var( --os-window-bg, #fff );
721 + border-inline-start: 1px solid var( --os-ui-border, #dcdcde );
722 + color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) );
736 723 }
737 724
738 -/* Selected file tile — light-blue accent on the wallpaper, soft
739 - * accent inside light-context windows. Variable-driven so plugins
740 - * can retune. */
741 -.desktop-mode-file-tile--selected,
742 -.desktop-mode-file-tile--selected:hover {
725 +/* Selected file tile — a fill, and an inset accent ring whose width
726 + * the palette decides (0 on OpenStation's, which lifts the tile with
727 + * the fill alone; 1px on Legacy). Variable-driven so plugins can
728 + * retune. */
729 +.os-file-tile--selected,
730 +.os-file-tile--selected:hover {
743 731 background: var(
744 - --desktop-mode-tile-selected-bg,
732 + --os-tile-selected-bg,
745 733 rgba( 34, 113, 177, 0.18 )
746 734 );
747 - box-shadow: inset 0 0 0 1px
748 - var( --desktop-mode-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) );
735 + box-shadow: inset 0 0 0 var( --os-tile-selected-ring-width, 1px )
736 + var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) );
749 737 }
750 738
751 739 /* Inside a folder window the selected highlight should read on
752 740 * white. */
753 741 .desktop-mode-folder-window {
754 - --desktop-mode-tile-selected-bg: rgba( 34, 113, 177, 0.12 );
742 + --os-tile-selected-bg: rgba( 34, 113, 177, 0.12 );
755 743 }
756 744
757 -.desktop-mode-folder-status-bar {
745 +/* Type breakdown under a multi-selection's count in a folder
746 + * window's preview pane. The count is the headline; this is the
747 + * detail that tells you which actions the menu will offer. */
748 +.os-files-preview__selection-breakdown {
749 + font-size: 12px;
750 + opacity: 0.75;
751 +}
752 +
753 +/* While a rubber band is live, nothing in the shell is selectable.
754 + *
755 + * The band starts on bare canvas, which — unlike a tile — IS
756 + * selectable, so the browser begins its own text selection on the
757 + * same press and keeps extending it as the pointer travels. Inside
758 + * the canvas that stays invisible; drag out over another window and
759 + * it starts highlighting that window's text in blue, mid-gesture.
760 + *
761 + * The controller also refuses `selectstart` outright while the band
762 + * is up. This rule is the second half: it covers anything already
763 + * selectable that the event route misses. */
764 +body[ data-os-marquee ] {
765 + -webkit-user-select: none;
766 + user-select: none;
767 +}
768 +
769 +/* Marquee (rubber-band) selection box. Painted by the shared
770 + * selection controller on any canvas that opts into it — the
771 + * wallpaper, folder windows, every My WordPress list — so this one
772 + * declaration dresses all of them.
773 + *
774 + * `pointer-events: none` is load-bearing: the box tracks under the
775 + * cursor for the whole gesture, and without it every hit-test
776 + * (drop targets, the controller's own tile lookup) would land on
777 + * the box instead of what's beneath it. */
778 +.os-selection-marquee {
779 + position: absolute;
780 + z-index: 5;
781 + pointer-events: none;
782 + border: 1px solid
783 + var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) );
784 + border-radius: 2px;
785 + /* A WASH, not a fill — the whole point of a rubber band is that
786 + * you can see what it is about to catch. `--os-ui-accent-dim` is a
787 + * solid hex (Pulse, one step back), so reaching for it directly
788 + * paints an opaque rectangle over the icons; `--os-ui-accent-soft`
789 + * is that same colour already taken down to a 14% wash, which is
790 + * what every other ambient accent surface in the shell uses. */
791 + background: var(
792 + --os-selection-marquee-bg,
793 + var( --os-ui-accent-soft, rgba( 34, 113, 177, 0.14 ) )
794 + );
795 +}
796 +
797 +.os-folder-status-bar {
758 798 display: flex;
759 799 justify-content: space-between;
760 800 align-items: center;
761 801 gap: 12px;
@@ -760,21 +800,21 @@
760 800 align-items: center;
761 801 gap: 12px;
762 802 padding: 6px 12px;
763 803 border-top: 1px solid rgba( 255, 255, 255, 0.08 );
764 - background: rgba( 0, 0, 0, 0.18 );
765 - color: var( --desktop-mode-fg-muted, rgba( 255, 255, 255, 0.7 ) );
804 + background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.18 ) );
805 + color: var( --os-fg-muted, rgba( 255, 255, 255, 0.7 ) );
766 806 font-size: 12px;
767 807 flex-shrink: 0;
768 808 }
769 809
770 -.desktop-mode-folder-status-bar__cluster {
810 +.os-folder-status-bar__cluster {
771 811 display: flex;
772 812 align-items: center;
773 813 gap: 12px;
774 814 }
775 815
776 -.desktop-mode-folder-status-bar__segment {
816 +.os-folder-status-bar__segment {
777 817 display: inline-flex;
778 818 align-items: center;
779 819 gap: 4px;
780 820 padding: 0;
@@ -783,78 +823,100 @@
783 823 color: inherit;
784 824 font: inherit;
785 825 }
786 826
787 -button.desktop-mode-folder-status-bar__segment {
827 +button.os-folder-status-bar__segment {
788 828 cursor: pointer;
789 829 border-radius: 4px;
790 830 padding: 2px 6px;
791 831 }
792 832
793 -button.desktop-mode-folder-status-bar__segment:hover {
833 +button.os-folder-status-bar__segment:hover {
794 834 background: rgba( 255, 255, 255, 0.1 );
795 835 }
796 836
797 -.desktop-mode-folder-status-bar__icon {
837 +.os-folder-status-bar__icon {
798 838 font-size: 14px;
799 839 }
800 840
801 841 /*
802 842 * File Associations OS Settings tab (Phase 5). One row per file
803 - * type: label on the left, a `<wpd-select>` of compatible
843 + * type: label on the left, a `<os-select>` of compatible
804 844 * openers on the right. Color tokens use the OS-Settings-canonical
805 - * `--wpd-text-muted` so the tab inherits theme colors instead of
845 + * `--os-ui-fg-muted` so the tab inherits theme colors instead of
806 846 * falling back to white-on-light invisibility.
807 847 */
808 -.desktop-mode-file-associations__intro {
848 +.os-file-associations__intro {
809 849 display: block;
810 850 margin: 0 0 16px;
811 - color: var( --wpd-text-muted, #50575e );
851 + color: var( --os-ui-fg-muted, #50575e );
812 852 font-size: 13px;
813 853 line-height: 1.5;
814 854 }
815 855
816 -.desktop-mode-file-associations__list {
856 +.os-file-associations__list {
817 857 display: flex;
818 858 flex-direction: column;
819 859 gap: 8px;
820 860 }
821 861
822 -.desktop-mode-file-associations__row {
862 +.os-file-associations__row {
823 863 display: flex;
824 864 align-items: center;
825 865 justify-content: space-between;
826 866 gap: 16px;
827 867 padding: 8px 12px;
828 - background: rgba( 0, 0, 0, 0.04 );
868 + background: var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) );
829 869 border-radius: 6px;
830 870 }
831 871
832 -.desktop-mode-file-associations__label {
872 +.os-file-associations__label {
833 873 font-weight: 500;
834 874 flex: 1;
835 875 }
836 876
837 -.desktop-mode-file-associations__select {
877 +.os-file-associations__select {
838 878 min-width: 220px;
839 879 }
840 880
841 -.desktop-mode-file-associations__none,
842 -.desktop-mode-file-associations__empty {
843 - color: var( --wpd-text-muted, #50575e );
881 +.os-file-associations__none,
882 +.os-file-associations__empty {
883 + color: var( --os-ui-fg-muted, #50575e );
844 884 font-size: 12px;
845 885 }
846 886
847 887 /*
848 - * "New folder" inline dialog. Centered modal with a focused
849 - * input — replaces the placeholder window.prompt. Plain HTML
850 - * controls so it works as a building block before richer
851 - * dialog primitives land.
888 + * The shell's two built-in modals — "New folder" / "Rename", and the
889 + * web-link dialog, which reuses this class set for its surface. Both
890 + * are light-DOM overlays that slot `<os-text-field>` +
891 + * `<os-button>` for their controls.
892 + *
893 + * The surface reads the same `--os-ui-modal-*` family that
894 + * `<os-modal>` and `<os-confirm-dialog>` do, so the three look like
895 + * one dialog system and answer to a desktop theme together. Two
896 + * things it must NOT do, both of which it used to:
897 + *
898 + * - Paint from `--os-bg`. That is the WALLPAPER token: the desk
899 + * default is a gradient and the wallpaper layer overwrites it
900 + * with whatever artwork is active. A dialog is a surface, not a
901 + * desk. Same note as `os-modal.styles.ts`.
902 + * - Style raw form controls. Core's `forms.css` reaches every
903 + * `<input>` in the parent shell through `input[type="text"]` —
904 + * (0,1,1), which outranks a single class of ours — so a plain
905 + * input rendered as a white core-chrome box on this dark
906 + * surface no matter what we declared. The controls live in
907 + * shadow DOM now, where that sheet cannot follow.
852 908 */
853 -.desktop-mode-create-folder-dialog__overlay {
909 +.os-create-folder-dialog__overlay {
854 910 position: fixed;
855 911 inset: 0;
856 - background: rgba( 0, 0, 0, 0.45 );
912 + background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.45 ) );
913 + /* Desktop-theme texture slot: unset resolves to none. Mirrors
914 + the scrim treatment in os-modal. */
915 + background-image: var( --os-ui-scrim-image, none );
916 + background-repeat: var( --os-ui-scrim-image-repeat, repeat );
917 + background-size: var( --os-ui-scrim-image-size, auto );
918 + background-position: var( --os-ui-scrim-image-position, center );
857 919 backdrop-filter: blur( 2px );
858 920 display: flex;
859 921 align-items: center;
860 922 justify-content: center;
@@ -860,13 +922,42 @@
860 922 justify-content: center;
861 923 z-index: 10000;
862 924 }
863 925
864 -.desktop-mode-create-folder-dialog {
926 +.os-create-folder-dialog {
927 + /*
928 + * Re-point the shared control tokens for a dark dialog surface,
929 + * exactly as `<os-modal>` does on its host. The slotted
930 + * `<os-text-field>` / `<os-button>` inherit these across the
931 + * shadow boundary, so the field resolves a dark input with light
932 + * ink instead of the light-admin defaults (`--os-window-bg` is
933 + * `#fff` in an unthemed shell, which under a light `--os-ui-fg`
934 + * is the invisible-value trap the modal styles call out).
935 + *
936 + * Each reads a palette-owned `--os-ui-modal-*` name first, so a
937 + * desktop theme can still reach every one of them; the literals
938 + * are only the floor for a shell whose stylesheet never loaded.
939 + */
940 + --os-ui-fg: var( --os-ui-modal-text, #f0f0f1 );
941 + --os-ui-fg-muted: var( --os-ui-modal-text-muted, #a7aaad );
942 + --os-ui-border: var( --os-ui-modal-border, rgba( 255, 255, 255, 0.25 ) );
943 + --os-window-bg: var( --os-ui-modal-field-bg, #2c3338 );
944 + --os-ui-button-bg-hover: var(
945 + --os-ui-modal-button-bg-hover,
946 + rgba( 255, 255, 255, 0.08 )
947 + );
948 +
865 949 width: min( 420px, 92vw );
866 - background: var( --desktop-mode-bg, #1d2327 );
867 - color: var( --desktop-mode-fg, #fff );
868 - border: 1px solid rgba( 255, 255, 255, 0.08 );
950 + /* Longhand, and a literal fallback: the shorthand would reset the
951 + texture slot below, and a gradient is invalid as a
952 + background-color — it would leave the dialog transparent. */
953 + background-color: var( --os-ui-modal-bg, #1d2327 );
954 + background-image: var( --os-ui-dialog-bg-image, none );
955 + background-repeat: var( --os-ui-dialog-bg-image-repeat, repeat );
956 + background-size: var( --os-ui-dialog-bg-image-size, auto );
957 + background-position: var( --os-ui-dialog-bg-image-position, center );
958 + color: var( --os-ui-modal-fg, var( --os-fg, #fff ) );
959 + border: 1px solid var( --os-ui-border, rgba( 255, 255, 255, 0.08 ) );
869 960 border-radius: 10px;
870 961 box-shadow: 0 20px 50px rgba( 0, 0, 0, 0.6 );
871 962 padding: 20px 22px 18px;
872 963 display: flex;
@@ -873,43 +964,29 @@
873 964 flex-direction: column;
874 965 gap: 10px;
875 966 }
876 967
877 -.desktop-mode-create-folder-dialog__title {
968 +.os-create-folder-dialog__title {
878 969 margin: 0 0 4px;
879 970 font-size: 16px;
880 971 font-weight: 600;
972 + color: var( --os-ui-fg, #f0f0f1 );
881 973 }
882 974
883 -.desktop-mode-create-folder-dialog__label {
975 +.os-url-dialog__description {
976 + margin: 0 0 4px;
884 977 font-size: 12px;
885 - color: var( --desktop-mode-fg-muted, rgba( 255, 255, 255, 0.7 ) );
978 + line-height: 1.5;
979 + color: var( --os-ui-fg-muted, #a7aaad );
886 980 }
887 981
888 -.desktop-mode-create-folder-dialog__input {
889 - width: 100%;
890 - padding: 8px 10px;
891 - background: rgba( 255, 255, 255, 0.06 );
892 - color: inherit;
893 - border: 1px solid rgba( 255, 255, 255, 0.16 );
894 - border-radius: 6px;
895 - font-size: 14px;
896 - outline: none;
897 - box-sizing: border-box;
898 -}
899 -
900 -.desktop-mode-create-folder-dialog__input:focus {
901 - border-color: var( --wp-admin-theme-color, #2271b1 );
902 - box-shadow: 0 0 0 2px rgba( 34, 113, 177, 0.4 );
903 -}
904 -
905 -.desktop-mode-create-folder-dialog__error {
982 +.os-create-folder-dialog__error {
906 983 margin: 0;
907 - color: #ff8a8a;
984 + color: var( --os-ui-danger-hover, #ff8a8a );
908 985 font-size: 12px;
909 986 }
910 987
911 -.desktop-mode-create-folder-dialog__actions {
988 +.os-create-folder-dialog__actions {
912 989 display: flex;
913 990 justify-content: flex-end;
914 991 gap: 8px;
915 992 margin-top: 6px;
@@ -914,55 +991,29 @@
914 991 gap: 8px;
915 992 margin-top: 6px;
916 993 }
917 994
918 -.desktop-mode-create-folder-dialog__btn {
919 - border: 0;
920 - border-radius: 6px;
921 - padding: 8px 14px;
922 - font-size: 13px;
923 - cursor: pointer;
924 - font-weight: 500;
925 -}
926 -
927 -.desktop-mode-create-folder-dialog__btn--secondary {
928 - background: rgba( 255, 255, 255, 0.08 );
929 - color: inherit;
930 -}
931 -
932 -.desktop-mode-create-folder-dialog__btn--secondary:hover {
933 - background: rgba( 255, 255, 255, 0.14 );
934 -}
935 -
936 -.desktop-mode-create-folder-dialog__btn--primary {
937 - background: var( --wp-admin-theme-color, #2271b1 );
938 - color: #fff;
939 -}
940 -
941 -.desktop-mode-create-folder-dialog__btn--primary:hover {
942 - filter: brightness( 1.08 );
943 -}
944 -
945 -.desktop-mode-create-folder-dialog__btn:disabled {
946 - opacity: 0.55;
947 - cursor: not-allowed;
948 -}
949 -
950 -.desktop-mode-create-folder-dialog--busy .desktop-mode-create-folder-dialog__input {
995 +/* The busy state dims the whole form area rather than the input
996 + alone — the buttons carry their own disabled treatment from
997 + os-button, and dimming one control of three read as a glitch. */
998 +.os-create-folder-dialog--busy .os-create-folder-dialog__field {
951 999 opacity: 0.7;
952 1000 }
953 1001
954 1002 /*
955 1003 * Single label rule for every tile, regardless of host surface.
956 - * Visual properties read from `--desktop-mode-tile-label-*`
1004 + * Visual properties read from `--os-tile-label-*`
957 1005 * tokens — defined in `variables.css` for the dark wallpaper and
958 1006 * rebound by any light-context scope (folder window, My
959 1007 * WordPress, …) so all surfaces share one source of truth.
960 1008 */
961 -.desktop-mode-file-tile__label {
1009 +.os-file-tile__label {
962 1010 font-size: 12px;
963 1011 line-height: 1.2;
964 - max-width: 84px;
1012 + /* Follows the tile rather than restating its width, so a section
1013 + * that re-points `--os-tile-w` (image-led sections do) gets a
1014 + * label that grows with the tile instead of staying narrow. */
1015 + max-width: calc( var( --os-tile-w, 88px ) - 4px );
965 1016 text-align: center;
966 1017 overflow: hidden;
967 1018 text-overflow: ellipsis;
968 1019 display: -webkit-box;
@@ -967,12 +1018,12 @@
967 1018 text-overflow: ellipsis;
968 1019 display: -webkit-box;
969 1020 -webkit-line-clamp: 2;
970 1021 -webkit-box-orient: vertical;
971 - color: var( --desktop-mode-tile-label-color, var( --desktop-mode-tile-fg, #fff ) );
972 - font-weight: var( --desktop-mode-tile-label-weight, 400 );
973 - text-shadow: var( --desktop-mode-tile-label-shadow, 0 1px 2px rgba( 0, 0, 0, 0.5 ) );
974 - -webkit-font-smoothing: var( --desktop-mode-tile-label-smoothing, auto );
1022 + color: var( --os-tile-label-color, var( --os-tile-fg, #fff ) );
1023 + font-weight: var( --os-tile-label-weight, 400 );
1024 + text-shadow: var( --os-tile-label-shadow, 0 1px 2px rgba( 0, 0, 0, 0.5 ) );
1025 + -webkit-font-smoothing: var( --os-tile-label-smoothing, auto );
975 1026 -moz-osx-font-smoothing: grayscale;
976 1027 }
977 1028
978 1029 /*
@@ -987,9 +1038,9 @@
987 1038 * accepting target, `no-drop` over rejecting (or no) target.
988 1039 *
989 1040 * @since 0.18.0
990 1041 */
991 -.desktop-mode-drag-ghost {
1042 +.os-drag-ghost {
992 1043 box-shadow: 0 6px 24px rgba( 0, 0, 0, 0.45 );
993 1044 border-radius: 6px;
994 1045 opacity: 0.95;
995 1046 transition: none;
@@ -994,35 +1045,94 @@
994 1045 opacity: 0.95;
995 1046 transition: none;
996 1047 }
997 1048
998 -.desktop-mode-drag-ghost--accept {
1049 +.os-drag-ghost--accept {
999 1050 cursor: copy;
1000 1051 }
1001 1052
1002 -.desktop-mode-drag-ghost--reject {
1053 +.os-drag-ghost--reject {
1003 1054 cursor: no-drop;
1004 1055 }
1005 1056
1057 +/* Multi-item drag ghost: the grabbed tile, with the rest of the set
1058 + * implied by two offset cards behind it and stated by a count badge.
1059 + * The stack is drawn with pseudo-elements rather than real clones —
1060 + * three cloned tiles cost three subtree copies per lift, and only
1061 + * the top one is ever legible. */
1062 +.os-drag-stack {
1063 + /* The drag manager overwrites this with `position: fixed` when it
1064 + * mounts the ghost. It stays declared so the stack still composes
1065 + * correctly if the helper is used outside a live drag — both
1066 + * values establish the containing block the cards and the badge
1067 + * are placed against. */
1068 + position: relative;
1069 +}
1070 +
1071 +.os-drag-stack::before,
1072 +.os-drag-stack::after {
1073 + content: '';
1074 + position: absolute;
1075 + inset: 0;
1076 + z-index: -1;
1077 + border-radius: 6px;
1078 + background: var( --os-ui-surface, rgba( 30, 30, 40, 0.9 ) );
1079 + box-shadow: 0 4px 14px rgba( 0, 0, 0, 0.35 );
1080 +}
1081 +
1082 +.os-drag-stack::before {
1083 + transform: translate( 6px, 6px );
1084 + opacity: 0.7;
1085 +}
1086 +
1087 +.os-drag-stack::after {
1088 + transform: translate( 12px, 12px );
1089 + opacity: 0.45;
1090 +}
1091 +
1092 +.os-drag-stack__count {
1093 + position: absolute;
1094 + inset-block-start: -8px;
1095 + inset-inline-end: -8px;
1096 + z-index: 1;
1097 + min-width: 20px;
1098 + height: 20px;
1099 + padding: 0 6px;
1100 + box-sizing: border-box;
1101 + display: flex;
1102 + align-items: center;
1103 + justify-content: center;
1104 + border-radius: 10px;
1105 + background: var(
1106 + --os-ui-accent,
1107 + var( --wp-admin-theme-color, #2271b1 )
1108 + );
1109 + color: #fff;
1110 + font-size: 11px;
1111 + font-weight: 600;
1112 + line-height: 1;
1113 + box-shadow: 0 2px 8px rgba( 0, 0, 0, 0.4 );
1114 +}
1115 +
1006 1116 /* ============================================================ *
1007 1117 * Upload progress HUD (since 0.31.0)
1008 1118 *
1009 1119 * Pinned floating panel that surfaces the OS-file-drop manager's
1010 - * in-flight uploads. One row per file with a `<wpd-progress-bar>`,
1120 + * in-flight uploads. One row per file with a `<os-progress-bar>`,
1011 1121 * a filename + status, and a Cancel/Dismiss action. The panel
1012 1122 * hides itself when there are no rows; it's purely reactive to
1013 1123 * the four upload-lifecycle hooks.
1014 1124 * ============================================================ */
1015 1125
1016 -.desktop-mode-upload-hud {
1126 +.os-upload-hud {
1017 1127 position: fixed;
1018 1128 inset-block-end: 16px;
1019 1129 inset-inline-end: 16px;
1020 1130 width: 320px;
1021 1131 max-width: calc( 100vw - 32px );
1022 - background: var( --desktop-mode-panel-bg, #fff );
1023 - color: var( --desktop-mode-fg-on-light, #1d2327 );
1024 - border: 1px solid var( --desktop-mode-border, #dcdcde );
1132 + background: var( --os-panel-bg, var( --os-ui-surface, #fff ) );
1133 + color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) );
1134 + border: 1px solid var( --os-ui-border, #dcdcde );
1025 1135 border-radius: 10px;
1026 1136 box-shadow: 0 8px 28px rgba( 0, 0, 0, 0.18 );
1027 1137 z-index: 99998;
1028 1138 font: inherit;
@@ -1029,25 +1139,25 @@
1029 1139 pointer-events: auto;
1030 1140 overflow: hidden;
1031 1141 }
1032 1142
1033 -.desktop-mode-upload-hud[ hidden ] {
1143 +.os-upload-hud[ hidden ] {
1034 1144 display: none;
1035 1145 }
1036 1146
1037 -.desktop-mode-upload-hud__header {
1147 +.os-upload-hud__header {
1038 1148 display: flex;
1039 1149 align-items: center;
1040 1150 justify-content: space-between;
1041 1151 gap: 8px;
1042 1152 padding: 8px 12px;
1043 - background: var( --desktop-mode-panel-header-bg, rgba( 0, 0, 0, 0.04 ) );
1044 - border-block-end: 1px solid var( --desktop-mode-border, #dcdcde );
1153 + background: var( --os-panel-header-bg, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) );
1154 + border-block-end: 1px solid var( --os-ui-border, #dcdcde );
1045 1155 font-weight: 600;
1046 1156 font-size: 12px;
1047 1157 }
1048 1158
1049 -.desktop-mode-upload-hud__title {
1159 +.os-upload-hud__title {
1050 1160 min-width: 0;
1051 1161 overflow: hidden;
1052 1162 text-overflow: ellipsis;
1053 1163 white-space: nowrap;
@@ -1052,9 +1162,9 @@
1052 1162 text-overflow: ellipsis;
1053 1163 white-space: nowrap;
1054 1164 }
1055 1165
1056 -.desktop-mode-upload-hud__close {
1166 +.os-upload-hud__close {
1057 1167 border: 0;
1058 1168 background: transparent;
1059 1169 color: inherit;
1060 1170 font-size: 16px;
@@ -1066,15 +1176,15 @@
1066 1176 display: inline-flex;
1067 1177 align-items: center;
1068 1178 justify-content: center;
1069 1179 }
1070 -.desktop-mode-upload-hud__close:hover,
1071 -.desktop-mode-upload-hud__close:focus-visible {
1072 - background: rgba( 0, 0, 0, 0.08 );
1180 +.os-upload-hud__close:hover,
1181 +.os-upload-hud__close:focus-visible {
1182 + background: var( --os-ui-hover, rgba( 0, 0, 0, 0.08 ) );
1073 1183 outline: none;
1074 1184 }
1075 1185
1076 -.desktop-mode-upload-hud__list {
1186 +.os-upload-hud__list {
1077 1187 display: flex;
1078 1188 flex-direction: column;
1079 1189 gap: 10px;
1080 1190 padding: 10px 12px;
@@ -1081,9 +1191,9 @@
1081 1191 max-height: 50vh;
1082 1192 overflow-y: auto;
1083 1193 }
1084 1194
1085 -.desktop-mode-upload-hud__row {
1195 +.os-upload-hud__row {
1086 1196 display: grid;
1087 1197 grid-template-columns: 1fr auto;
1088 1198 grid-template-areas:
1089 1199 'meta actions'
@@ -1092,9 +1202,9 @@
1092 1202 align-items: center;
1093 1203 font-size: 12px;
1094 1204 }
1095 1205
1096 -.desktop-mode-upload-hud__meta {
1206 +.os-upload-hud__meta {
1097 1207 grid-area: meta;
1098 1208 min-width: 0;
1099 1209 display: flex;
1100 1210 flex-direction: column;
@@ -1100,9 +1210,9 @@
1100 1210 flex-direction: column;
1101 1211 gap: 2px;
1102 1212 }
1103 1213
1104 -.desktop-mode-upload-hud__name {
1214 +.os-upload-hud__name {
1105 1215 overflow: hidden;
1106 1216 text-overflow: ellipsis;
1107 1217 white-space: nowrap;
1108 1218 font-weight: 500;
@@ -1107,19 +1217,19 @@
1107 1217 white-space: nowrap;
1108 1218 font-weight: 500;
1109 1219 }
1110 1220
1111 -.desktop-mode-upload-hud__status {
1221 +.os-upload-hud__status {
1112 1222 font-size: 11px;
1113 1223 opacity: 0.75;
1114 1224 font-variant-numeric: tabular-nums;
1115 1225 }
1116 1226
1117 -.desktop-mode-upload-hud__row wpd-progress-bar {
1227 +.os-upload-hud__row os-progress-bar {
1118 1228 grid-area: bar;
1119 1229 }
1120 1230
1121 -.desktop-mode-upload-hud__actions {
1231 +.os-upload-hud__actions {
1122 1232 grid-area: actions;
1123 1233 display: inline-flex;
1124 1234 gap: 4px;
1125 1235 }