apps/web/src/index.css
/* Clerk injects its component styles into the clerk layer (clerkAppearance.cssLayerName). */
@layer theme, base, clerk, components, utilities;
@import "tailwindcss";
@custom-variant dark (&:is(.dark, .dark *));
@custom-variant light (&:not(.dark, .dark *));
/* Window Controls Overlay: active when Electron exposes native titlebar control geometry. */
@custom-variant wco (&:is(.wco, .wco *));
/* Secondary hovering pointers reveal sidebar actions on touch-primary devices. */
@custom-variant any-hover {
@media (any-hover: hover) {
&:hover {
@slot;
}
}
}
/* On mobile, morph the expanded hero composer into the compact docked
composer while it moves; the rest of the app remains visually stationary. */
html[data-mobile-composer-route-transition="true"]::view-transition-old(root),
html[data-mobile-composer-route-transition="true"]::view-transition-new(root) {
animation: none;
}
html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-composer) {
animation-duration: var(--mobile-composer-transition-duration);
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
html[data-mobile-composer-route-transition="true"]::view-transition-image-pair(t3-mobile-composer) {
isolation: isolate;
}
html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-composer) {
animation: t3-mobile-composer-old var(--mobile-composer-transition-duration) linear both;
mix-blend-mode: normal;
}
html[data-mobile-composer-route-transition="true"]::view-transition-new(t3-mobile-composer) {
animation: t3-mobile-composer-new var(--mobile-composer-transition-duration) linear both;
mix-blend-mode: normal;
}
/* The expanded and compact composers have different internal layouts. Use a
brief crossfade while their shared wrapper moves so the layout change does
not read as a single-frame cut. */
@keyframes t3-mobile-composer-old {
0%,
35% {
opacity: 1;
}
65%,
100% {
opacity: 0;
}
}
@keyframes t3-mobile-composer-new {
0%,
35% {
opacity: 0;
}
65%,
100% {
opacity: 1;
}
}
html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-draft-headline) {
animation-duration: var(--mobile-composer-transition-duration);
}
html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-draft-headline) {
animation: t3-mobile-draft-headline-exit var(--mobile-composer-transition-duration)
cubic-bezier(0.4, 0, 1, 1) both;
mix-blend-mode: normal;
}
@keyframes t3-mobile-draft-headline-exit {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-6px);
}
}
:root {
--app-scrollbar-width: 6px;
--appearance-contrast-base: 100%;
--appearance-contrast-boost: 0%;
--appearance-contrast-border-boost: 0%;
--appearance-contrast-target: black;
--app-scrollbar-thumb: rgb(217 217 217);
--app-scrollbar-thumb-hover: rgb(191 191 191);
/*
* Compact UI geometry. Keep these values semantic so sidebar, palette,
* tooltip, and toolbar controls cannot quietly drift apart.
*/
--control-radius: 0.5rem;
--sidebar-content-inset: 0.5rem;
--sidebar-control-gap: 0.5rem;
--sidebar-icon-color: color-mix(
in srgb,
var(--contrast-sidebar-muted-foreground) 85%,
var(--sidebar)
);
--sidebar-row-content-inset: 0.625rem;
--command-shell-inset: 0.5rem;
--command-content-inset: 1rem;
--floating-content-inset: 0.75rem;
/* App layer order: the bottom terminal drawer (45, above the timeline minimap),
docked sheets, then the floating preview player (47–49, see
previewMiniPlayerLayout.ts), then dialogs and popovers (50 and up). */
--z-sheet: 46;
--glass-blur: 12px;
--glass-opacity: 80%;
--glass-saturation: 1.14;
--desktop-window-right-resize-inset: 0px;
--workspace-topbar-height: 52px;
--workspace-controls-top: 0px;
--workspace-controls-left: calc(env(safe-area-inset-left) + 0.75rem);
--workspace-controls-right: calc(env(safe-area-inset-right) + 0.75rem);
--workspace-native-controls-inset: 0px;
--workspace-titlebar-control-size: 1.75rem;
--workspace-titlebar-control-gap: 0.75rem;
--workspace-titlebar-scroll-fade-height: 1.5rem;
--chat-content-max-width: 46rem;
--thread-details-panel-width: 17.5rem;
/* The prompt on phones: iOS zooms into any input under 16px on focus. */
--font-size-prompt-touch: max(var(--font-size-prompt, 1rem), 16px);
/* Horizontal gutter shared by the page header and the chat column, clear of
the device's safe area. */
--workspace-gutter: 0.75rem;
--workspace-gutter-start: calc(env(safe-area-inset-left) + var(--workspace-gutter));
--workspace-gutter-end: calc(env(safe-area-inset-right) + var(--workspace-gutter));
@variant sm {
--workspace-gutter: 1.25rem;
}
@variant dark {
--appearance-contrast-target: white;
--app-scrollbar-thumb: rgb(255 255 255 / 8%);
--app-scrollbar-thumb-hover: rgb(255 255 255 / 12%);
--glass-blur: 16px;
--glass-saturation: 1.08;
}
}
.wco {
--native-titlebar-height: env(titlebar-area-height, 0px);
--workspace-topbar-height: max(40px, env(titlebar-area-height, 52px));
--workspace-controls-top: env(titlebar-area-y, 0px);
--workspace-controls-left: calc(env(titlebar-area-x, 0px) + 0.75rem);
--workspace-controls-right: calc(
100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px) + 0.75rem
);
--workspace-native-controls-inset: calc(
100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px) + 0.75rem
);
}
/* The font tokens are declared outside the inline theme so utilities reference
the variables and Settings -> Appearance can override them at runtime. The
default stacks are mirrored in `appearanceFonts.ts`. */
@theme {
--font-sans:
-apple-system, BlinkMacSystemFont, "Inter Variable", "Segoe UI", system-ui, sans-serif;
--font-mono:
ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
}
@theme inline {
/* Two steps below Tailwind's text-xs (12px) for dense UI: metadata rows,
badges, counters, keyboard hints and diagnostics tables. 2xs is 11px,
3xs is 10px. Provider icon overlays use 4xs (8px) and 5xs (7px) to fit
their smaller badges. Registered with tailwind-merge in
lib/utils.ts so cn() treats them as font sizes, not colours. */
--text-2xs: 0.6875rem;
--text-2xs--line-height: calc(1 / 0.6875);
--text-3xs: 0.625rem;
--text-3xs--line-height: calc(0.875 / 0.625);
--text-4xs: 0.5rem;
--text-4xs--line-height: 1;
--text-5xs: 0.4375rem;
--text-5xs--line-height: 1;
/* Motion. drawer is the decelerating curve for surfaces that slide or pop in
(composer context, pills). */
--inset-shadow-control-highlight: 0 1px rgb(255 255 255 / 16%);
--inset-shadow-control-pressed: 0 1px rgb(0 0 0 / 8%);
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
/* The composer's elevation: a soft drop pulled in under the surface, shared
by the composer, its context strip and attached banners. */
--shadow-composer: 0 1px 2px rgb(0 0 0 / 5%);
--shadow-composer-dark: 0 14px 32px -18px rgb(0 0 0 / 75%);
/* Overlap the lower toolbar backdrop by one pixel to cover the composer seam. */
--spacing-composer-seam: calc(var(--spacing) * 4 - 1px);
/* Dark-mode shade where an attached surface meets the composer: 10px of
shadow fading away from the seam. Inline, so the offsets resolve on the
element. below: the context strip under the composer (1rem seam, faint
white fill). above: an attached banner over it (its tint on top). */
--background-image-composer-seam-below:
linear-gradient(
to bottom,
transparent 0 1rem,
rgb(0 0 0 / 18%) 1rem,
transparent calc(1rem + 10px)
),
linear-gradient(rgb(255 255 255 / 1%), rgb(255 255 255 / 1%));
--background-image-composer-seam-above:
linear-gradient(var(--chat-composer-attached-tint), var(--chat-composer-attached-tint)),
linear-gradient(
to top,
transparent 0 var(--chat-composer-attachment-overlap),
rgb(0 0 0 / 18%) var(--chat-composer-attachment-overlap),
transparent calc(var(--chat-composer-attachment-overlap) + 10px)
);
--color-zinc-25: oklch(99.2% 0 0);
--animate-skeleton: skeleton 2.4s infinite;
/* Duty-cycled indicator animations: long holds with stepped ramps, so the
compositor updates discrete frames instead of every vsync. */
--animate-status-pulse: status-pulse 2s infinite;
--animate-status-ping: status-ping 2s infinite;
/* Stash pulses hold a highlight for 1.2s; the row then eases back over 200ms, the count snaps.
The row alternates two identical keyframes so each stash restarts it without a remount. */
--animate-stash-pulse-row: stash-pulse-row 1400ms;
--animate-stash-pulse-row-alt: stash-pulse-row-alt 1400ms;
--animate-stash-pulse-count: stash-pulse-count 1400ms step-start;
--color-warning-foreground: var(--warning-foreground);
--color-warning: var(--warning);
--color-warning-surface: var(--warning-surface);
--color-error-foreground: var(--error-foreground);
--color-tool-error-icon: var(--tool-error-icon);
--color-error: var(--error);
--color-error-surface: var(--error-surface);
--color-update-foreground: var(--update-foreground);
--color-update: var(--update);
--color-success-foreground: var(--success-foreground);
--color-success: var(--success);
--color-diff-addition: var(--diff-addition);
--color-diff-deletion: var(--diff-deletion);
--color-diff-addition-foreground: var(--diff-addition-foreground);
--color-diff-deletion-foreground: var(--diff-deletion-foreground);
--color-info-foreground: var(--info-foreground);
--color-info: var(--info);
--color-guest: var(--guest);
--color-guest-foreground: var(--guest-foreground);
--color-guest-surface: var(--guest-surface);
--color-destructive-foreground: var(--destructive-foreground);
--color-ring: var(--ring);
--color-input: var(--contrast-input);
--color-border: var(--contrast-border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--contrast-accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--contrast-muted-foreground);
--color-muted: var(--muted);
--color-placeholder: var(--contrast-placeholder);
--color-secondary-label: var(--contrast-secondary-label);
--color-icon-muted: var(--contrast-icon-muted);
--color-secondary-foreground: var(--contrast-secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--contrast-popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--contrast-card-foreground);
--color-card: var(--card);
--color-code: var(--code-background);
--color-foreground: var(--contrast-foreground);
--color-background: var(--background);
--color-message: var(--message-surface);
--color-message-foreground: var(--contrast-message-foreground);
--color-message-action: var(--message-action);
--color-message-action-foreground: var(--message-action-foreground);
--color-message-action-hover: var(--message-action-hover);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--contrast-sidebar-foreground);
--color-sidebar-muted-foreground: var(--contrast-sidebar-muted-foreground);
--color-sidebar-control-surface: var(--sidebar-control-surface);
--color-sidebar-row-hover: var(--sidebar-row-hover);
--color-sidebar-row-active: var(--sidebar-row-active);
--color-sidebar-row-selected: var(--sidebar-row-selected);
--color-sidebar-border: var(--contrast-sidebar-border);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
@keyframes skeleton {
/* The single loading-bar breath used by every skeleton: one opacity pulse
per container, stepped so however many bars sit under it, the compositor
draws a handful of discrete frames per cycle rather than one per vsync —
which on a 120Hz display is the difference between ~14 and ~288 updates. */
0%,
42% {
opacity: 1;
animation-timing-function: steps(4);
}
50%,
92% {
opacity: 0.55;
animation-timing-function: steps(4);
}
100% {
opacity: 1;
}
}
@keyframes stash-pulse-row {
from,
85.7% {
color: var(--color-foreground);
}
}
@keyframes stash-pulse-row-alt {
from,
85.7% {
color: var(--color-foreground);
}
}
@keyframes stash-pulse-count {
from,
85.7% {
color: var(--color-primary);
}
}
@keyframes status-pulse {
0%,
40% {
opacity: 1;
animation-timing-function: steps(6);
}
50%,
90% {
opacity: 0.5;
animation-timing-function: steps(6);
}
100% {
opacity: 1;
}
}
@keyframes status-ping {
/* Burst first (immediate feedback for click ripples), then hold
invisible for the rest of the cycle. Mirrors animate-ping's
75%-scale start. */
0% {
opacity: 0.9;
scale: 0.75;
animation-timing-function: steps(8);
}
40%,
100% {
opacity: 0;
scale: 2;
}
}
}
@utility surface-glass {
background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
background: var(--background) !important;
}
}
@utility alert-glass {
--alert-glass-tint: transparent;
background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
&[data-variant="error"] {
--alert-glass-tint: var(--destructive);
}
&[data-variant="info"] {
--alert-glass-tint: var(--info);
}
&[data-variant="success"] {
--alert-glass-tint: var(--success);
}
&[data-variant="warning"] {
--alert-glass-tint: var(--warning);
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
background: var(--background) !important;
}
}
/* Paint find results without changing rendered markdown or copied text. */
::highlight(t3-thread-find) {
color: var(--app-theme-search-match-foreground, oklch(0.216164 0.028283 77.423));
background-color: var(--app-theme-search-match-background, oklch(0.915 0.11 85));
@variant dark {
color: var(--app-theme-search-match-foreground, oklch(0.970151 0 0));
background-color: var(--app-theme-search-match-background, oklch(0.55 0.1 85));
}
}
::highlight(t3-thread-find-active) {
color: var(--app-theme-search-match-active-foreground, oklch(0.216164 0.028283 77.423));
background-color: var(
--app-theme-search-match-active-background,
oklch(0.774202 0.151946 74.331)
);
}
@utility dialog-glass {
background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
border-color: color-mix(in srgb, var(--contrast-foreground) 10%, transparent);
box-shadow: 0 24px 64px -24px rgb(0 0 0 / 65%);
@variant dark {
border-color: color-mix(in srgb, var(--color-white) 8%, transparent);
box-shadow:
inset 0 1px rgb(255 255 255 / 4%),
0 24px 72px -20px rgb(0 0 0 / 90%);
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
background: var(--popover) !important;
}
}
@utility dialog-backdrop {
background: color-mix(in srgb, var(--background) 60%, transparent);
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
@variant dark {
background: color-mix(in srgb, var(--background) 64%, transparent);
}
}
@utility dropdown-glass {
background: color-mix(
in srgb,
var(--popover) 18%,
color-mix(in srgb, var(--popover) var(--glass-opacity), transparent)
);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
border: 1px solid color-mix(in srgb, var(--contrast-foreground) 10%, transparent);
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
background: var(--popover) !important;
}
}
@utility topbar-scroll-fade {
-webkit-mask-image:
linear-gradient(
to bottom,
transparent 0%,
rgb(0 0 0 / 10%) 10%,
rgb(0 0 0 / 30%) 24%,
rgb(0 0 0 / 58%) 42%,
rgb(0 0 0 / 82%) 62%,
rgb(0 0 0 / 96%) 82%,
black 100%
),
linear-gradient(black, black), linear-gradient(black, black);
-webkit-mask-position: top, bottom, right;
-webkit-mask-repeat: no-repeat;
-webkit-mask-size:
100% calc(var(--workspace-titlebar-scroll-fade-height) + 1px),
100% calc(100% - var(--workspace-titlebar-scroll-fade-height)),
var(--app-scrollbar-width) 100%;
mask-image:
linear-gradient(
to bottom,
transparent 0%,
rgb(0 0 0 / 10%) 10%,
rgb(0 0 0 / 30%) 24%,
rgb(0 0 0 / 58%) 42%,
rgb(0 0 0 / 82%) 62%,
rgb(0 0 0 / 96%) 82%,
black 100%
),
linear-gradient(black, black), linear-gradient(black, black);
mask-position: top, bottom, right;
mask-repeat: no-repeat;
mask-size:
100% calc(var(--workspace-titlebar-scroll-fade-height) + 1px),
100% calc(100% - var(--workspace-titlebar-scroll-fade-height)),
var(--app-scrollbar-width) 100%;
}
/* Virtualizers own their native scroll element, so they cannot use ScrollArea's
viewport fade. Keep the scrollbar lane opaque while sharing the same fade
contract across those lists. */
@utility virtualized-scroll-fade {
-webkit-mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black);
mask-image: var(--virtualized-scroll-fade-mask), linear-gradient(black, black);
-webkit-mask-position: left, right;
mask-position: left, right;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size:
calc(100% - var(--app-scrollbar-width)) 100%,
var(--app-scrollbar-width) 100%;
mask-size:
calc(100% - var(--app-scrollbar-width)) 100%,
var(--app-scrollbar-width) 100%;
}
/* Stage-channel art needs a mask and pseudo-element gradient, so keep the
behavior composable without tying it to the global components layer. */
@utility sidebar-stage-backdrop {
--stage-fade: var(--sidebar-stage-fade, var(--app-chrome-background));
mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%);
-webkit-mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 92%);
&::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
to bottom,
transparent 0%,
transparent 28%,
color-mix(in srgb, var(--stage-fade) 10%, transparent) 40%,
color-mix(in srgb, var(--stage-fade) 30%, transparent) 52%,
color-mix(in srgb, var(--stage-fade) 58%, transparent) 64%,
color-mix(in srgb, var(--stage-fade) 82%, transparent) 75%,
color-mix(in srgb, var(--stage-fade) 96%, transparent) 85%,
var(--stage-fade) 93%
);
}
}
@keyframes live-tool-shine {
from {
background-position:
-4.5rem 0,
0 0;
}
to {
background-position:
calc(100% + 4.5rem) 0,
0 0;
}
}
@utility live-tool-shine {
@media (prefers-reduced-motion: no-preference) and (forced-colors: none) {
color: transparent;
background-image:
linear-gradient(to right, transparent, var(--contrast-foreground), transparent),
linear-gradient(var(--contrast-secondary-label), var(--contrast-secondary-label));
background-size:
4.5rem 100%,
100% 100%;
background-repeat: no-repeat;
background-clip: text;
animation: live-tool-shine 2.2s steps(30) infinite;
animation-play-state: var(--visible-animation-state, paused);
}
}
@utility visible-animate-spin {
animation: var(--animate-spin);
animation-play-state: var(--visible-animation-state, paused);
will-change: var(--visible-animation-will-change, auto);
}
@keyframes live-activity-focus {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@keyframes live-activity-focus-counter {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@utility live-activity-focus {
--live-activity-focus-width: 4.5rem;
right: auto;
left: calc(-1 * var(--live-activity-focus-width));
width: calc(100% + var(--live-activity-focus-width) + var(--live-activity-focus-width));
-webkit-mask-image: linear-gradient(
to right,
transparent 0,
rgb(0 0 0 / 12%) 0.675rem,
rgb(0 0 0 / 55%) 1.575rem,
black 2.25rem,
rgb(0 0 0 / 55%) 2.925rem,
rgb(0 0 0 / 12%) 3.825rem,
transparent var(--live-activity-focus-width),
transparent 100%
);
-webkit-mask-repeat: no-repeat;
mask-image: linear-gradient(
to right,
transparent 0,
rgb(0 0 0 / 12%) 0.675rem,
rgb(0 0 0 / 55%) 1.575rem,
black 2.25rem,
rgb(0 0 0 / 55%) 2.925rem,
rgb(0 0 0 / 12%) 3.825rem,
transparent var(--live-activity-focus-width),
transparent 100%
);
mask-repeat: no-repeat;
animation: live-activity-focus 2.2s steps(30) infinite;
animation-play-state: var(--visible-animation-state, paused);
will-change: var(--visible-animation-will-change, auto);
@media (prefers-reduced-motion: reduce), (forced-colors: active) {
animation: none;
opacity: 0;
will-change: auto;
}
}
@utility live-activity-focus-counter {
width: 100%;
animation: live-activity-focus-counter 2.2s steps(30) infinite;
animation-play-state: var(--visible-animation-state, paused);
will-change: var(--visible-animation-will-change, auto);
@media (prefers-reduced-motion: reduce), (forced-colors: active) {
animation: none;
will-change: auto;
}
}
@utility live-activity-focus-aligned {
width: calc(100% - var(--live-activity-focus-width) - var(--live-activity-focus-width));
margin-left: var(--live-activity-focus-width);
}
@property --assistant-citation-highlight-opacity {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@layer base {
::highlight(t3-assistant-citation) {
background-color: color-mix(
in oklab,
var(--primary) calc(var(--assistant-citation-highlight-opacity) * 100%),
transparent
);
}
::highlight(t3-assistant-citation-comment) {
background-color: color-mix(in oklab, var(--primary) 45%, transparent);
}
:root {
/* Keep the original T3 Code artwork palettes as the defaults. Built-in
themes override them with their own pigments in the components layer. */
--stage-art-top: oklch(0.782169 0.123386 240.226);
--stage-art-mid: oklch(0.616111 0.195824 259.735);
--stage-art-bottom: oklch(0.441553 0.232394 265.474);
--stage-art-highlight: oklch(0.951597 0.037289 215.482);
--stage-art-secondary: oklch(0.794668 0.12136 235.46);
--stage-art-tertiary: oklch(0.678991 0.170261 275.365);
--stage-art-line: oklch(0.959666 0.029238 218.179);
--stage-art-celeste-highlight: oklch(0.968763 0.045822 196.42);
--stage-art-celeste-secondary: oklch(0.827395 0.126071 211.26);
--stage-art-violet-highlight: oklch(0.895381 0.053248 286.447);
--stage-art-grid-line: oklch(0.966822 0.01757 239.99);
--stage-night-base-top: oklch(0.283792 0.117327 297.201);
--stage-night-base-mid: oklch(0.227147 0.086086 277.99);
--stage-night-base-bottom: oklch(0.200528 0.055699 261.216);
--stage-night-top: var(--stage-night-base-top);
--stage-night-mid: var(--stage-night-base-mid);
--stage-night-bottom: var(--stage-night-base-bottom);
--stage-night-highlight: oklch(0.707246 0.157418 252.091);
--stage-night-secondary: oklch(0.600473 0.182225 277.296);
--stage-night-tertiary: oklch(0.62583 0.210886 305.994);
--stage-night-line: oklch(0.938794 0.029114 273.103);
--stage-night-glow-highlight: oklch(0.553749 0.176543 271.958);
--stage-night-glow-secondary: oklch(0.345571 0.117466 273.568);
--stage-night-sparkle: oklch(0.880867 0.057747 269.011);
@variant dark {
--stage-art-top: oklch(0.581473 0.149124 256.9);
--stage-art-mid: oklch(0.456509 0.159377 261.945);
--stage-art-bottom: oklch(0.291327 0.136578 267.649);
}
}
* {
@apply border-border outline-ring/50;
}
/* The app's 2px outlines stay inside controls at clipped and scrollport edges. */
*,
::before,
::after {
outline-offset: -2px;
}
:where([data-slot="menu-popup"], [data-slot="select-popup"], [data-slot="popover-popup"]):focus,
:where(
[data-slot="menu-popup"],
[data-slot="select-popup"],
[data-slot="popover-popup"]
):focus-visible {
@apply outline-none ring-0;
}
html,
body {
background-color: var(--app-chrome-background);
}
body {
@apply text-foreground relative;
}
}
@layer components {
/* AnimatedHeight owns resizing for its contents. Nested collapsibles must
expose their final layout immediately, otherwise each animation frame
restarts the parent's transition and the dialog trails behind its content. */
[data-slot="animated-height"] [data-slot="collapsible-panel"] {
height: auto !important;
transition: none !important;
}
[data-slot="animated-height"] [data-slot="collapsible-panel"][data-closed] {
display: none;
}
/* Each maintainer palette gives the same line art its own material: rose
vellum, forest drafting paper, marine cyanotype, copper, and violet ink.
These colors stay deliberately deep at the top edge so the white stage
wordmark and controls retain contrast in both appearance modes. */
html[data-theme-id="t3-chat"] {
--stage-art-top: oklch(0.760675 0.141361 348.019);
--stage-art-mid: oklch(0.588093 0.188832 353.138);
--stage-art-bottom: oklch(0.383959 0.135096 348.656);
--stage-art-highlight: oklch(0.949689 0.034241 339.171);
--stage-art-secondary: oklch(0.763402 0.163836 352.525);
--stage-art-tertiary: oklch(0.70819 0.180285 311.949);
--stage-art-line: oklch(0.952158 0.034194 336.179);
@variant dark {
--stage-art-top: oklch(0.540689 0.143665 347.587);
--stage-art-mid: oklch(0.396586 0.126592 347.6);
--stage-art-bottom: oklch(0.249959 0.079694 340.523);
--stage-art-highlight: oklch(0.921297 0.051708 343.229);
--stage-art-secondary: oklch(0.667398 0.165674 352.549);
--stage-art-tertiary: oklch(0.609315 0.163722 306.315);
--stage-art-line: oklch(0.945349 0.036045 341.433);
}
}
html[data-theme-id="grove"] {
--stage-art-top: oklch(0.751465 0.112731 157.313);
--stage-art-mid: oklch(0.567105 0.11099 158.859);
--stage-art-bottom: oklch(0.367918 0.067635 160.558);
--stage-art-highlight: oklch(0.976387 0.034353 161.456);
--stage-art-secondary: oklch(0.80968 0.117071 158.817);
--stage-art-tertiary: oklch(0.784102 0.108047 88.516);
--stage-art-line: oklch(0.981098 0.027812 158.718);
--stage-night-top: oklch(0.45805 0.079616 160.092);
--stage-night-mid: oklch(0.368483 0.065631 162.719);
--stage-night-bottom: oklch(0.264797 0.044397 168.088);
--stage-night-highlight: oklch(0.912563 0.055173 158.974);
--stage-night-secondary: oklch(0.665652 0.109731 156.599);
--stage-night-tertiary: oklch(0.698651 0.103024 89.828);
--stage-night-line: oklch(0.945336 0.041923 157.222);
@variant dark {
--stage-art-top: oklch(0.58719 0.09869 157.426);
--stage-art-mid: oklch(0.454979 0.079031 159.756);
--stage-art-bottom: oklch(0.297856 0.050355 161.167);
--stage-art-highlight: oklch(0.952407 0.053872 158.44);
--stage-art-secondary: oklch(0.732591 0.120606 155.853);
--stage-art-tertiary: oklch(0.716282 0.116547 80.563);
--stage-art-line: oklch(0.961577 0.035285 157.03);
--stage-night-top: oklch(0.398632 0.065534 158.601);
--stage-night-mid: oklch(0.290561 0.049694 160.456);
--stage-night-bottom: oklch(0.210147 0.03173 169.818);
--stage-night-highlight: oklch(0.866303 0.057526 156.796);
--stage-night-secondary: oklch(0.586553 0.093722 157.365);
--stage-night-tertiary: oklch(0.6364 0.101769 82.985);
--stage-night-line: oklch(0.913292 0.035718 156.976);
}
}
html[data-theme-id="ocean"] {
--stage-art-top: oklch(0.792993 0.088072 220.421);
--stage-art-mid: oklch(0.590296 0.116841 243.184);
--stage-art-bottom: oklch(0.409123 0.093718 247.98);
--stage-art-highlight: oklch(0.971217 0.023008 213.615);
--stage-art-secondary: oklch(0.788391 0.090856 215.684);
--stage-art-tertiary: oklch(0.76441 0.099607 187.893);
--stage-art-line: oklch(0.976025 0.019647 212.543);
@variant dark {
--stage-art-top: oklch(0.59663 0.089167 233.427);
--stage-art-mid: oklch(0.461094 0.084904 243.478);
--stage-art-bottom: oklch(0.294818 0.05947 250.526);
--stage-art-highlight: oklch(0.952907 0.032224 221.27);
--stage-art-secondary: oklch(0.732079 0.09296 224.414);
--stage-art-tertiary: oklch(0.720885 0.095495 190.903);
--stage-art-line: oklch(0.961039 0.027355 219.756);
}
}
html[data-theme-id="ember"] {
--stage-art-top: oklch(0.782439 0.118806 52.569);
--stage-art-mid: oklch(0.62863 0.15922 39.401);
--stage-art-bottom: oklch(0.404251 0.117664 30.424);
--stage-art-highlight: oklch(0.962469 0.02774 70.95);
--stage-art-secondary: oklch(0.815735 0.11904 53.659);
--stage-art-tertiary: oklch(0.69361 0.159346 24.787);
--stage-art-line: oklch(0.970313 0.02052 67.583);
--stage-night-top: oklch(0.470236 0.102322 39.355);
--stage-night-mid: oklch(0.345367 0.080697 33.217);
--stage-night-bottom: oklch(0.227386 0.056077 27.437);
--stage-night-highlight: oklch(0.903891 0.059281 56.181);
--stage-night-secondary: oklch(0.641705 0.126508 44.376);
--stage-night-tertiary: oklch(0.538694 0.129931 25.865);
--stage-night-line: oklch(0.926348 0.046029 58.73);
@variant dark {
--stage-art-top: oklch(0.597533 0.120694 43.455);
--stage-art-mid: oklch(0.437763 0.101287 34.86);
--stage-art-bottom: oklch(0.264269 0.055858 26.548);
--stage-art-highlight: oklch(0.929214 0.042638 55.801);
--stage-art-secondary: oklch(0.705592 0.137369 43.176);
--stage-art-tertiary: oklch(0.629583 0.158322 24.088);
--stage-art-line: oklch(0.945058 0.033906 58.824);
--stage-night-top: oklch(0.392352 0.081287 36.444);
--stage-night-mid: oklch(0.271305 0.056352 31.135);
--stage-night-bottom: oklch(0.182126 0.028154 27.774);
--stage-night-highlight: oklch(0.851007 0.061294 53.805);
--stage-night-secondary: oklch(0.560789 0.10645 42.953);
--stage-night-tertiary: oklch(0.476228 0.106656 24.165);
--stage-night-line: oklch(0.884931 0.046607 56.556);
}
}
html[data-theme-id="iris"] {
--stage-art-top: oklch(0.738305 0.113454 298.926);
--stage-art-mid: oklch(0.551826 0.159189 294.531);
--stage-art-bottom: oklch(0.37147 0.133317 289.999);
--stage-art-highlight: oklch(0.949472 0.029204 303.081);
--stage-art-secondary: oklch(0.745085 0.125892 298.647);
--stage-art-tertiary: oklch(0.73066 0.167815 340.964);
--stage-art-line: oklch(0.960278 0.024064 306.969);
@variant dark {
--stage-art-top: oklch(0.57297 0.145973 295.185);
--stage-art-mid: oklch(0.419499 0.13752 292.131);
--stage-art-bottom: oklch(0.274235 0.095798 286.608);
--stage-art-highlight: oklch(0.916698 0.047206 300.224);
--stage-art-secondary: oklch(0.670994 0.13095 296.689);
--stage-art-tertiary: oklch(0.679357 0.165376 340.439);
--stage-art-line: oklch(0.940582 0.032921 299.076);
}
}
:is(html[data-theme-id="t3-chat"], html[data-theme-id="ocean"], html[data-theme-id="iris"]) {
--stage-night-top: color-mix(in oklch, var(--stage-art-top) 38%, var(--stage-night-base-top));
--stage-night-mid: color-mix(in oklch, var(--stage-art-mid) 38%, var(--stage-night-base-mid));
--stage-night-bottom: color-mix(
in oklch,
var(--stage-art-bottom) 38%,
var(--stage-night-base-bottom)
);
--stage-night-highlight: var(--stage-art-highlight);
--stage-night-secondary: color-mix(
in oklch,
var(--stage-art-secondary) 55%,
var(--stage-night-mid)
);
--stage-night-tertiary: color-mix(
in oklch,
var(--stage-art-tertiary) 60%,
var(--stage-night-top)
);
--stage-night-line: color-mix(in oklch, var(--stage-art-line) 82%, oklch(1 0 0));
}
:is(
html[data-theme-id="t3-chat"],
html[data-theme-id="grove"],
html[data-theme-id="ocean"],
html[data-theme-id="ember"],
html[data-theme-id="iris"]
) {
--stage-art-celeste-highlight: var(--stage-art-highlight);
--stage-art-celeste-secondary: var(--stage-art-secondary);
--stage-art-violet-highlight: var(--stage-art-highlight);
--stage-art-grid-line: var(--stage-art-line);
--stage-night-glow-highlight: var(--stage-night-highlight);
--stage-night-glow-secondary: var(--stage-night-secondary);
--stage-night-sparkle: var(--stage-night-line);
}
@keyframes settings-search-target-pulse {
0%,
100% {
box-shadow: inset 0 0 0 0 transparent;
}
50% {
box-shadow: inset 0 0 0 2px color-mix(in oklab, var(--color-primary) 70%, transparent);
}
}
[data-settings-page-scroll] div.settings-search-target-pulse,
[data-settings-page-scroll] section.settings-search-target-pulse > div:first-child {
animation: settings-search-target-pulse 650ms ease-in-out 2;
border-radius: 0.75rem;
}
/* The pulse is the destination indicator; without it (reduced motion), the
focus outline takes over, so exactly one indicator shows at a time. */
[data-settings-page-scroll] .settings-search-target-pulse:focus {
outline: none;
}
[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}
.chat-content-lane {
margin-inline: auto;
width: 100%;
min-width: 0;
max-width: var(--chat-content-max-width);
}
.messages-timeline-row-frame {
padding-inline: 0;
}
.settings-slider {
--settings-slider-progress: 0%;
--settings-slider-fill-offset: 0.5rem;
--settings-slider-fill-position: calc(
var(--settings-slider-progress) + var(--settings-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}
.settings-slider::-webkit-slider-runnable-track {
height: 0.375rem;
border-radius: 9999px;
background: linear-gradient(
to right,
var(--primary) 0 var(--settings-slider-fill-position),
color-mix(in srgb, var(--contrast-muted-foreground) 22%, transparent)
var(--settings-slider-fill-position) 100%
);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--contrast-border) 55%, transparent);
}
.settings-slider::-moz-range-track {
height: 0.375rem;
border-radius: 9999px;
background: color-mix(in srgb, var(--contrast-muted-foreground) 22%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--contrast-border) 55%, transparent);
}
.settings-slider::-moz-range-progress {
height: 0.375rem;
border-radius: 9999px;
background: var(--primary);
}
.settings-slider::-webkit-slider-thumb {
appearance: none;
width: 1rem;
height: 1rem;
margin-top: -0.3125rem;
border: 2px solid var(--primary);
border-radius: 9999px;
background: var(--background);
box-shadow: 0 1px 2px color-mix(in srgb, var(--contrast-foreground) 12%, transparent);
transition:
transform 120ms ease,
box-shadow 120ms ease;
}
.settings-slider::-moz-range-thumb {
width: 1rem;
height: 1rem;
border: 2px solid var(--primary);
border-radius: 9999px;
background: var(--background);
box-shadow: 0 1px 2px color-mix(in srgb, var(--contrast-foreground) 12%, transparent);
transition:
transform 120ms ease,
box-shadow 120ms ease;
}
.settings-slider:hover::-webkit-slider-thumb {
transform: scale(1.08);
box-shadow: 0 1px 3px color-mix(in srgb, var(--contrast-foreground) 20%, transparent);
}
.settings-slider:hover::-moz-range-thumb {
transform: scale(1.08);
box-shadow: 0 1px 3px color-mix(in srgb, var(--contrast-foreground) 20%, transparent);
}
.settings-slider:active::-webkit-slider-thumb {
transform: scale(0.94);
}
.settings-slider:active::-moz-range-thumb {
transform: scale(0.94);
}
.settings-slider:focus-visible {
outline: none;
}
.settings-slider:focus-visible::-webkit-slider-thumb {
box-shadow:
inset 0 0 0 1px var(--background),
inset 0 0 0 3px var(--ring);
}
.settings-slider:focus-visible::-moz-range-thumb {
box-shadow:
inset 0 0 0 1px var(--background),
inset 0 0 0 3px var(--ring);
}
@media (forced-colors: active) {
.settings-slider {
appearance: auto;
accent-color: Highlight;
}
.settings-slider::-webkit-slider-runnable-track,
.settings-slider::-webkit-slider-thumb {
all: revert;
}
.settings-slider::-moz-range-track,
.settings-slider::-moz-range-progress,
.settings-slider::-moz-range-thumb {
all: revert;
}
}
}
/* Safe-area inset utilities for surfaces that opt into edge-to-edge rendering.
Insets resolve to 0 when the browser already constrains the layout viewport
to the safe area. */
@utility pt-safe {
padding-top: max(env(safe-area-inset-top), 0px);
}
@utility pb-safe {
padding-bottom: max(env(safe-area-inset-bottom), 0px);
}
@utility pl-safe {
padding-left: max(env(safe-area-inset-left), 0px);
}
@utility pr-safe {
padding-right: max(env(safe-area-inset-right), 0px);
}
/* Grain texture for chrome surfaces that float above the body (see the
--surface-grain note). Layered over the element's background-color, so it
composes with bg-* utilities. */
@utility surface-grain {
background-image: var(--surface-grain);
background-repeat: repeat;
background-size: var(--surface-grain-size);
}
/* Suppress all transitions during theme changes */
.no-transitions,
.no-transitions *,
.no-transitions *::before,
.no-transitions *::after {
transition-duration: 0s !important;
animation-duration: 0s !important;
}
:root {
color-scheme: light;
--radius: 0.5rem;
--background: var(--color-zinc-25);
--app-chrome-background: var(--background);
--toolbar-background: var(--app-chrome-background);
--toolbar-foreground: var(--foreground);
--toolbar-border: var(--border);
--toolbar-control: var(--popover);
--toolbar-control-foreground: var(--foreground);
--toolbar-control-hover: var(--accent);
--surface-raised: color-mix(in srgb, var(--card) 20%, transparent);
--foreground: var(--color-zinc-900);
--card: var(--color-white);
--card-foreground: var(--color-zinc-900);
--popover: var(--color-white);
--popover-foreground: var(--color-zinc-900);
--primary: oklch(0.488 0.217 264);
--primary-foreground: var(--color-white);
--secondary: var(--color-zinc-50);
--secondary-foreground: var(--color-zinc-800);
--muted: var(--color-zinc-50);
--muted-foreground: var(--color-zinc-500);
--placeholder: var(--muted-foreground);
--secondary-label: var(--muted-foreground);
--icon-muted: var(--muted-foreground);
/* One step past the accent, so a user turn stands off the near-white canvas. */
--message-surface: color-mix(in srgb, var(--color-zinc-200) 55%, var(--color-zinc-100));
--message-foreground: var(--foreground);
--message-action: var(--primary);
--message-action-foreground: var(--primary-foreground);
--message-action-hover: color-mix(in srgb, var(--primary) 90%, var(--background));
--accent: var(--color-zinc-100);
--accent-foreground: var(--color-zinc-900);
--error: var(--color-red-500);
--error-foreground: var(--color-red-700);
--tool-error-icon: var(--error);
--error-surface: color-mix(in srgb, var(--error) 8%, transparent);
--destructive: var(--error);
--border: var(--color-zinc-200);
--input: var(--color-zinc-300);
--ring: var(--primary);
--destructive-foreground: var(--error-foreground);
--info: var(--color-blue-500);
--info-foreground: var(--color-blue-700);
/* Messages from a guest link, and the sidebar Request status. */
--guest: var(--color-fuchsia-500);
--guest-foreground: var(--color-fuchsia-600);
--guest-surface: color-mix(in srgb, var(--guest) 14%, var(--message-surface));
--success: var(--color-emerald-500);
--success-foreground: var(--color-emerald-700);
--warning: var(--color-amber-500);
--warning-foreground: var(--color-amber-700);
--warning-surface: color-mix(in srgb, var(--warning) 8%, transparent);
--update: var(--primary);
--update-foreground: var(--primary);
/* Keep every sidebar primitive on the same light surface hierarchy, including
portaled mobile sheets and settings navigation outside the app sidebar. */
--sidebar: var(--color-zinc-50);
--sidebar-foreground: var(--foreground);
--sidebar-muted-foreground: var(--muted-foreground);
--sidebar-control-surface: var(--color-zinc-100);
--sidebar-row-hover: var(--color-zinc-25);
--sidebar-row-active: var(--color-white);
--sidebar-row-selected: var(--color-white);
--sidebar-border: var(--border);
--sidebar-stage-fade: var(--sidebar);
--code-background: color-mix(in srgb, var(--card) 90%, var(--background));
--code-foreground: var(--foreground);
--terminal-background: var(--background);
--terminal-foreground: var(--foreground);
--terminal-cursor: rgb(38 56 78);
--terminal-selection-background: rgb(37 63 99 / 20%);
@variant dark {
color-scheme: dark;
/* The canvas is true black, matching the sidebar. Raised surfaces keep the tone they had
over neutral-950, so cards, popovers, and the composer still lift. Borders and hover
states provide separation without milky gray fills. */
--background: #000;
--surface-raised: color-mix(in srgb, var(--color-neutral-950) 97%, var(--color-white));
--foreground: var(--color-neutral-100);
--card: color-mix(in srgb, var(--color-neutral-950) 97%, var(--color-white));
--card-foreground: var(--color-neutral-100);
--popover: color-mix(in srgb, var(--color-neutral-950) 97%, var(--color-white));
--popover-foreground: var(--color-neutral-100);
--primary: oklch(0.571 0.21 264);
--secondary: --alpha(var(--color-white) / 3%);
--secondary-foreground: var(--color-neutral-100);
--muted: --alpha(var(--color-white) / 3%);
--muted-foreground: var(--color-neutral-400);
--accent: --alpha(var(--color-white) / 4%);
--accent-foreground: var(--color-neutral-100);
/* A user turn sits above the composer tone, level with the selected sidebar row. */
--message-surface: color-mix(in srgb, var(--color-neutral-950) 92%, var(--color-white));
--error: color-mix(in srgb, var(--color-red-500) 90%, var(--color-white));
--error-foreground: var(--color-red-400);
--tool-error-icon: #fca5a5;
--error-surface: color-mix(in srgb, var(--error) 16%, transparent);
--border: --alpha(var(--color-white) / 8%);
--input: --alpha(var(--color-white) / 11%);
--info-foreground: var(--color-blue-400);
--guest-foreground: var(--color-fuchsia-300);
--success-foreground: var(--color-emerald-400);
--warning-foreground: var(--color-amber-400);
--warning-surface: color-mix(in srgb, var(--warning) 16%, transparent);
--update-foreground: var(--color-blue-400);
--sidebar: var(--card);
--sidebar-control-surface: var(--muted);
--sidebar-row-hover: var(--accent);
--sidebar-row-active: var(--accent);
--sidebar-row-selected: var(--muted);
--sidebar-stage-fade: var(--card);
--terminal-cursor: rgb(180 203 255);
--terminal-selection-background: rgb(180 203 255 / 25%);
}
}
/* Keep both sidebar implementations (default and legacy) on the same quiet
zinc hierarchy: zinc-50 navigation, zinc-25 hover, and white selected/raised
surfaces. */
[data-app-sidebar] {
--background: var(--color-zinc-25);
--foreground: var(--color-zinc-900);
--card: var(--color-white);
--card-foreground: var(--color-zinc-900);
--accent: var(--color-zinc-100);
--accent-foreground: var(--color-zinc-900);
--muted: var(--color-zinc-50);
--muted-foreground: var(--color-zinc-500);
--border: var(--color-zinc-200);
--input: var(--color-zinc-300);
--sidebar: var(--color-zinc-50);
--sidebar-foreground: var(--color-zinc-900);
--sidebar-muted-foreground: var(--color-zinc-500);
--sidebar-control-surface: var(--color-zinc-100);
--sidebar-row-hover: var(--color-zinc-25);
--sidebar-row-active: var(--color-white);
--sidebar-row-selected: var(--color-white);
--sidebar-border: var(--color-zinc-200);
--sidebar-stage-fade: var(--sidebar);
@variant dark {
--background: #000;
--foreground: #f1f3f7;
--card: #000;
--card-foreground: var(--foreground);
--accent: #191a1d;
--accent-foreground: #f7f9ff;
--muted: #0a0a0a;
--muted-foreground: #a3a3a3;
--border: rgb(255 255 255 / 8%);
--input: rgb(255 255 255 / 18%);
--sidebar: var(--card);
--sidebar-foreground: var(--foreground);
--sidebar-muted-foreground: var(--muted-foreground);
--sidebar-control-surface: var(--muted);
--sidebar-row-hover: color-mix(in srgb, var(--contrast-foreground) 8%, transparent);
--sidebar-row-active: color-mix(in srgb, var(--contrast-foreground) 11%, transparent);
--sidebar-row-selected: color-mix(in srgb, var(--contrast-foreground) 7%, transparent);
--sidebar-border: var(--border);
--sidebar-stage-fade: var(--card);
/* Row metadata sits a step below read titles (75% white). */
--secondary-label: #8f8f8f;
}
}
/* Modified files get a muted blue so added and deleted files lead. Built from the info token,
which themes do not override, so it stays blue in every theme. */
:root {
--diff-modified-icon: color-mix(in srgb, var(--info-foreground) 50%, var(--muted-foreground));
@variant dark {
/* On black a gray mix stays as bright as the blue, so dim with transparency instead. */
--diff-modified-icon: color-mix(in srgb, var(--info-foreground) 65%, transparent);
--diff-added-icon: var(--success-foreground);
}
}
/* Blue-orange already spends blue on additions. Same specificity as the dark block above and
later, so it wins there too. */
:root[data-diff-color-scheme="blue-orange"] {
--diff-modified-icon: var(--muted-foreground);
--diff-added-icon: initial;
@variant dark {
--diff-modified-icon: color-mix(in srgb, var(--muted-foreground) 65%, transparent);
}
}
/* Sections in the provider detail pane sit flat in its card instead of nesting cards inside
it. Headings and row dividers still separate them. */
[data-provider-detail] [data-slot="settings-group"] {
border-color: transparent;
background: transparent;
box-shadow: none;
}
/* A diff header's copy button shows on file hover or focus. */
@media (hover: hover) {
diffs-container:not(:hover) [data-diff-copy-path]:not(:focus-within) {
opacity: 0;
}
}
/* Theme files are expressed in app color roles and mapped to the existing
semantic surface tokens here. Keep this block after the sidebar-version
compatibility overrides so both navigation implementations receive the
same palette. Success, info, provider, and channel identity colors remain
independent; error, warning, and update roles are themeable below. */
/* The non-empty marker adds enough specificity to outrank the generated root
dark variant without reintroducing raw `.dark` selectors. */
html[data-theme-id]:not([data-theme-id=""]) {
--background: var(--app-theme-canvas);
--app-chrome-background: var(--app-theme-chrome);
--toolbar-background: var(--app-theme-toolbar);
--toolbar-foreground: var(--app-theme-toolbar-foreground);
--toolbar-border: var(--app-theme-toolbar-border);
--toolbar-control: var(--app-theme-toolbar-control);
--toolbar-control-foreground: var(--app-theme-toolbar-control-foreground);
--toolbar-control-hover: var(--app-theme-toolbar-control-hover);
--surface-raised: var(--app-theme-surface-raised);
--foreground: var(--app-theme-text);
--card: var(--app-theme-surface);
--card-foreground: var(--app-theme-text);
--popover: var(--app-theme-surface-overlay);
--popover-foreground: var(--app-theme-text);
/* Solid controls (CTAs, switches, sliders, the send button) all share the
theme's action color so every "act now" affordance reads consistently;
the accent keeps driving surfaces, selection, and focus. Palettes without
a distinct action color make these identical. */
--primary: var(--app-theme-message-action);
--primary-foreground: var(--app-theme-message-action-foreground);
--secondary: var(--app-theme-secondary);
--secondary-foreground: var(--app-theme-secondary-foreground);
--muted: var(--app-theme-muted);
--muted-foreground: var(--app-theme-muted-foreground);
--placeholder: var(--app-theme-placeholder);
--secondary-label: var(--app-theme-secondary-label);
--icon-muted: var(--app-theme-icon-muted);
--accent: var(--app-theme-accent-surface);
--accent-foreground: var(--app-theme-accent-surface-foreground);
--message-surface: var(--app-theme-message-surface);
--message-foreground: var(--app-theme-message-foreground);
--message-action: var(--app-theme-message-action);
--message-action-foreground: var(--app-theme-message-action-foreground);
--message-action-hover: var(--app-theme-message-action-hover);
--error: var(--app-theme-error);
--error-foreground: var(--app-theme-error-foreground);
--tool-error-icon: var(--error-foreground);
--error-surface: var(--app-theme-error-surface);
--destructive: var(--error);
--destructive-foreground: var(--error-foreground);
--warning: var(--app-theme-warning);
--warning-foreground: var(--app-theme-warning-foreground);
--warning-surface: var(--app-theme-warning-surface);
--update: var(--app-theme-update);
--update-foreground: var(--app-theme-update-foreground);
--border: var(--app-theme-border);
--input: var(--app-theme-input);
--ring: var(--app-theme-focus);
--sidebar: var(--app-theme-sidebar);
--sidebar-foreground: var(--app-theme-sidebar-foreground);
--sidebar-muted-foreground: var(--app-theme-sidebar-muted-foreground);
--sidebar-control-surface: var(--app-theme-sidebar-control-surface);
--sidebar-row-hover: var(--app-theme-sidebar-row-hover);
--sidebar-row-active: var(--app-theme-sidebar-row-active);
--sidebar-row-selected: var(--app-theme-sidebar-row-selected);
--sidebar-border: var(--app-theme-sidebar-border);
--sidebar-stage-fade: var(--app-theme-sidebar);
--code-background: var(--app-theme-code-background);
--code-foreground: var(--app-theme-code-foreground);
--terminal-background: var(--app-theme-terminal-background);
--terminal-foreground: var(--app-theme-terminal-foreground);
--terminal-cursor: var(--app-theme-terminal-cursor);
--terminal-selection-background: var(--app-theme-terminal-selection-background);
}
/* Theme-token dependency probes are restored synchronously, before paint. Keep
transitions from observing the temporary sentinel color in between. */
html[data-theme-token-probe],
html[data-theme-token-probe] *,
html[data-theme-token-probe] *::before,
html[data-theme-token-probe] *::after {
transition: none !important;
}
#theme-inspector-spotlight {
position: fixed;
z-index: 100;
inset: 0;
width: 100vw;
height: 100dvh;
overflow: visible;
pointer-events: none;
}
[data-slot="popover-positioner"]:has([data-theme-editor-panel]),
[data-slot="tooltip-positioner"]:has([data-theme-editor-panel]) {
z-index: 120;
}
.theme-inspector-spotlight-dimmer {
fill: rgb(2 3 8 / 54%);
}
.theme-inspector-spotlight-glow {
fill: color-mix(in oklab, var(--ring) 9%, transparent);
stroke: color-mix(in oklab, var(--ring) 72%, white);
stroke-width: 2px;
opacity: 0.92;
vector-effect: non-scaling-stroke;
}
#theme-inspector-hover {
position: fixed;
z-index: 101;
pointer-events: none;
background: color-mix(in oklab, var(--ring) 7%, transparent);
border: 1px solid color-mix(in oklab, var(--ring) 70%, white);
box-shadow:
0 0 0 3px color-mix(in oklab, var(--ring) 16%, transparent),
0 0 18px color-mix(in oklab, var(--ring) 38%, transparent);
}
#theme-inspector-hover [data-theme-inspector-hover-label] {
position: absolute;
left: 0;
max-width: 12rem;
overflow: hidden;
padding: 0.2rem 0.45rem;
border: 1px solid color-mix(in oklab, var(--ring) 48%, var(--contrast-border));
border-radius: 0.4rem;
background: var(--popover);
box-shadow: 0 4px 16px rgb(0 0 0 / 24%);
color: var(--contrast-popover-foreground);
font-size: 0.6875rem;
font-weight: 600;
line-height: 1rem;
text-overflow: ellipsis;
white-space: nowrap;
}
#theme-inspector-hover[data-placement="above"] [data-theme-inspector-hover-label] {
bottom: calc(100% + 0.35rem);
}
#theme-inspector-hover[data-placement="below"] [data-theme-inspector-hover-label] {
top: calc(100% + 0.35rem);
}
/* Workspace actions use a small, separate chrome hierarchy. Keeping these
roles explicit prevents the thread toolbar from falling back to the default
dark-mode whites while preserving the same theme file across clients. */
html[data-theme-id] [data-chat-header] {
background-color: var(--toolbar-background);
color: var(--contrast-toolbar-foreground);
}
html[data-theme-id] [data-chat-header] [data-slot="button"],
html[data-theme-id] [data-chat-header] [data-slot="menu-trigger"],
html[data-theme-id] [data-chat-header] [data-toolbar-control] {
--control-icon-color: var(--contrast-toolbar-control-foreground);
background-color: var(--toolbar-control);
border-color: var(--contrast-toolbar-border);
color: var(--contrast-toolbar-control-foreground);
}
/* The panel layout toggles stay ghost: they render both inside the header and
in the titlebar strip, so filling them would make them change appearance as
the panel opens. They only take the themed foreground; hover and pressed
keep the base ghost accent. The tooltip trigger's data-slot wins over the
toggle's when the trigger renders the toggle, so match both. */
html[data-theme-id] [data-panel-layout-controls] [data-slot="toggle"],
html[data-theme-id] [data-panel-layout-controls] [data-slot="tooltip-trigger"],
html[data-theme-id] [data-workspace-titlebar-controls] [data-slot="toggle"],
html[data-theme-id] [data-workspace-titlebar-controls] [data-slot="tooltip-trigger"] {
--control-icon-color: var(--contrast-toolbar-foreground);
color: var(--contrast-toolbar-foreground);
}
html[data-theme-id] [data-panel-layout-controls] [data-slot="toggle"]:disabled,
html[data-theme-id] [data-panel-layout-controls] [data-slot="tooltip-trigger"]:disabled,
html[data-theme-id] [data-workspace-titlebar-controls] [data-slot="toggle"]:disabled,
html[data-theme-id] [data-workspace-titlebar-controls] [data-slot="tooltip-trigger"]:disabled {
--control-icon-color: color-mix(
in oklab,
var(--contrast-toolbar-foreground) 55%,
var(--toolbar-background)
);
color: color-mix(in oklab, var(--contrast-toolbar-foreground) 55%, var(--toolbar-background));
}
html[data-theme-id] [data-chat-header] [data-slot="button"]:hover,
html[data-theme-id] [data-chat-header] [data-slot="button"][data-pressed],
html[data-theme-id] [data-chat-header] [data-slot="menu-trigger"]:hover,
html[data-theme-id] [data-chat-header] [data-slot="menu-trigger"][data-pressed],
html[data-theme-id] [data-chat-header] [data-toolbar-control]:hover,
html[data-theme-id] [data-chat-header] [data-toolbar-control][data-pressed] {
background-color: var(--toolbar-control-hover);
}
html[data-theme-id] [data-chat-header] [data-slot="separator"] {
background-color: var(--contrast-toolbar-border);
}
/* Chat code blocks join the other code surfaces on the code tokens. The
default look keeps its secondary/input tints; themed palettes tint those
surfaces with the accent, which fights syntax highlighting. */
html[data-theme-id] :is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock {
background-color: var(--code-background);
border-color: var(--contrast-border);
color: var(--code-foreground);
}
html[data-theme-id] .chat-markdown pre {
background-color: var(--code-background);
color: var(--code-foreground);
}
html[data-theme-id] :is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock pre {
background-color: transparent;
}
html[data-theme-id] :is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock-header {
color: color-mix(in srgb, var(--code-foreground) 72%, transparent);
}
/* T3 Chat renders inline code and compact chat artifacts with its translucent
secondary surface flattened over the light chat canvas. The raw muted and
secondary tokens are substantially darker than those visible pixels. */
html[data-theme-id="t3-chat"] {
@variant light {
& .chat-markdown :not(pre) > code,
& [data-changed-files-state],
& [data-changed-files-header] {
background-color: var(--message-surface);
}
& .chat-markdown :not(pre) > code,
& [data-changed-files-state] {
border-color: transparent;
}
& .chat-markdown :not(pre) > code {
color: var(--contrast-message-foreground);
}
}
}
html[data-theme-id] [data-app-sidebar] {
--background: var(--app-theme-canvas);
--foreground: var(--app-theme-text);
--card: var(--app-theme-surface);
--card-foreground: var(--app-theme-text);
--accent: var(--app-theme-accent-surface);
--accent-foreground: var(--app-theme-accent-surface-foreground);
--muted: var(--app-theme-muted);
--muted-foreground: var(--app-theme-muted-foreground);
--border: var(--app-theme-border);
--input: var(--app-theme-input);
--sidebar: var(--app-theme-sidebar);
--sidebar-foreground: var(--app-theme-sidebar-foreground);
--sidebar-muted-foreground: var(--app-theme-sidebar-muted-foreground);
--sidebar-control-surface: var(--app-theme-sidebar-control-surface);
--sidebar-row-hover: var(--app-theme-sidebar-row-hover);
--sidebar-row-active: var(--app-theme-sidebar-row-active);
--sidebar-row-selected: var(--app-theme-sidebar-row-selected);
--sidebar-border: var(--app-theme-sidebar-border);
--sidebar-stage-fade: var(--app-theme-sidebar);
border-color: color-mix(in srgb, var(--contrast-sidebar-foreground) 10%, transparent);
@variant dark {
border-color: color-mix(in srgb, var(--contrast-sidebar-foreground) 8%, transparent);
}
}
/* T3 Chat's panel divider is deliberately pink, and its resize affordance
keeps that color while hovered. Do not neutralize this branded edge. */
html[data-theme-id="t3-chat"] [data-app-sidebar] {
@variant dark {
border-color: var(--contrast-sidebar-border);
}
}
/* Recompute contrast wherever a subtree owns its own semantic color palette. */
:root,
[data-app-sidebar] {
--contrast-toolbar-foreground: color-mix(
in oklab,
color-mix(
in oklab,
var(--toolbar-foreground) var(--appearance-contrast-base),
var(--toolbar-background)
),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-toolbar-border: color-mix(
in srgb,
color-mix(in srgb, var(--toolbar-border) var(--appearance-contrast-base), transparent),
var(--toolbar-foreground) var(--appearance-contrast-border-boost)
);
--contrast-toolbar-control-foreground: color-mix(
in oklab,
color-mix(
in oklab,
var(--toolbar-control-foreground) var(--appearance-contrast-base),
var(--toolbar-control)
),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-input: color-mix(
in srgb,
color-mix(in srgb, var(--input) var(--appearance-contrast-base), transparent),
var(--foreground) var(--appearance-contrast-border-boost)
);
--contrast-border: color-mix(
in srgb,
color-mix(in srgb, var(--border) var(--appearance-contrast-base), transparent),
var(--foreground) var(--appearance-contrast-border-boost)
);
--contrast-foreground: color-mix(
in oklab,
color-mix(in oklab, var(--foreground) var(--appearance-contrast-base), var(--background)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-muted-foreground: color-mix(
in oklab,
color-mix(in oklab, var(--muted-foreground) var(--appearance-contrast-base), var(--background)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-placeholder: color-mix(
in oklab,
color-mix(in oklab, var(--placeholder) var(--appearance-contrast-base), var(--background)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-secondary-label: color-mix(
in oklab,
color-mix(in oklab, var(--secondary-label) var(--appearance-contrast-base), var(--background)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-icon-muted: color-mix(
in oklab,
color-mix(in oklab, var(--icon-muted) var(--appearance-contrast-base), var(--background)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-card-foreground: color-mix(
in oklab,
color-mix(in oklab, var(--card-foreground) var(--appearance-contrast-base), var(--card)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-popover-foreground: color-mix(
in oklab,
color-mix(in oklab, var(--popover-foreground) var(--appearance-contrast-base), var(--popover)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-accent-foreground: color-mix(
in oklab,
color-mix(in oklab, var(--accent-foreground) var(--appearance-contrast-base), var(--accent)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-secondary-foreground: color-mix(
in oklab,
color-mix(
in oklab,
var(--secondary-foreground) var(--appearance-contrast-base),
var(--secondary)
),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-message-foreground: color-mix(
in oklab,
color-mix(
in oklab,
var(--message-foreground) var(--appearance-contrast-base),
var(--message-surface)
),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-sidebar-foreground: color-mix(
in oklab,
color-mix(in oklab, var(--sidebar-foreground) var(--appearance-contrast-base), var(--sidebar)),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-sidebar-muted-foreground: color-mix(
in oklab,
color-mix(
in oklab,
var(--sidebar-muted-foreground) var(--appearance-contrast-base),
var(--sidebar)
),
var(--appearance-contrast-target) var(--appearance-contrast-boost)
);
--contrast-sidebar-border: color-mix(
in srgb,
color-mix(in srgb, var(--sidebar-border) var(--appearance-contrast-base), transparent),
var(--sidebar-foreground) var(--appearance-contrast-border-boost)
);
}
body {
/* Reference the theme token (not a literal stack) so the Settings ->
Appearance runtime override of --font-sans reaches all interface text. */
font-family: var(--font-sans);
margin: 0;
padding: 0;
}
/* Lighter default icon stroke. In the base layer so stroke-* utilities still win; any
CSS beats lucide's stroke-width="2" attribute. */
@layer base {
:where(svg.lucide[stroke-width="2"]) {
stroke-width: 1.75;
}
}
html,
body {
min-height: calc(100svh + env(safe-area-inset-top));
overscroll-behavior: none;
}
body {
height: 100%;
overflow: hidden;
}
#root {
height: 100%;
width: 100%;
max-width: 100%;
overflow-x: hidden;
overflow-x: clip;
overflow-y: hidden;
overscroll-behavior-y: none;
padding-top: max(env(safe-area-inset-top), 0px);
padding-right: var(--desktop-window-right-resize-inset);
}
.electron-windows {
--desktop-window-right-resize-inset: 6px;
}
/* App-chrome grain. Baked into each surface's own background (behind
content) rather than a fixed overlay on top: a full-viewport overlay
forces the compositor to re-blend every frame any animation produces,
which multiplied idle GPU cost. The overlay's 0.035 opacity lives in the
SVG rect instead. Surfaces that float over the body (e.g. the inset main
card) must opt in via the surface-grain utility, or they lose the
grain's subtle brightening and stand out against the body. */
:root {
--surface-grain: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
--surface-grain-size: 256px 256px;
}
/* The live T3 Chat texture is a 128px grayscale tile with a constant 7/255
alpha. Keep our procedural texture cheap while matching that scale and
strength for the branded preset. */
html[data-theme-id="t3-chat"] {
--surface-grain: url("data:image/svg+xml,%3Csvg viewBox='0 0 128 128' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.02745'/%3E%3C/svg%3E");
--surface-grain-size: 128px 128px;
}
body {
background-image: var(--surface-grain);
background-repeat: repeat;
background-size: var(--surface-grain-size);
}
pre,
code {
font-family: var(--font-mono);
}
/* Code blocks in chat carry the size preference. Scoped to chat markdown
rather than every pre: a global rule would beat text-size utilities and
inherited sizes on unrelated pre surfaces (terminal previews, approvals).
Inline code stays relative to its sentence so it never towers over prose;
diffs and file previews take the size through --diffs-font-size. */
.chat-markdown .chat-markdown-shiki :is(.shiki, .diffs),
.chat-markdown .chat-markdown-codeblock pre,
:is(.chat-markdown, .composer-tiptap) pre code {
font-size: var(--font-size-code, inherit);
}
/* @pierre/diffs surfaces (diffs, file previews, annotatable code, search
lines) render inside shadow roots but consult these hooks with their own
literal stacks as fallback. Custom properties inherit across the shadow
boundary, so defining them once here routes every code surface through the
appearance font tokens. */
:root {
--diffs-font-family: var(--font-mono);
--diffs-header-font-family: var(--font-sans);
}
/* Pele diagrams read their colors through these, so a theme change recolors
them without re-rendering. */
.pele {
--pele-bg: var(--background);
--pele-fg: var(--foreground);
--pele-muted: var(--muted-foreground);
--pele-line: var(--muted-foreground);
--pele-surface: var(--secondary);
--pele-surface-alt: var(--accent);
--pele-border: color-mix(in srgb, var(--foreground) 28%, transparent);
--pele-accent: var(--primary);
--pele-critical: var(--destructive);
}
/* Window drag region (frameless titlebar) */
.drag-region {
-webkit-app-region: drag;
}
/* Electron 44 inherits app-region. Reset children to the initial value so they
do not add drag rectangles over fixed controls. Explicit `none` is treated
as `no-drag` and breaks titlebar dragging; `initial` emits no rectangle.
The base layer lets explicit drag/no-drag utilities take precedence. */
@layer base {
.drag-region > * {
-webkit-app-region: initial;
}
}
.drag-region button,
.drag-region input,
.drag-region textarea,
.drag-region select,
.drag-region a {
-webkit-app-region: no-drag;
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: var(--app-scrollbar-width);
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--app-scrollbar-thumb);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--app-scrollbar-thumb-hover);
}
/* Chat markdown rendering */
.chat-markdown > :first-child {
margin-top: 0;
}
.chat-markdown > :last-child {
margin-bottom: 0;
}
.chat-markdown p,
.chat-markdown ul,
.chat-markdown ol,
.chat-markdown blockquote,
.chat-markdown pre,
.chat-markdown .chat-markdown-table-container,
.composer-tiptap ul,
.composer-tiptap ol {
margin: 0.65rem 0;
}
.chat-markdown-math-display .katex-display {
margin: 0;
}
:is(.chat-markdown, .composer-tiptap) h1,
:is(.chat-markdown, .composer-tiptap) h2,
:is(.chat-markdown, .composer-tiptap) h3,
:is(.chat-markdown, .composer-tiptap) h4,
:is(.chat-markdown, .composer-tiptap) h5,
:is(.chat-markdown, .composer-tiptap) h6 {
margin: 1.25rem 0 0.5rem;
font-weight: 600;
line-height: 1.3;
color: var(--contrast-foreground);
}
:is(.chat-markdown, .composer-tiptap) h1 {
font-size: 1.25rem;
}
:is(.chat-markdown, .composer-tiptap) h2 {
font-size: 1.125rem;
}
:is(.chat-markdown, .composer-tiptap) h3 {
font-size: 1rem;
}
:is(.chat-markdown, .composer-tiptap) h4,
:is(.chat-markdown, .composer-tiptap) h5,
:is(.chat-markdown, .composer-tiptap) h6 {
font-size: 0.875rem;
}
:is(.chat-markdown, .composer-tiptap) h6 {
color: var(--contrast-muted-foreground);
}
:is(.chat-markdown, .composer-tiptap) ul {
/* Reset for nested uls under a widened ol. --list-gutter is an inherited
custom property, so without this a task-list under a multi-digit ordered
list would inherit the outer gutter instead of its own default. */
--list-gutter: 1.25rem;
padding-inline-start: 1.25rem;
list-style-type: disc;
}
/* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but
ChatMarkdown's `ol` renderer widens it (via inline style) for lists whose
widest marker has multiple characters so it isn't clipped by list-style-position:
outside painting the marker past the padding box. Reset it here too so a
nested ol without its own widened marker doesn't inherit the outer one. */
:is(.chat-markdown, .composer-tiptap) ol {
--list-gutter: 1.25rem;
padding-inline-start: var(--list-gutter, 1.25rem);
list-style-type: decimal;
}
:is(.chat-markdown, .composer-tiptap) ol > li::marker {
font-variant-numeric: tabular-nums;
}
/* The composer shows the marker the source has, `3)` or a nested `7.`, not the
browser counter. Browsers without ::marker content fall back to `start`. */
.composer-tiptap ol > li[data-marker]::marker {
content: attr(data-marker) " ";
}
/* The prompt font and size can differ from the interface, so its gutter must
also follow the prompt's font metrics. */
.composer-tiptap ol {
--list-gutter: max(1.25rem, 3ch);
}
.composer-tiptap ol:has(> li[data-marker-width="3"]) {
--list-gutter: max(1.25rem, 4ch);
}
.composer-tiptap ol:has(> li[data-marker-width="4"]) {
--list-gutter: max(1.25rem, 5ch);
}
.composer-tiptap ol:has(> li[data-marker-width="5"]) {
--list-gutter: max(1.25rem, 6ch);
}
.composer-tiptap ol:has(> li[data-marker-width="6"]) {
--list-gutter: max(1.25rem, 7ch);
}
.composer-tiptap ol:has(> li[data-marker-width="7"]) {
--list-gutter: max(1.25rem, 8ch);
}
.composer-tiptap ol:has(> li[data-marker-width="8"]) {
--list-gutter: max(1.25rem, 9ch);
}
.composer-tiptap ol:has(> li[data-marker-width="9"]) {
--list-gutter: max(1.25rem, 10ch);
}
.composer-tiptap ol:has(> li[data-marker-width="10"]) {
--list-gutter: max(1.25rem, 11ch);
}
.composer-tiptap ul {
padding-inline-start: max(1.25rem, 2ch);
}
:is(.chat-markdown, .composer-tiptap) ul ul {
list-style-type: circle;
}
:is(.chat-markdown, .composer-tiptap) ul ul ul {
list-style-type: square;
}
.chat-markdown ol ol {
list-style-type: lower-alpha;
}
.chat-markdown ol ol ol {
list-style-type: lower-roman;
}
.chat-markdown li + li {
margin-top: 0.25rem;
}
/* GitHub-style task lists: the checkbox replaces the list marker. */
.chat-markdown li.task-list-item {
list-style-type: none;
}
.chat-markdown li.task-list-item input[type="checkbox"] {
margin-block: 0 0.15em;
margin-inline: calc(-1 * var(--list-gutter, 1.25rem)) 0.35em;
vertical-align: middle;
}
/* Markdown assumes inline images — GitHub comments lay button images out in a row — and the
Tailwind preflight's block-level img would stack them one per line. */
.chat-markdown img {
display: inline-block;
}
.chat-markdown a {
text-decoration: none;
}
/* File links and context chips own their color and focus ring. */
.chat-markdown a:not(.chat-markdown-file-link, :has([data-slot="context-chip-label"])) {
color: var(--info-foreground);
}
.chat-markdown
a:not(
.chat-markdown-file-link,
:has([data-slot="context-chip-label"]),
[data-markdown-image-link]
):hover,
.chat-markdown
a:not(
.chat-markdown-file-link,
:has([data-slot="context-chip-label"]),
[data-markdown-image-link]
):focus-visible {
background-image: radial-gradient(circle, currentcolor 0.75px, transparent 1px);
background-position: left bottom;
background-repeat: repeat-x;
background-size: 4px 2px;
}
:is(.chat-markdown, .composer-tiptap) blockquote {
border-inline-start: 2px solid color-mix(in srgb, currentcolor 35%, transparent);
padding-inline-start: 0.8rem;
color: inherit;
}
.composer-tiptap blockquote {
margin: 0.65rem 0;
}
/* Preflight already draws the 1px rule, as it does in chat; only the block
spacing needs to match. */
.composer-tiptap hr {
margin: 0.65rem 0;
}
/* Block spacing separates blocks, not the draft from the composer edge. A
leading list or quote would otherwise push the first line down, and the
one-line resting composer clips it. */
.composer-tiptap > :first-child {
margin-top: 0;
}
.composer-tiptap > :last-child {
margin-bottom: 0;
}
/* A heading at the top of a draft should not push the first line down. */
.composer-tiptap :is(h1, h2, h3, h4, h5, h6):first-child {
margin-top: 0;
}
.chat-markdown section[data-footnotes] {
margin-top: 1.25rem;
border-top: 1px solid var(--contrast-border);
padding-top: 0.75rem;
color: var(--contrast-muted-foreground);
font-size: 0.75rem;
}
.chat-markdown section[data-footnotes] ol {
margin: 0;
}
.chat-markdown section[data-footnotes] li + li {
margin-top: 0.35rem;
}
.chat-markdown a[data-footnote-ref],
.chat-markdown a[data-footnote-backref] {
display: inline-flex;
min-width: 1rem;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
text-decoration: none;
font-size: 0.6875rem;
font-weight: 600;
}
.chat-markdown :not(pre) > code {
border: 1px solid transparent;
border-radius: 0.25rem;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
background: color-mix(in srgb, var(--contrast-foreground) 10%, transparent);
padding: 0.1em 0.35em;
color: var(--contrast-foreground);
font-size: 0.85em;
}
.chat-markdown strong {
font-weight: 600;
}
.chat-markdown pre {
max-width: 100%;
overflow-x: auto;
border: 1px solid var(--contrast-border);
border-radius: 0.5rem;
background: var(--muted);
padding: 0.8rem 0.9rem;
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--contrast-border) 78%, transparent) transparent;
}
.chat-markdown pre code {
border: none;
background: transparent;
padding: 0;
}
.chat-markdown pre::-webkit-scrollbar {
height: var(--app-scrollbar-width);
}
.chat-markdown pre::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--contrast-border) 78%, transparent);
}
:is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock-header {
color: color-mix(in srgb, var(--contrast-foreground) 72%, transparent);
}
:is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock pre {
margin: 0;
border: none;
border-radius: 0;
background: transparent;
}
:is(.chat-markdown, .composer-tiptap) .chat-markdown-codeblock[data-wrap="true"] pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
:is(.chat-markdown, .composer-tiptap) .chat-markdown-shiki :is(.shiki, .diffs) {
background: transparent !important;
}
/* Paragraphs and code blocks arrive in chunks while a response streams. Fade
each new block in so the chunk does not pop. @starting-style only applies
when an element is first inserted, and the rule is gated on data-streaming,
so opening a finished thread never replays the fade. Opacity only, one
shot, no layout change. */
@media (prefers-reduced-motion: no-preference) {
.chat-markdown[data-streaming] > *,
.chat-markdown[data-streaming] .chat-markdown-shiki,
.chat-markdown[data-streaming] .chat-markdown-mermaid {
transition: opacity 600ms ease-out;
@starting-style {
opacity: 0;
}
}
}
/* Diagnostics-style tables: row separators only, and a scroll-fade container
for horizontal overflow. Cells wrap and the table fits its lane. The root
chat-markdown wrapping rules (overflow-wrap: anywhere) would let columns
shrink to single characters, so restore word-boundary wrapping: the min
column width is the longest word, and only wider content scrolls. */
.chat-markdown table {
width: 100%;
border-collapse: collapse;
overflow-wrap: normal;
word-break: normal;
/* One step under 15px prose. */
font-size: 0.875rem;
}
/* Top-aligned so wrapped rows share a first line, and the outer columns sit
flush with the surrounding prose. */
.chat-markdown th,
.chat-markdown td {
padding: 0.45rem 0.75rem;
text-wrap: pretty;
text-align: start;
vertical-align: top;
}
.chat-markdown :is(th, td):first-child {
padding-inline-start: 0;
}
.chat-markdown :is(th, td):last-child {
padding-inline-end: 0;
}
.chat-markdown thead th {
border-bottom: 1px solid color-mix(in srgb, var(--contrast-border) 60%, transparent);
padding-block: 0.55rem;
font-weight: 600;
}
.chat-markdown tbody td {
border-bottom: 1px solid color-mix(in srgb, var(--contrast-border) 60%, transparent);
}
/* Caps a long path, URL, or chip at 24rem; text past the cap wraps. */
.chat-markdown :is(th, td) {
max-width: 24rem;
overflow-wrap: break-word;
}
/* Formula cells keep their natural width and scroll with the table. */
.chat-markdown :is(th, td):has([data-markdown-math]) {
max-width: none;
}
:root {
--diff-addition: var(--success);
--diff-deletion: var(--destructive);
--diff-addition-foreground: light-dark(var(--color-emerald-700), var(--color-emerald-400));
--diff-deletion-foreground: light-dark(var(--color-red-700), var(--color-red-400));
}
/* Chat width setting. Comfortable keeps the default --chat-content-max-width,
which sizes the timeline lane, the composer, and ChatCanvas's width probe. */
:root[data-chat-width="wide"] {
--chat-content-max-width: 72rem;
}
:root[data-chat-width="full"] {
--chat-content-max-width: 100%;
}
:root[data-diff-color-scheme="blue-orange"] {
--diff-addition: light-dark(var(--color-blue-600), var(--color-blue-400));
--diff-deletion: light-dark(var(--color-orange-600), var(--color-orange-400));
--diff-addition-foreground: var(--diff-addition);
--diff-deletion-foreground: var(--diff-deletion);
--diffs-addition-color-override: var(--diff-addition);
--diffs-deletion-color-override: var(--diff-deletion);
--trees-status-added-override: var(--diff-addition);
--trees-status-deleted-override: var(--diff-deletion);
--trees-status-untracked-override: var(--diff-addition);
--trees-git-added-color-override: var(--diff-addition);
--trees-git-deleted-color-override: var(--diff-deletion);
--trees-git-untracked-color-override: var(--diff-addition);
}
.diff-render-surface diffs-container {
border: 0;
border-radius: 0;
overflow: clip;
background: var(--code-background);
color: var(--code-foreground);
}
/* Pierre's find panel and match highlights read these. */
.diff-render-surface {
--diffs-search-panel-bg: var(--popover);
--diffs-search-panel-fg: var(--popover-foreground);
--diffs-search-panel-accent: var(--primary);
--diffs-search-panel-font: var(--font-sans);
}
/* Pierre's file renderer owns its own surface tokens. Keep the root of a file
preview on the same themeable code surface as the diff panel. */
.diff-render-surface diffs-container,
.file-preview-virtualizer diffs-container {
--diffs-bg: var(--code-background) !important;
--diffs-light-bg: var(--code-background) !important;
--diffs-dark-bg: var(--code-background) !important;
background-color: var(--code-background) !important;
color: var(--code-foreground) !important;
}
.ultrathink-frame {
--ultrathink-spectrum: linear-gradient(
120deg,
#ff6b6b 0%,
#f59e0b 18%,
#22c55e 36%,
#14b8a6 54%,
#3b82f6 72%,
#ec4899 90%,
#ff6b6b 100%
);
position: relative;
}
.ultrathink-frame::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 2px;
background-image: var(--ultrathink-spectrum);
background-size: 220% 220%;
background-position: 0% 50%;
pointer-events: none;
filter: saturate(0.82) brightness(0.92);
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
mask-composite: exclude;
}
.ultrathink-frame > * {
position: relative;
}
.ultrathink-chroma {
filter: saturate(1.2);
}
@keyframes preview-loading-progress {
from {
transform: scaleX(0.04);
}
to {
transform: scaleX(0.9);
}
}
.preview-loading-progress {
opacity: 0;
transform: scaleX(1);
transition:
transform 150ms ease-out,
opacity 150ms ease-out 220ms;
}
.preview-loading-progress[data-loading="true"] {
opacity: 1;
animation: preview-loading-progress 5.3s cubic-bezier(0.1, 0.5, 0.2, 1) forwards;
}
@media (prefers-reduced-motion: reduce) {
.preview-loading-progress,
.preview-loading-progress[data-loading="true"] {
transition: none;
animation: none;
}
.preview-loading-progress[data-loading="true"] {
transform: scaleX(0.9);
}
}
/* Let the sortable rows open label clearance before painting the dividers.
This runs once per pickup; rows retain dnd-kit's normal 200ms transitions. */
.sidebar-drag-boundary-label {
animation: sidebar-drag-label-reveal 200ms step-end;
}
@keyframes sidebar-drag-label-reveal {
from {
visibility: hidden;
}
to {
visibility: visible;
}
}
/* Rich text composer (Tiptap). The stored prompt stays markdown; only the
surface renders styled text. Markers (** * ` ~~) appear as non-editable
widgets while the caret is on styled text. */
.composer-tiptap {
position: relative;
}
.composer-tiptap p {
margin: 0;
}
.composer-tiptap:focus,
.composer-tiptap .ProseMirror:focus {
outline: none;
}
.composer-tiptap :not(pre) > code {
font-family: var(--font-mono);
font-size: 0.92em;
background: color-mix(in oklab, var(--muted-foreground) 12%, transparent);
border-radius: 0.3em;
padding: 0.05em 0.25em;
}
.composer-rich-marker {
color: var(--muted-foreground);
opacity: 0.75;
font-weight: 400;
font-style: normal;
text-decoration: none;
user-select: none;
pointer-events: none;
}
/* Selected chips (node selection from arrowing into or clicking a chip). */
.composer-tiptap .ProseMirror-selectednode {
outline: 2px solid var(--ring);
outline-offset: -2px;
border-radius: 6px;
}
.composer-tiptap .composer-chip-range-selected {
position: relative;
}
.composer-tiptap.ProseMirror-focused .composer-chip-range-selected::after {
content: "";
position: absolute;
inset: 0;
border-radius: 6px;
background: Highlight;
opacity: 0.3;
pointer-events: none;
}
/* Rich-text task lists render as real checkboxes. Tiptap's TaskItem NodeView
builds the <li> imperatively with only a data-checked attribute (no
data-type), so items are targeted that way. Indentation comes from list
nesting (two spaces per level from Tab), matching the stored Markdown. */
.composer-tiptap ul[data-type="taskList"] {
list-style: none;
margin: 0;
padding: 0;
}
.composer-tiptap ul[data-type="taskList"] ul[data-type="taskList"] {
padding-inline-start: 1.375rem;
}
.composer-tiptap li[data-checked] {
display: flex;
align-items: flex-start;
gap: 0.5rem;
margin: 0;
padding: 0;
}
.composer-tiptap li[data-checked] > label {
display: flex;
align-items: center;
flex: none;
height: 1lh;
cursor: pointer;
}
.composer-tiptap li[data-checked] > label input[type="checkbox"] {
display: block;
margin: 0;
width: 0.95rem;
height: 0.95rem;
cursor: pointer;
}
.composer-tiptap li[data-checked] > div {
flex: 1 1 auto;
min-width: 0;
}
.composer-tiptap li[data-checked] > div > p {
margin: 0;
}
@layer components {
.chat-composer-lane {
padding-inline-start: calc(
env(safe-area-inset-left) + 0.75rem + var(--chat-lane-inset-start, 0px)
);
padding-inline-end: calc(
env(safe-area-inset-right) + 0.75rem + var(--chat-lane-inset-end, 0px)
);
}
[data-chat-canvas] .messages-timeline-scroll {
padding-inline-start: calc(
0.75rem + var(--chat-lane-inset-start, 0px) - var(--chat-timeline-gutter, 0px)
);
padding-inline-end: calc(
0.75rem + var(--chat-lane-inset-end, 0px) - var(--chat-timeline-gutter, 0px)
);
}
.chat-scroll-to-bottom,
.chat-banner-lane {
inset-inline-start: var(--chat-lane-inset-start, 0px);
inset-inline-end: var(--chat-lane-inset-end, 0px);
}
@media (min-width: 640px) {
.chat-composer-lane {
padding-inline-start: calc(
env(safe-area-inset-left) + 3rem + var(--chat-lane-inset-start, 0px)
);
padding-inline-end: calc(env(safe-area-inset-right) + 3rem + var(--chat-lane-inset-end, 0px));
}
[data-chat-canvas] .messages-timeline-scroll {
padding-inline-start: calc(
3rem + var(--chat-lane-inset-start, 0px) - var(--chat-timeline-gutter, 0px)
);
padding-inline-end: calc(
3rem + var(--chat-lane-inset-end, 0px) - var(--chat-timeline-gutter, 0px)
);
}
}
}
/* A workspace area with no control of its own takes F6 focus itself; keep that visible. */
[data-workspace-area]:focus-visible {
outline: 2px solid var(--ring);
outline-offset: -2px;
}