PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.0.1
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.0.1
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 1.0.1, at assets/css/os-settings.css

1,445 lines 36.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * OpenStation — 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 .os-settings {
14 font-size: 13px;
15 line-height: 1.5;
16 }
17
18 .os-settings__intro {
19 margin: 0 0 16px;
20 color: var( --os-ui-fg-muted, #50575e );
21 }
22
23 /*
24 * Card styling for each OS-Settings section. `<os-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 .os-settings os-section {
31 display: block;
32 margin: 0 0 20px;
33 padding: 16px 16px 18px;
34 background: var( --os-ui-surface-elevated, #f6f7f7 );
35 border: 1px solid var( --os-ui-border, #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 .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 `<os-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 .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 .os-settings__swatch-label {
71 display: inline-block;
72 /* Sit above the live-preview overlay (z-index 0) — the caption
73 * must stay readable over a moving canvas. */
74 position: relative;
75 z-index: 1;
76 padding: 3px 10px;
77 font-size: 11px;
78 font-weight: 600;
79 color: var( --os-ui-fg-on-accent, #fff );
80 background: var( --os-ui-scrim, rgba(0, 0, 0, 0.45) );
81 backdrop-filter: blur(8px) saturate(160%);
82 -webkit-backdrop-filter: blur(8px) saturate(160%);
83 border-radius: 999px;
84 text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
85 letter-spacing: 0.01em;
86 /* Stop the chip from hugging the swatch edge — the 6 8 padding
87 * on the parent already gives a gap, but this keeps things
88 * consistent if the parent padding is ever tuned. */
89 pointer-events: none;
90 }
91
92 /*
93 * Live wallpaper preview — an overlay div slotted into a wallpaper
94 * `<os-swatch>` by the preview manager (see
95 * settings/sections/wallpaper-previews.ts). Fills the tile button
96 * (which is position: relative in the component's shadow styles) and
97 * paints between the CSS `preview` background and the label chip.
98 * pointer-events: none so the tile button keeps receiving the click.
99 */
100 .os-settings__wallpaper-live-preview {
101 position: absolute;
102 inset: 0;
103 z-index: 0;
104 overflow: hidden;
105 pointer-events: none;
106 /* Match the swatch button's rounding so canvas corners don't
107 * poke out of the tile. */
108 border-radius: 8px;
109 }
110
111 .os-settings__wallpaper-live-preview canvas {
112 display: block;
113 width: 100%;
114 height: 100%;
115 }
116
117 /* Accent swatches now render as `<os-swatch>` via `<os-swatch-grid>`.
118 * The old `__swatch--accent` / `__swatch-label` / `__segmented` /
119 * `__segment` rules moved into those component shadow-DOM
120 * stylesheets; nothing at host level needs to override them. */
121
122 .os-settings__footer {
123 display: flex;
124 justify-content: flex-end;
125 margin-top: 12px;
126 }
127
128 /* Top-level tab strip separating Appearance from AI Settings.
129 * Don't add a `display` rule for `os-tabpanel` here — the component's
130 * shadow-DOM `:host([hidden]) { display: none }` has lower specificity
131 * than any outer descendant selector, and overriding it from outside
132 * breaks the auto-swap (hidden panels stay visible). The component's
133 * own `:host { display: block }` already handles the shown state. */
134 .os-settings os-tabs {
135 display: block;
136 }
137
138 /* `__reset` now renders as `<os-button variant="ghost">`. */
139
140 /* ---------------------------------------------------------------
141 * Custom gradient editor — color inputs + angle slider.
142 *
143 * Toggled by `data-expanded` on the outer wrapper. Uses the modern
144 * grid-template-rows 0fr ↔ 1fr trick so we can animate to natural
145 * content height without hard-coding a max-height. The inner wrapper
146 * owns the visible chrome (padding, border, background) and is
147 * clipped during the transition by overflow: hidden; the outer
148 * wrapper animates the row track + spacing + opacity.
149 *
150 * Supported in all evergreen browsers (Chrome 117+, Safari 17.2+,
151 * Firefox 128+). On older engines the transition is simply skipped —
152 * the editor still appears, just without the glide.
153 * --------------------------------------------------------------- */
154 /*
155 * Editor slot — the per-wallpaper panel that slides in below the
156 * swatch grid when a wallpaper with `renderEditor` is selected.
157 * Collapses via the grid-template-rows 0fr → 1fr trick; TS drives
158 * the `[data-expanded]` attribute on the slot wrapper.
159 */
160 .os-settings__editor-slot {
161 display: grid;
162 grid-template-rows: 0fr;
163 margin-top: 0;
164 opacity: 0;
165 transition:
166 grid-template-rows 0.25s cubic-bezier(0.2, 0, 0.2, 1),
167 margin-top 0.25s cubic-bezier(0.2, 0, 0.2, 1),
168 opacity 0.2s ease;
169 }
170
171 .os-settings__editor-slot[data-expanded="true"] {
172 grid-template-rows: 1fr;
173 margin-top: 12px;
174 opacity: 1;
175 }
176
177 .os-settings__editor-slot-inner {
178 overflow: hidden;
179 min-height: 0;
180 padding: 12px;
181 background: var( --os-ui-surface, #fff );
182 border: 1px solid var( --os-ui-border, #dcdcde );
183 border-radius: 6px;
184 }
185
186 /*
187 * Wallpaper description slot — a card that slides in below the swatch
188 * grid describing the ACTIVE wallpaper (its story, where its data comes
189 * from). Same 0fr → 1fr collapse as the editor slot; TS drives
190 * `[data-expanded]` from `syncWallpaperDescription()`.
191 */
192 .os-settings__wallpaper-description-slot {
193 display: grid;
194 grid-template-rows: 0fr;
195 margin-top: 0;
196 opacity: 0;
197 transition:
198 grid-template-rows 0.25s cubic-bezier(0.2, 0, 0.2, 1),
199 margin-top 0.25s cubic-bezier(0.2, 0, 0.2, 1),
200 opacity 0.2s ease;
201 }
202
203 .os-settings__wallpaper-description-slot[data-expanded="true"] {
204 grid-template-rows: 1fr;
205 margin-top: 12px;
206 opacity: 1;
207 }
208
209 .os-settings__wallpaper-description-slot-inner {
210 overflow: hidden;
211 min-height: 0;
212 }
213
214 .os-settings__wallpaper-description {
215 padding: 12px 14px;
216 background: color-mix(in srgb, var(--os-ui-accent, #2271b1) 6%, var( --os-ui-surface, #fff ));
217 border: 1px solid var( --os-ui-border, #dcdcde );
218 border-inline-start: 3px solid var(--os-ui-accent, #2271b1);
219 border-radius: 6px;
220 }
221
222 /* Glyph sits inline, right before the wallpaper's name. */
223 .os-settings__wallpaper-description-header {
224 display: flex;
225 align-items: center;
226 gap: 7px;
227 margin-bottom: 4px;
228 }
229
230 .os-settings__wallpaper-description-icon {
231 color: var(--os-ui-accent, #2271b1);
232 flex-shrink: 0;
233 }
234
235 .os-settings__wallpaper-description-header strong {
236 font-size: 13px;
237 color: var( --os-ui-fg, #1d2327 );
238 }
239
240 .os-settings__wallpaper-description p {
241 margin: 0;
242 font-size: 12px;
243 line-height: 1.55;
244 color: var( --os-ui-fg-muted, #50575e );
245 }
246
247 /*
248 * Wallpaper config slot — hosts the "Wallpaper settings" button when
249 * the ACTIVE wallpaper ships a `renderConfig` dialog. Same 0fr → 1fr
250 * collapse as the slots above; TS drives `[data-expanded]` from
251 * `syncWallpaperConfigButton()`.
252 */
253 .os-settings__wallpaper-config-slot {
254 display: grid;
255 grid-template-rows: 0fr;
256 margin-top: 0;
257 opacity: 0;
258 transition:
259 grid-template-rows 0.25s cubic-bezier(0.2, 0, 0.2, 1),
260 margin-top 0.25s cubic-bezier(0.2, 0, 0.2, 1),
261 opacity 0.2s ease;
262 }
263
264 .os-settings__wallpaper-config-slot[data-expanded="true"] {
265 grid-template-rows: 1fr;
266 margin-top: 12px;
267 opacity: 1;
268 }
269
270 .os-settings__wallpaper-config-slot-inner {
271 overflow: hidden;
272 min-height: 0;
273 }
274
275 .os-settings__wallpaper-config os-icon {
276 margin-inline-end: 4px;
277 }
278
279 /*
280 * Wallpaper config dialog body — the container `renderConfig` renders
281 * into inside the `<os-modal>`. A simple vertical rhythm; each
282 * `<os-*>` field owns its own label / control styling. The critical
283 * column rules are ALSO inlined by the dialog opener (long-lived
284 * sessions can lazy-load the dialog with a stylesheet that predates
285 * this block); keep the two in sync.
286 */
287 .os-settings__wallpaper-config-form {
288 display: flex;
289 flex-direction: column;
290 gap: 14px;
291 min-width: 0;
292 }
293
294 /* Bare buttons in a config form (Reset to defaults and friends) are
295 * actions, not fields — don't stretch them to the column width. */
296 .os-settings__wallpaper-config-form > os-button {
297 align-self: flex-start;
298 }
299
300 /*
301 * Custom-gradient editor fills the inner slot. Kept as a separate
302 * class (vs. scoping to __editor-slot-inner) so a future plugin
303 * renderEditor can opt out of these per-control rules.
304 */
305 .os-settings__gradient-editor-inner {
306 display: flex;
307 flex-direction: column;
308 gap: 12px;
309 }
310
311 /*
312 * Gradient editor row — laid out as a flex of `<os-color-field>` +
313 * `<os-range-field>`. Each field owns its own label / control /
314 * readout styling internally; only spacing between fields lives here.
315 */
316 .os-settings__gradient-row {
317 display: flex;
318 gap: 16px;
319 }
320
321 .os-settings__gradient-row os-color-field {
322 flex: 1;
323 }
324
325 .os-settings__gradient-editor-inner os-range-field {
326 margin-top: 12px;
327 }
328
329 /* ---------------------------------------------------------------
330 * Image uploader tile.
331 *
332 * Empty state: dashed border, "+" glyph, drop-and-click zone.
333 * Filled state: thumbnail preview that doubles as a wallpaper swatch.
334 * --------------------------------------------------------------- */
335 .os-settings__uploader {
336 margin-top: 16px;
337 }
338
339 .os-settings__uploader-heading {
340 margin: 0 0 6px;
341 font-size: 12px;
342 font-weight: 600;
343 color: var( --os-ui-fg-muted, #50575e );
344 text-transform: uppercase;
345 letter-spacing: 0.04em;
346 }
347
348 .os-settings__file-input {
349 display: none;
350 }
351
352 .os-settings__upload-tile {
353 position: relative;
354 display: flex;
355 align-items: center;
356 justify-content: center;
357 min-height: 120px;
358 padding: 16px;
359 background: var( --os-ui-surface, #fff );
360 background-size: cover;
361 background-position: center;
362 border: 2px dashed var( --os-ui-border, #c3c4c7 );
363 border-radius: 8px;
364 color: var( --os-ui-fg-muted, #50575e );
365 cursor: pointer;
366 text-align: center;
367 transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
368 }
369
370 .os-settings__upload-tile:hover {
371 border-color: var(--wp-admin-theme-color, #2271b1);
372 color: var(--wp-admin-theme-color, #2271b1);
373 }
374
375 .os-settings__upload-tile:focus-within,
376 .os-settings__upload-tile:focus-visible {
377 outline: 2px solid var(--wp-admin-theme-color, #2271b1);
378 outline-offset: 2px;
379 }
380
381 .os-settings__upload-tile--dragover {
382 border-style: solid;
383 border-color: var(--wp-admin-theme-color, #2271b1);
384 background-color: rgba(34, 113, 177, 0.06);
385 transform: scale(1.01);
386 }
387
388 .os-settings__upload-tile--busy {
389 cursor: progress;
390 opacity: 0.8;
391 }
392
393 /* Filled state — thumbnail is the whole tile; selection uses the same
394 * aria-pressed pattern as the other swatches. */
395 .os-settings__upload-tile--filled {
396 border-style: solid;
397 border-color: transparent;
398 padding: 0;
399 min-height: 140px;
400 color: transparent;
401 }
402
403 .os-settings__upload-tile--filled:hover {
404 color: transparent;
405 }
406
407 .os-settings__upload-tile[aria-pressed="true"] {
408 border-color: var(--wp-admin-theme-color, #2271b1);
409 box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9) inset;
410 }
411
412 .os-settings__upload-inner {
413 display: flex;
414 flex-direction: column;
415 align-items: center;
416 gap: 6px;
417 pointer-events: none;
418 }
419
420 .os-settings__upload-plus {
421 display: inline-flex;
422 align-items: center;
423 justify-content: center;
424 width: 32px;
425 height: 32px;
426 border-radius: 50%;
427 background: var( --os-ui-hover, rgba(0, 0, 0, 0.05) );
428 font-size: 22px;
429 font-weight: 300;
430 line-height: 1;
431 color: inherit;
432 }
433
434 .os-settings__upload-tile:hover .os-settings__upload-plus {
435 background: rgba(34, 113, 177, 0.12);
436 }
437
438 .os-settings__upload-prompt {
439 font-size: 13px;
440 font-weight: 500;
441 }
442
443 .os-settings__upload-hint {
444 font-size: 11px;
445 color: var( --os-ui-fg-muted, #8c8f94 );
446 }
447
448 .os-settings__upload-status {
449 font-size: 13px;
450 font-weight: 500;
451 color: var( --os-ui-fg-muted, #50575e );
452 }
453
454 .os-settings__upload-error {
455 position: absolute;
456 inset-inline: 10px;
457 bottom: 10px;
458 padding: 6px 10px;
459 background: var( --os-ui-danger, #d63638 );
460 color: var( --os-ui-fg-on-accent, #fff );
461 border-radius: 4px;
462 font-size: 11px;
463 line-height: 1.3;
464 text-align: start;
465 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
466 }
467
468 /*
469 * `__upload-remove` renders as `<os-button variant="danger">`
470 * with component-owned styling. Only the positioning on the
471 * filled tile is caller-specific — stays here.
472 */
473 .os-settings__upload-remove {
474 position: absolute;
475 top: 8px;
476 inset-inline-end: 8px;
477 }
478
479 /* Tab strip moved into `<os-tabs>` + `<os-tab>`
480 * (src/ui/components/os-tabs/). Only the surrounding pane keeps
481 * a host-level rule — it's an OS-Settings layout choice, not
482 * component-intrinsic. */
483 .os-settings__tab-pane {
484 min-height: 140px;
485 }
486
487 /* ---------------------------------------------------------------
488 * Media Library picker.
489 *
490 * Toolbar (search + HD toggle), responsive thumbnail grid, footer
491 * with count + Load more button.
492 * --------------------------------------------------------------- */
493 .os-settings__library-toolbar {
494 display: flex;
495 align-items: center;
496 gap: 12px;
497 margin-bottom: 10px;
498 flex-wrap: wrap;
499 }
500
501 .os-settings__library-search {
502 flex: 1;
503 min-width: 180px;
504 padding: 6px 10px;
505 border: 1px solid var( --os-ui-border, #dcdcde );
506 border-radius: 4px;
507 background: var( --os-ui-surface, #fff );
508 font: inherit;
509 font-size: 12px;
510 color: var( --os-ui-fg, #1d2327 );
511 }
512
513 .os-settings__library-search:focus {
514 border-color: var(--wp-admin-theme-color, #2271b1);
515 box-shadow: 0 0 0 1px var(--wp-admin-theme-color, #2271b1);
516 outline: none;
517 }
518
519 /* `__library-hd` now renders as `<os-checkbox-label>`. */
520
521 .os-settings__library-grid {
522 display: grid;
523 grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
524 gap: 8px;
525 min-height: 140px;
526 }
527
528 .os-settings__library-tile {
529 position: relative;
530 aspect-ratio: 16 / 10;
531 padding: 0;
532 border: 2px solid transparent;
533 border-radius: 6px;
534 background-color: var( --os-ui-surface-sunken, #eef0f1 );
535 background-size: cover;
536 background-position: center;
537 background-repeat: no-repeat;
538 cursor: pointer;
539 overflow: hidden;
540 transition: border-color 0.15s ease, transform 0.15s ease;
541 }
542
543 .os-settings__library-tile:hover {
544 transform: translateY(-1px);
545 border-color: rgba(34, 113, 177, 0.4);
546 }
547
548 .os-settings__library-tile:focus-visible {
549 outline: 2px solid var(--wp-admin-theme-color, #2271b1);
550 outline-offset: 2px;
551 }
552
553 .os-settings__library-tile--selected,
554 .os-settings__library-tile[aria-pressed="true"] {
555 border-color: var(--wp-admin-theme-color, #2271b1);
556 box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9) inset;
557 }
558
559 /* Dimension badge in the corner — readable over any thumbnail. */
560 .os-settings__library-tile-dims {
561 position: absolute;
562 bottom: 4px;
563 inset-inline-end: 4px;
564 padding: 2px 6px;
565 background: var( --os-ui-scrim, rgba(0, 0, 0, 0.6) );
566 color: var( --os-ui-fg-on-accent, #fff );
567 font-size: 10px;
568 font-weight: 500;
569 line-height: 1.3;
570 border-radius: 3px;
571 letter-spacing: 0.02em;
572 font-variant-numeric: tabular-nums;
573 pointer-events: none;
574 }
575
576 /* Skeleton tiles during the first-page fetch. Animated subtle pulse
577 * so the user perceives progress without us needing a spinner. */
578 .os-settings__library-tile--skeleton {
579 cursor: default;
580 background: linear-gradient(
581 90deg,
582 var( --os-ui-surface-sunken, #eef0f1 ) 0%,
583 #dcdcde 50%,
584 var( --os-ui-surface-sunken, #eef0f1 ) 100%
585 );
586 background-size: 200% 100%;
587 animation: os-library-shimmer 1.1s ease-in-out infinite;
588 }
589
590 .os-settings__library-tile--skeleton:hover {
591 transform: none;
592 border-color: transparent;
593 }
594
595 @keyframes os-library-shimmer {
596 from {
597 background-position: 200% 0;
598 }
599 to {
600 background-position: -200% 0;
601 }
602 }
603
604 .os-settings__library-empty,
605 .os-settings__library-error {
606 grid-column: 1 / -1;
607 margin: 0;
608 padding: 24px 16px;
609 text-align: center;
610 color: var( --os-ui-fg-muted, #646970 );
611 font-size: 12px;
612 }
613
614 .os-settings__library-error {
615 color: var( --os-ui-danger-hover, #b32d2e );
616 }
617
618 .os-settings__library-footer {
619 display: flex;
620 align-items: center;
621 justify-content: space-between;
622 margin-top: 10px;
623 gap: 10px;
624 min-height: 24px;
625 }
626
627 .os-settings__library-meta {
628 color: var( --os-ui-fg-muted, #8c8f94 );
629 font-size: 11px;
630 }
631
632 /* `__library-load-more` now renders as `<os-button variant="ghost">`. */
633
634 /* ---------------------------------------------------------------
635 * Reduced-motion: collapse every OS-Settings animation/transition to
636 * near-zero so motion-sensitive users still get the layout changes
637 * (hover feedback, expanded editor, tile highlights) but without the
638 * glide. Kept as a single scoped block so the intent is readable at
639 * a glance rather than spread across each rule.
640 * --------------------------------------------------------------- */
641 @media (prefers-reduced-motion: reduce) {
642 .os-settings__editor-slot,
643 .os-settings__wallpaper-config-slot,
644 .os-settings__upload-tile,
645 .os-settings__library-tile,
646 os-swatch {
647 transition-duration: 0.01ms !important;
648 }
649
650 .os-settings__library-tile--skeleton {
651 animation: none;
652 }
653 }
654
655 /* AI settings section — error and saving feedback */
656 .os-ai-settings__error {
657 margin: 6px 0 0;
658 font-size: 12px;
659 color: var( --os-ui-danger, #d63638 );
660 }
661
662 .os-ai-settings__saving {
663 margin: 6px 0 0;
664 font-size: 12px;
665 color: var( --os-ui-fg-muted, #646970 );
666 font-style: italic;
667 }
668
669 /* Extended options section — hint paragraph + error/saving states */
670 .os-ext__hint {
671 margin: 6px 0 0;
672 font-size: 12px;
673 color: var( --os-ui-fg-muted, #646970 );
674 line-height: 1.5;
675 }
676
677 .os-ext__error {
678 margin: 6px 0 0;
679 font-size: 12px;
680 color: var( --os-ui-danger, #d63638 );
681 }
682
683 .os-ext__saving {
684 margin: 6px 0 0;
685 font-size: 12px;
686 color: var( --os-ui-fg-muted, #646970 );
687 font-style: italic;
688 }
689
690 /*
691 * Help tab — developer-facing component reference. Two-column layout
692 * (nav + detail) on desktop, stacks naturally on narrow windows via
693 * the same container the OS Settings panel lives in.
694 */
695 .os-settings__help-count {
696 margin: 4px 0 0;
697 font-size: 12px;
698 color: var( --os-ui-fg-muted, #646970 );
699 }
700
701 .os-settings__help-layout {
702 display: grid;
703 grid-template-columns: 200px 1fr;
704 gap: 20px;
705 }
706
707 @container (max-width: 640px) {
708 .os-settings__help-layout {
709 grid-template-columns: 1fr;
710 }
711 }
712
713 /*
714 * Search box + nav share one column so the field stays pinned while
715 * the component list below it scrolls.
716 */
717 .os-settings__help-sidebar {
718 display: flex;
719 flex-direction: column;
720 gap: 8px;
721 min-height: 0;
722 }
723
724 .os-settings__help-nav {
725 display: flex;
726 flex-direction: column;
727 gap: 2px;
728 align-self: stretch;
729 min-height: 0;
730 overflow-y: auto;
731 padding: 4px;
732 background: var( --os-ui-surface-elevated, #f6f7f7 );
733 border: 1px solid var( --os-ui-border, #dcdcde );
734 border-radius: 8px;
735 }
736
737 .os-settings__help-nav-empty {
738 margin: 0;
739 padding: 8px 10px;
740 font-size: 12px;
741 color: var( --os-ui-fg-muted, #646970 );
742 }
743
744 .os-settings__help-nav-item {
745 display: flex;
746 flex-direction: column;
747 gap: 2px;
748 align-items: flex-start;
749 width: 100%;
750 padding: 6px 10px;
751 background: transparent;
752 border: 1px solid transparent;
753 border-radius: 6px;
754 text-align: start;
755 cursor: pointer;
756 font: inherit;
757 color: inherit;
758 }
759
760 .os-settings__help-nav-item:hover {
761 background: var( --os-ui-surface, #fff );
762 border-color: var( --os-ui-border, #dcdcde );
763 }
764
765 .os-settings__help-nav-item.is-active {
766 background: var( --os-ui-surface, #fff );
767 border-color: var( --wp-admin-theme-color, #2271b1 );
768 }
769
770 .os-settings__help-nav-title {
771 font-weight: 600;
772 font-size: 13px;
773 }
774
775 .os-settings__help-nav-tag {
776 font-family: var( --os-ui-font-mono, Menlo, Consolas, monospace );
777 font-size: 11px;
778 color: var( --os-ui-fg-muted, #646970 );
779 }
780
781 .os-settings__help-detail {
782 min-width: 0;
783 padding: 16px 18px 18px;
784 background: var( --os-ui-surface-elevated, #f6f7f7 );
785 border: 1px solid var( --os-ui-border, #dcdcde );
786 border-radius: 8px;
787 }
788
789 .os-settings__help-head {
790 display: flex;
791 flex-wrap: wrap;
792 align-items: baseline;
793 gap: 8px;
794 margin: 0 0 8px;
795 }
796
797 .os-settings__help-title {
798 margin: 0;
799 font-size: 16px;
800 font-weight: 600;
801 }
802
803 .os-settings__help-code {
804 font-family: var( --os-ui-font-mono, Menlo, Consolas, monospace );
805 font-size: 12px;
806 color: var( --os-ui-fg-muted, #646970 );
807 }
808
809 .os-settings__help-badge {
810 padding: 1px 8px;
811 border-radius: 999px;
812 font-size: 11px;
813 font-weight: 600;
814 text-transform: uppercase;
815 letter-spacing: 0.04em;
816 background: var( --os-ui-surface-sunken, #e4e7eb );
817 color: var( --os-ui-fg, #1d2327 );
818 }
819
820 .os-settings__help-badge.is-experimental {
821 background: var( --os-ui-warning-bg, #fcf9e8 );
822 color: var( --os-ui-warning-fg, #996800 );
823 }
824
825 .os-settings__help-badge.is-planned {
826 background: var( --os-ui-info-bg, #e1e8ff );
827 color: var( --os-ui-info-fg, #123fb3 );
828 }
829
830 .os-settings__help-summary {
831 margin: 0 0 16px;
832 color: var( --os-ui-fg, #1d2327 );
833 }
834
835 .os-settings__help-group {
836 margin: 0 0 16px;
837 }
838
839 .os-settings__help-group h4 {
840 margin: 0 0 8px;
841 font-size: 12px;
842 font-weight: 600;
843 text-transform: uppercase;
844 letter-spacing: 0.04em;
845 color: var( --os-ui-fg-muted, #646970 );
846 }
847
848 .os-settings__help-example {
849 padding: 16px;
850 background: var( --os-ui-surface, #fff );
851 border: 1px dashed var( --os-ui-border, #c3c4c7 );
852 border-radius: 6px;
853 }
854
855 .os-settings__help-table {
856 width: 100%;
857 border-collapse: collapse;
858 font-size: 12px;
859 }
860
861 .os-settings__help-table th,
862 .os-settings__help-table td {
863 padding: 6px 8px;
864 border-bottom: 1px solid var( --os-ui-border, #dcdcde );
865 text-align: start;
866 vertical-align: top;
867 }
868
869 .os-settings__help-table th {
870 font-weight: 600;
871 color: var( --os-ui-fg-muted, #646970 );
872 background: var( --os-ui-surface, #fff );
873 }
874
875 .os-settings__help-table code,
876 .os-settings__help-list code {
877 font-family: var( --os-ui-font-mono, Menlo, Consolas, monospace );
878 font-size: 11px;
879 padding: 1px 4px;
880 background: var( --os-ui-surface, #fff );
881 border: 1px solid var( --os-ui-border, #dcdcde );
882 border-radius: 3px;
883 }
884
885 .os-settings__help-list {
886 margin: 0;
887 padding: 0;
888 list-style: none;
889 }
890
891 .os-settings__help-list li {
892 padding: 4px 0;
893 border-bottom: 1px solid var( --os-ui-border, #ececee );
894 font-size: 12px;
895 }
896
897 .os-settings__help-list li:last-child {
898 border-bottom: 0;
899 }
900
901 .os-settings__help-note {
902 margin: 12px 0 0;
903 padding: 10px 12px;
904 background: var( --os-ui-warning-bg, #fcf9e8 );
905 border: 1px solid var( --os-ui-warning-border, #f5e6a5 );
906 border-radius: 6px;
907 font-size: 12px;
908 color: var( --os-ui-warning-fg, #996800 );
909 }
910
911 /* ---------------------------------------------------------------
912 * Window-fit scrolling.
913 *
914 * Without this block the OS Settings panel grows past the native
915 * window's viewport and the whole body scrolls — fine for short tabs
916 * but actively wrong for the Components tab, where a 200px nav rail
917 * and a tall detail pane should each scroll inside their own column,
918 * not in lockstep down a giant single-column page.
919 *
920 * Strategy:
921 * 1. The native window body normally has overflow:auto. Override it
922 * to `hidden` ONLY when the OS Settings root is mounted (via
923 * :has()) so other native windows keep their default page-scroll.
924 * 2. Make the OS Settings root a flex column that fills the body.
925 * The tab strip + footer hug the top/bottom; the active
926 * tabpanel takes the remaining height.
927 * 3. Each tabpanel scrolls itself (overflow:auto) — replaces the
928 * whole-body scroll, visually identical for the simple tabs
929 * (Appearance, AI, Extended) but pins the reset footer.
930 * 4. The Components tab opts OUT of overflow:auto so its inner
931 * grid (nav + detail) can scroll independently per pane.
932 *
933 * `:has()` is supported in every evergreen engine (Chrome 105+,
934 * Safari 15.4+, Firefox 121+); fine for an admin-context shell.
935 * --------------------------------------------------------------- */
936 .os-window__body--native:has( .os-settings ) {
937 overflow: hidden;
938 display: flex;
939 flex-direction: column;
940 }
941
942 .os-settings {
943 flex: 1;
944 min-height: 0;
945 display: flex;
946 flex-direction: column;
947 }
948
949 /* The tab strip + footer must NOT shrink when the active panel
950 wants more height. Marking them flex-shrink:0 also keeps them
951 pinned visually as the panel below scrolls. */
952 .os-settings > os-tabs,
953 .os-settings > .os-settings__footer {
954 flex: 0 0 auto;
955 }
956
957 .os-settings > os-tabpanel {
958 flex: 1;
959 min-height: 0;
960 overflow: auto;
961 }
962
963 /* Components tab — split-pane layout. Don't scroll the whole panel;
964 let the inner grid distribute height to nav + detail, each with
965 their own overflow.
966 *
967 * The `:not([hidden])` guard is load-bearing: a bare
968 * `os-tabpanel[for='help'] { display: flex }` rule has specificity
969 * (0,0,2,1) which outranks the shadow-DOM `:host([hidden]) {
970 * display: none }` rule (specificity 0,0,1,0), so the panel would
971 * keep rendering when it should be hidden — bleeding the Components
972 * UI into every other tab. Pinning the rule to the visible state
973 * leaves the hidden state to the shadow-DOM default. */
974 .os-settings > os-tabpanel[ for='help' ]:not( [ hidden ] ) {
975 overflow: hidden;
976 display: flex;
977 flex-direction: column;
978 }
979 .os-settings
980 > os-tabpanel[ for='help' ]:not( [ hidden ] )
981 > os-panel {
982 flex: 1;
983 min-height: 0;
984 display: flex;
985 flex-direction: column;
986 }
987
988 .os-settings__help {
989 flex: 1;
990 min-height: 0;
991 display: flex;
992 flex-direction: column;
993 gap: 12px;
994 }
995 /* The intro card (Component library description) shouldn't grow —
996 it's content-sized so the grid below gets the remaining height. */
997 .os-settings__help > os-section {
998 flex: 0 0 auto;
999 }
1000 .os-settings__help-layout {
1001 flex: 1;
1002 min-height: 0;
1003 }
1004
1005 /* Each split pane is its own scroll container. min-height:0 unlocks
1006 shrinking in a grid item (default is `auto` which equals content
1007 height — would defeat the overflow). max-height:100% pins the
1008 pane to the grid track. */
1009 .os-settings__help-nav,
1010 .os-settings__help-detail {
1011 min-height: 0;
1012 max-height: 100%;
1013 overflow-y: auto;
1014 }
1015
1016 /*
1017 * Features section — per-user opt-in toggles.
1018 *
1019 * Save status pill renders the live OS-settings save lifecycle
1020 * (`pending` → `saving` → `saved` / `failed`) so toggling a
1021 * per-user feature flag gets immediate, honest feedback instead
1022 * of an optimistic flicker.
1023 */
1024 .os-features__status-row {
1025 min-height: 22px;
1026 }
1027
1028 /* One toggle + hint pair. Stacking these without separators reads as
1029 * a wall of checkboxes; a hairline above each item (after the first)
1030 * groups the label with its description and gives the eye a stopping
1031 * point between unrelated settings. The first item sits flush so the
1032 * section heading already serves as its top boundary. */
1033 .os-features__item {
1034 display: flex;
1035 flex-direction: column;
1036 gap: 6px;
1037 }
1038
1039 .os-features__item + .os-features__item {
1040 margin-top: 16px;
1041 padding-top: 16px;
1042 border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
1043 }
1044
1045 /* "Reset what's-new dialogs" row — separates the action from the
1046 * native-Posts toggle above and gives breathing room around the
1047 * button. `align-items: flex-start` keeps the inline-flex
1048 * `<os-button>` from stretching full-width inside the column,
1049 * and the explicit margin on the button below works around the
1050 * fact that flex `gap` doesn't always render visibly when one
1051 * neighbour is an inline-flex custom element with zero outer
1052 * margin. */
1053 .os-features__row {
1054 display: flex;
1055 flex-direction: column;
1056 align-items: flex-start;
1057 gap: 12px;
1058 margin-top: 20px;
1059 padding-top: 20px;
1060 border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
1061 }
1062
1063 .os-features__row > os-button {
1064 display: inline-flex;
1065 margin-block: 4px 8px;
1066 }
1067
1068 .os-features__row > .os-features__hint {
1069 margin: 0;
1070 }
1071
1072 .os-features__hint {
1073 margin: 0;
1074 font-size: 12px;
1075 color: var( --os-ui-fg-muted, #50575e );
1076 line-height: 1.5;
1077 }
1078
1079 .os-features__status {
1080 display: inline-flex;
1081 align-items: center;
1082 gap: 6px;
1083 font-size: 12px;
1084 font-weight: 500;
1085 padding: 2px 10px;
1086 border-radius: 999px;
1087 white-space: nowrap;
1088 transition: opacity 200ms ease;
1089 }
1090
1091 .os-features__status--saving {
1092 background: var( --os-ui-surface-elevated, #f0f0f1 );
1093 color: var( --os-ui-fg-muted, #50575e );
1094 }
1095
1096 .os-features__status--saved {
1097 background: rgba( 30, 132, 73, 0.12 );
1098 color: var( --os-ui-success-fg, #1d6f42 );
1099 }
1100
1101 .os-features__status--saved .dashicons {
1102 font-size: 14px;
1103 width: 14px;
1104 height: 14px;
1105 }
1106
1107 .os-features__status--failed {
1108 background: rgba( 214, 54, 56, 0.12 );
1109 color: var( --os-ui-danger-hover, #a02622 );
1110 }
1111
1112 .os-features__status--failed .dashicons {
1113 font-size: 14px;
1114 width: 14px;
1115 height: 14px;
1116 }
1117
1118 .os-features__status-dot {
1119 display: inline-block;
1120 width: 8px;
1121 height: 8px;
1122 border-radius: 50%;
1123 background: currentColor;
1124 animation: os-features-pulse 1.2s ease-in-out infinite;
1125 }
1126
1127 @keyframes os-features-pulse {
1128 0%, 100% { opacity: 0.35; transform: scale( 0.85 ); }
1129 50% { opacity: 1; transform: scale( 1 ); }
1130 }
1131
1132 /*
1133 * OS Settings panel header — hosts the global tabs strip and a single
1134 * `<os-save-status>` indicator that auto-listens to the save
1135 * lifecycle. The save status sits on the trailing edge of the header
1136 * so it's visible across every tab without competing with section
1137 * content.
1138 */
1139 .os-settings__header {
1140 display: flex;
1141 align-items: center;
1142 justify-content: space-between;
1143 gap: 16px;
1144 margin-bottom: 12px;
1145 }
1146
1147 .os-settings__header os-tabs {
1148 flex: 1 1 auto;
1149 min-width: 0;
1150 }
1151
1152 .os-settings__header os-save-status {
1153 flex: 0 0 auto;
1154 margin-inline-start: auto;
1155 }
1156
1157 /*
1158 * About tab — full-tabpanel PixiJS canvas. Every credit string and
1159 * the AUTOMATTIC logotype (formed by particles) render inside Pixi
1160 * itself; this stylesheet only sizes the host so the canvas fills
1161 * the available height + width without any HTML chrome around it.
1162 *
1163 * The chain is: tabpanel → os-panel → .about (flex column) →
1164 * .about-stage-host (the canvas mounts here). Each link sets
1165 * `flex: 1; min-height: 0;` so the canvas inherits the tabpanel's
1166 * full inner height instead of hugging an intrinsic minimum.
1167 *
1168 * @since 0.8.0
1169 */
1170 .os-settings
1171 > os-tabpanel[ for='about' ]:not( [ hidden ] ) {
1172 display: flex;
1173 flex-direction: column;
1174 overflow: hidden;
1175 }
1176
1177 .os-settings
1178 > os-tabpanel[ for='about' ]:not( [ hidden ] )
1179 > os-panel {
1180 flex: 1;
1181 min-height: 0;
1182 display: flex;
1183 flex-direction: column;
1184 }
1185
1186 .os-settings__about {
1187 flex: 1;
1188 min-height: 0;
1189 display: flex;
1190 flex-direction: column;
1191 margin: 0;
1192 /* Edge-to-edge — no border-radius / box-shadow framing — so the
1193 * canvas (and the logotype centred inside it) consume the entire
1194 * tabpanel surface. The gradient is just the colour-of-last-resort
1195 * underneath while Pixi initialises; once the canvas is rendering
1196 * it covers every pixel of this element anyway. */
1197 background: linear-gradient( 180deg, #060a1c 0%, #0c1733 100% );
1198 overflow: hidden;
1199 }
1200
1201 .os-settings__about-stage-host {
1202 flex: 1;
1203 min-height: 0;
1204 width: 100%;
1205 overflow: hidden;
1206 cursor: crosshair;
1207 }
1208
1209 .os-settings__about-stage-host canvas {
1210 display: block;
1211 width: 100%;
1212 height: 100%;
1213 }
1214
1215
1216 /* Apps & Icons section (since 0.25.0) — per-item placement chooser. */
1217 .os-apps-icons__list {
1218 display: flex;
1219 flex-direction: column;
1220 gap: 4px;
1221 }
1222
1223 .os-apps-icons__row {
1224 display: flex;
1225 align-items: center;
1226 gap: 12px;
1227 padding: 8px 4px;
1228 border-bottom: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
1229 }
1230
1231 .os-apps-icons__row:last-child {
1232 border-bottom: 0;
1233 }
1234
1235 .os-apps-icons__identity {
1236 display: flex;
1237 align-items: center;
1238 gap: 10px;
1239 flex: 1;
1240 min-width: 0;
1241 }
1242
1243 .os-apps-icons__icon {
1244 width: 24px;
1245 height: 24px;
1246 flex-shrink: 0;
1247 display: inline-flex;
1248 align-items: center;
1249 justify-content: center;
1250 }
1251
1252 .os-apps-icons__icon.dashicons {
1253 font-size: 20px;
1254 }
1255
1256 .os-apps-icons__title {
1257 overflow: hidden;
1258 text-overflow: ellipsis;
1259 white-space: nowrap;
1260 }
1261
1262 .os-apps-icons__row os-select {
1263 min-width: 180px;
1264 }
1265
1266 /* ------------------------------------------------------------------
1267 * OS Settings → Themes.
1268 *
1269 * Card grid of the site's desktop-theme library. Deliberately styled
1270 * with the SAME neutral palette as the rest of the panel rather than
1271 * with themed tokens: the picker has to stay legible while the user
1272 * is trying on themes, including ones with hostile contrast.
1273 * ------------------------------------------------------------------ */
1274
1275 .os-settings__theme-grid {
1276 display: grid;
1277 grid-template-columns: repeat( auto-fill, minmax( 160px, 1fr ) );
1278 gap: 12px;
1279 margin: 12px 0 16px;
1280 }
1281
1282 .os-settings__theme-card-wrap {
1283 position: relative;
1284 }
1285
1286 .os-settings__theme-card {
1287 display: flex;
1288 flex-direction: column;
1289 gap: 6px;
1290 width: 100%;
1291 padding: 8px;
1292 background: var( --os-ui-surface, #fff );
1293 border: 2px solid var( --os-ui-border, #dcdcde );
1294 border-radius: 10px;
1295 cursor: pointer;
1296 text-align: start;
1297 font: inherit;
1298 color: var( --os-ui-fg, #1d2327 );
1299 transition: border-color 0.15s ease, box-shadow 0.15s ease;
1300 }
1301
1302 .os-settings__theme-card:hover {
1303 border-color: var( --wp-admin-theme-color, #2271b1 );
1304 }
1305
1306 .os-settings__theme-card:focus-visible {
1307 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
1308 outline-offset: 2px;
1309 }
1310
1311 .os-settings__theme-card[ aria-pressed="true" ] {
1312 border-color: var( --wp-admin-theme-color, #2271b1 );
1313 box-shadow: 0 0 0 3px rgba( 34, 113, 177, 0.18 );
1314 }
1315
1316 .os-settings__theme-preview {
1317 display: flex;
1318 align-items: center;
1319 justify-content: center;
1320 aspect-ratio: 16 / 10;
1321 overflow: hidden;
1322 border-radius: 6px;
1323 background: var( --os-ui-surface-elevated, #f0f0f1 );
1324 }
1325
1326 .os-settings__theme-preview img {
1327 width: 100%;
1328 height: 100%;
1329 object-fit: cover;
1330 display: block;
1331 }
1332
1333 /* Stand-in preview for "System default" — a miniature of the shipped
1334 * graphite desktop, so the card reads as a real option rather than an
1335 * absence. */
1336 .os-settings__theme-preview--system {
1337 background: linear-gradient( 135deg, #1d2327 0%, #2c3338 50%, #1d2327 100% );
1338 }
1339
1340 .os-settings__theme-initials {
1341 font-size: 24px;
1342 font-weight: 700;
1343 letter-spacing: 1px;
1344 color: var( --os-ui-fg-muted, #787c82 );
1345 }
1346
1347 .os-settings__theme-name {
1348 font-weight: 600;
1349 line-height: 1.3;
1350 overflow-wrap: anywhere;
1351 }
1352
1353 .os-settings__theme-meta {
1354 font-size: 11px;
1355 color: var( --os-ui-fg-muted, #646970 );
1356 line-height: 1.3;
1357 min-height: 1.3em;
1358 }
1359
1360 /* Delete overlay — outside the card button, because nesting a button
1361 * inside a button is invalid HTML and breaks keyboard traversal. */
1362 .os-settings__theme-delete {
1363 position: absolute;
1364 inset-block-start: 4px;
1365 inset-inline-end: 4px;
1366 display: flex;
1367 align-items: center;
1368 justify-content: center;
1369 width: 22px;
1370 height: 22px;
1371 padding: 0;
1372 border: none;
1373 border-radius: 50%;
1374 background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.55 ) );
1375 color: var( --os-ui-fg-on-accent, #fff );
1376 font-size: 15px;
1377 line-height: 1;
1378 cursor: pointer;
1379 opacity: 0;
1380 transition: opacity 0.15s ease, background-color 0.15s ease;
1381 }
1382
1383 .os-settings__theme-card-wrap:hover
1384 .os-settings__theme-delete,
1385 .os-settings__theme-delete:focus-visible {
1386 opacity: 1;
1387 }
1388
1389 .os-settings__theme-delete:hover {
1390 background: var( --os-ui-danger, #d63638 );
1391 }
1392
1393 /*
1394 * "Apply <theme>'s recommended layout" — sits between the theme grid
1395 * and the upload tile, and only for a theme that recommends
1396 * something. Just the button: the dock resizing and the layout
1397 * moving is the feedback.
1398 */
1399 .os-settings__theme-recommendation {
1400 display: flex;
1401 margin-block: 16px;
1402 }
1403
1404 .os-settings__theme-upload {
1405 display: block;
1406 border: 2px dashed var( --os-ui-border, #c3c4c7 );
1407 border-radius: 8px;
1408 background: var( --os-ui-surface, #fff );
1409 transition: border-color 0.15s ease, background-color 0.15s ease;
1410 }
1411
1412 .os-settings__theme-upload-label {
1413 display: flex;
1414 flex-direction: column;
1415 align-items: center;
1416 justify-content: center;
1417 gap: 6px;
1418 min-height: 96px;
1419 padding: 16px;
1420 color: var( --os-ui-fg-muted, #50575e );
1421 cursor: pointer;
1422 text-align: center;
1423 }
1424
1425 .os-settings__theme-upload:hover,
1426 .os-settings__theme-upload--dragover {
1427 border-color: var( --wp-admin-theme-color, #2271b1 );
1428 background-color: rgba( 34, 113, 177, 0.06 );
1429 }
1430
1431 .os-settings__theme-upload--dragover {
1432 border-style: solid;
1433 }
1434
1435 .os-settings__theme-upload--busy
1436 .os-settings__theme-upload-label {
1437 cursor: progress;
1438 opacity: 0.7;
1439 }
1440
1441 .os-settings__theme-upload-plus {
1442 font-size: 22px;
1443 line-height: 1;
1444 }
1445