PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 1.1.11 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 All 36 releases
desktop-mode / assets / css / ai-assistant.css

ai-assistant.css in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.12, at assets/css/ai-assistant.css

1,013 lines 28.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * OpenStation — AI Assistant spotlight overlay.
3 *
4 * A system-level command palette that floats above the entire shell,
5 * including the WP admin bar. Visually modeled on macOS Spotlight and
6 * Linear's command menu — frosted-glass backdrop, spring-physics panel
7 * entrance, large search input.
8 *
9 * ## How much accent the chrome shows
10 *
11 * Every accent-derived surface below (the header tint and its mark, the
12 * search icon, a selected or hovered row, the icon tiles) mixes the
13 * accent by --os-ai-accent towards a neutral. Unset that is 100%, the
14 * accent-tinted panel this has always been; the OpenStation palette
15 * answers 0% and lifts rows and tiles with --os-ai-row-fill and
16 * --os-ai-tile-fill instead. The command icons themselves are not part
17 * of it: they read the accent directly, in every palette.
18 *
19 * z-index 10000 puts this above --os-z-adminbar (9991) so the
20 * overlay truly covers the full viewport.
21 *
22 * ## Desktop-theme awareness
23 *
24 * The overlay mounts on `document.body`, so it sits inside the
25 * `body.os-desktop-theme-<slug>` half of a compiled theme's
26 * doubled selector and every `--os-ui-*` token below resolves against the
27 * active theme. Each site reads `var( --os-ui-x, <the literal that was
28 * always there> )`, so an unthemed shell paints exactly as before.
29 *
30 * Getting this half-right is worse than not doing it at all: when the
31 * text tones followed the theme but the panel stayed hardcoded white,
32 * a dark theme's light `--os-ui-fg` landed on white and the results were
33 * unreadable. Any new surface added here MUST take its background,
34 * border and text from the palette — not from a literal.
35 */
36
37 /* ---------------------------------------------------------------------------
38 * Root overlay — full viewport, fades in/out
39 * ------------------------------------------------------------------------- */
40
41 .os-ai {
42 position: fixed;
43 inset: 0;
44 z-index: 10000;
45
46 display: flex;
47 align-items: flex-start;
48 justify-content: center;
49
50 /* Land the panel at ~16-18% from the top — feels like Spotlight.
51 clamp keeps it 60px from the top on very short screens and caps
52 at 180px on tall displays so it never feels too low. */
53 padding-top: clamp( 60px, 16vh, 180px );
54 padding-inline: 16px;
55 padding-bottom: 40px;
56
57 opacity: 0;
58 pointer-events: none;
59 transition: opacity 0.18s ease;
60 }
61
62 /* [hidden] keeps the element out of the a11y tree between sessions.
63 Only removed while the overlay is visually visible (open or animating). */
64 .os-ai[hidden] {
65 display: none;
66 }
67
68 .os-ai.is-open {
69 opacity: 1;
70 pointer-events: auto;
71 }
72
73 /* ---------------------------------------------------------------------------
74 * Backdrop — frosted glass
75 * ------------------------------------------------------------------------- */
76
77 .os-ai__backdrop {
78 position: absolute;
79 inset: 0;
80 background-color: var( --os-ui-scrim, rgba( 0, 0, 0, 0.36 ) );
81 /* Desktop-theme SCRIM texture slot: unset resolves to none. */
82 background-image: var( --os-ui-scrim-image, none );
83 background-repeat: var( --os-ui-scrim-image-repeat, repeat );
84 background-size: var( --os-ui-scrim-image-size, auto );
85 background-position: var( --os-ui-scrim-image-position, center );
86 backdrop-filter: blur( 8px ) saturate( 0.75 );
87 -webkit-backdrop-filter: blur( 8px ) saturate( 0.75 );
88 /* Non-interactive: clicks in the dimmed area fall through to the overlay
89 container, which closes the assistant (click-outside-to-dismiss). */
90 pointer-events: none;
91 }
92
93 /* ---------------------------------------------------------------------------
94 * Panel — the visible card
95 * ------------------------------------------------------------------------- */
96
97 .os-ai__panel {
98 position: relative; /* sits above the backdrop */
99 width: 100%;
100 max-width: 600px;
101
102 /* Longhand on purpose: the DIALOG texture slot below owns
103 background-image, and the shorthand would reset it. */
104 background-color: var(
105 --os-ai-panel-bg,
106 var( --os-ui-surface, rgba( 255, 255, 255, 0.97 ) )
107 );
108 /* Desktop-theme DIALOG texture slot: unset resolves to none. */
109 background-image: var( --os-ui-dialog-bg-image, none );
110 background-repeat: var( --os-ui-dialog-bg-image-repeat, repeat );
111 background-size: var( --os-ui-dialog-bg-image-size, auto );
112 background-position: var( --os-ui-dialog-bg-image-position, center );
113 color: var( --os-ui-fg, #1d2327 );
114 font-family: var( --os-ui-font, inherit );
115 border-radius: 16px;
116 overflow: hidden;
117
118 /* Tight under-shadow + wide atmospheric shadow. The hairline ring
119 is the panel's only edge, so it follows the palette; the two
120 atmospheric shadows stay black because they read as depth
121 against the wallpaper, not as part of the surface. */
122 box-shadow:
123 0 0 0 1px var( --os-ui-border, rgba( 0, 0, 0, 0.07 ) ),
124 0 4px 16px rgba( 0, 0, 0, 0.12 ),
125 0 24px 64px rgba( 0, 0, 0, 0.22 );
126
127 /* Spring-physics entrance: starts slightly small + shifted up,
128 overshoots minimally, then settles at 1.0. */
129 transform: scale( 0.96 ) translateY( -8px );
130 transition: transform 0.28s cubic-bezier( 0.34, 1.56, 0.64, 1 );
131 will-change: transform;
132 }
133
134 .os-ai.is-open .os-ai__panel {
135 transform: scale( 1 ) translateY( 0 );
136 }
137
138 /* Mode switch (Commands ↔ AI) — a small segmented pill at the end of
139 the input row. Always shown; replaces the `/` shortcut. */
140 .os-ai__modes {
141 flex-shrink: 0;
142 display: inline-flex;
143 gap: 2px;
144 padding: 2px;
145 border-radius: 999px;
146 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
147 }
148
149 .os-ai__mode {
150 appearance: none;
151 border: 0;
152 cursor: pointer;
153 padding: 3px 12px;
154 border-radius: 999px;
155 background: transparent;
156 color: var( --os-ui-fg-muted, #50575e );
157 font-size: 11px;
158 font-weight: 600;
159 line-height: 1.6;
160 }
161
162 .os-ai__mode:hover {
163 color: var( --os-ui-fg, #1d2327 );
164 }
165
166 .os-ai__mode.is-active {
167 /* Elevated first: the pill sits ON the panel, which is already
168 `--os-ui-surface`, so a theme that distinguishes the two keeps the
169 active segment readable. Falls through to plain surface, which is
170 the default look. */
171 background: var( --os-ui-surface-elevated, var( --os-ui-surface, #fff ) );
172 color: var( --wp-admin-theme-color, #2271b1 );
173 box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.12 );
174 }
175
176 .os-ai__mode:focus-visible {
177 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
178 outline-offset: 1px;
179 }
180
181 /* ---------------------------------------------------------------------------
182 * Input row — the main interaction surface
183 * ------------------------------------------------------------------------- */
184
185 .os-ai__input-wrap {
186 display: flex;
187 align-items: center;
188 gap: 10px;
189 padding: 14px 16px;
190 /* Thin highlight at the bottom of the input area, visible when focused */
191 border-bottom: 1px solid transparent;
192 transition: border-color 0.15s ease;
193 }
194
195 .os-ai__input-wrap:has( .os-ai__input:focus ) {
196 border-bottom-color: var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) );
197 }
198
199 /* Input-row icon: dim at rest, full strength on focus. At rest it is
200 the accent at 55% alpha, mixed by --os-ai-accent towards the faint
201 text colour; focused, the accent mixed towards the text colour. */
202 .os-ai__input-icon {
203 flex-shrink: 0;
204 display: flex;
205 align-items: center;
206 color: color-mix(
207 in srgb,
208 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 55%, transparent ) var( --os-ai-accent, 100% ),
209 var( --os-ui-fg-faint, #c3c4c7 )
210 );
211 transition: color 0.18s ease, transform 0.18s ease;
212 }
213
214 .os-ai__input-wrap:has( .os-ai__input:focus ) .os-ai__input-icon {
215 color: color-mix(
216 in srgb,
217 var( --wp-admin-theme-color, #2271b1 ) var( --os-ai-accent, 100% ),
218 var( --os-ui-fg, #1d2327 )
219 );
220 transform: scale( 1.08 );
221 }
222
223 /*
224 * The search field is a raw `<input type="text">` in the parent shell,
225 * so core's `forms.css` reaches it:
226 *
227 * input[type="text"], … { background-color: #fff; color: #2c3338;
228 * border: 1px solid #8c8f94;
229 * border-radius: 4px;
230 * box-shadow: 0 0 0 transparent }
231 * input[type="text"]:focus { border-color: #2271b1;
232 * box-shadow: 0 0 0 1px #2271b1 }
233 *
234 * That weighs (0,1,1) — one type selector plus one attribute selector —
235 * which beats a bare `.os-ai__input` at (0,1,0). The result
236 * was a bordered white box floating inside the panel: core's chrome,
237 * core's text colour, ignoring both the design intent (a chromeless
238 * Spotlight-style field) and any active desktop theme.
239 *
240 * Scoping to the overlay root takes the selector to (0,2,0) and wins.
241 * Same fix as the native-window form-control rule in window-chrome.css.
242 */
243 .os-ai .os-ai__input {
244 flex: 1;
245 min-width: 0;
246 appearance: none;
247 border: none;
248 border-radius: 0;
249 outline: none;
250 box-shadow: none;
251 padding: 0;
252 /* Matches the height core gave the control, so the row keeps its
253 comfortable hit target now that core's box is gone. */
254 min-height: 30px;
255 background: transparent;
256 font: inherit;
257 font-size: 17px;
258 font-weight: 400;
259 line-height: 1.4;
260 color: var( --os-ui-fg, #1d2327 );
261 }
262
263 /* Core paints a focus border + ring on the element itself; the focus
264 affordance here is the input row's bottom hairline instead. */
265 .os-ai .os-ai__input:focus {
266 border-color: transparent;
267 box-shadow: none;
268 outline: none;
269 }
270
271 .os-ai .os-ai__input::placeholder {
272 color: var( --os-ui-fg-faint, #c3c4c7 );
273 font-weight: 400;
274 }
275
276 /* Submit button — hidden until the user types something, then
277 springs in via opacity + scale transition. */
278 .os-ai__submit {
279 flex-shrink: 0;
280 display: flex;
281 align-items: center;
282 justify-content: center;
283 width: 30px;
284 height: 30px;
285 border: none;
286 border-radius: 8px;
287 /* An accent key at --os-ai-accent's strength. At 0 it is an icon tile
288 like the command rows' (the neutral fill, the glyph in the accent);
289 at 100% the filled accent button with its ink. */
290 background-color: color-mix(
291 in srgb,
292 var( --wp-admin-theme-color, #2271b1 ) var( --os-ai-accent, 100% ),
293 transparent
294 );
295 background-image: linear-gradient(
296 var( --os-ai-tile-fill, transparent ),
297 var( --os-ai-tile-fill, transparent )
298 );
299 color: color-mix(
300 in srgb,
301 var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) ) var( --os-ai-accent, 100% ),
302 var( --wp-admin-theme-color, #2271b1 )
303 );
304 cursor: pointer;
305 padding: 0;
306 opacity: 0;
307 transform: scale( 0.7 );
308 pointer-events: none;
309 transition:
310 opacity 0.2s ease,
311 transform 0.2s cubic-bezier( 0.34, 1.56, 0.64, 1 );
312 }
313
314 .os-ai__submit.has-value {
315 opacity: 1;
316 transform: scale( 1 );
317 pointer-events: auto;
318 }
319
320 .os-ai__submit:hover {
321 filter: brightness( 1.1 );
322 }
323
324 .os-ai__submit:active {
325 transform: scale( 0.92 );
326 }
327
328 .os-ai__submit:focus-visible {
329 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
330 outline-offset: 2px;
331 }
332
333 /* ---------------------------------------------------------------------------
334 * Footer — hints and keyboard shortcut legend
335 * ------------------------------------------------------------------------- */
336
337 .os-ai__footer {
338 display: flex;
339 align-items: center;
340 justify-content: space-between;
341 gap: 8px;
342 padding: 8px 16px 10px;
343 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.018 ) );
344 border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.05 ) );
345 }
346
347 .os-ai__footer-hint {
348 font-size: 11px;
349 color: var( --os-ui-fg-muted, #a7aaad );
350 }
351
352 /* ---------------------------------------------------------------------------
353 * Admin bar AI button — ⌘K badge + sparkle icon
354 * Styles applied via wp_add_inline_style( 'admin-bar', ... ) in PHP.
355 * The styles here are for reference; the actual output lives in
356 * includes/admin-bar.php :: openstation_enqueue_toggle_assets().
357 * ------------------------------------------------------------------------- */
358
359 /* (The admin-bar button styles are added inline in PHP to keep them
360 on the admin-bar handle so they load even before os.js.) */
361
362 /* ---------------------------------------------------------------------------
363 * Results / conversation area — three render modes:
364 * • Suggestions (empty state, example prompts)
365 * • Thinking (spinner + "Thinking…")
366 * • Response (assistant bubble + entity card | admin links | nothing)
367 * ------------------------------------------------------------------------- */
368
369 .os-ai__results {
370 border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) );
371 max-height: 420px;
372 overflow-y: auto;
373 overflow-x: hidden;
374 padding: 14px 16px;
375 display: flex;
376 flex-direction: column;
377 gap: 12px;
378 }
379
380 /* -----------------------------------------------------------------
381 * Suggestion chips (empty state)
382 * ---------------------------------------------------------------- */
383
384 .os-ai__suggestions-label {
385 margin: 0 0 4px;
386 font-size: 11px;
387 font-weight: 600;
388 text-transform: uppercase;
389 letter-spacing: 0.08em;
390 color: var( --os-ui-fg-muted, #8c8f94 );
391 }
392
393 .os-ai__suggestions-list {
394 display: flex;
395 flex-wrap: wrap;
396 gap: 6px;
397 }
398
399 .os-ai__suggestion {
400 appearance: none;
401 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) );
402 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
403 border-radius: 999px;
404 padding: 5px 12px;
405 font: inherit;
406 font-size: 12px;
407 color: var( --os-ui-fg-muted, #50575e );
408 cursor: pointer;
409 transition: background 0.12s, border-color 0.12s, color 0.12s;
410 }
411
412 .os-ai__suggestion:hover {
413 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 8%, var( --os-ui-surface, #fff ) );
414 border-color: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 30%, var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) ) );
415 color: var( --wp-admin-theme-color, #2271b1 );
416 }
417
418 /* -----------------------------------------------------------------
419 * Thinking / error state
420 * ---------------------------------------------------------------- */
421
422 .os-ai__state {
423 display: flex;
424 align-items: center;
425 gap: 10px;
426 font-size: 13px;
427 color: var( --os-ui-fg-muted, #646970 );
428 }
429
430 .os-ai__state--error {
431 color: var( --os-ui-danger, #d63638 );
432 align-items: flex-start;
433 }
434
435 /* Inline "AI Settings" link inside the "AI not enabled" error — opens
436 * OS Settings on the AI tab. Styled as a link within the surrounding
437 * error text, not a standalone button: inherits the error color and
438 * flows with the sentence. */
439 .os-ai__settings-link {
440 appearance: none;
441 border: 0;
442 margin: 0;
443 padding: 0;
444 background: none;
445 font: inherit;
446 font-weight: 600;
447 color: inherit;
448 text-decoration: underline;
449 text-underline-offset: 2px;
450 }
451
452 /* Keeps the underline and nudges the colour toward the foreground, so
453 hovering reads as a response rather than the link going away. */
454 .os-ai__settings-link:hover {
455 color: color-mix( in srgb, currentColor 65%, var( --os-ui-fg, #1d2327 ) );
456 }
457
458 /* The shell forces `cursor: default !important` on every clickable
459 * surface (see the cursor policy at the top of desktop.css). This
460 * recovery link is an explicit, user-approved exception: it keeps a
461 * pointer so it reads as the actionable link it is. Scoped under
462 * `body.os-active` so it out-specifies the policy's
463 * `!important` rule, same trick the resize-handle exceptions use. */
464 body.os-active .os-ai__settings-link {
465 cursor: pointer !important;
466 }
467
468 @keyframes os-spin {
469 to { transform: rotate( 360deg ); }
470 }
471
472 .os-ai__spinner-icon {
473 animation: os-spin 0.9s linear infinite;
474 flex-shrink: 0;
475 color: var( --wp-admin-theme-color, #2271b1 );
476 }
477
478 /* -----------------------------------------------------------------
479 * Assistant message bubble — prefix for every response
480 * ---------------------------------------------------------------- */
481
482 .os-ai__bubble {
483 display: flex;
484 align-items: flex-start;
485 gap: 10px;
486 }
487
488 .os-ai__bubble-icon {
489 flex-shrink: 0;
490 display: flex;
491 align-items: center;
492 justify-content: center;
493 width: 24px;
494 height: 24px;
495 border-radius: 50%;
496 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 12%, var( --os-ui-surface, #fff ) );
497 color: var( --wp-admin-theme-color, #2271b1 );
498 }
499
500 .os-ai__bubble-text {
501 padding-top: 3px;
502 font-size: 13px;
503 line-height: 1.5;
504 color: var( --os-ui-fg, #1d2327 );
505 flex: 1;
506 min-width: 0;
507 }
508
509 /* Markdown output inside the assistant bubble — the renderer emits
510 <p>, <ul>/<ol>/<li>, <strong>, <em>, <code>, <a>. Reset margins on
511 outer paragraphs so single-paragraph replies (the common case)
512 don't get top/bottom spacing; list items get the usual disc/decimal
513 markers inside a compact indent. */
514 .os-ai__bubble-text > p,
515 .os-ai__bubble-text > ul,
516 .os-ai__bubble-text > ol {
517 margin: 0 0 6px;
518 }
519 .os-ai__bubble-text > :last-child {
520 margin-bottom: 0;
521 }
522 .os-ai__bubble-text ul,
523 .os-ai__bubble-text ol {
524 padding-inline-start: 20px;
525 }
526 .os-ai__bubble-text li {
527 margin-bottom: 2px;
528 }
529 .os-ai__bubble-text code {
530 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
531 padding: 1px 5px;
532 border-radius: 4px;
533 font-family: var( --os-ui-font-mono, Menlo, Consolas, monospace );
534 font-size: 11.5px;
535 }
536 .os-ai__bubble-text a {
537 color: var( --wp-admin-theme-color, #2271b1 );
538 text-decoration: underline;
539 text-underline-offset: 2px;
540 }
541 .os-ai__bubble-text a:hover {
542 text-decoration: none;
543 }
544 .os-ai__bubble-text strong {
545 font-weight: 600;
546 color: var( --os-ui-fg, #1d2327 );
547 }
548
549 /* -----------------------------------------------------------------
550 * Entity card — shown under the bubble when answer_type=entity
551 * ---------------------------------------------------------------- */
552
553 .os-ai__entity {
554 display: flex;
555 flex-direction: column;
556 gap: 8px;
557 padding: 12px 14px;
558 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
559 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
560 border-radius: 10px;
561 }
562
563 .os-ai__entity-header {
564 display: flex;
565 align-items: center;
566 gap: 8px;
567 flex-wrap: wrap;
568 }
569
570 .os-ai__entity-topic {
571 display: inline-block;
572 font-size: 10px;
573 font-weight: 600;
574 text-transform: uppercase;
575 letter-spacing: 0.07em;
576 color: var( --wp-admin-theme-color, #2271b1 );
577 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 10%, transparent );
578 padding: 2px 7px;
579 border-radius: 999px;
580 }
581
582 .os-ai__entity-type {
583 font-size: 10px;
584 font-weight: 600;
585 text-transform: uppercase;
586 letter-spacing: 0.07em;
587 color: var( --os-ui-fg-muted, #8c8f94 );
588 }
589
590 .os-ai__entity-title {
591 margin: 0;
592 font-size: 14px;
593 font-weight: 600;
594 color: var( --os-ui-fg, #1d2327 );
595 line-height: 1.35;
596 }
597
598 .os-ai__entity-summary {
599 margin: 0;
600 font-size: 12px;
601 line-height: 1.5;
602 color: var( --os-ui-fg-muted, #50575e );
603 }
604
605 /* Primary action — opens the entity in a legacy iframe window inside
606 the desktop (wp-admin edit URL). */
607 .os-ai__entity-open {
608 appearance: none;
609 display: inline-flex;
610 align-items: center;
611 gap: 6px;
612 align-self: flex-start;
613 padding: 6px 14px;
614 border: none;
615 border-radius: 8px;
616 background: var( --wp-admin-theme-color, #2271b1 );
617 color: var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) );
618 font: inherit;
619 font-size: 12px;
620 font-weight: 500;
621 cursor: pointer;
622 transition: filter 0.1s, transform 0.1s;
623 }
624
625 .os-ai__entity-open:hover {
626 filter: brightness( 1.1 );
627 }
628
629 .os-ai__entity-open:active {
630 transform: scale( 0.97 );
631 }
632
633 .os-ai__entity-open:focus-visible {
634 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
635 outline-offset: 2px;
636 }
637
638 .os-ai__entity-open svg {
639 margin-inline-start: 2px;
640 }
641
642 /* -----------------------------------------------------------------
643 * Admin-links list — shown when answer_type=navigation
644 * ---------------------------------------------------------------- */
645
646 .os-ai__admin-links {
647 display: flex;
648 flex-direction: column;
649 gap: 6px;
650 }
651
652 .os-ai__admin-link {
653 appearance: none;
654 display: flex;
655 align-items: center;
656 gap: 12px;
657 padding: 10px 12px;
658 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
659 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
660 border-radius: 10px;
661 cursor: pointer;
662 text-align: start;
663 font: inherit;
664 color: inherit;
665 transition: background 0.12s, border-color 0.12s, transform 0.08s;
666 }
667
668 .os-ai__admin-link:hover {
669 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 6%, var( --os-ui-surface, #fff ) );
670 border-color: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 25%, var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) ) );
671 }
672
673 .os-ai__admin-link:active {
674 transform: scale( 0.99 );
675 }
676
677 .os-ai__admin-link:focus-visible {
678 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
679 outline-offset: 2px;
680 }
681
682 .os-ai__admin-link-icon {
683 flex-shrink: 0;
684 display: flex;
685 align-items: center;
686 justify-content: center;
687 width: 32px;
688 height: 32px;
689 font-size: 18px;
690 color: var( --wp-admin-theme-color, #2271b1 );
691 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 10%, var( --os-ui-surface, #fff ) );
692 border-radius: 8px;
693 }
694
695 .os-ai__admin-link-body {
696 flex: 1;
697 display: flex;
698 flex-direction: column;
699 gap: 2px;
700 min-width: 0;
701 }
702
703 .os-ai__admin-link-title {
704 font-size: 13px;
705 font-weight: 600;
706 color: var( --os-ui-fg, #1d2327 );
707 }
708
709 .os-ai__admin-link-desc {
710 font-size: 11px;
711 color: var( --os-ui-fg-muted, #646970 );
712 line-height: 1.4;
713 overflow: hidden;
714 text-overflow: ellipsis;
715 white-space: nowrap;
716 }
717
718 .os-ai__admin-link-arrow {
719 flex-shrink: 0;
720 color: var( --os-ui-fg-faint, #c3c4c7 );
721 transition: color 0.12s, transform 0.12s;
722 }
723
724 .os-ai__admin-link:hover .os-ai__admin-link-arrow {
725 color: var( --wp-admin-theme-color, #2271b1 );
726 transform: translateX( 2px );
727 }
728
729 /* -----------------------------------------------------------------
730 * Command palette — shown when the user types "/" in the input.
731 * Filtered list of plugin-registered slash-commands. Mirrors the
732 * admin-link list's visual shape (icon + title + description) but
733 * uses a dedicated set of classes so each can evolve independently.
734 * ---------------------------------------------------------------- */
735
736 .os-ai__cmd-list {
737 display: flex;
738 flex-direction: column;
739 gap: 4px;
740 }
741
742 .os-ai__cmd-item {
743 appearance: none;
744 display: flex;
745 align-items: flex-start;
746 gap: 10px;
747 padding: 8px 10px;
748 background: transparent;
749 border: 1px solid transparent;
750 border-radius: 8px;
751 cursor: pointer;
752 text-align: start;
753 font: inherit;
754 color: inherit;
755 transition: background 0.1s, border-color 0.1s;
756 }
757
758 /*
759 * Single-line rows (no `.os-ai__cmd-desc` child) need the
760 * icon + label baseline-centered instead of top-aligned, otherwise
761 * the label floats to the top of the taller icon tile and reads as
762 * misaligned. `:has()` is widely supported in evergreen browsers the
763 * desktop shell already targets.
764 */
765 .os-ai__cmd-item:not(:has(.os-ai__cmd-desc)) {
766 align-items: center;
767 }
768
769 /* The lit row: an accent wash and edge at --os-ai-accent's strength,
770 over the row fill the palette names. Shared with the args-mode rows
771 below through the same two custom properties. */
772 .os-ai {
773 --_row-wash: color-mix(
774 in srgb,
775 var( --wp-admin-theme-color, #2271b1 ) calc( var( --os-ai-accent, 100% ) * 0.08 ),
776 var( --os-ui-surface, #fff )
777 );
778 --_row-edge: color-mix(
779 in srgb,
780 color-mix(
781 in srgb,
782 var( --wp-admin-theme-color, #2271b1 ) 25%,
783 var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) )
784 )
785 var( --os-ai-accent, 100% ),
786 transparent
787 );
788 --_row-fill: linear-gradient(
789 var( --os-ai-row-fill, transparent ),
790 var( --os-ai-row-fill, transparent )
791 );
792 }
793
794 .os-ai__cmd-item:hover,
795 .os-ai__cmd-item.is-selected {
796 background-color: var( --_row-wash );
797 background-image: var( --_row-fill );
798 border-color: var( --_row-edge );
799 }
800
801 /*
802 * When keyboard navigation is driving selection, suppress the :hover
803 * style on rows. Without this, arrowing the cursor past the row that
804 * happens to sit under the mouse pointer paints BOTH rows as active
805 * — the keyboard target from `.is-selected` and the pointer target
806 * from `:hover`. The JS toggles this class on the list container on
807 * ArrowUp/Down and clears it on the next real `mousemove`.
808 */
809 .os-ai__cmd-list--kb-nav .os-ai__cmd-item:hover {
810 background: transparent;
811 border-color: transparent;
812 }
813 .os-ai__cmd-list--kb-nav .os-ai__cmd-item.is-selected:hover {
814 background-color: var( --_row-wash );
815 background-image: var( --_row-fill );
816 border-color: var( --_row-edge );
817 }
818
819 .os-ai__cmd-item:focus-visible {
820 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
821 outline-offset: 2px;
822 }
823
824 .os-ai__cmd-icon {
825 flex-shrink: 0;
826 display: flex;
827 align-items: center;
828 justify-content: center;
829 width: 36px;
830 height: 36px;
831 font-size: 20px;
832 /* The glyph is the accent in every palette; only its tile follows
833 --os-ai-accent. */
834 color: var( --wp-admin-theme-color, #2271b1 );
835 background-color: color-mix(
836 in srgb,
837 var( --wp-admin-theme-color, #2271b1 ) calc( var( --os-ai-accent, 100% ) * 0.1 ),
838 var( --os-ui-surface, #fff )
839 );
840 background-image: linear-gradient(
841 var( --os-ai-tile-fill, transparent ),
842 var( --os-ai-tile-fill, transparent )
843 );
844 border-radius: 7px;
845 }
846
847 /*
848 * Dashicons ship at a 20px baseline; the tile was scaled up but
849 * dashicons glyphs need explicit sizing to follow.
850 */
851 .os-ai__cmd-icon.dashicons {
852 font-size: 22px;
853 width: 36px;
854 height: 36px;
855 line-height: 36px;
856 }
857
858 /*
859 * Raw-SVG icon slot (command bridge forwards rendered
860 * `@wordpress/icons` markup). Constrain the inline SVG so it sits
861 * centered inside the tile regardless of the viewBox the source icon
862 * shipped with.
863 */
864 .os-ai__cmd-icon--svg svg {
865 width: 24px;
866 height: 24px;
867 fill: currentColor;
868 }
869
870 .os-ai__cmd-body {
871 flex: 1;
872 display: flex;
873 flex-direction: column;
874 gap: 2px;
875 min-width: 0;
876 }
877
878 .os-ai__cmd-title {
879 font-size: 13px;
880 font-weight: 600;
881 color: var( --os-ui-fg, #1d2327 );
882 font-family: var( --os-ui-font-mono, Menlo, Consolas, monospace );
883 letter-spacing: -0.01em;
884 }
885
886 .os-ai__cmd-hint {
887 font-weight: 400;
888 color: var( --os-ui-fg-muted, #8c8f94 );
889 font-size: 12px;
890 margin-inline-start: 6px;
891 }
892
893 .os-ai__cmd-desc {
894 font-size: 11.5px;
895 color: var( --os-ui-fg-muted, #646970 );
896 line-height: 1.4;
897 font-family: inherit;
898 letter-spacing: 0;
899 }
900
901 /* Entity search results (posts, pages) — rendered in the same list as
902 commands but are navigable destinations, not actions. Use regular font
903 (not the monospace used for slash-commands) so they read as content
904 search results, matching the classic command palette UX. */
905 .os-ai__cmd-item.is-entity-result .os-ai__cmd-title {
906 font-family: inherit;
907 letter-spacing: 0;
908 }
909
910 /* Args mode — a single row showing the locked-in command. Same
911 visual language as the list item but with an "Enter to run" hint. */
912 .os-ai__cmd-active {
913 display: flex;
914 align-items: flex-start;
915 gap: 10px;
916 padding: 10px 12px;
917 background-color: var( --_row-wash );
918 background-image: var( --_row-fill );
919 border: 1px solid var( --_row-edge );
920 border-radius: 8px;
921 }
922
923 .os-ai__cmd-enter-hint {
924 font-size: 11px;
925 color: var( --os-ui-fg-muted, #646970 );
926 margin-top: 2px;
927 font-family: inherit;
928 }
929
930 .os-ai__cmd-enter-hint kbd {
931 font-family: inherit;
932 font-size: 10px;
933 padding: 1px 4px;
934 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.12 ) );
935 border-radius: 3px;
936 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.03 ) );
937 color: var( --os-ui-fg-muted, #646970 );
938 }
939
940 /* Suggestion list shown under the locked-in command in args mode.
941 Visually identical to the command picker rows but without the
942 border-highlight — the "active" glow already lives on the command
943 header directly above. */
944 .os-ai__cmd-suggest-list {
945 display: flex;
946 flex-direction: column;
947 gap: 2px;
948 margin-top: 6px;
949 }
950
951 .os-ai__cmd-suggest-item {
952 appearance: none;
953 display: flex;
954 align-items: flex-start;
955 gap: 10px;
956 padding: 6px 10px;
957 background: transparent;
958 border: 1px solid transparent;
959 border-radius: 6px;
960 cursor: pointer;
961 text-align: start;
962 font: inherit;
963 color: inherit;
964 transition: background 0.1s, border-color 0.1s;
965 }
966
967 .os-ai__cmd-suggest-item:hover,
968 .os-ai__cmd-suggest-item.is-selected {
969 background-color: var( --_row-wash );
970 background-image: var( --_row-fill );
971 border-color: var( --_row-edge );
972 }
973
974 .os-ai__cmd-suggest-label {
975 font-size: 13px;
976 font-weight: 500;
977 color: var( --os-ui-fg, #1d2327 );
978 }
979
980 /* Empty / "no matches" state for the palette */
981 .os-ai__state--empty {
982 color: var( --os-ui-fg-muted, #8c8f94 );
983 font-size: 13px;
984 }
985
986 /* -----------------------------------------------------------------
987 * Continue button (budget-exhausted state)
988 * ---------------------------------------------------------------- */
989
990 .os-ai__continue-btn {
991 appearance: none;
992 display: inline-flex;
993 align-items: center;
994 align-self: flex-start;
995 gap: 6px;
996 font: inherit;
997 font-size: 12px;
998 font-weight: 500;
999 padding: 6px 12px;
1000 border: 1px dashed var( --os-ui-border-strong, rgba( 0, 0, 0, 0.2 ) );
1001 border-radius: 6px;
1002 background: transparent;
1003 color: var( --os-ui-fg-muted, #50575e );
1004 cursor: pointer;
1005 transition: background 0.1s, border-color 0.1s, color 0.1s;
1006 }
1007
1008 .os-ai__continue-btn:hover {
1009 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) );
1010 border-color: var( --os-ui-border-strong, rgba( 0, 0, 0, 0.3 ) );
1011 color: var( --os-ui-fg, #1d2327 );
1012 }
1013