@import 'tailwindcss'; @plugin '@tailwindcss/typography'; @import 'tw-shimmer'; @import 'katex/dist/katex.min.css'; @import '@vscode/codicons/dist/codicon.css'; /* Titlebar clusters: 24×24 hit targets, 13.9px glyphs. codicon.css is unlayered (`font: 16px/1`); Tailwind utilities in @layer lose that fight. */ .titlebar-icon-button .codicon[class*='codicon-'] { font-size: var(--titlebar-icon-size); } /* v4 gates hover: on @media (hover: hover). Some Windows hosts with a digitizer answer false even with a mouse, so hover-reveal controls stay opacity-0 (clickable, invisible). Trust :hover itself. */ @custom-variant hover (&:hover); @custom-variant dark (&:is(.dark *)); /* Blanket reduced-motion override: kill ALL CSS animations and transitions when the user (or the E2E test harness via prefers-reduced-motion) requests it. Per-component @media rules below handle specific cases; this catches everything else so overlays and loading bars resolve instantly instead of being caught mid-fade by a screenshot. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } /* Pause continuous animations only when the window is hidden/minimized, not merely unfocused. main.tsx owns this attribute for the primary window; wake/pet overlays retain their purpose-built visibility behavior. */ :root[data-renderer-animations-paused] :is( .shimmer, .quest-glow, .pet-egg, .pet-egg__glow, .pet-egg-shadow, .pet-wobble, .progress-slide, .kanban-arc, .glyph-spinner__strip ), :root[data-renderer-animations-paused] .arc-border::before, :root[data-renderer-animations-paused] [data-slot='aui_assistant-message-content'] .aui-md [data-slot='code-card'][data-streaming='true'] { animation-play-state: paused !important; } /* Sidebar sections: tall viewports give each its own scroller; compact ones (this variant) flatten everything into one shared scroll. See ChatSidebar. */ @custom-variant compact (@media (max-height: 768px)); @font-face { font-family: 'Collapse'; font-style: normal; font-weight: 700; font-display: swap; src: url('../../../node_modules/@nous-research/ui/dist/fonts/Collapse-Bold.woff2') format('woff2'); } /* JetBrains Mono — bundled terminal font (Apache-2.0) so bold/italic share the regular face's metrics instead of squeezing against a system fallback. */ @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/JetBrainsMono-Regular.woff2') format('woff2'); } @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/JetBrainsMono-Bold.woff2') format('woff2'); } @font-face { font-family: 'JetBrains Mono'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/JetBrainsMono-Italic.woff2') format('woff2'); } @theme inline { --color-background: var(--dt-background); --color-foreground: var(--dt-foreground); --color-card: var(--dt-card); --color-card-foreground: var(--dt-card-foreground); --color-muted: var(--dt-muted); --color-muted-foreground: var(--dt-muted-foreground); --color-popover: var(--dt-popover); --color-popover-foreground: var(--dt-popover-foreground); --color-primary: var(--dt-primary); --color-primary-foreground: var(--dt-primary-foreground); --color-secondary: var(--dt-secondary); --color-secondary-foreground: var(--dt-secondary-foreground); --color-accent: var(--dt-accent); --color-accent-foreground: var(--dt-accent-foreground); --color-border: var(--dt-border); --color-input: var(--dt-input); --color-ring: var(--dt-ring); --color-destructive: var(--dt-destructive); --color-destructive-foreground: var(--dt-destructive-foreground); --color-midground: var(--dt-midground); --color-midground-foreground: var(--dt-midground-foreground); --font-sans: var(--dt-font-sans); --font-mono: var(--dt-font-mono); --spacing-mul: var(--dt-spacing-mul, 1); --radius-xs: calc(var(--radius-scalar) * 0.125rem); --radius-sm: calc(var(--radius-scalar) * 0.5rem); --radius-md: calc(var(--radius-scalar) * 0.625rem); --radius-lg: calc(var(--radius-scalar) * 0.75rem); --radius-xl: calc(var(--radius-scalar) * 1rem); --radius-2xl: calc(var(--radius-scalar) * 1.5rem); --radius-3xl: calc(var(--radius-scalar) * 2rem); --radius-4xl: calc(var(--radius-scalar) * 2.5rem); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar: var(--sidebar); --shadow-xs: 0 0.0625rem 0.125rem color-mix(in srgb, #000 5%, transparent); --shadow-sm: 0 0 0 0.0625rem color-mix(in srgb, var(--dt-foreground) 6%, transparent), 0 0.125rem 0.5rem color-mix(in srgb, #000 4%, transparent); --shadow-md: 0 0 0 0.0625rem color-mix(in srgb, var(--dt-foreground) 8%, transparent), 0 0.25rem 1rem color-mix(in srgb, #000 8%, transparent), 0 1rem 2rem -1.5rem color-mix(in srgb, #000 18%, transparent); /* Soft floating shadow for borderless modals/overlays. Single top light source: every layer is centered (x=0) and cast downward, with negative spread that grows with the blur so each layer is pulled horizontally inward — the shadow pools below the panel instead of bleeding out every side. Layered (contact → ambient) for a smooth, natural falloff. */ --shadow-nous: 0 0.125rem 0.25rem -0.125rem color-mix(in srgb, #000 7%, transparent), 0 0.5rem 0.75rem -0.375rem color-mix(in srgb, #000 6%, transparent), 0 1.25rem 1.75rem -0.875rem color-mix(in srgb, #000 6%, transparent), 0 2.25rem 3rem -1.75rem color-mix(in srgb, #000 0%, transparent); /* Hairline border paired with --shadow-nous on borderless overlays. currentColor resolves per-element, so it adapts to text color/theme. */ --stroke-nous: color-mix(in srgb, currentColor 3%, transparent); --shadow-lg: inset 0 0.0625rem 0 color-mix(in srgb, #fff 28%, transparent), 0 0 0 0.0625rem color-mix(in srgb, var(--dt-foreground) 8%, transparent), 0 0.75rem 2rem color-mix(in srgb, #000 12%, transparent); --shadow-composer: 0 0.0625rem 0.125rem color-mix(in srgb, #000 5%, transparent); } @layer base { :root { color-scheme: light; --theme-foreground: #17171a; --theme-primary: #0053fd; --theme-secondary: color-mix(in srgb, #0053fd 7%, #ffffff); --theme-accent-soft: color-mix(in srgb, #0053fd 10%, #ffffff); --theme-midground: #0053fd; --theme-warm: #cf806d; --theme-background-seed: #f8faff; --theme-sidebar-seed: #f3f7ff; --theme-card-seed: #ffffff; --theme-elevated-seed: #ffffff; --theme-bubble-seed: color-mix(in srgb, #0053fd 6%, #ffffff); --theme-neutral-chrome: #f3f3f3; --theme-neutral-sidebar: #f3f3f3; --theme-neutral-card: #fcfcfc; --theme-mix-chrome: 92%; --theme-mix-sidebar: 100%; --theme-mix-card: 22%; --theme-mix-elevated: 28%; --theme-mix-bubble: 0%; --theme-fill-primary-accent-mix: 16%; --theme-fill-secondary-accent-mix: 11%; --theme-fill-tertiary-accent-mix: 8%; --theme-fill-quaternary-accent-mix: 5%; --theme-fill-quinary-accent-mix: 3%; --theme-stroke-primary-accent-mix: 24%; --theme-stroke-secondary-accent-mix: 16%; --theme-stroke-tertiary-accent-mix: 10%; --theme-stroke-quaternary-accent-mix: 6%; --theme-row-hover-accent-mix: 4%; --theme-row-active-accent-mix: 8%; --theme-control-hover-accent-mix: 6%; --theme-control-active-accent-mix: 8%; --ui-base: var(--theme-foreground); --ui-accent: var(--theme-midground); --ui-accent-secondary: var(--theme-primary); --ui-warm: var(--theme-warm); --ui-red: #cf2d56; --ui-orange: #db704b; --ui-yellow: #c08532; --ui-green: #1f8a65; --ui-cyan: #4c7f8c; --ui-blue: #0053fd; --ui-purple: #9e94d5; --context-usage-system: color-mix(in srgb, var(--ui-base) 55%, transparent); --context-usage-tools: var(--ui-purple); --context-usage-rules: var(--ui-green); --context-usage-skills: var(--ui-yellow); --context-usage-mcp: color-mix(in srgb, var(--ui-red) 72%, var(--ui-purple)); --context-usage-subagents: color-mix(in srgb, var(--ui-blue) 70%, var(--ui-cyan)); --context-usage-memory: color-mix(in srgb, var(--ui-orange) 80%, var(--ui-yellow)); --context-usage-conversation: var(--ui-cyan); /* Landed memory-write tool row — gold → purple, never amber warning. */ --tool-memory-legendary-from: color-mix(in srgb, var(--ui-yellow) 72%, #f5d08a); --tool-memory-legendary-mid: color-mix(in srgb, var(--ui-orange) 55%, var(--ui-purple)); --tool-memory-legendary-to: color-mix(in srgb, var(--ui-purple) 82%, #c4b5fd); --tool-memory-legendary-icon: color-mix(in srgb, var(--ui-yellow) 55%, var(--ui-purple)); --tool-memory-legendary-meta: color-mix(in srgb, var(--ui-purple) 58%, var(--ui-text-tertiary)); --tool-memory-legendary-glow: color-mix(in srgb, var(--ui-purple) 38%, transparent); /* Diff add/remove, derived from the semantic palette so every diff surface (tool cards, preview, review pane) tracks the theme's green/red. Only the foregrounds need a dark override — they mix toward the page instead of away from it. */ --ui-diff-add-border: var(--ui-green); --ui-diff-add-background: color-mix(in srgb, var(--ui-green) 12%, transparent); --ui-diff-add-foreground: color-mix(in srgb, var(--ui-green) 70%, #000); --ui-diff-remove-border: var(--ui-red); --ui-diff-remove-background: color-mix(in srgb, var(--ui-red) 12%, transparent); --ui-diff-remove-foreground: color-mix(in srgb, var(--ui-red) 70%, #000); /* Overlay ladder. DESIGN.md: app-wide surfaces must not compete through ad-hoc z-index literals — pick the rung that describes the surface. Values are deliberately sparse so a one-off can slot between two rungs without a renumber. Local stacking inside a component (a sticky header over its own scroll area) stays on plain `z-10`/`z-20`; these rungs are only for surfaces that float over the app. */ --z-modal-backdrop: 120; --z-modal: 130; /* A select/dropdown/popover opened from inside a modal, portaled to body. */ --z-modal-popover: 140; /* Must clear any open modal: toasts, tooltips, command-surface backdrops. */ --z-over-modal: 200; --z-over-modal-content: 210; --z-switcher-backdrop: 219; --z-switcher: 220; /* Boot and blocking states, in the order they can stack. */ --z-connecting: 1200; --z-onboarding: 1300; --z-onboarding-popover: 1310; --z-setup: 1400; --z-crash: 1500; --ui-bg-chrome: color-mix( in srgb, var(--theme-background-seed) var(--theme-mix-chrome), var(--theme-neutral-chrome) ); --ui-bg-sidebar: color-mix( in srgb, var(--theme-sidebar-seed) var(--theme-mix-sidebar), var(--theme-neutral-sidebar) ); --ui-bg-editor: color-mix(in srgb, var(--theme-card-seed) var(--theme-mix-card), var(--theme-neutral-card)); --ui-bg-elevated: color-mix( in srgb, var(--theme-elevated-seed) var(--theme-mix-elevated), var(--theme-neutral-card) ); --ui-bg-card: color-mix(in srgb, var(--ui-accent) 4%, color-mix(in srgb, var(--ui-base) 4%, transparent)); --ui-bg-input: #fcfcfc; --ui-bg-primary: color-mix( in srgb, var(--ui-accent) var(--theme-fill-primary-accent-mix), color-mix(in srgb, var(--ui-base) 10%, transparent) ); --ui-bg-secondary: color-mix( in srgb, var(--ui-accent) var(--theme-fill-secondary-accent-mix), color-mix(in srgb, var(--ui-base) 7%, transparent) ); --ui-bg-tertiary: color-mix( in srgb, var(--ui-accent) var(--theme-fill-tertiary-accent-mix), color-mix(in srgb, var(--ui-base) 5%, transparent) ); --ui-bg-quaternary: color-mix( in srgb, var(--ui-accent) var(--theme-fill-quaternary-accent-mix), color-mix(in srgb, var(--ui-base) 4%, transparent) ); --ui-bg-quinary: color-mix( in srgb, var(--ui-accent) var(--theme-fill-quinary-accent-mix), color-mix(in srgb, var(--ui-base) 3%, transparent) ); --ui-row-hover-background: color-mix( in srgb, var(--ui-accent) var(--theme-row-hover-accent-mix), color-mix(in srgb, var(--ui-base) 3%, transparent) ); --ui-row-active-background: color-mix( in srgb, var(--ui-accent) var(--theme-row-active-accent-mix), color-mix(in srgb, var(--ui-base) 5%, transparent) ); /* Open in a pane, but not the FOCUSED one: the active band at reduced strength, so every session on screen is marked and the one you're in still wins. Mixed from the active token so a theme tunes both at once. */ --ui-row-open-background: color-mix(in srgb, var(--ui-row-active-background) 28%, transparent); --ui-control-hover-background: color-mix( in srgb, var(--ui-accent) var(--theme-control-hover-accent-mix), color-mix(in srgb, var(--ui-base) 4%, transparent) ); --ui-control-active-background: color-mix( in srgb, var(--ui-accent) var(--theme-control-active-accent-mix), color-mix(in srgb, var(--ui-base) 5%, transparent) ); --ui-text-primary: color-mix(in srgb, var(--ui-base) 94%, transparent); --ui-text-secondary: color-mix(in srgb, var(--ui-base) 74%, transparent); --ui-text-tertiary: color-mix(in srgb, var(--ui-base) 54%, transparent); --ui-text-quaternary: color-mix(in srgb, var(--ui-base) 36%, transparent); /* Transcript scaffolding — thinking headers, settled tool runs, the live activity ticker. One colour for all of them (see `ScaffoldRow`), pitched between the secondary and tertiary greys those lines used to pick individually, and dimmed once more by the fade rule below so the prose reading column stays primary. */ --conversation-scaffold-text: color-mix(in srgb, var(--ui-base) 64%, transparent); --conversation-scaffold-meta: color-mix(in srgb, var(--ui-base) 44%, transparent); --ui-stroke-primary: color-mix( in srgb, var(--ui-accent) var(--theme-stroke-primary-accent-mix), color-mix(in srgb, var(--ui-base) 10%, transparent) ); --ui-stroke-secondary: color-mix( in srgb, var(--ui-accent) var(--theme-stroke-secondary-accent-mix), color-mix(in srgb, var(--ui-base) 7%, transparent) ); --ui-stroke-tertiary: color-mix( in srgb, var(--ui-accent) var(--theme-stroke-tertiary-accent-mix), color-mix(in srgb, var(--ui-base) 5%, transparent) ); --ui-stroke-quaternary: color-mix( in srgb, var(--ui-accent) var(--theme-stroke-quaternary-accent-mix), color-mix(in srgb, var(--ui-base) 3%, transparent) ); --ui-sash-hover-border: color-mix(in srgb, var(--ui-accent) 18%, var(--ui-stroke-tertiary)); --ui-sash-hover-background: color-mix(in srgb, var(--ui-accent) 6%, transparent); --ui-surface-background: var(--ui-bg-editor); --ui-sidebar-surface-background: var(--ui-bg-sidebar); --ui-chat-surface-background: var(--ui-bg-chrome); --ui-editor-surface-background: var(--ui-bg-chrome); /* The integrated terminal wears the transcript's background, not the editor's — it reads as part of the conversation surface. Its own token so the two can diverge without dragging the terminal along, and so a skin gets one knob for it. xterm's canvas can't use a var, so the probe in terminal/selection.ts resolves this same name to a concrete rgb. */ --ui-terminal-surface-background: var(--ui-chat-surface-background); /* Inline chat widgets (clarify, artifact card) — the card fill in light mode, nudged down in dark so a widget settles into the transcript instead of glowing above it (see the `.dark` override). */ --ui-widget-surface-background: var(--ui-bg-editor); --ui-chat-bubble-background: color-mix( in srgb, var(--theme-bubble-seed) var(--theme-mix-bubble), var(--theme-neutral-card) ); --ui-chat-bubble-opaque-background: var(--ui-bg-editor); --ui-inline-code-background: color-mix(in srgb, #141414 5%, transparent); --ui-inline-code-foreground: color-mix(in srgb, #141414 88%, transparent); --ui-selection-background: color-mix(in srgb, #ffd24a 55%, transparent); --dt-background: var(--ui-bg-chrome); --dt-foreground: var(--ui-text-primary); --dt-card: var(--ui-bg-editor); --dt-card-foreground: var(--ui-text-primary); --dt-muted: var(--ui-bg-tertiary); --dt-muted-foreground: var(--ui-text-tertiary); --dt-popover: color-mix(in srgb, var(--ui-bg-elevated) 96%, transparent); --dt-popover-foreground: var(--ui-text-primary); --dt-primary: var(--theme-primary); --dt-primary-foreground: #fcfcfc; /* Boot-paint fallbacks. themes/context.tsx replaces --dt-primary-solid with the hue deepened until #fcfcfc clears AA on it; until it runs, the raw primary is the closest honest guess. */ --dt-primary-solid: var(--theme-primary); --dt-primary-solid-foreground: #fcfcfc; --dt-secondary: var(--theme-secondary); --dt-secondary-foreground: var(--ui-text-secondary); --dt-accent: var(--theme-accent-soft); --dt-accent-foreground: var(--ui-text-primary); --dt-border: var(--ui-stroke-secondary); --dt-input: var(--ui-stroke-primary); /* THE single knob for input-field borders: the resting alpha (% of the ring color). Hover doubles it; focus/open go full. 0% = invisible at rest. */ --dt-input-border: 7%; /* Knob for input-field background fill: alpha (% of --dt-card) across all states. 100% = fully opaque, lower = translucent over the blurred chrome. */ --dt-input-bg: 0%; /* Classic recessed "inset" — a crisp 1px inner shadow at the TOP edge. :root.dark bumps the alpha since a dark card swallows shadow. Removed on focus (the focus border carries the state). */ --dt-input-inset: inset 0 1px 1px color-mix(in srgb, #000 10%, transparent); /* Hovered inactive tab: deepen it AWAY from the lighter active surface, so hover can never be mistaken for selected. Dark mode needs more — black over a dark gutter shows less. */ --ui-tab-hover-darken: 2.5%; --dt-ring: var(--ui-stroke-primary); --dt-midground: var(--theme-midground); /* Scrollbars carry no theme colour: the accent-tinted thumb read as a coloured element in the corner of every list. Same lightness as the midground so each theme still gets a thumb that sits right against its own surfaces — chroma 0 is the only thing removed. */ --dt-scrollbar-thumb: oklch(from var(--dt-midground) l 0 h); --dt-composer-ring: var(--ui-base); --dt-destructive: #cf2d56; --dt-destructive-foreground: #ffffff; --dt-sidebar-bg: var(--ui-bg-sidebar); --dt-sidebar-border: var(--ui-stroke-secondary); /* `--user-bubble-keep` is the Settings → Appearance → Message Bubble lever (store/user-bubble-transparency.ts): the share of the theme's bubble fill to keep. Unset at the default, so the mix is a no-op and the bubble is the theme's fill exactly as before. The border stays put either way — at full transparency the outline is what still marks your turn. */ --dt-user-bubble: color-mix(in srgb, var(--ui-chat-bubble-background) var(--user-bubble-keep, 100%), transparent); --dt-user-bubble-border: var(--ui-stroke-tertiary); --dt-font-sans: 'Segoe WPC', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', emoji; /* Key caps always use the native UI face — never theme typography overrides. */ --dt-font-kbd: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif; /* Menlo/Monaco first — Apple's native monospace faces — so code/diff read in the system mono on macOS, with SF Mono and bundled Courier Prime as fallbacks. */ --dt-font-mono: Menlo, Monaco, 'SF Mono', 'Courier Prime', monospace, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', emoji; --dt-base-size: 1rem; --dt-line-height: 1.5; --dt-letter-spacing: 0; --dt-spacing-mul: 1; --radius: 0.75rem; /* Effective value everywhere: the chat backdrop used to force 0.2 onto the root element at runtime (an ungated leva slider default), so this 0.6 only ever applied to the windows that don't mount the backdrop. Pinned to the value the app actually renders at. */ --radius-scalar: 0.2; /* Space under last message vs overlay composer — driven by the measured composer DOCK height (see composer/index.tsx), which covers the micro-action strip, the status stack, the composer, and the underside strip in one number. This is the default; each chat surface re-declares the calc against its own measurement (see below). */ --thread-last-message-clearance: calc(var(--composer-measured-height) + 2rem); --composer-shell-pad-block-end: 0.625rem; --message-text-indent: 0.75rem; --conversation-text-font-size: 0.8125rem; --conversation-tool-font-size: 0.6875rem; --conversation-caption-font-size: 0.75rem; --conversation-line-height: 1.125rem; --conversation-caption-line-height: 1rem; --conversation-turn-gap: 0.375rem; /* Gap between top-level turn blocks (prose ↔ tools ↔ thinking) — enough air that scaffolding reads as separate from the reply, not crammed into it. */ --turn-block-gap: 0.75rem; /* Between adjacent scaffolding, though: a back-to-back run of thinking headers and tool rows is one sequence, not blocks to hold apart. Derived so retuning the block gap carries it along. */ --scaffold-block-gap: calc(var(--turn-block-gap) / 3); /* Tight gap between tool rows inside a single action group, so a back-to-back run still reads as one cohesive sequence. */ --tool-row-gap: 0.375rem; /* Resting opacity of a disclosure caret — invisible until hover, except where a surface opts into a faint hint of the affordance. */ --disclosure-caret-rest: 0; /* Paragraph spacing — vertical gap between prose paragraphs, both inside a markdown block and between consecutive prose parts. Single knob; tweak freely. */ --paragraph-gap: 0.7rem; --sticky-human-top: 0.23rem; --file-tree-row-height: 1.375rem; --composer-width: 100%; --composer-control-size: 1.5rem; --composer-control-primary-size: 1.625rem; --composer-control-gap: 0.25rem; --composer-row-gap: 0.25rem; --composer-ring-strength: 1; --composer-surface-pad-x: 0.5rem; --composer-surface-pad-y: 0.3125rem; --composer-input-min-height: 1.625rem; --composer-input-max-height: 9.375rem; --composer-input-inline-min-width: 8rem; --composer-fallback-height: 2.75rem; --composer-measured-height: calc(0.5rem + var(--composer-shell-pad-block-end) + var(--composer-fallback-height)); --composer-surface-measured-height: var(--composer-fallback-height); --thread-viewport-height: max( 0rem, calc(100% - var(--composer-measured-height) + var(--composer-surface-measured-height)) ); --vsq: min(0.5vh, 0.5vw); --image-preview-max-width: 34rem; --image-preview-height: clamp(16.25rem, calc(var(--vsq) * 100), 26.25rem); --sidebar-width: 14.8125rem; --chat-min-width: 28rem; --titlebar-control-size: 24px; --titlebar-control-height: 24px; --titlebar-icon-size: 13.9px; --sidebar-content-inline-padding: 1rem; --sidebar: var(--dt-sidebar-bg); --sidebar-foreground: var(--dt-foreground); --sidebar-primary: var(--dt-primary); --sidebar-primary-foreground: var(--dt-primary-foreground); --sidebar-accent: var(--ui-control-active-background); --sidebar-accent-foreground: var(--dt-accent-foreground); --sidebar-border: var(--dt-sidebar-border); --sidebar-ring: var(--dt-ring); --sidebar-edge-border: color-mix(in srgb, var(--ui-base) 7.5%, transparent); --chrome-action-hover: var(--ui-control-hover-background); --midground: var(--dt-midground); --background: var(--dt-background); --foreground: var(--dt-foreground); --warm-glow: color-mix(in srgb, var(--ui-warm) 32%, color-mix(in srgb, var(--ui-accent) 6%, transparent)); /* `--noise-opacity-mul` is set per-mode by `applyTheme()`. */ --noise-opacity-mul: 1; --backdrop-invert-mul: 1; } :root.dark { /* Per-mode mix knobs — overridden inline by `applyTheme()` per skin. */ --theme-mix-chrome: 74%; --theme-mix-card: 38%; --theme-mix-elevated: 46%; --theme-mix-bubble: 46%; --theme-neutral-chrome: #0d0d0e; --theme-neutral-sidebar: #0a0a0b; --theme-neutral-card: #161618; /* Dark-only accent palette overrides. */ --ui-red: #e75e78; --ui-green: #55a583; --ui-cyan: #6f9ba6; --ui-diff-add-foreground: color-mix(in srgb, var(--ui-green) 62%, #fff); --ui-diff-remove-foreground: color-mix(in srgb, var(--ui-red) 62%, #fff); --sidebar-edge-border: color-mix(in srgb, var(--ui-base) 12%, transparent); --composer-ring-strength: 1.3; --backdrop-invert-mul: 0; /* Dark mode: a dark card needs a stronger black inset to show the recess. */ --dt-input-inset: inset 0 1px 1px color-mix(in srgb, #000 38%, transparent); --ui-tab-hover-darken: 6%; /* Dark needs a lighter resting border than light mode. */ --dt-input-border: 4%; --ui-inline-code-background: color-mix(in srgb, #ffffff 7%, transparent); --ui-inline-code-foreground: color-mix(in srgb, #ffffff 88%, transparent); --ui-selection-background: color-mix(in srgb, #ffd24a 38%, transparent); /* A dark card sits ABOVE the chrome (#161618 over #0d0d0e), so an inline widget wearing the raw card fill reads as a lit panel. Take it down a touch — still clearly a surface, no longer the brightest thing in the transcript. Derived from the card so every skin follows its own seed. */ --ui-widget-surface-background: color-mix(in srgb, var(--ui-bg-editor) 88%, #000); } /* ── Window glass (Settings → Appearance → Window Translucency: Glass) ── Set on by store/translucency.ts; chat windows on a glass-capable OS (macOS vibrancy, Windows 11 DWM acrylic/mica). The native material composites BELOW the web contents; the page normally buries it under fully opaque surfaces. ONE PAINTER: paints the glass tint exactly once, at `--translucency-glass-keep` (set inline from the Tint slider, linear to zero — the top of the lever is bare material), and the field tokens (chat / sidebar / editor surface) go fully transparent. The field surfaces NEST — body > pane container > chat section > transcript wrapper all wear these tokens — so thinning the tokens themselves stacks the tint once per layer and a session pane ends up near-opaque (~0.93 at 60%) while the landing page, with fewer layers, reads far clearer. With a single painter the field alpha is the same number on every route. Raised content (cards, popovers, bubbles, the composer) keeps its own opaque fills over full-contrast text; regions still read distinct through their borders and the material itself. Unlike Clear, nothing on the page fades — the separate Fade lever does that natively, at the window level, and defaults to off. The boot script in index.html pins an opaque inline background on before first paint; it has to go transparent here or it sits behind the translucent and blocks the material. The terminal surface is pinned back to the opaque chrome mix: xterm resolves its surface var to a concrete color for its canvas (see right-sidebar/terminal/selection.ts), which does not composite page alpha. */ :root[data-hermes-glass] { background-color: transparent !important; --ui-chat-surface-background: transparent; --ui-sidebar-surface-background: transparent; --ui-editor-surface-background: transparent; --ui-terminal-surface-background: var(--ui-bg-chrome); } :root[data-hermes-glass] body { background: color-mix(in srgb, var(--ui-bg-chrome) var(--translucency-glass-keep, 100%), transparent); } /* Sidebar scope (the Finder shape): glass rail, opaque content column. stays the one painter but splits at the rail's visual edge — the store publishes it as --glass-rail-edge (live-tracked through collapse animation and pane resizes). Left of the seam: the normal glass mix. Right of it: the full opaque chrome. A hard gradient stop, so there is no smear across the seam; RTL mirrors via html[dir='rtl']. The content column's own surfaces stay transparent (they'd double-paint the tint otherwise) — the body backdrop alone restores the opaque look. */ :root[data-hermes-glass][data-hermes-glass-scope='sidebar'] body { background: linear-gradient( to right, color-mix(in srgb, var(--ui-bg-chrome) var(--translucency-glass-keep, 100%), transparent) var(--glass-rail-edge, 0px), var(--ui-bg-chrome) var(--glass-rail-edge, 0px) ); } :root[data-hermes-glass][data-hermes-glass-scope='sidebar'][dir='rtl'] body { background: linear-gradient( to left, color-mix(in srgb, var(--ui-bg-chrome) var(--translucency-glass-keep, 100%), transparent) var(--glass-rail-edge, 0px), var(--ui-bg-chrome) var(--glass-rail-edge, 0px) ); } /* The app shell's two full-window opaque painters (the contrib controller root and the SidebarProvider wrapper) sit between and every surface. Their fills are Tailwind utilities (bg-(--ui-bg-chrome) / bg-background), which cascade-layer above these base rules — hence the !important. Clearing them makes the window's only field paint. */ :root[data-hermes-glass] [data-contrib-shell], :root[data-hermes-glass] [data-slot='sidebar-wrapper'] { background-color: transparent !important; } /* Surfaces that MASK sibling content must stay filled under glass — a see-through mask reads as text bleeding through text, not as glass. Anything that paints over its own siblings declares `data-glass-opaque` at its call site (the diff gutter, a dragged sidebar row), rather than styles.css reaching in by class name. */ :root[data-hermes-glass] [data-glass-opaque] { --ui-chat-surface-background: var(--ui-bg-chrome); --ui-editor-surface-background: var(--ui-bg-chrome); --ui-sidebar-surface-background: var(--ui-bg-sidebar); } /* Content RAISED above the field (overlay cards, the inline edit box) runs counter to it: near-opaque over a glass field, never thinner than the field itself — max() covers low slider values where the field still keeps more than 94%. The card wears the chat token and its internal nav asides wear the sidebar token, so both come back as almost-solid fills and the two-tone layout survives. */ :root[data-hermes-glass] [data-glass-raised] { --ui-chat-surface-background: color-mix( in srgb, var(--ui-bg-chrome) max(94%, var(--translucency-glass-keep, 100%)), transparent ); --ui-sidebar-surface-background: color-mix( in srgb, var(--ui-bg-sidebar) max(94%, var(--translucency-glass-keep, 100%)), transparent ); --ui-editor-surface-background: var(--ui-bg-chrome); } /* The status bar spans the rail AND the content column. Transparent, it showed the body's hard stop through itself, and the seam cut whichever item sat on it (the free-tier chip, in the guided window). Finder's shape: the bar belongs to the opaque content column across the full width, and the rail's glass ends at its top edge. Window scope has no seam, so the bar stays transparent there. */ :root[data-hermes-glass][data-hermes-glass-scope='sidebar'] [data-slot='statusbar'] { --ui-sidebar-surface-background: var(--ui-bg-chrome); } /* Clear mode (native window opacity) fades EVERYTHING in the window uniformly, so when an overlay opens over a session two layers of text fade by the same amount and fight through the overlay's light default scrim (bg-black/22 + 2px blur). While clear is active, the scrim works for a living: darken it and widen the blur so the covered transcript drops away and the overlay's own text is the only line of type in front of the desktop. Set on by store/translucency.ts whenever mode='clear' and intensity > 0; !important because the utility classes live in a later cascade layer. */ :root[data-hermes-clear] [data-overlay-surface] { background-color: rgb(0 0 0 / 0.55) !important; backdrop-filter: blur(0.5rem) !important; } /* Translucency peek: while the user holds the intensity slider (or for a short pulse after a frost/area/mode click) the entire overlay layer — scrim, blur and near-opaque card as ONE group — ghosts down so the live window behind it becomes the preview. The layer keeps pointer events: a held drag keeps delivering to the slider it started on. Opacity is the one property that fades the scrim, its backdrop-blur and the card together without relayout. Asymmetric easing: quick reveal while the hand is on the control, unhurried return. Both the peek and its return are scoped to a settings overlay that has ARMED the interaction ([data-translucency-peek-scope], set by the appearance row). An unscoped `[data-overlay-surface] { transition: opacity … }` would tax every overlay in the app — command center, cron, agents, model picker — with a 420ms opacity transition for the life of the process, to serve one slider. */ :root [data-overlay-surface]:has([data-translucency-peek-scope]) { transition: opacity 420ms cubic-bezier(0.22, 1, 0.36, 1); } :root[data-hermes-translucency-peek] [data-overlay-surface]:has([data-translucency-peek-scope]) { opacity: 0.08; transition: opacity 160ms cubic-bezier(0.32, 0.72, 0, 1); } * { box-sizing: border-box; border-color: var(--dt-border); } html, body, #root { height: 100%; /* App shell, not a document: the window itself never scrolls on either axis (panes own their own scroll). Belt to the auto-scroll axis-lock in the sidebar reorder DnD — nothing can drag the whole shell sideways. */ overflow: hidden; } html { font-size: var(--dt-base-size, 0.875rem); } body { margin: 0; background: var(--ui-chat-surface-background); color: var(--dt-foreground); font-family: var(--dt-font-sans); font-size: 0.8125rem; line-height: var(--dt-line-height, 1.55); letter-spacing: var(--dt-letter-spacing, 0); -webkit-user-select: none; user-select: none; -webkit-font-smoothing: antialiased; } button, textarea { font: inherit; } :where( a, .underline, [class~='hover:underline'], [class~='focus:underline'], [class~='focus-visible:underline'], [class~='group-hover:underline'], [class~='peer-hover:underline'] ) { text-decoration-color: color-mix(in srgb, currentColor 20%, transparent); text-underline-offset: 0.25rem; } *::selection { background: var(--ui-selection-background); color: inherit; } } .dither { background: repeating-conic-gradient(currentColor 0% 25%, transparent 0% 50%) 0 0 / 0.125rem 0.125rem; } /* ───────────────────────────────────────────────────────────────────────── INLINE REFERENCES — the one system for anything that points at something from inside a run of text. A pasted link, an `@file:` the user picked, a `/skill`, a `@session:` the agent wrote: all the same species. They render the same way in the composer and in a sent message, because they're the same thing before and after send. A reference is TEXT, not a badge — colour and an optional icon, no fill, no padding, no border. A pill turns every mention into a widget the eye has to step over, and mid-sentence that's most of the sentence. Usage: `class="ref"` plus `data-ref=""` for the accent. Kinds live in `components/assistant-ui/reference-kinds.ts` (icon + label); their colour lives here, so a theme restyles every reference at once and TS never ships a hex. No `data-ref` = an ordinary link, which keeps the primary link colour. ───────────────────────────────────────────────────────────────────────── */ /* Kind → accent. Keyed on the attribute ALONE so anything can adopt a reference's colour without also taking its inline-text layout (a completion row's icon column wants the hue, not the margin). Grouped by what a reference DOES, so the palette reads as meaning rather than decoration: · things you point at (paths) → neutral, they're the common case · things you fetch (links, media) → secondary · things that act (commands, tools) → accent · things that change code (skills, git)→ warm */ [data-ref] { --ref-color: var(--dt-primary); } [data-ref='file'], [data-ref='folder'], [data-ref='line'], [data-ref='terminal'] { --ref-color: var(--ui-text-secondary); } [data-ref='url'], [data-ref='image'], [data-ref='session'], [data-ref='theme'] { --ref-color: color-mix(in srgb, var(--ui-accent-secondary) 82%, var(--foreground)); } [data-ref='command'], [data-ref='tool'] { --ref-color: color-mix(in srgb, var(--ui-accent) 82%, var(--foreground)); } [data-ref='skill'], [data-ref='git'], [data-ref='diff'], [data-ref='staged'] { --ref-color: color-mix(in srgb, var(--ui-warm) 82%, var(--foreground)); } .ref { --ref-color: var(--dt-primary); /* Prose links inherit the surrounding text's weight. `@tailwindcss/typography` sets `prose a { font-weight: 500 }`, which outranks a utility class on the anchor — so the override belongs here, on the shared class. */ font-weight: inherit; color: var(--ref-color); /* Explicit: the base layer underlines every `a` (see the :where(a, …) reset). */ text-decoration: none; /* A wrapped reference breaks per line fragment, not as one ragged box. */ -webkit-box-decoration-break: clone; box-decoration-break: clone; } /* Affordance without chrome: only the ones you can actually activate respond, and they do it with an underline rather than a background. */ :where(a, button).ref:hover { text-decoration: underline; text-underline-offset: 0.15em; } /* The leading glyph. Sized in `em` so it tracks the text at any scale, and spaced with `margin` — not flex `gap` — so `.ref` stays an inline box whose label can wrap mid-word (a long URL has to break across lines like the prose around it, which a flex container would prevent). The margin is unconditional. A `:not(:only-child)` guard looks right and is silently wrong: CSS counts ELEMENT siblings, so an icon followed by a bare text-node label still matches `:only-child` — which is exactly how every chip lost its spacer. Icon-only users take the hue via `[data-ref]` instead of this class. */ .ref > :where(svg, i.codicon) { margin-inline-end: 0.25em; opacity: 0.8; } .ref > svg { display: inline-block; width: 0.875em; height: 0.875em; vertical-align: -0.1em; } .ref > i.codicon { font-size: 0.875em; } /* Hover-reveal suppression — the shared, declarative escape hatch. A collapsed pane slides in when the pointer dwells on its thin edge trigger. Controls that sit over that edge gutter would drag the panel in by accident. Mark any such region and, while it's hovered, the matching edge trigger(s) go pointer-transparent. Auto-resets on mouse-out, no JS. - data-suppress-pane-reveal → kills BOTH edges (use for small regions like the thread timeline). - data-suppress-pane-reveal-side → kills only the hovered region's OWN side (side read from its [data-pane-side] pane ancestor), so the opposite sidebar stays summonable while you work a pane. */ [data-pane-shell]:has([data-suppress-pane-reveal]:hover) [data-pane-reveal-trigger] { pointer-events: none; } [data-pane-shell]:has([data-pane-side='left'] [data-suppress-pane-reveal-side]:hover) [data-pane-side='left'] [data-pane-reveal-trigger], [data-pane-shell]:has([data-pane-side='right'] [data-suppress-pane-reveal-side]:hover) [data-pane-side='right'] [data-pane-reveal-trigger] { pointer-events: none; } /* A pane's self-naming label ("REVIEW" atop the review pane) is redundant when its zone header is showing a tab with the same name — hide it there. Headerless zones (and the pane used outside the tree) keep the label. */ [data-tree-group][data-zone-header] [data-pane-self-label] { display: none; } :root:not([style*='--theme-asset-bg:']) .theme-default-filler { display: block; } :root[style*='--theme-asset-bg:'] .theme-default-filler { display: none; } /* ───────────────────────────────────────────────────────────────────────── Composer placeholder. The prompt is a PAINTED HINT, never content. Rendering `::before` inline — the obvious way — puts a box in the contenteditable's text flow, and the caret sits after it: click an empty composer and the insertion point lands at the right edge of the hint rather than the left edge of the field, and a hint that wraps makes the empty composer two lines tall. Taking it out of flow is what makes it a hint: the caret keeps the field's real origin, the box keeps its real height, and no pointer or selection can reach the text. `data-empty` carries emptiness because `:empty` can't: a cleared editor keeps a scaffolding
. See markEditorEmptiness in rich-editor.ts. ───────────────────────────────────────────────────────────────────────── */ [data-slot='composer-rich-input'] { position: relative; } [data-slot='composer-rich-input']:is(:empty, [data-empty])::before { content: attr(data-placeholder); position: absolute; inset: 0; padding: inherit; overflow: hidden; color: color-mix(in srgb, var(--muted-foreground) 60%, transparent); /* One line, clipped — the hint states the field's purpose; it never reflows the box it sits in. A narrow composer or a long locale string would otherwise wrap and double the empty composer's height. */ white-space: nowrap; text-overflow: ellipsis; pointer-events: none; user-select: none; } /* Primitive-level pointer cursor for every interactive control (buttons, selects, menu items, switches, tabs, summaries). Keeps individual components from having to hardcode `cursor-pointer`; explicit cursor utilities (cursor-grab, cursor-default, disabled:cursor-*) still win since they live in the utilities layer. */ @layer base { button:not(:disabled):not([aria-disabled='true']), summary, [role='button']:not([aria-disabled='true']), [role='menuitem']:not([aria-disabled='true']), [role='menuitemradio']:not([aria-disabled='true']), [role='menuitemcheckbox']:not([aria-disabled='true']), [role='option']:not([aria-disabled='true']), [role='switch']:not([aria-disabled='true']), [role='tab']:not([aria-disabled='true']) { cursor: pointer; } } @layer utilities { [class*='rounded-full'], [class*=':rounded-full'] { border-radius: calc(var(--radius-scalar) * 9999rem); } } /* Interactive list-row hover — the sessions-list interaction, shared by every row list (sessions, capabilities, messaging, file trees, timeline): the highlight lands instantly on hover-in and fades out over 100ms on leave. */ @utility row-hover { cursor: pointer; transition: color 100ms ease-out, background-color 100ms ease-out; &:hover { background-color: var(--ui-row-hover-background); transition: none; } } /* The guided-onboarding thread is marked with [data-thread-type='onboarding'] — the hook for onboarding-only transcript styling. */ /* Guided chat is a conversation, not a work surface: hide per-message action chrome (branch/copy/read-aloud/regenerate bar, reaction slot) and the turn- duration stamp — regenerate/branch would derail the scripted flow. */ [data-thread-type='onboarding'] [data-slot='aui_msg-actions'], [data-thread-type='onboarding'] [data-slot='aui_msg-reactions'], [data-thread-type='onboarding'] [data-slot='aui_turn-duration'] { display: none; } /* Hermes speaks first here: no user bubble sits above the opening assistant turn, so the first painted row would press against the pane chrome. The utility class on the row pulls the top pad 0.5rem ABOVE the titlebar; give the air back, and only in this thread. */ [data-slot='aui_thread-content'][data-thread-type='onboarding'] { padding-top: calc(var(--titlebar-height) + 1rem); } @keyframes arc-border { /* Compositor-only travel. The ::before layer is 300% × 300% of the host (mirroring the old `background-size: 300%`), so translating it from -10% → -50% of its own size reproduces the old `background-position: 15% → 75%` exactly (offset = -2·W·p), while `transform` animates on the compositor with zero per-frame style recalc/paint. The old background-position version cost ~3,600 main-thread style recalcs per minute per arc, pinning idle renderers (#53902, #73082). */ 0% { transform: translate(-10%, -10%); } 100% { transform: translate(-50%, -50%); } } .arc-border { --arc-c0: color-mix(in srgb, var(--dt-foreground) 0%, transparent); --arc-c1: var(--dt-midground); --arc-c2: var(--dt-background); --arc-angle: 160deg; --arc-width: 0.078125rem; /* How far the ring stands off its host, per side. */ --arc-standoff: 0.125rem; /* An outset ring is only concentric when its radius grows by the standoff (r = r_host + gap); `border-radius: inherit` leaves the corner pinched. Defaults to the shared `rounded-md`; a host on another radius sets it. */ --arc-radius: var(--radius-md); --arc-duration: 2.23s; pointer-events: none; position: absolute; overflow: hidden; border-radius: calc(var(--arc-radius) + var(--arc-standoff)); inset: calc(var(--arc-standoff) * -1); padding: var(--arc-width); mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; } :root.dark .arc-border { --arc-c1: var(--dt-foreground); } /* Landed `memory` tool row — gold → purple on the brain glyph + title. Pair with `text-transparent` so the scaffold label color can't win. */ .tool-memory-legendary-title { background-image: linear-gradient( 105deg, var(--tool-memory-legendary-from) 0%, var(--tool-memory-legendary-mid) 48%, var(--tool-memory-legendary-to) 100% ); -webkit-background-clip: text; background-clip: text; color: transparent !important; font-weight: 600; } .tool-memory-legendary-glyph { filter: drop-shadow(0 0 0.28rem var(--tool-memory-legendary-glow)); } .tool-memory-legendary-meta { color: var(--tool-memory-legendary-meta); } /* Command-palette deep-link: briefly flash the targeted settings row. */ @keyframes setting-field-flash { 0% { background-color: color-mix(in srgb, var(--dt-primary, #f59e0b) 22%, transparent); } 100% { background-color: transparent; } } .setting-field-highlight { animation: setting-field-flash 1.6s ease-out; } @media (prefers-reduced-motion: reduce) { .setting-field-highlight { animation: none; } } .arc-border::before { content: ''; position: absolute; top: 0; left: 0; /* The gradient layer IS 300% of the ring (the old version kept a host-sized ::before and slid a 300%-sized background through it). Sizing the layer itself lets the travel be a `transform` — composited, no main-thread style/paint work — while the host's mask + overflow clip it to the ring. */ width: 300%; height: 300%; background: linear-gradient( var(--arc-angle), transparent 0%, var(--arc-c0) 15%, var(--arc-c1) 20%, var(--arc-c2) 25%, transparent 35%, transparent 40%, var(--arc-c0) 55%, var(--arc-c1) 60%, var(--arc-c2) 65%, transparent 75%, transparent 80%, var(--arc-c0) 95%, var(--arc-c1) 100% ); will-change: transform; animation: arc-border var(--arc-duration) linear infinite; } /* Flip the arc's travel direction (e.g. the Nous Portal hero row). */ .arc-border.arc-reverse::before { animation-direction: reverse; } /* Nous Portal hero: slower, blue → orange arc. */ .arc-border.arc-nous, :root.dark .arc-border.arc-nous { --arc-c1: #4f8cff; --arc-c2: #ff8c42; --arc-duration: 3.27s; } /* Sidebar session row — a thin, clipped, single-line host, so it differs twice from the default ring: 1. Flush, not outset. The sidebar scroller is `overflow-x-hidden` and rows sit `gap-px` apart, so an outset ring loses its left/right runs to the clip. 2. A visible tail. `--arc-c2` defaults to `--dt-background` — invisible against the sidebar. With c0 a transparent sentinel and c2 invisible, only the bright c1 stop survives each pass, and the gradient has three of them: three moving dots instead of an arc. Tying c2 to c1 gives each spike a falloff. (`arc-nous` already overrides c2, so the hero is unaffected.) */ .arc-border.arc-row, :root.dark .arc-border.arc-row { --arc-standoff: 0rem; --arc-c2: color-mix(in srgb, var(--arc-c1) 45%, transparent); } /* HUD composer — same travelling arc as a working sidebar row, 2px and pill- rounded to match the bar. */ .arc-border.arc-composer, :root.dark .arc-border.arc-composer { --arc-width: 2px; --arc-standoff: 0rem; --arc-radius: 0.75rem; --arc-c2: color-mix(in srgb, var(--arc-c1) 45%, transparent); z-index: 6; } @media (prefers-reduced-motion: reduce) { .arc-border::before { animation: none; } } /* No focus rings, anywhere. Kills the native outline plus Tailwind's `focus-visible:ring-*` (a box-shadow driven by --tw-ring-*). Unlayered so it beats the utilities layer without !important on the outline. The composer / .desktop-input-chrome focus glow is untouched — those set `box-shadow` directly rather than through the ring vars. */ *:focus, *:focus-visible { outline: none; } *:focus-visible { --tw-ring-shadow: 0 0 #0000 !important; --tw-ring-offset-shadow: 0 0 #0000 !important; } button { -webkit-app-region: no-drag; } /* Button variant styling lives entirely in the cva in components/ui/button.tsx (the single source of truth). Don't re-add [data-slot='button'] rules here — attribute selectors out-specify the Tailwind utilities and silently override the variants. */ [data-slot='dropdown-menu-content'], [data-slot='select-content'], [data-slot='dialog-content'], [data-slot='thread-timeline-popover'] { border-color: var(--ui-stroke-secondary); background: color-mix(in srgb, var(--ui-bg-elevated) 96%, transparent); box-shadow: var(--shadow-md); backdrop-filter: blur(0.75rem) saturate(1.08); -webkit-backdrop-filter: blur(0.75rem) saturate(1.08); } [data-slot='dropdown-menu-item']:focus, [data-slot='dropdown-menu-checkbox-item']:focus, [data-slot='dropdown-menu-radio-item']:focus { background: var(--ui-bg-tertiary); color: var(--ui-text-primary); } input, textarea, [contenteditable]:not([contenteditable='false']), [data-slot='aui_user-message-root'], [data-slot='aui_assistant-message-content'], [data-slot='aui_system-message-root'], [data-selectable-text='true'], [data-selectable-text='true'] * { -webkit-user-select: text; user-select: text; } button, [role='button'] { -webkit-user-select: none; user-select: none; } /* User bubbles are