/** * OpenStation — AI Assistant spotlight overlay. * * A system-level command palette that floats above the entire shell, * including the WP admin bar. Visually modeled on macOS Spotlight and * Linear's command menu — frosted-glass backdrop, spring-physics panel * entrance, large search input, accent-tinted header. * * z-index 10000 puts this above --os-z-adminbar (9991) so the * overlay truly covers the full viewport. * * ## Desktop-theme awareness * * The overlay mounts on `document.body`, so it sits inside the * `body.os-desktop-theme-` half of a compiled theme's * doubled selector and every `--os-ui-*` token below resolves against the * active theme. Each site reads `var( --os-ui-x, )`, so an unthemed shell paints exactly as before. * * Getting this half-right is worse than not doing it at all: when the * text tones followed the theme but the panel stayed hardcoded white, * a dark theme's light `--os-ui-fg` landed on white and the results were * unreadable. Any new surface added here MUST take its background, * border and text from the palette — not from a literal. */ /* --------------------------------------------------------------------------- * Root overlay — full viewport, fades in/out * ------------------------------------------------------------------------- */ .os-ai { position: fixed; inset: 0; z-index: 10000; display: flex; align-items: flex-start; justify-content: center; /* Land the panel at ~16-18% from the top — feels like Spotlight. clamp keeps it 60px from the top on very short screens and caps at 180px on tall displays so it never feels too low. */ padding-top: clamp( 60px, 16vh, 180px ); padding-inline: 16px; padding-bottom: 40px; opacity: 0; pointer-events: none; transition: opacity 0.18s ease; } /* [hidden] keeps the element out of the a11y tree between sessions. Only removed while the overlay is visually visible (open or animating). */ .os-ai[hidden] { display: none; } .os-ai.is-open { opacity: 1; pointer-events: auto; } /* --------------------------------------------------------------------------- * Backdrop — frosted glass * ------------------------------------------------------------------------- */ .os-ai__backdrop { position: absolute; inset: 0; background-color: var( --os-ui-scrim, rgba( 0, 0, 0, 0.36 ) ); /* Desktop-theme SCRIM texture slot: unset resolves to none. */ background-image: var( --os-ui-scrim-image, none ); background-repeat: var( --os-ui-scrim-image-repeat, repeat ); background-size: var( --os-ui-scrim-image-size, auto ); background-position: var( --os-ui-scrim-image-position, center ); backdrop-filter: blur( 8px ) saturate( 0.75 ); -webkit-backdrop-filter: blur( 8px ) saturate( 0.75 ); /* Non-interactive: clicks in the dimmed area fall through to the overlay container, which closes the assistant (click-outside-to-dismiss). */ pointer-events: none; } /* --------------------------------------------------------------------------- * Panel — the visible card * ------------------------------------------------------------------------- */ .os-ai__panel { position: relative; /* sits above the backdrop */ width: 100%; max-width: 600px; /* Longhand on purpose: the DIALOG texture slot below owns background-image, and the shorthand would reset it. */ background-color: var( --os-ai-panel-bg, var( --os-ui-surface, rgba( 255, 255, 255, 0.97 ) ) ); /* Desktop-theme DIALOG texture slot: unset resolves to none. */ background-image: var( --os-ui-dialog-bg-image, none ); background-repeat: var( --os-ui-dialog-bg-image-repeat, repeat ); background-size: var( --os-ui-dialog-bg-image-size, auto ); background-position: var( --os-ui-dialog-bg-image-position, center ); color: var( --os-ui-fg, #1d2327 ); border-radius: 16px; overflow: hidden; /* Tight under-shadow + wide atmospheric shadow. The hairline ring is the panel's only edge, so it follows the palette; the two atmospheric shadows stay black because they read as depth against the wallpaper, not as part of the surface. */ box-shadow: 0 0 0 1px var( --os-ui-border, rgba( 0, 0, 0, 0.07 ) ), 0 4px 16px rgba( 0, 0, 0, 0.12 ), 0 24px 64px rgba( 0, 0, 0, 0.22 ); /* Spring-physics entrance: starts slightly small + shifted up, overshoots minimally, then settles at 1.0. */ transform: scale( 0.96 ) translateY( -8px ); transition: transform 0.28s cubic-bezier( 0.34, 1.56, 0.64, 1 ); will-change: transform; } .os-ai.is-open .os-ai__panel { transform: scale( 1 ) translateY( 0 ); } /* --------------------------------------------------------------------------- * Header — accent-tinted identity strip * ------------------------------------------------------------------------- */ .os-ai__header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; /* Gradient fades from a very faint accent tint to white — visible on fresh/blue schemes, subtle on light schemes. */ background: linear-gradient( to bottom, color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 8%, var( --os-ui-surface, #fff ) ), var( --os-ui-surface, #fff ) ); border-bottom: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) ); } .os-ai__header-icon { display: flex; align-items: center; color: var( --wp-admin-theme-color, #2271b1 ); /* Glow so the sparkle feels alive on dark or saturated themes */ filter: drop-shadow( 0 0 5px color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 55%, transparent ) ); } .os-ai__header-label { flex: 1; font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; color: var( --wp-admin-theme-color, #2271b1 ); } /* Mode switch (Commands ↔ AI) — a small segmented pill in the header. Shown only when AI is available; replaces the `/` shortcut. */ .os-ai__modes { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) ); } .os-ai__modes[hidden] { display: none; } .os-ai__mode { appearance: none; border: 0; cursor: pointer; padding: 3px 12px; border-radius: 999px; background: transparent; color: var( --os-ui-fg-muted, #50575e ); font-size: 11px; font-weight: 600; line-height: 1.6; } .os-ai__mode:hover { color: var( --os-ui-fg, #1d2327 ); } .os-ai__mode.is-active { /* Elevated first: the pill sits ON the header, which is already `--os-ui-surface`, so a theme that distinguishes the two keeps the active segment readable. Falls through to plain surface, which is the default look. */ background: var( --os-ui-surface-elevated, var( --os-ui-surface, #fff ) ); color: var( --wp-admin-theme-color, #2271b1 ); box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.12 ); } .os-ai__mode:focus-visible { outline: 2px solid var( --wp-admin-theme-color, #2271b1 ); outline-offset: 1px; } /* .os-ai__close styles moved to the bottom results section */ /* --------------------------------------------------------------------------- * Input row — the main interaction surface * ------------------------------------------------------------------------- */ .os-ai__input-wrap { display: flex; align-items: center; gap: 10px; padding: 14px 16px; /* Thin highlight at the bottom of the input area, visible when focused */ border-bottom: 1px solid transparent; transition: border-color 0.15s ease; } .os-ai__input-wrap:has( .os-ai__input:focus ) { border-bottom-color: var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) ); } /* Input-row sparkle — visible always, tints toward the accent on focus so the panel reads as "AI assistant" rather than "search box". */ .os-ai__input-icon { flex-shrink: 0; display: flex; align-items: center; color: var( --wp-admin-theme-color, #2271b1 ); opacity: 0.55; transition: opacity 0.18s ease, transform 0.18s ease; } .os-ai__input-wrap:has( .os-ai__input:focus ) .os-ai__input-icon { opacity: 1; transform: scale( 1.08 ); } /* * The search field is a raw `` in the parent shell, * so core's `forms.css` reaches it: * * input[type="text"], … { background-color: #fff; color: #2c3338; * border: 1px solid #8c8f94; * border-radius: 4px; * box-shadow: 0 0 0 transparent } * input[type="text"]:focus { border-color: #2271b1; * box-shadow: 0 0 0 1px #2271b1 } * * That weighs (0,1,1) — one type selector plus one attribute selector — * which beats a bare `.os-ai__input` at (0,1,0). The result * was a bordered white box floating inside the panel: core's chrome, * core's text colour, ignoring both the design intent (a chromeless * Spotlight-style field) and any active desktop theme. * * Scoping to the overlay root takes the selector to (0,2,0) and wins. * Same fix as the native-window form-control rule in window-chrome.css. */ .os-ai .os-ai__input { flex: 1; min-width: 0; appearance: none; border: none; border-radius: 0; outline: none; box-shadow: none; padding: 0; /* Matches the height core gave the control, so the row keeps its comfortable hit target now that core's box is gone. */ min-height: 30px; background: transparent; font: inherit; font-size: 17px; font-weight: 400; line-height: 1.4; color: var( --os-ui-fg, #1d2327 ); } /* Core paints a focus border + ring on the element itself; the focus affordance here is the input row's bottom hairline instead. */ .os-ai .os-ai__input:focus { border-color: transparent; box-shadow: none; outline: none; } .os-ai .os-ai__input::placeholder { color: var( --os-ui-fg-faint, #c3c4c7 ); font-weight: 400; } /* Submit button — hidden until the user types something, then springs in via opacity + scale transition. */ .os-ai__submit { flex-shrink: 0; display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: none; border-radius: 8px; background: var( --wp-admin-theme-color, #2271b1 ); color: var( --os-ui-fg-on-accent, #fff ); cursor: pointer; padding: 0; opacity: 0; transform: scale( 0.7 ); pointer-events: none; transition: opacity 0.2s ease, transform 0.2s cubic-bezier( 0.34, 1.56, 0.64, 1 ); } .os-ai__submit.has-value { opacity: 1; transform: scale( 1 ); pointer-events: auto; } .os-ai__submit:hover { filter: brightness( 1.1 ); } .os-ai__submit:active { transform: scale( 0.92 ); } .os-ai__submit:focus-visible { outline: 2px solid var( --wp-admin-theme-color, #2271b1 ); outline-offset: 2px; } /* --------------------------------------------------------------------------- * Footer — hints and keyboard shortcut legend * ------------------------------------------------------------------------- */ .os-ai__footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 16px 10px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.018 ) ); border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.05 ) ); } .os-ai__footer-hint { font-size: 11px; color: var( --os-ui-fg-muted, #a7aaad ); } /* --------------------------------------------------------------------------- * Admin bar AI button — ⌘K badge + sparkle icon * Styles applied via wp_add_inline_style( 'admin-bar', ... ) in PHP. * The styles here are for reference; the actual output lives in * includes/admin-bar.php :: openstation_enqueue_toggle_assets(). * ------------------------------------------------------------------------- */ /* (The admin-bar button styles are added inline in PHP to keep them on the admin-bar handle so they load even before os.js.) */ /* --------------------------------------------------------------------------- * Close button — × icon, top-right of header * ------------------------------------------------------------------------- */ .os-ai__close { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; appearance: none; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) ); border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) ); border-radius: 6px; cursor: pointer; color: var( --os-ui-fg-muted, #646970 ); padding: 0; flex-shrink: 0; transition: background 0.1s, color 0.1s, border-color 0.1s; } .os-ai__close:hover { background: var( --os-ui-hover, rgba( 0, 0, 0, 0.09 ) ); border-color: var( --os-ui-border-strong, rgba( 0, 0, 0, 0.2 ) ); color: var( --os-ui-fg, #1d2327 ); } .os-ai__close:focus-visible { outline: 2px solid var( --wp-admin-theme-color, #2271b1 ); outline-offset: 2px; } /* --------------------------------------------------------------------------- * Results / conversation area — three render modes: * • Suggestions (empty state, example prompts) * • Thinking (spinner + "Thinking…") * • Response (assistant bubble + entity card | admin links | nothing) * ------------------------------------------------------------------------- */ .os-ai__results { border-top: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.06 ) ); max-height: 420px; overflow-y: auto; overflow-x: hidden; padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; } /* ----------------------------------------------------------------- * Suggestion chips (empty state) * ---------------------------------------------------------------- */ .os-ai__suggestions-label { margin: 0 0 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var( --os-ui-fg-muted, #8c8f94 ); } .os-ai__suggestions-list { display: flex; flex-wrap: wrap; gap: 6px; } .os-ai__suggestion { appearance: none; border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) ); background: var( --os-ui-hover, rgba( 0, 0, 0, 0.02 ) ); border-radius: 999px; padding: 5px 12px; font: inherit; font-size: 12px; color: var( --os-ui-fg-muted, #50575e ); cursor: pointer; transition: background 0.12s, border-color 0.12s, color 0.12s; } .os-ai__suggestion:hover { background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 8%, var( --os-ui-surface, #fff ) ); border-color: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 30%, var( --os-ui-border, rgba( 0, 0, 0, 0.1 ) ) ); color: var( --wp-admin-theme-color, #2271b1 ); } /* ----------------------------------------------------------------- * Thinking / error state * ---------------------------------------------------------------- */ .os-ai__state { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var( --os-ui-fg-muted, #646970 ); } .os-ai__state--error { color: var( --os-ui-danger, #d63638 ); align-items: flex-start; } /* Inline "AI Settings" link inside the "AI not enabled" error — opens * OS Settings on the AI tab. Styled as a link within the surrounding * error text, not a standalone button: inherits the error color and * flows with the sentence. */ .os-ai__settings-link { appearance: none; border: 0; margin: 0; padding: 0; background: none; font: inherit; font-weight: 600; color: inherit; text-decoration: underline; text-underline-offset: 2px; } .os-ai__settings-link:hover { text-decoration: none; } /* The shell forces `cursor: default !important` on every clickable * surface (see the cursor policy at the top of desktop.css). This * recovery link is an explicit, user-approved exception: it keeps a * pointer so it reads as the actionable link it is. Scoped under * `body.os-active` so it out-specifies the policy's * `!important` rule, same trick the resize-handle exceptions use. */ body.os-active .os-ai__settings-link { cursor: pointer !important; } @keyframes os-spin { to { transform: rotate( 360deg ); } } .os-ai__spinner-icon { animation: os-spin 0.9s linear infinite; flex-shrink: 0; color: var( --wp-admin-theme-color, #2271b1 ); } /* ----------------------------------------------------------------- * Assistant message bubble — prefix for every response * ---------------------------------------------------------------- */ .os-ai__bubble { display: flex; align-items: flex-start; gap: 10px; } .os-ai__bubble-icon { flex-shrink: 0; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; background: color-mix( in srgb, var( --wp-admin-theme-color, #2271b1 ) 12%, var( --os-ui-surface, #fff ) ); color: var( --wp-admin-theme-color, #2271b1 ); } .os-ai__bubble-text { padding-top: 3px; font-size: 13px; line-height: 1.5; color: var( --os-ui-fg, #1d2327 ); flex: 1; min-width: 0; } /* Markdown output inside the assistant bubble — the renderer emits

,