@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); } @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; } } /* 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) ); --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; --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); --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); --dt-user-bubble: var(--ui-chat-bubble-background); --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: 48.75rem; --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); } * { 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; } } @keyframes arc-border { 0% { background-position: 15% 15%; } 100% { background-position: 75% 75%; } } .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; inset: 0; border-radius: inherit; 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% ); background-size: 300% 300%; 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