PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.8.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.8.7
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / assets / css / os-settings.css

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

1,107 lines 28.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Desktop Mode — OS Settings native panel.
3 *
4 * The preferences panel rendered inside the OS Settings native
5 * window: wallpaper swatches + editor slot, accent colors, dock size,
6 * and the "Or use your own image" uploader + Media Library grid.
7 * Currently shipped alongside the window-chrome stylesheet because
8 * historically the rules lived there; marked for a future move to its
9 * own enqueue handle once the coupling to the shell CSS is removed.
10 *
11 * @since 0.5.0
12 */
13 .desktop-mode-os-settings {
14 font-size: 13px;
15 line-height: 1.5;
16 }
17
18 .desktop-mode-os-settings__intro {
19 margin: 0 0 16px;
20 color: #50575e;
21 }
22
23 /*
24 * Card styling for each OS-Settings section. `<wpd-section>`'s
25 * shadow DOM handles the heading + description; the outer card
26 * (padding, background, border) lives here because it's a
27 * per-surface visual choice, not a property of the component
28 * itself. Targets the tag so shadow-DOM internals stay private.
29 */
30 .desktop-mode-os-settings wpd-section {
31 display: block;
32 margin: 0 0 20px;
33 padding: 16px 16px 18px;
34 background: #f6f7f7;
35 border: 1px solid #dcdcde;
36 border-radius: 8px;
37 }
38
39 /*
40 * Generic grid. Auto-fill + minmax gives us a truly responsive
41 * layout — the panel reflows columns as OS Settings is resized,
42 * rather than locking to a fixed 3-across.
43 */
44 .desktop-mode-os-settings__grid {
45 display: grid;
46 grid-template-columns: repeat( auto-fill, minmax( 140px, 1fr ) );
47 gap: 10px;
48 }
49
50 /*
51 * Wallpaper swatches — each is a `<wpd-swatch variant="wallpaper">`
52 * with its own shadow-DOM button + aspect ratio + selected state.
53 * The only outer knobs we need are: wider minmax for wallpapers
54 * (so each tile shows meaningful preview area), and the 16:9
55 * aspect ratio override. Everything else lives in the component.
56 */
57 .desktop-mode-os-settings__grid--wallpapers {
58 grid-template-columns: repeat( auto-fill, minmax( 160px, 1fr ) );
59 }
60
61 /*
62 * Wallpaper preset label — sits above the gradient/image preview.
63 * Presets span the full tonal range (graphite dark, mono near-
64 * black, aurora blue, sunset hot pink) so neither pure white nor
65 * pure black text is universally readable. A frosted glass chip
66 * behind the text gives the label its own local contrast that's
67 * legible on every preset and respects the tile's visual language
68 * (rounded pill shape matches the selected-state corners).
69 */
70 .desktop-mode-os-settings__swatch-label {
71 display: inline-block;
72 padding: 3px 10px;
73 font-size: 11px;
74 font-weight: 600;
75 color: #fff;
76 background: rgba(0, 0, 0, 0.45);
77 backdrop-filter: blur(8px) saturate(160%);
78 -webkit-backdrop-filter: blur(8px) saturate(160%);
79 border-radius: 999px;
80 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
81 letter-spacing: 0.01em;
82 /* Stop the chip from hugging the swatch edge — the 6 8 padding
83 * on the parent already gives a gap, but this keeps things
84 * consistent if the parent padding is ever tuned. */
85 pointer-events: none;
86 }
87
88 /* Accent swatches now render as `<wpd-swatch>` via `<wpd-swatch-grid>`.
89 * The old `__swatch--accent` / `__swatch-label` / `__segmented` /
90 * `__segment` rules moved into those component shadow-DOM
91 * stylesheets; nothing at host level needs to override them. */
92
93 .desktop-mode-os-settings__footer {
94 display: flex;
95 justify-content: flex-end;
96 margin-top: 12px;
97 }
98
99 /* Top-level tab strip separating Appearance from AI Settings.
100 * Don't add a `display` rule for `wpd-tabpanel` here — the component's
101 * shadow-DOM `:host([hidden]) { display: none }` has lower specificity
102 * than any outer descendant selector, and overriding it from outside
103 * breaks the auto-swap (hidden panels stay visible). The component's
104 * own `:host { display: block }` already handles the shown state. */
105 .desktop-mode-os-settings wpd-tabs {
106 display: block;
107 }
108
109 /* `__reset` now renders as `<wpd-button variant="ghost">`. */
110
111 /* ---------------------------------------------------------------
112 * Custom gradient editor — color inputs + angle slider.
113 *
114 * Toggled by `data-expanded` on the outer wrapper. Uses the modern
115 * grid-template-rows 0fr ↔ 1fr trick so we can animate to natural
116 * content height without hard-coding a max-height. The inner wrapper
117 * owns the visible chrome (padding, border, background) and is
118 * clipped during the transition by overflow: hidden; the outer
119 * wrapper animates the row track + spacing + opacity.
120 *
121 * Supported in all evergreen browsers (Chrome 117+, Safari 17.2+,
122 * Firefox 128+). On older engines the transition is simply skipped —
123 * the editor still appears, just without the glide.
124 * --------------------------------------------------------------- */
125 /*
126 * Editor slot — the per-wallpaper panel that slides in below the
127 * swatch grid when a wallpaper with `renderEditor` is selected.
128 * Collapses via the grid-template-rows 0fr → 1fr trick; TS drives
129 * the `[data-expanded]` attribute on the slot wrapper.
130 */
131 .desktop-mode-os-settings__editor-slot {
132 display: grid;
133 grid-template-rows: 0fr;
134 margin-top: 0;
135 opacity: 0;
136 transition:
137 grid-template-rows 0.25s cubic-bezier(0.2, 0, 0.2, 1),
138 margin-top 0.25s cubic-bezier(0.2, 0, 0.2, 1),
139 opacity 0.2s ease;
140 }
141
142 .desktop-mode-os-settings__editor-slot[data-expanded="true"] {
143 grid-template-rows: 1fr;
144 margin-top: 12px;
145 opacity: 1;
146 }
147
148 .desktop-mode-os-settings__editor-slot-inner {
149 overflow: hidden;
150 min-height: 0;
151 padding: 12px;
152 background: #fff;
153 border: 1px solid #dcdcde;
154 border-radius: 6px;
155 }
156
157 /*
158 * Custom-gradient editor fills the inner slot. Kept as a separate
159 * class (vs. scoping to __editor-slot-inner) so a future plugin
160 * renderEditor can opt out of these per-control rules.
161 */
162 .desktop-mode-os-settings__gradient-editor-inner {
163 display: flex;
164 flex-direction: column;
165 gap: 12px;
166 }
167
168 /*
169 * Gradient editor row — laid out as a flex of `<wpd-color-field>` +
170 * `<wpd-range-field>`. Each field owns its own label / control /
171 * readout styling internally; only spacing between fields lives here.
172 */
173 .desktop-mode-os-settings__gradient-row {
174 display: flex;
175 gap: 16px;
176 }
177
178 .desktop-mode-os-settings__gradient-row wpd-color-field {
179 flex: 1;
180 }
181
182 .desktop-mode-os-settings__gradient-editor-inner wpd-range-field {
183 margin-top: 12px;
184 }
185
186 /* ---------------------------------------------------------------
187 * Image uploader tile.
188 *
189 * Empty state: dashed border, "+" glyph, drop-and-click zone.
190 * Filled state: thumbnail preview that doubles as a wallpaper swatch.
191 * --------------------------------------------------------------- */
192 .desktop-mode-os-settings__uploader {
193 margin-top: 16px;
194 }
195
196 .desktop-mode-os-settings__uploader-heading {
197 margin: 0 0 6px;
198 font-size: 12px;
199 font-weight: 600;
200 color: #50575e;
201 text-transform: uppercase;
202 letter-spacing: 0.04em;
203 }
204
205 .desktop-mode-os-settings__file-input {
206 display: none;
207 }
208
209 .desktop-mode-os-settings__upload-tile {
210 position: relative;
211 display: flex;
212 align-items: center;
213 justify-content: center;
214 min-height: 120px;
215 padding: 16px;
216 background: #fff;
217 background-size: cover;
218 background-position: center;
219 border: 2px dashed #c3c4c7;
220 border-radius: 8px;
221 color: #50575e;
222 cursor: pointer;
223 text-align: center;
224 transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
225 }
226
227 .desktop-mode-os-settings__upload-tile:hover {
228 border-color: var(--wp-admin-theme-color, #2271b1);
229 color: var(--wp-admin-theme-color, #2271b1);
230 }
231
232 .desktop-mode-os-settings__upload-tile:focus-within,
233 .desktop-mode-os-settings__upload-tile:focus-visible {
234 outline: 2px solid var(--wp-admin-theme-color, #2271b1);
235 outline-offset: 2px;
236 }
237
238 .desktop-mode-os-settings__upload-tile--dragover {
239 border-style: solid;
240 border-color: var(--wp-admin-theme-color, #2271b1);
241 background-color: rgba(34, 113, 177, 0.06);
242 transform: scale(1.01);
243 }
244
245 .desktop-mode-os-settings__upload-tile--busy {
246 cursor: progress;
247 opacity: 0.8;
248 }
249
250 /* Filled state — thumbnail is the whole tile; selection uses the same
251 * aria-pressed pattern as the other swatches. */
252 .desktop-mode-os-settings__upload-tile--filled {
253 border-style: solid;
254 border-color: transparent;
255 padding: 0;
256 min-height: 140px;
257 color: transparent;
258 }
259
260 .desktop-mode-os-settings__upload-tile--filled:hover {
261 color: transparent;
262 }
263
264 .desktop-mode-os-settings__upload-tile[aria-pressed="true"] {
265 border-color: var(--wp-admin-theme-color, #2271b1);
266 box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9) inset;
267 }
268
269 .desktop-mode-os-settings__upload-inner {
270 display: flex;
271 flex-direction: column;
272 align-items: center;
273 gap: 6px;
274 pointer-events: none;
275 }
276
277 .desktop-mode-os-settings__upload-plus {
278 display: inline-flex;
279 align-items: center;
280 justify-content: center;
281 width: 32px;
282 height: 32px;
283 border-radius: 50%;
284 background: rgba(0, 0, 0, 0.05);
285 font-size: 22px;
286 font-weight: 300;
287 line-height: 1;
288 color: inherit;
289 }
290
291 .desktop-mode-os-settings__upload-tile:hover .desktop-mode-os-settings__upload-plus {
292 background: rgba(34, 113, 177, 0.12);
293 }
294
295 .desktop-mode-os-settings__upload-prompt {
296 font-size: 13px;
297 font-weight: 500;
298 }
299
300 .desktop-mode-os-settings__upload-hint {
301 font-size: 11px;
302 color: #8c8f94;
303 }
304
305 .desktop-mode-os-settings__upload-status {
306 font-size: 13px;
307 font-weight: 500;
308 color: #50575e;
309 }
310
311 .desktop-mode-os-settings__upload-error {
312 position: absolute;
313 inset-inline: 10px;
314 bottom: 10px;
315 padding: 6px 10px;
316 background: #d63638;
317 color: #fff;
318 border-radius: 4px;
319 font-size: 11px;
320 line-height: 1.3;
321 text-align: start;
322 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
323 }
324
325 /*
326 * `__upload-remove` renders as `<wpd-button variant="danger">`
327 * with component-owned styling. Only the positioning on the
328 * filled tile is caller-specific — stays here.
329 */
330 .desktop-mode-os-settings__upload-remove {
331 position: absolute;
332 top: 8px;
333 inset-inline-end: 8px;
334 }
335
336 /* Tab strip moved into `<wpd-tabs>` + `<wpd-tab>`
337 * (src/ui/components/wpd-tabs/). Only the surrounding pane keeps
338 * a host-level rule — it's an OS-Settings layout choice, not
339 * component-intrinsic. */
340 .desktop-mode-os-settings__tab-pane {
341 min-height: 140px;
342 }
343
344 /* ---------------------------------------------------------------
345 * Media Library picker.
346 *
347 * Toolbar (search + HD toggle), responsive thumbnail grid, footer
348 * with count + Load more button.
349 * --------------------------------------------------------------- */
350 .desktop-mode-os-settings__library-toolbar {
351 display: flex;
352 align-items: center;
353 gap: 12px;
354 margin-bottom: 10px;
355 flex-wrap: wrap;
356 }
357
358 .desktop-mode-os-settings__library-search {
359 flex: 1;
360 min-width: 180px;
361 padding: 6px 10px;
362 border: 1px solid #dcdcde;
363 border-radius: 4px;
364 background: #fff;
365 font: inherit;
366 font-size: 12px;
367 color: #1d2327;
368 }
369
370 .desktop-mode-os-settings__library-search:focus {
371 border-color: var(--wp-admin-theme-color, #2271b1);
372 box-shadow: 0 0 0 1px var(--wp-admin-theme-color, #2271b1);
373 outline: none;
374 }
375
376 /* `__library-hd` now renders as `<wpd-checkbox-label>`. */
377
378 .desktop-mode-os-settings__library-grid {
379 display: grid;
380 grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
381 gap: 8px;
382 min-height: 140px;
383 }
384
385 .desktop-mode-os-settings__library-tile {
386 position: relative;
387 aspect-ratio: 16 / 10;
388 padding: 0;
389 border: 2px solid transparent;
390 border-radius: 6px;
391 background-color: #eef0f1;
392 background-size: cover;
393 background-position: center;
394 background-repeat: no-repeat;
395 cursor: pointer;
396 overflow: hidden;
397 transition: border-color 0.15s ease, transform 0.15s ease;
398 }
399
400 .desktop-mode-os-settings__library-tile:hover {
401 transform: translateY(-1px);
402 border-color: rgba(34, 113, 177, 0.4);
403 }
404
405 .desktop-mode-os-settings__library-tile:focus-visible {
406 outline: 2px solid var(--wp-admin-theme-color, #2271b1);
407 outline-offset: 2px;
408 }
409
410 .desktop-mode-os-settings__library-tile--selected,
411 .desktop-mode-os-settings__library-tile[aria-pressed="true"] {
412 border-color: var(--wp-admin-theme-color, #2271b1);
413 box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9) inset;
414 }
415
416 /* Dimension badge in the corner — readable over any thumbnail. */
417 .desktop-mode-os-settings__library-tile-dims {
418 position: absolute;
419 bottom: 4px;
420 inset-inline-end: 4px;
421 padding: 2px 6px;
422 background: rgba(0, 0, 0, 0.6);
423 color: #fff;
424 font-size: 10px;
425 font-weight: 500;
426 line-height: 1.3;
427 border-radius: 3px;
428 letter-spacing: 0.02em;
429 font-variant-numeric: tabular-nums;
430 pointer-events: none;
431 }
432
433 /* Skeleton tiles during the first-page fetch. Animated subtle pulse
434 * so the user perceives progress without us needing a spinner. */
435 .desktop-mode-os-settings__library-tile--skeleton {
436 cursor: default;
437 background: linear-gradient(
438 90deg,
439 #eef0f1 0%,
440 #dcdcde 50%,
441 #eef0f1 100%
442 );
443 background-size: 200% 100%;
444 animation: desktop-mode-library-shimmer 1.1s ease-in-out infinite;
445 }
446
447 .desktop-mode-os-settings__library-tile--skeleton:hover {
448 transform: none;
449 border-color: transparent;
450 }
451
452 @keyframes desktop-mode-library-shimmer {
453 from {
454 background-position: 200% 0;
455 }
456 to {
457 background-position: -200% 0;
458 }
459 }
460
461 .desktop-mode-os-settings__library-empty,
462 .desktop-mode-os-settings__library-error {
463 grid-column: 1 / -1;
464 margin: 0;
465 padding: 24px 16px;
466 text-align: center;
467 color: #646970;
468 font-size: 12px;
469 }
470
471 .desktop-mode-os-settings__library-error {
472 color: #b32d2e;
473 }
474
475 .desktop-mode-os-settings__library-footer {
476 display: flex;
477 align-items: center;
478 justify-content: space-between;
479 margin-top: 10px;
480 gap: 10px;
481 min-height: 24px;
482 }
483
484 .desktop-mode-os-settings__library-meta {
485 color: #8c8f94;
486 font-size: 11px;
487 }
488
489 /* `__library-load-more` now renders as `<wpd-button variant="ghost">`. */
490
491 /* ---------------------------------------------------------------
492 * Reduced-motion: collapse every OS-Settings animation/transition to
493 * near-zero so motion-sensitive users still get the layout changes
494 * (hover feedback, expanded editor, tile highlights) but without the
495 * glide. Kept as a single scoped block so the intent is readable at
496 * a glance rather than spread across each rule.
497 * --------------------------------------------------------------- */
498 @media (prefers-reduced-motion: reduce) {
499 .desktop-mode-os-settings__editor-slot,
500 .desktop-mode-os-settings__upload-tile,
501 .desktop-mode-os-settings__library-tile,
502 wpd-swatch {
503 transition-duration: 0.01ms !important;
504 }
505
506 .desktop-mode-os-settings__library-tile--skeleton {
507 animation: none;
508 }
509 }
510
511 /* AI settings section — error and saving feedback */
512 .desktop-mode-ai-settings__error {
513 margin: 6px 0 0;
514 font-size: 12px;
515 color: #d63638;
516 }
517
518 .desktop-mode-ai-settings__saving {
519 margin: 6px 0 0;
520 font-size: 12px;
521 color: #646970;
522 font-style: italic;
523 }
524
525 /* Extended options section — hint paragraph + error/saving states */
526 .desktop-mode-ext__hint {
527 margin: 6px 0 0;
528 font-size: 12px;
529 color: #646970;
530 line-height: 1.5;
531 }
532
533 .desktop-mode-ext__error {
534 margin: 6px 0 0;
535 font-size: 12px;
536 color: #d63638;
537 }
538
539 .desktop-mode-ext__saving {
540 margin: 6px 0 0;
541 font-size: 12px;
542 color: #646970;
543 font-style: italic;
544 }
545
546 /*
547 * Help tab — developer-facing component reference. Two-column layout
548 * (nav + detail) on desktop, stacks naturally on narrow windows via
549 * the same container the OS Settings panel lives in.
550 */
551 .desktop-mode-os-settings__help-count {
552 margin: 4px 0 0;
553 font-size: 12px;
554 color: #646970;
555 }
556
557 .desktop-mode-os-settings__help-layout {
558 display: grid;
559 grid-template-columns: 200px 1fr;
560 gap: 20px;
561 }
562
563 @container (max-width: 640px) {
564 .desktop-mode-os-settings__help-layout {
565 grid-template-columns: 1fr;
566 }
567 }
568
569 .desktop-mode-os-settings__help-nav {
570 display: flex;
571 flex-direction: column;
572 gap: 2px;
573 align-self: stretch;
574 overflow-y: auto;
575 padding: 4px;
576 background: #f6f7f7;
577 border: 1px solid #dcdcde;
578 border-radius: 8px;
579 }
580
581 .desktop-mode-os-settings__help-nav-item {
582 display: flex;
583 flex-direction: column;
584 gap: 2px;
585 align-items: flex-start;
586 width: 100%;
587 padding: 6px 10px;
588 background: transparent;
589 border: 1px solid transparent;
590 border-radius: 6px;
591 text-align: start;
592 cursor: pointer;
593 font: inherit;
594 color: inherit;
595 }
596
597 .desktop-mode-os-settings__help-nav-item:hover {
598 background: #fff;
599 border-color: #dcdcde;
600 }
601
602 .desktop-mode-os-settings__help-nav-item.is-active {
603 background: #fff;
604 border-color: var( --wp-admin-theme-color, #2271b1 );
605 }
606
607 .desktop-mode-os-settings__help-nav-title {
608 font-weight: 600;
609 font-size: 13px;
610 }
611
612 .desktop-mode-os-settings__help-nav-tag {
613 font-family: Menlo, Consolas, monospace;
614 font-size: 11px;
615 color: #646970;
616 }
617
618 .desktop-mode-os-settings__help-detail {
619 min-width: 0;
620 padding: 16px 18px 18px;
621 background: #f6f7f7;
622 border: 1px solid #dcdcde;
623 border-radius: 8px;
624 }
625
626 .desktop-mode-os-settings__help-head {
627 display: flex;
628 flex-wrap: wrap;
629 align-items: baseline;
630 gap: 8px;
631 margin: 0 0 8px;
632 }
633
634 .desktop-mode-os-settings__help-title {
635 margin: 0;
636 font-size: 16px;
637 font-weight: 600;
638 }
639
640 .desktop-mode-os-settings__help-code {
641 font-family: Menlo, Consolas, monospace;
642 font-size: 12px;
643 color: #646970;
644 }
645
646 .desktop-mode-os-settings__help-badge {
647 padding: 1px 8px;
648 border-radius: 999px;
649 font-size: 11px;
650 font-weight: 600;
651 text-transform: uppercase;
652 letter-spacing: 0.04em;
653 background: #e4e7eb;
654 color: #1d2327;
655 }
656
657 .desktop-mode-os-settings__help-badge.is-experimental {
658 background: #fcf9e8;
659 color: #996800;
660 }
661
662 .desktop-mode-os-settings__help-badge.is-planned {
663 background: #e1e8ff;
664 color: #123fb3;
665 }
666
667 .desktop-mode-os-settings__help-since {
668 font-size: 11px;
669 color: #646970;
670 }
671
672 .desktop-mode-os-settings__help-summary {
673 margin: 0 0 16px;
674 color: #1d2327;
675 }
676
677 .desktop-mode-os-settings__help-group {
678 margin: 0 0 16px;
679 }
680
681 .desktop-mode-os-settings__help-group h4 {
682 margin: 0 0 8px;
683 font-size: 12px;
684 font-weight: 600;
685 text-transform: uppercase;
686 letter-spacing: 0.04em;
687 color: #646970;
688 }
689
690 .desktop-mode-os-settings__help-example {
691 padding: 16px;
692 background: #fff;
693 border: 1px dashed #c3c4c7;
694 border-radius: 6px;
695 }
696
697 .desktop-mode-os-settings__help-table {
698 width: 100%;
699 border-collapse: collapse;
700 font-size: 12px;
701 }
702
703 .desktop-mode-os-settings__help-table th,
704 .desktop-mode-os-settings__help-table td {
705 padding: 6px 8px;
706 border-bottom: 1px solid #dcdcde;
707 text-align: start;
708 vertical-align: top;
709 }
710
711 .desktop-mode-os-settings__help-table th {
712 font-weight: 600;
713 color: #646970;
714 background: #fff;
715 }
716
717 .desktop-mode-os-settings__help-table code,
718 .desktop-mode-os-settings__help-list code {
719 font-family: Menlo, Consolas, monospace;
720 font-size: 11px;
721 padding: 1px 4px;
722 background: #fff;
723 border: 1px solid #dcdcde;
724 border-radius: 3px;
725 }
726
727 .desktop-mode-os-settings__help-list {
728 margin: 0;
729 padding: 0;
730 list-style: none;
731 }
732
733 .desktop-mode-os-settings__help-list li {
734 padding: 4px 0;
735 border-bottom: 1px solid #ececee;
736 font-size: 12px;
737 }
738
739 .desktop-mode-os-settings__help-list li:last-child {
740 border-bottom: 0;
741 }
742
743 .desktop-mode-os-settings__help-note {
744 margin: 12px 0 0;
745 padding: 10px 12px;
746 background: #fcf9e8;
747 border: 1px solid #f5e6a5;
748 border-radius: 6px;
749 font-size: 12px;
750 color: #996800;
751 }
752
753 /* ---------------------------------------------------------------
754 * Window-fit scrolling.
755 *
756 * Without this block the OS Settings panel grows past the native
757 * window's viewport and the whole body scrolls — fine for short tabs
758 * but actively wrong for the Components tab, where a 200px nav rail
759 * and a tall detail pane should each scroll inside their own column,
760 * not in lockstep down a giant single-column page.
761 *
762 * Strategy:
763 * 1. The native window body normally has overflow:auto. Override it
764 * to `hidden` ONLY when the OS Settings root is mounted (via
765 * :has()) so other native windows keep their default page-scroll.
766 * 2. Make the OS Settings root a flex column that fills the body.
767 * The tab strip + footer hug the top/bottom; the active
768 * tabpanel takes the remaining height.
769 * 3. Each tabpanel scrolls itself (overflow:auto) — replaces the
770 * whole-body scroll, visually identical for the simple tabs
771 * (Appearance, AI, Extended) but pins the reset footer.
772 * 4. The Components tab opts OUT of overflow:auto so its inner
773 * grid (nav + detail) can scroll independently per pane.
774 *
775 * `:has()` is supported in every evergreen engine (Chrome 105+,
776 * Safari 15.4+, Firefox 121+); fine for an admin-context shell.
777 * --------------------------------------------------------------- */
778 .desktop-mode-window__body--native:has( .desktop-mode-os-settings ) {
779 overflow: hidden;
780 display: flex;
781 flex-direction: column;
782 }
783
784 .desktop-mode-os-settings {
785 flex: 1;
786 min-height: 0;
787 display: flex;
788 flex-direction: column;
789 }
790
791 /* The tab strip + footer must NOT shrink when the active panel
792 wants more height. Marking them flex-shrink:0 also keeps them
793 pinned visually as the panel below scrolls. */
794 .desktop-mode-os-settings > wpd-tabs,
795 .desktop-mode-os-settings > .desktop-mode-os-settings__footer {
796 flex: 0 0 auto;
797 }
798
799 .desktop-mode-os-settings > wpd-tabpanel {
800 flex: 1;
801 min-height: 0;
802 overflow: auto;
803 }
804
805 /* Components tab — split-pane layout. Don't scroll the whole panel;
806 let the inner grid distribute height to nav + detail, each with
807 their own overflow.
808 *
809 * The `:not([hidden])` guard is load-bearing: a bare
810 * `wpd-tabpanel[for='help'] { display: flex }` rule has specificity
811 * (0,0,2,1) which outranks the shadow-DOM `:host([hidden]) {
812 * display: none }` rule (specificity 0,0,1,0), so the panel would
813 * keep rendering when it should be hidden — bleeding the Components
814 * UI into every other tab. Pinning the rule to the visible state
815 * leaves the hidden state to the shadow-DOM default. */
816 .desktop-mode-os-settings > wpd-tabpanel[ for='help' ]:not( [ hidden ] ) {
817 overflow: hidden;
818 display: flex;
819 flex-direction: column;
820 }
821 .desktop-mode-os-settings
822 > wpd-tabpanel[ for='help' ]:not( [ hidden ] )
823 > wpd-panel {
824 flex: 1;
825 min-height: 0;
826 display: flex;
827 flex-direction: column;
828 }
829
830 .desktop-mode-os-settings__help {
831 flex: 1;
832 min-height: 0;
833 display: flex;
834 flex-direction: column;
835 gap: 12px;
836 }
837 /* The intro card (Component library description) shouldn't grow —
838 it's content-sized so the grid below gets the remaining height. */
839 .desktop-mode-os-settings__help > wpd-section {
840 flex: 0 0 auto;
841 }
842 .desktop-mode-os-settings__help-layout {
843 flex: 1;
844 min-height: 0;
845 }
846
847 /* Each split pane is its own scroll container. min-height:0 unlocks
848 shrinking in a grid item (default is `auto` which equals content
849 height — would defeat the overflow). max-height:100% pins the
850 pane to the grid track. */
851 .desktop-mode-os-settings__help-nav,
852 .desktop-mode-os-settings__help-detail {
853 min-height: 0;
854 max-height: 100%;
855 overflow-y: auto;
856 }
857
858 /*
859 * Features section — per-user opt-in toggles.
860 *
861 * Save status pill renders the live OS-settings save lifecycle
862 * (`pending` → `saving` → `saved` / `failed`) so toggling a
863 * per-user feature flag gets immediate, honest feedback instead
864 * of an optimistic flicker.
865 */
866 .desktop-mode-features__status-row {
867 min-height: 22px;
868 }
869
870 /* One toggle + hint pair. Stacking these without separators reads as
871 * a wall of checkboxes; a hairline above each item (after the first)
872 * groups the label with its description and gives the eye a stopping
873 * point between unrelated settings. The first item sits flush so the
874 * section heading already serves as its top boundary. */
875 .desktop-mode-features__item {
876 display: flex;
877 flex-direction: column;
878 gap: 6px;
879 }
880
881 .desktop-mode-features__item + .desktop-mode-features__item {
882 margin-top: 16px;
883 padding-top: 16px;
884 border-top: 1px solid var( --wpd-border, rgba( 0, 0, 0, 0.08 ) );
885 }
886
887 /* "Reset what's-new dialogs" row — separates the action from the
888 * native-Posts toggle above and gives breathing room around the
889 * button. `align-items: flex-start` keeps the inline-flex
890 * `<wpd-button>` from stretching full-width inside the column,
891 * and the explicit margin on the button below works around the
892 * fact that flex `gap` doesn't always render visibly when one
893 * neighbour is an inline-flex custom element with zero outer
894 * margin. */
895 .desktop-mode-features__row {
896 display: flex;
897 flex-direction: column;
898 align-items: flex-start;
899 gap: 12px;
900 margin-top: 20px;
901 padding-top: 20px;
902 border-top: 1px solid var( --wpd-border, rgba( 0, 0, 0, 0.08 ) );
903 }
904
905 .desktop-mode-features__row > wpd-button {
906 display: inline-flex;
907 margin-block: 4px 8px;
908 }
909
910 .desktop-mode-features__row > .desktop-mode-features__hint {
911 margin: 0;
912 }
913
914 .desktop-mode-features__hint {
915 margin: 0;
916 font-size: 12px;
917 color: var( --wpd-text-muted, #50575e );
918 line-height: 1.5;
919 }
920
921 .desktop-mode-features__status {
922 display: inline-flex;
923 align-items: center;
924 gap: 6px;
925 font-size: 12px;
926 font-weight: 500;
927 padding: 2px 10px;
928 border-radius: 999px;
929 white-space: nowrap;
930 transition: opacity 200ms ease;
931 }
932
933 .desktop-mode-features__status--saving {
934 background: var( --wpd-surface-elevated, #f0f0f1 );
935 color: var( --wpd-text-muted, #50575e );
936 }
937
938 .desktop-mode-features__status--saved {
939 background: rgba( 30, 132, 73, 0.12 );
940 color: #1d6f42;
941 }
942
943 .desktop-mode-features__status--saved .dashicons {
944 font-size: 14px;
945 width: 14px;
946 height: 14px;
947 }
948
949 .desktop-mode-features__status--failed {
950 background: rgba( 214, 54, 56, 0.12 );
951 color: #a02622;
952 }
953
954 .desktop-mode-features__status--failed .dashicons {
955 font-size: 14px;
956 width: 14px;
957 height: 14px;
958 }
959
960 .desktop-mode-features__status-dot {
961 display: inline-block;
962 width: 8px;
963 height: 8px;
964 border-radius: 50%;
965 background: currentColor;
966 animation: desktop-mode-features-pulse 1.2s ease-in-out infinite;
967 }
968
969 @keyframes desktop-mode-features-pulse {
970 0%, 100% { opacity: 0.35; transform: scale( 0.85 ); }
971 50% { opacity: 1; transform: scale( 1 ); }
972 }
973
974 /*
975 * OS Settings panel header — hosts the global tabs strip and a single
976 * `<wpd-save-status>` indicator that auto-listens to the save
977 * lifecycle. The save status sits on the trailing edge of the header
978 * so it's visible across every tab without competing with section
979 * content.
980 */
981 .desktop-mode-os-settings__header {
982 display: flex;
983 align-items: center;
984 justify-content: space-between;
985 gap: 16px;
986 margin-bottom: 12px;
987 }
988
989 .desktop-mode-os-settings__header wpd-tabs {
990 flex: 1 1 auto;
991 min-width: 0;
992 }
993
994 .desktop-mode-os-settings__header wpd-save-status {
995 flex: 0 0 auto;
996 margin-inline-start: auto;
997 }
998
999 /*
1000 * About tab — full-tabpanel PixiJS canvas. Every credit string and
1001 * the AUTOMATTIC logotype (formed by particles) render inside Pixi
1002 * itself; this stylesheet only sizes the host so the canvas fills
1003 * the available height + width without any HTML chrome around it.
1004 *
1005 * The chain is: tabpanel → wpd-panel → .about (flex column) →
1006 * .about-stage-host (the canvas mounts here). Each link sets
1007 * `flex: 1; min-height: 0;` so the canvas inherits the tabpanel's
1008 * full inner height instead of hugging an intrinsic minimum.
1009 *
1010 * @since 0.8.0
1011 */
1012 .desktop-mode-os-settings
1013 > wpd-tabpanel[ for='about' ]:not( [ hidden ] ) {
1014 display: flex;
1015 flex-direction: column;
1016 overflow: hidden;
1017 }
1018
1019 .desktop-mode-os-settings
1020 > wpd-tabpanel[ for='about' ]:not( [ hidden ] )
1021 > wpd-panel {
1022 flex: 1;
1023 min-height: 0;
1024 display: flex;
1025 flex-direction: column;
1026 }
1027
1028 .desktop-mode-os-settings__about {
1029 flex: 1;
1030 min-height: 0;
1031 display: flex;
1032 flex-direction: column;
1033 margin: 0;
1034 /* Edge-to-edge — no border-radius / box-shadow framing — so the
1035 * canvas (and the logotype centred inside it) consume the entire
1036 * tabpanel surface. The gradient is just the colour-of-last-resort
1037 * underneath while Pixi initialises; once the canvas is rendering
1038 * it covers every pixel of this element anyway. */
1039 background: linear-gradient( 180deg, #060a1c 0%, #0c1733 100% );
1040 overflow: hidden;
1041 }
1042
1043 .desktop-mode-os-settings__about-stage-host {
1044 flex: 1;
1045 min-height: 0;
1046 width: 100%;
1047 overflow: hidden;
1048 cursor: crosshair;
1049 }
1050
1051 .desktop-mode-os-settings__about-stage-host canvas {
1052 display: block;
1053 width: 100%;
1054 height: 100%;
1055 }
1056
1057
1058 /* Apps & Icons section (since 0.25.0) — per-item placement chooser. */
1059 .desktop-mode-apps-icons__list {
1060 display: flex;
1061 flex-direction: column;
1062 gap: 4px;
1063 }
1064
1065 .desktop-mode-apps-icons__row {
1066 display: flex;
1067 align-items: center;
1068 gap: 12px;
1069 padding: 8px 4px;
1070 border-bottom: 1px solid var( --desktop-mode-border, rgba( 0, 0, 0, 0.08 ) );
1071 }
1072
1073 .desktop-mode-apps-icons__row:last-child {
1074 border-bottom: 0;
1075 }
1076
1077 .desktop-mode-apps-icons__identity {
1078 display: flex;
1079 align-items: center;
1080 gap: 10px;
1081 flex: 1;
1082 min-width: 0;
1083 }
1084
1085 .desktop-mode-apps-icons__icon {
1086 width: 24px;
1087 height: 24px;
1088 flex-shrink: 0;
1089 display: inline-flex;
1090 align-items: center;
1091 justify-content: center;
1092 }
1093
1094 .desktop-mode-apps-icons__icon.dashicons {
1095 font-size: 20px;
1096 }
1097
1098 .desktop-mode-apps-icons__title {
1099 overflow: hidden;
1100 text-overflow: ellipsis;
1101 white-space: nowrap;
1102 }
1103
1104 .desktop-mode-apps-icons__row wpd-select {
1105 min-width: 180px;
1106 }
1107