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

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

1,103 lines 30.5 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 /* ---------------------------------------------------------------------------
139 * Header — accent-tinted identity strip
140 * ------------------------------------------------------------------------- */
141
142 .os-ai__header {
143 display: flex;
144 align-items: center;
145 gap: 8px;
146 padding: 10px 14px;
147 /* A faint accent tint fading to the surface, at --os-ai-accent's
148 strength: flat when that is 0. */
149 background: linear-gradient(
150 to bottom,
151 color-mix(
152 in srgb,
153 var( --wp-admin-theme-color, #2271b1 ) calc( var( --os-ai-accent, 100% ) * 0.08 ),
154 var( --os-ui-surface, #fff )
155 ),
156 var( --os-ui-surface, #fff )
157 );
158 border-bottom: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) );
159 }
160
161 .os-ai__header-icon {
162 display: flex;
163 align-items: center;
164 color: color-mix(
165 in srgb,
166 var( --wp-admin-theme-color, #2271b1 ) var( --os-ai-accent, 100% ),
167 var( --os-ui-fg, #1d2327 )
168 );
169 /* The glow fades out with the accent, so a neutral mark has none. */
170 filter: drop-shadow(
171 0 0 5px
172 color-mix(
173 in srgb,
174 var( --wp-admin-theme-color, #2271b1 ) calc( var( --os-ai-accent, 100% ) * 0.55 ),
175 transparent
176 )
177 );
178 }
179
180 /* Sentence case at body size, like a window title, rather than a
181 tracked uppercase eyebrow. */
182 .os-ai__header-label {
183 flex: 1;
184 font-size: 13px;
185 font-weight: 500;
186 color: color-mix(
187 in srgb,
188 var( --wp-admin-theme-color, #2271b1 ) var( --os-ai-accent, 100% ),
189 var( --os-ui-fg, #1d2327 )
190 );
191 }
192
193 /* Mode switch (Commands ↔ AI) — a small segmented pill in the header.
194 Shown only when AI is available; replaces the `/` shortcut. */
195 .os-ai__modes {
196 display: inline-flex;
197 gap: 2px;
198 padding: 2px;
199 border-radius: 999px;
200 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
201 }
202
203 .os-ai__modes[hidden] {
204 display: none;
205 }
206
207 .os-ai__mode {
208 appearance: none;
209 border: 0;
210 cursor: pointer;
211 padding: 3px 12px;
212 border-radius: 999px;
213 background: transparent;
214 color: var( --os-ui-fg-muted, #50575e );
215 font-size: 11px;
216 font-weight: 600;
217 line-height: 1.6;
218 }
219
220 .os-ai__mode:hover {
221 color: var( --os-ui-fg, #1d2327 );
222 }
223
224 .os-ai__mode.is-active {
225 /* Elevated first: the pill sits ON the header, which is already
226 `--os-ui-surface`, so a theme that distinguishes the two keeps the
227 active segment readable. Falls through to plain surface, which is
228 the default look. */
229 background: var( --os-ui-surface-elevated, var( --os-ui-surface, #fff ) );
230 color: var( --wp-admin-theme-color, #2271b1 );
231 box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.12 );
232 }
233
234 .os-ai__mode:focus-visible {
235 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
236 outline-offset: 1px;
237 }
238
239 /* .os-ai__close styles moved to the bottom results section */
240
241 /* ---------------------------------------------------------------------------
242 * Input row — the main interaction surface
243 * ------------------------------------------------------------------------- */
244
245 .os-ai__input-wrap {
246 display: flex;
247 align-items: center;
248 gap: 10px;
249 padding: 14px 16px;
250 /* Thin highlight at the bottom of the input area, visible when focused */
251 border-bottom: 1px solid transparent;
252 transition: border-color 0.15s ease;
253 }
254
255 .os-ai__input-wrap:has( .os-ai__input:focus ) {
256 border-bottom-color: var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) );
257 }
258
259 /* Input-row icon: dim at rest, full strength on focus. At rest it is
260 the accent at 55% alpha, mixed by --os-ai-accent towards the faint
261 text colour; focused, the accent mixed towards the text colour. */
262 .os-ai__input-icon {
263 flex-shrink: 0;
264 display: flex;
265 align-items: center;
266 color: color-mix(
267 in srgb,
268 color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 55%, transparent ) var( --os-ai-accent, 100% ),
269 var( --os-ui-fg-faint, #c3c4c7 )
270 );
271 transition: color 0.18s ease, transform 0.18s ease;
272 }
273
274 .os-ai__input-wrap:has( .os-ai__input:focus ) .os-ai__input-icon {
275 color: color-mix(
276 in srgb,
277 var( --wp-admin-theme-color, #2271b1 ) var( --os-ai-accent, 100% ),
278 var( --os-ui-fg, #1d2327 )
279 );
280 transform: scale( 1.08 );
281 }
282
283 /*
284 * The search field is a raw `<input type="text">` in the parent shell,
285 * so core's `forms.css` reaches it:
286 *
287 * input[type="text"], … { background-color: #fff; color: #2c3338;
288 * border: 1px solid #8c8f94;
289 * border-radius: 4px;
290 * box-shadow: 0 0 0 transparent }
291 * input[type="text"]:focus { border-color: #2271b1;
292 * box-shadow: 0 0 0 1px #2271b1 }
293 *
294 * That weighs (0,1,1) — one type selector plus one attribute selector —
295 * which beats a bare `.os-ai__input` at (0,1,0). The result
296 * was a bordered white box floating inside the panel: core's chrome,
297 * core's text colour, ignoring both the design intent (a chromeless
298 * Spotlight-style field) and any active desktop theme.
299 *
300 * Scoping to the overlay root takes the selector to (0,2,0) and wins.
301 * Same fix as the native-window form-control rule in window-chrome.css.
302 */
303 .os-ai .os-ai__input {
304 flex: 1;
305 min-width: 0;
306 appearance: none;
307 border: none;
308 border-radius: 0;
309 outline: none;
310 box-shadow: none;
311 padding: 0;
312 /* Matches the height core gave the control, so the row keeps its
313 comfortable hit target now that core's box is gone. */
314 min-height: 30px;
315 background: transparent;
316 font: inherit;
317 font-size: 17px;
318 font-weight: 400;
319 line-height: 1.4;
320 color: var( --os-ui-fg, #1d2327 );
321 }
322
323 /* Core paints a focus border + ring on the element itself; the focus
324 affordance here is the input row's bottom hairline instead. */
325 .os-ai .os-ai__input:focus {
326 border-color: transparent;
327 box-shadow: none;
328 outline: none;
329 }
330
331 .os-ai .os-ai__input::placeholder {
332 color: var( --os-ui-fg-faint, #c3c4c7 );
333 font-weight: 400;
334 }
335
336 /* Submit button — hidden until the user types something, then
337 springs in via opacity + scale transition. */
338 .os-ai__submit {
339 flex-shrink: 0;
340 display: flex;
341 align-items: center;
342 justify-content: center;
343 width: 30px;
344 height: 30px;
345 border: none;
346 border-radius: 8px;
347 /* An accent key at --os-ai-accent's strength. At 0 it is an icon tile
348 like the command rows' (the neutral fill, the glyph in the accent);
349 at 100% the filled accent button with its ink. */
350 background-color: color-mix(
351 in srgb,
352 var( --wp-admin-theme-color, #2271b1 ) var( --os-ai-accent, 100% ),
353 transparent
354 );
355 background-image: linear-gradient(
356 var( --os-ai-tile-fill, transparent ),
357 var( --os-ai-tile-fill, transparent )
358 );
359 color: color-mix(
360 in srgb,
361 var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) ) var( --os-ai-accent, 100% ),
362 var( --wp-admin-theme-color, #2271b1 )
363 );
364 cursor: pointer;
365 padding: 0;
366 opacity: 0;
367 transform: scale( 0.7 );
368 pointer-events: none;
369 transition:
370 opacity 0.2s ease,
371 transform 0.2s cubic-bezier( 0.34, 1.56, 0.64, 1 );
372 }
373
374 .os-ai__submit.has-value {
375 opacity: 1;
376 transform: scale( 1 );
377 pointer-events: auto;
378 }
379
380 .os-ai__submit:hover {
381 filter: brightness( 1.1 );
382 }
383
384 .os-ai__submit:active {
385 transform: scale( 0.92 );
386 }
387
388 .os-ai__submit:focus-visible {
389 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
390 outline-offset: 2px;
391 }
392
393 /* ---------------------------------------------------------------------------
394 * Footer — hints and keyboard shortcut legend
395 * ------------------------------------------------------------------------- */
396
397 .os-ai__footer {
398 display: flex;
399 align-items: center;
400 justify-content: space-between;
401 gap: 8px;
402 padding: 8px 16px 10px;
403 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.018 ) );
404 border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.05 ) );
405 }
406
407 .os-ai__footer-hint {
408 font-size: 11px;
409 color: var( --os-ui-fg-muted, #a7aaad );
410 }
411
412 /* ---------------------------------------------------------------------------
413 * Admin bar AI button — ⌘K badge + sparkle icon
414 * Styles applied via wp_add_inline_style( 'admin-bar', ... ) in PHP.
415 * The styles here are for reference; the actual output lives in
416 * includes/admin-bar.php :: openstation_enqueue_toggle_assets().
417 * ------------------------------------------------------------------------- */
418
419 /* (The admin-bar button styles are added inline in PHP to keep them
420 on the admin-bar handle so they load even before os.js.) */
421
422 /* ---------------------------------------------------------------------------
423 * Close button — × icon, top-right of header
424 * ------------------------------------------------------------------------- */
425
426 .os-ai__close {
427 display: inline-flex;
428 align-items: center;
429 justify-content: center;
430 width: 28px;
431 height: 28px;
432 appearance: none;
433 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
434 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) );
435 border-radius: 6px;
436 cursor: pointer;
437 color: var( --os-ui-fg-muted, #646970 );
438 padding: 0;
439 flex-shrink: 0;
440 transition: background 0.1s, color 0.1s, border-color 0.1s;
441 }
442
443 .os-ai__close:hover {
444 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.09 ) );
445 border-color: var( --os-ui-border-strong, rgba( 0, 0, 0, 0.2 ) );
446 color: var( --os-ui-fg, #1d2327 );
447 }
448
449 .os-ai__close:focus-visible {
450 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
451 outline-offset: 2px;
452 }
453
454 /* ---------------------------------------------------------------------------
455 * Results / conversation area — three render modes:
456 * • Suggestions (empty state, example prompts)
457 * • Thinking (spinner + "Thinking…")
458 * • Response (assistant bubble + entity card | admin links | nothing)
459 * ------------------------------------------------------------------------- */
460
461 .os-ai__results {
462 border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) );
463 max-height: 420px;
464 overflow-y: auto;
465 overflow-x: hidden;
466 padding: 14px 16px;
467 display: flex;
468 flex-direction: column;
469 gap: 12px;
470 }
471
472 /* -----------------------------------------------------------------
473 * Suggestion chips (empty state)
474 * ---------------------------------------------------------------- */
475
476 .os-ai__suggestions-label {
477 margin: 0 0 4px;
478 font-size: 11px;
479 font-weight: 600;
480 text-transform: uppercase;
481 letter-spacing: 0.08em;
482 color: var( --os-ui-fg-muted, #8c8f94 );
483 }
484
485 .os-ai__suggestions-list {
486 display: flex;
487 flex-wrap: wrap;
488 gap: 6px;
489 }
490
491 .os-ai__suggestion {
492 appearance: none;
493 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) );
494 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
495 border-radius: 999px;
496 padding: 5px 12px;
497 font: inherit;
498 font-size: 12px;
499 color: var( --os-ui-fg-muted, #50575e );
500 cursor: pointer;
501 transition: background 0.12s, border-color 0.12s, color 0.12s;
502 }
503
504 .os-ai__suggestion:hover {
505 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 8%, var( --os-ui-surface, #fff ) );
506 border-color: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 30%, var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) ) );
507 color: var( --wp-admin-theme-color, #2271b1 );
508 }
509
510 /* -----------------------------------------------------------------
511 * Thinking / error state
512 * ---------------------------------------------------------------- */
513
514 .os-ai__state {
515 display: flex;
516 align-items: center;
517 gap: 10px;
518 font-size: 13px;
519 color: var( --os-ui-fg-muted, #646970 );
520 }
521
522 .os-ai__state--error {
523 color: var( --os-ui-danger, #d63638 );
524 align-items: flex-start;
525 }
526
527 /* Inline "AI Settings" link inside the "AI not enabled" error — opens
528 * OS Settings on the AI tab. Styled as a link within the surrounding
529 * error text, not a standalone button: inherits the error color and
530 * flows with the sentence. */
531 .os-ai__settings-link {
532 appearance: none;
533 border: 0;
534 margin: 0;
535 padding: 0;
536 background: none;
537 font: inherit;
538 font-weight: 600;
539 color: inherit;
540 text-decoration: underline;
541 text-underline-offset: 2px;
542 }
543
544 .os-ai__settings-link:hover {
545 text-decoration: none;
546 }
547
548 /* The shell forces `cursor: default !important` on every clickable
549 * surface (see the cursor policy at the top of desktop.css). This
550 * recovery link is an explicit, user-approved exception: it keeps a
551 * pointer so it reads as the actionable link it is. Scoped under
552 * `body.os-active` so it out-specifies the policy's
553 * `!important` rule, same trick the resize-handle exceptions use. */
554 body.os-active .os-ai__settings-link {
555 cursor: pointer !important;
556 }
557
558 @keyframes os-spin {
559 to { transform: rotate( 360deg ); }
560 }
561
562 .os-ai__spinner-icon {
563 animation: os-spin 0.9s linear infinite;
564 flex-shrink: 0;
565 color: var( --wp-admin-theme-color, #2271b1 );
566 }
567
568 /* -----------------------------------------------------------------
569 * Assistant message bubble — prefix for every response
570 * ---------------------------------------------------------------- */
571
572 .os-ai__bubble {
573 display: flex;
574 align-items: flex-start;
575 gap: 10px;
576 }
577
578 .os-ai__bubble-icon {
579 flex-shrink: 0;
580 display: flex;
581 align-items: center;
582 justify-content: center;
583 width: 24px;
584 height: 24px;
585 border-radius: 50%;
586 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 12%, var( --os-ui-surface, #fff ) );
587 color: var( --wp-admin-theme-color, #2271b1 );
588 }
589
590 .os-ai__bubble-text {
591 padding-top: 3px;
592 font-size: 13px;
593 line-height: 1.5;
594 color: var( --os-ui-fg, #1d2327 );
595 flex: 1;
596 min-width: 0;
597 }
598
599 /* Markdown output inside the assistant bubble — the renderer emits
600 <p>, <ul>/<ol>/<li>, <strong>, <em>, <code>, <a>. Reset margins on
601 outer paragraphs so single-paragraph replies (the common case)
602 don't get top/bottom spacing; list items get the usual disc/decimal
603 markers inside a compact indent. */
604 .os-ai__bubble-text > p,
605 .os-ai__bubble-text > ul,
606 .os-ai__bubble-text > ol {
607 margin: 0 0 6px;
608 }
609 .os-ai__bubble-text > :last-child {
610 margin-bottom: 0;
611 }
612 .os-ai__bubble-text ul,
613 .os-ai__bubble-text ol {
614 padding-inline-start: 20px;
615 }
616 .os-ai__bubble-text li {
617 margin-bottom: 2px;
618 }
619 .os-ai__bubble-text code {
620 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
621 padding: 1px 5px;
622 border-radius: 4px;
623 font-family: var( --os-ui-font-mono, Menlo, Consolas, monospace );
624 font-size: 11.5px;
625 }
626 .os-ai__bubble-text a {
627 color: var( --wp-admin-theme-color, #2271b1 );
628 text-decoration: underline;
629 text-underline-offset: 2px;
630 }
631 .os-ai__bubble-text a:hover {
632 text-decoration: none;
633 }
634 .os-ai__bubble-text strong {
635 font-weight: 600;
636 color: var( --os-ui-fg, #1d2327 );
637 }
638
639 /* -----------------------------------------------------------------
640 * Entity card — shown under the bubble when answer_type=entity
641 * ---------------------------------------------------------------- */
642
643 .os-ai__entity {
644 display: flex;
645 flex-direction: column;
646 gap: 8px;
647 padding: 12px 14px;
648 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
649 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
650 border-radius: 10px;
651 }
652
653 .os-ai__entity-header {
654 display: flex;
655 align-items: center;
656 gap: 8px;
657 flex-wrap: wrap;
658 }
659
660 .os-ai__entity-topic {
661 display: inline-block;
662 font-size: 10px;
663 font-weight: 600;
664 text-transform: uppercase;
665 letter-spacing: 0.07em;
666 color: var( --wp-admin-theme-color, #2271b1 );
667 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 10%, transparent );
668 padding: 2px 7px;
669 border-radius: 999px;
670 }
671
672 .os-ai__entity-type {
673 font-size: 10px;
674 font-weight: 600;
675 text-transform: uppercase;
676 letter-spacing: 0.07em;
677 color: var( --os-ui-fg-muted, #8c8f94 );
678 }
679
680 .os-ai__entity-title {
681 margin: 0;
682 font-size: 14px;
683 font-weight: 600;
684 color: var( --os-ui-fg, #1d2327 );
685 line-height: 1.35;
686 }
687
688 .os-ai__entity-summary {
689 margin: 0;
690 font-size: 12px;
691 line-height: 1.5;
692 color: var( --os-ui-fg-muted, #50575e );
693 }
694
695 /* Primary action — opens the entity in a legacy iframe window inside
696 the desktop (wp-admin edit URL). */
697 .os-ai__entity-open {
698 appearance: none;
699 display: inline-flex;
700 align-items: center;
701 gap: 6px;
702 align-self: flex-start;
703 padding: 6px 14px;
704 border: none;
705 border-radius: 8px;
706 background: var( --wp-admin-theme-color, #2271b1 );
707 color: var( --os-ui-accent-ink, var( --os-ui-fg-on-accent, #fff ) );
708 font: inherit;
709 font-size: 12px;
710 font-weight: 500;
711 cursor: pointer;
712 transition: filter 0.1s, transform 0.1s;
713 }
714
715 .os-ai__entity-open:hover {
716 filter: brightness( 1.1 );
717 }
718
719 .os-ai__entity-open:active {
720 transform: scale( 0.97 );
721 }
722
723 .os-ai__entity-open:focus-visible {
724 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
725 outline-offset: 2px;
726 }
727
728 .os-ai__entity-open svg {
729 margin-inline-start: 2px;
730 }
731
732 /* -----------------------------------------------------------------
733 * Admin-links list — shown when answer_type=navigation
734 * ---------------------------------------------------------------- */
735
736 .os-ai__admin-links {
737 display: flex;
738 flex-direction: column;
739 gap: 6px;
740 }
741
742 .os-ai__admin-link {
743 appearance: none;
744 display: flex;
745 align-items: center;
746 gap: 12px;
747 padding: 10px 12px;
748 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) );
749 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
750 border-radius: 10px;
751 cursor: pointer;
752 text-align: start;
753 font: inherit;
754 color: inherit;
755 transition: background 0.12s, border-color 0.12s, transform 0.08s;
756 }
757
758 .os-ai__admin-link:hover {
759 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 6%, var( --os-ui-surface, #fff ) );
760 border-color: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 25%, var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) ) );
761 }
762
763 .os-ai__admin-link:active {
764 transform: scale( 0.99 );
765 }
766
767 .os-ai__admin-link:focus-visible {
768 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
769 outline-offset: 2px;
770 }
771
772 .os-ai__admin-link-icon {
773 flex-shrink: 0;
774 display: flex;
775 align-items: center;
776 justify-content: center;
777 width: 32px;
778 height: 32px;
779 font-size: 18px;
780 color: var( --wp-admin-theme-color, #2271b1 );
781 background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 10%, var( --os-ui-surface, #fff ) );
782 border-radius: 8px;
783 }
784
785 .os-ai__admin-link-body {
786 flex: 1;
787 display: flex;
788 flex-direction: column;
789 gap: 2px;
790 min-width: 0;
791 }
792
793 .os-ai__admin-link-title {
794 font-size: 13px;
795 font-weight: 600;
796 color: var( --os-ui-fg, #1d2327 );
797 }
798
799 .os-ai__admin-link-desc {
800 font-size: 11px;
801 color: var( --os-ui-fg-muted, #646970 );
802 line-height: 1.4;
803 overflow: hidden;
804 text-overflow: ellipsis;
805 white-space: nowrap;
806 }
807
808 .os-ai__admin-link-arrow {
809 flex-shrink: 0;
810 color: var( --os-ui-fg-faint, #c3c4c7 );
811 transition: color 0.12s, transform 0.12s;
812 }
813
814 .os-ai__admin-link:hover .os-ai__admin-link-arrow {
815 color: var( --wp-admin-theme-color, #2271b1 );
816 transform: translateX( 2px );
817 }
818
819 /* -----------------------------------------------------------------
820 * Command palette — shown when the user types "/" in the input.
821 * Filtered list of plugin-registered slash-commands. Mirrors the
822 * admin-link list's visual shape (icon + title + description) but
823 * uses a dedicated set of classes so each can evolve independently.
824 * ---------------------------------------------------------------- */
825
826 .os-ai__cmd-list {
827 display: flex;
828 flex-direction: column;
829 gap: 4px;
830 }
831
832 .os-ai__cmd-item {
833 appearance: none;
834 display: flex;
835 align-items: flex-start;
836 gap: 10px;
837 padding: 8px 10px;
838 background: transparent;
839 border: 1px solid transparent;
840 border-radius: 8px;
841 cursor: pointer;
842 text-align: start;
843 font: inherit;
844 color: inherit;
845 transition: background 0.1s, border-color 0.1s;
846 }
847
848 /*
849 * Single-line rows (no `.os-ai__cmd-desc` child) need the
850 * icon + label baseline-centered instead of top-aligned, otherwise
851 * the label floats to the top of the taller icon tile and reads as
852 * misaligned. `:has()` is widely supported in evergreen browsers the
853 * desktop shell already targets.
854 */
855 .os-ai__cmd-item:not(:has(.os-ai__cmd-desc)) {
856 align-items: center;
857 }
858
859 /* The lit row: an accent wash and edge at --os-ai-accent's strength,
860 over the row fill the palette names. Shared with the args-mode rows
861 below through the same two custom properties. */
862 .os-ai {
863 --_row-wash: color-mix(
864 in srgb,
865 var( --wp-admin-theme-color, #2271b1 ) calc( var( --os-ai-accent, 100% ) * 0.08 ),
866 var( --os-ui-surface, #fff )
867 );
868 --_row-edge: color-mix(
869 in srgb,
870 color-mix(
871 in srgb,
872 var( --wp-admin-theme-color, #2271b1 ) 25%,
873 var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) )
874 )
875 var( --os-ai-accent, 100% ),
876 transparent
877 );
878 --_row-fill: linear-gradient(
879 var( --os-ai-row-fill, transparent ),
880 var( --os-ai-row-fill, transparent )
881 );
882 }
883
884 .os-ai__cmd-item:hover,
885 .os-ai__cmd-item.is-selected {
886 background-color: var( --_row-wash );
887 background-image: var( --_row-fill );
888 border-color: var( --_row-edge );
889 }
890
891 /*
892 * When keyboard navigation is driving selection, suppress the :hover
893 * style on rows. Without this, arrowing the cursor past the row that
894 * happens to sit under the mouse pointer paints BOTH rows as active
895 * — the keyboard target from `.is-selected` and the pointer target
896 * from `:hover`. The JS toggles this class on the list container on
897 * ArrowUp/Down and clears it on the next real `mousemove`.
898 */
899 .os-ai__cmd-list--kb-nav .os-ai__cmd-item:hover {
900 background: transparent;
901 border-color: transparent;
902 }
903 .os-ai__cmd-list--kb-nav .os-ai__cmd-item.is-selected:hover {
904 background-color: var( --_row-wash );
905 background-image: var( --_row-fill );
906 border-color: var( --_row-edge );
907 }
908
909 .os-ai__cmd-item:focus-visible {
910 outline: 2px solid var( --wp-admin-theme-color, #2271b1 );
911 outline-offset: 2px;
912 }
913
914 .os-ai__cmd-icon {
915 flex-shrink: 0;
916 display: flex;
917 align-items: center;
918 justify-content: center;
919 width: 36px;
920 height: 36px;
921 font-size: 20px;
922 /* The glyph is the accent in every palette; only its tile follows
923 --os-ai-accent. */
924 color: var( --wp-admin-theme-color, #2271b1 );
925 background-color: color-mix(
926 in srgb,
927 var( --wp-admin-theme-color, #2271b1 ) calc( var( --os-ai-accent, 100% ) * 0.1 ),
928 var( --os-ui-surface, #fff )
929 );
930 background-image: linear-gradient(
931 var( --os-ai-tile-fill, transparent ),
932 var( --os-ai-tile-fill, transparent )
933 );
934 border-radius: 7px;
935 }
936
937 /*
938 * Dashicons ship at a 20px baseline; the tile was scaled up but
939 * dashicons glyphs need explicit sizing to follow.
940 */
941 .os-ai__cmd-icon.dashicons {
942 font-size: 22px;
943 width: 36px;
944 height: 36px;
945 line-height: 36px;
946 }
947
948 /*
949 * Raw-SVG icon slot (command bridge forwards rendered
950 * `@wordpress/icons` markup). Constrain the inline SVG so it sits
951 * centered inside the tile regardless of the viewBox the source icon
952 * shipped with.
953 */
954 .os-ai__cmd-icon--svg svg {
955 width: 24px;
956 height: 24px;
957 fill: currentColor;
958 }
959
960 .os-ai__cmd-body {
961 flex: 1;
962 display: flex;
963 flex-direction: column;
964 gap: 2px;
965 min-width: 0;
966 }
967
968 .os-ai__cmd-title {
969 font-size: 13px;
970 font-weight: 600;
971 color: var( --os-ui-fg, #1d2327 );
972 font-family: var( --os-ui-font-mono, Menlo, Consolas, monospace );
973 letter-spacing: -0.01em;
974 }
975
976 .os-ai__cmd-hint {
977 font-weight: 400;
978 color: var( --os-ui-fg-muted, #8c8f94 );
979 font-size: 12px;
980 margin-inline-start: 6px;
981 }
982
983 .os-ai__cmd-desc {
984 font-size: 11.5px;
985 color: var( --os-ui-fg-muted, #646970 );
986 line-height: 1.4;
987 font-family: inherit;
988 letter-spacing: 0;
989 }
990
991 /* Entity search results (posts, pages) — rendered in the same list as
992 commands but are navigable destinations, not actions. Use regular font
993 (not the monospace used for slash-commands) so they read as content
994 search results, matching the classic command palette UX. */
995 .os-ai__cmd-item.is-entity-result .os-ai__cmd-title {
996 font-family: inherit;
997 letter-spacing: 0;
998 }
999
1000 /* Args mode — a single row showing the locked-in command. Same
1001 visual language as the list item but with an "Enter to run" hint. */
1002 .os-ai__cmd-active {
1003 display: flex;
1004 align-items: flex-start;
1005 gap: 10px;
1006 padding: 10px 12px;
1007 background-color: var( --_row-wash );
1008 background-image: var( --_row-fill );
1009 border: 1px solid var( --_row-edge );
1010 border-radius: 8px;
1011 }
1012
1013 .os-ai__cmd-enter-hint {
1014 font-size: 11px;
1015 color: var( --os-ui-fg-muted, #646970 );
1016 margin-top: 2px;
1017 font-family: inherit;
1018 }
1019
1020 .os-ai__cmd-enter-hint kbd {
1021 font-family: inherit;
1022 font-size: 10px;
1023 padding: 1px 4px;
1024 border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.12 ) );
1025 border-radius: 3px;
1026 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.03 ) );
1027 color: var( --os-ui-fg-muted, #646970 );
1028 }
1029
1030 /* Suggestion list shown under the locked-in command in args mode.
1031 Visually identical to the command picker rows but without the
1032 border-highlight — the "active" glow already lives on the command
1033 header directly above. */
1034 .os-ai__cmd-suggest-list {
1035 display: flex;
1036 flex-direction: column;
1037 gap: 2px;
1038 margin-top: 6px;
1039 }
1040
1041 .os-ai__cmd-suggest-item {
1042 appearance: none;
1043 display: flex;
1044 align-items: flex-start;
1045 gap: 10px;
1046 padding: 6px 10px;
1047 background: transparent;
1048 border: 1px solid transparent;
1049 border-radius: 6px;
1050 cursor: pointer;
1051 text-align: start;
1052 font: inherit;
1053 color: inherit;
1054 transition: background 0.1s, border-color 0.1s;
1055 }
1056
1057 .os-ai__cmd-suggest-item:hover,
1058 .os-ai__cmd-suggest-item.is-selected {
1059 background-color: var( --_row-wash );
1060 background-image: var( --_row-fill );
1061 border-color: var( --_row-edge );
1062 }
1063
1064 .os-ai__cmd-suggest-label {
1065 font-size: 13px;
1066 font-weight: 500;
1067 color: var( --os-ui-fg, #1d2327 );
1068 }
1069
1070 /* Empty / "no matches" state for the palette */
1071 .os-ai__state--empty {
1072 color: var( --os-ui-fg-muted, #8c8f94 );
1073 font-size: 13px;
1074 }
1075
1076 /* -----------------------------------------------------------------
1077 * Continue button (budget-exhausted state)
1078 * ---------------------------------------------------------------- */
1079
1080 .os-ai__continue-btn {
1081 appearance: none;
1082 display: inline-flex;
1083 align-items: center;
1084 align-self: flex-start;
1085 gap: 6px;
1086 font: inherit;
1087 font-size: 12px;
1088 font-weight: 500;
1089 padding: 6px 12px;
1090 border: 1px dashed var( --os-ui-border-strong, rgba( 0, 0, 0, 0.2 ) );
1091 border-radius: 6px;
1092 background: transparent;
1093 color: var( --os-ui-fg-muted, #50575e );
1094 cursor: pointer;
1095 transition: background 0.1s, border-color 0.1s, color 0.1s;
1096 }
1097
1098 .os-ai__continue-btn:hover {
1099 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) );
1100 border-color: var( --os-ui-border-strong, rgba( 0, 0, 0, 0.3 ) );
1101 color: var( --os-ui-fg, #1d2327 );
1102 }
1103