Something went wrong. Try again.
ATProto-based AI Assistant Chat interface.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483/* * The theme: every value the app's look is made of. Change the look here, and only here. * * A quiet, native-feeling window, after Ryo Lu's "Closer to the Material" and calm technology. * * - A near-white sidebar and a white content panel fill the window side by side, split by a * faint line, with the sidebar's top row and the chat's header on one line. * - Things you can touch are small white cards with a hairline border, flat on the panel. * - Status is a small badge: pale green with dark green text for what is live or good, grey for * what is done, pale red for what failed. * - The model's work is a quiet numbered timeline of faintly tinted rows, below the cards, each * step opening to show exactly what happened. Tool input and output are in mono; everything else * is the system sans. * - Slow, small motion: new messages fade in, live work breathes. It all stops under * prefers-reduced-motion. * * Color comes in three layers: scales of raw values, roles that name what each step is for, and * component colors made from roles. Every step is light-dark(<light>, <dark>), so it follows the * page's color scheme: the system's, or the one chosen in Preferences. The gray is the app's own * twelve steps; the hues began as oklch.fyi's Radix scales in OKLCH, the reds turned to a warmer * hue. * * Tailwind's own palette, type scale, radii, shadows, easings, animations, widths, and breakpoints * are cleared, so a component can only name the values below. specs/styling.md has the rules. */
@theme static { --color-*: initial; --text-*: initial; --font-*: initial; --radius-*: initial; --shadow-*: initial; --inset-shadow-*: initial; --drop-shadow-*: initial; --text-shadow-*: initial; --ease-*: initial; --animate-*: initial; --container-*: initial; --breakpoint-*: initial; --tracking-*: initial; --leading-*: initial;
/* The scales: the only raw colors in the app. Each step holds its light and its dark value, so a role takes one step for both schemes. The gray's steps run from the page out, as Radix's do: 1–2 the window and panel, 3–5 tints and fills, 6–7 lines, 8–12 inks. The hues keep Radix's numbers for what each step is for: 3 a soft fill, 4 its hover, 6 its border, 9 the solid, 10 a mark, 11 the ink, and 12 ink on the solid. A new look is a change here; a new accent is the accent roles pointed at another hue. */ --gray-1: light-dark(oklch(1 0 0), oklch(0.162 0 0)); --gray-2: light-dark(oklch(0.993 0 0), oklch(0.195 0 0)); --gray-3: light-dark(oklch(0.975 0 0), oklch(0.225 0 0)); --gray-4: light-dark(oklch(0.94 0 0), oklch(0.254 0 0)); --gray-5: light-dark(oklch(0.918 0 0), oklch(0.302 0 0)); --gray-6: light-dark(oklch(0.894 0 0), oklch(0.34 0 0)); --gray-7: light-dark(oklch(0.865 0 0), oklch(0.4 0 0)); --gray-8: light-dark(oklch(0.606 0 0), oklch(0.549 0 0)); --gray-9: light-dark(oklch(0.555 0 0), oklch(0.635 0 0)); --gray-10: light-dark(oklch(0.503 0 0), oklch(0.72 0 0)); --gray-11: light-dark(oklch(0.47 0 0), oklch(0.863 0 0)); --gray-12: light-dark(oklch(0.375 0 0), oklch(0.933 0 0)); --green-3: light-dark(oklch(0.931 0.062 145), oklch(0.322 0.103 145)); --green-4: light-dark(oklch(0.902 0.082 145), oklch(0.367 0.125 145)); --green-6: light-dark(oklch(0.868 0.102 145), oklch(0.417 0.145 145)); --green-9: light-dark(oklch(0.762 0.153 145), oklch(0.843 0.107 145)); --green-10: light-dark(oklch(0.579 0.179 145), oklch(0.676 0.167 145)); --green-11: light-dark(oklch(0.464 0.143 145), oklch(0.843 0.107 145)); --green-12: oklch(0.348 0.095 145); /* A true red, warmer than the crimson the Red scale leans to, so it never reads as pink or maroon. */ --red-3: light-dark(oklch(0.946 0.051 25), oklch(0.227 0.041 25)); --red-4: light-dark(oklch(0.924 0.067 25), oklch(0.295 0.082 25)); --red-6: light-dark(oklch(0.895 0.083 25), oklch(0.348 0.111 25)); --red-11: light-dark(oklch(0.52 0.19 25), oklch(0.7 0.16 25)); --amber-3: light-dark(oklch(0.973 0.052 75), oklch(0.288 0.072 75)); --amber-11: light-dark(oklch(0.534 0.141 75), oklch(0.894 0.11 75)); --blue-3: light-dark(oklch(0.91 0.06 250), oklch(0.345 0.09 258));
/* The roles: what each color is for, each a step of a scale. A component uses roles and the component colors below, never a scale.
The window frame, the panel inside it, and the cards on the panel. The frame is a step under the panel in both schemes, so it pairs two steps. chromeColors in shared/theme.ts and the server's web-manifest.ts repeat it as hex, for what takes only a plain color, and a test fails when they drift. */ --color-chrome: light-dark(var(--gray-3), var(--gray-1)); --color-panel: var(--gray-2); --color-surface: var(--gray-4); /* A faint tint for asides on the panel. */ --color-sunken: var(--gray-3); /* A card of settings: a grey tint with no edge, as in system settings. */ --color-card: var(--gray-3); /* The well a quote sits in, a step deeper than a card so it shows inside one too. */ --color-well: var(--gray-4); --color-raised: var(--gray-2); --color-raised-hover: var(--gray-3); /* Every floating menu: white in light, and a step lighter than the panel in dark. */ --color-lifted: light-dark(var(--gray-1), var(--gray-4)); --color-hover: var(--gray-5); /* The sidebar's rows: a faint tint when hovered, and a firmer one for the search field and a row's menu button. The open row's tint is fainter still, so it never reads as New chat's. */ --color-row-hover: light-dark(#0000000a, #ffffff0a); --color-row-active: light-dark(#00000012, #ffffff14); --color-row-current: light-dark(#00000007, #ffffff08);
/* Soft grey ink, never black. */ --color-ink: var(--gray-12); /* A reply's text, in ink in light and a step softer in dark. */ --color-prose: light-dark(var(--gray-12), var(--gray-11)); /* A chat's title in the list, a touch softer than ink, so the open chat and one with a reply not yet seen stand out in full ink. */ --color-row-ink: var(--gray-11); /* Secondary text, readable on the panel, the frame, the sunken tint, and the grey fills of badges and hovered rows. */ --color-muted: var(--gray-10); /* The quietest readable text, a visible step under muted, such as section headings, a reply's model name, and a search's Show more. */ --color-subtle: var(--gray-9); /* For placeholders, markers, dots, and icon buttons that stay quiet until hovered, never for text someone has to read. */ --color-faint: var(--gray-8);
--color-line: var(--gray-6); /* A field's border while you type in it. */ --color-line-strong: var(--gray-7); /* The line between the sidebar and the panel, wider and quieter than a card's border. */ --color-divider: light-dark(var(--gray-4), var(--gray-2)); /* The darker bottom edge under a floating menu. */ --color-bevel: light-dark(var(--gray-7), var(--gray-1)); /* The shade over the page under an open drawer. */ --color-backdrop: oklch(0 0 0 / 0.25); /* The edge drawn inside a picture: a shade of its own colors rather than a grey frame. */ --color-image-edge: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.1));
/* Green for what is live or good and for the main action, with dark green ink on its pale badges. */ --color-accent: var(--green-9); --color-accent-strong: var(--green-11); --color-accent-soft: var(--green-3); --color-accent-soft-hover: var(--green-4); --color-accent-border: var(--green-6); /* Green for small marks, such as live dots, dark enough to show on any panel. */ --color-accent-mark: var(--green-10); /* Dark ink on an accent fill, in both schemes. */ --color-on-accent: var(--green-12); --color-focus: var(--color-accent-mark); /* Red for what failed. Its ink reads on every panel and on its own pale fills, hovered too. */ --color-danger: var(--red-11); --color-danger-soft: var(--red-3); --color-danger-soft-hover: var(--red-4); --color-danger-border: var(--red-6); /* Pale amber for search matches and notices, with amber-brown ink. */ --color-highlight: var(--amber-3); --color-highlight-ink: var(--amber-11); /* A calm blue behind selected text, a hue nothing else in the app means anything by, so a selection never reads as a match, a notice, or the green of an action. The text keeps its own ink. */ --color-selection: var(--blue-3);
/* The component colors, each made from roles. */ /* Your message's bubble, the panel with a faint tint of the accent, and a quote inside it, a step deeper. */ --color-bubble: color-mix(in srgb, var(--color-accent) 12%, var(--color-panel)); --color-bubble-quote: color-mix(in srgb, var(--color-accent) 26%, var(--color-panel)); /* The quiet dot before a reply's model name. */ --color-author-dot: color-mix(in srgb, var(--color-faint) 55%, var(--color-panel)); /* The hovered row's tint as a solid color, for an edge that must match it. */ --color-row-hover-solid: color-mix(in srgb, var(--color-chrome), light-dark(#000, #fff) 4%); /* A switch's sliding knob: the lifted white in light, and ink in dark. */ --color-knob: light-dark(var(--color-lifted), var(--color-ink)); /* The empty chat's illustration: a light face inside a slightly darker rim. */ --color-illo-face: var(--color-card); --color-illo-rim: var(--color-hover); --color-illo-back: var(--color-surface); /* The logo's bubble letters: a face inside a rim. */ --color-logo-face: light-dark(#ffffff, #111110); --color-logo-rim: light-dark(#b9b8b0, #8c8b85); /* A softer red for the detail under a red heading. */ --color-danger-muted: color-mix(in srgb, var(--color-danger) 90%, var(--color-panel)); /* A search match: a faint tint in the text's own ink. In light it is half the highlight, so a list of matches stays quiet. In dark it is the whole highlight, since half all but vanishes. */ --color-match: light-dark( color-mix(in oklch, var(--color-highlight) 55%, transparent), var(--color-highlight) );
/* Code in replies, which Shiki colors through these names. Each hue is calm enough to read in a long block, and is code's own, apart from the scales. Comments sit back in grey, keywords are violet, strings green, functions and links blue, constants amber. */ --color-code-background: var(--color-raised); --color-code-foreground: var(--color-ink); --color-code-token-comment: var(--color-subtle); --color-code-token-punctuation: var(--color-muted); --color-code-token-keyword: light-dark(oklch(0.5 0.17 300), oklch(0.78 0.12 300)); --color-code-token-string: light-dark(oklch(0.5 0.13 145), oklch(0.8 0.13 145)); --color-code-token-string-expression: var(--color-code-token-string); --color-code-token-function: light-dark(oklch(0.5 0.15 255), oklch(0.78 0.11 250)); --color-code-token-link: var(--color-code-token-function); --color-code-token-constant: light-dark(oklch(0.53 0.13 55), oklch(0.8 0.12 65)); --color-code-token-parameter: light-dark(oklch(0.52 0.13 25), oklch(0.78 0.1 30)); --color-code-token-inserted: var(--color-code-token-string); --color-code-token-deleted: light-dark(oklch(0.52 0.19 25), oklch(0.72 0.15 25));
/* The system's own fonts, so the app loads none: its sans, then each platform's emoji, and its mono. */ --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-prose: var(--font-ui); --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, "Cascadia Mono", Consolas, "Liberation Mono", "DejaVu Sans Mono", monospace; --default-font-family: var(--font-ui); --default-mono-font-family: var(--font-mono);
/* The type scale. Every size in the app is one of these steps, and code inside text sizes itself against them in em. Each step carries its line height, looser for small text and tighter for big, and the big ones their tracking, so a size reads right wherever it is used. Weights are 400 for reading, 500 for controls and emphasis, and 600 for headings and the main action. */ /* Badges, timestamps, counts, and small labels, at the 12px floor. */ --text-caption: 0.78rem; --text-caption--line-height: 1.4; /* Secondary text: hints, menus, step rows, and details under a name. */ --text-small: 0.86rem; --text-small--line-height: 1.45; /* The sidebar, titles in a bar, and the controls around a chat. */ --text-ui: 0.92rem; --text-ui--line-height: 1.45; /* Messages, replies, and the composer. */ --text-body: 1rem; --text-body--line-height: 1.55; /* A section's heading. */ --text-heading: 1.25rem; --text-heading--line-height: 1.25; --text-heading--letter-spacing: var(--tracking-heading); /* The settings title beside its sections, and each section's own title. */ --text-title: 1.5rem; --text-title--line-height: 1.25; --text-title--letter-spacing: var(--tracking-title); /* The app's name on the login page. */ --text-display: 1.75rem; --text-display--line-height: 1.2; --text-display--letter-spacing: var(--tracking-title); /* Code inside text, against the text around it, and a block of code a step smaller. */ --text-code: 0.85em; --text-code-block: 0.8em;
/* Line heights for a block whose lines differ from its size's own, such as a name over its details set close, or text that matches a reply's, with Tailwind's names. */ --leading-tight: 1.25; --leading-snug: 1.35; --leading-normal: 1.45; --leading-relaxed: 1.65;
/* Big titles draw in a touch, and small labels in capitals' weight open up. */ --tracking-title: -0.02em; --tracking-heading: -0.01em; --tracking-label: 0.02em; --tracking-key: 0.04em;
/* The corners as one ladder, as Mantine's are: xs for a search match's tint, sm for badges, chips, and rows, md for controls and cards, lg for panels, and xl for your message's bubble. Where a surface holds rounded things at an even inset, its corner is theirs plus the inset and its border, so the curves nest. */ --radius-xs: 2px; --radius-sm: 0.375rem; --radius-md: 0.6rem; --radius-lg: 0.9rem; --radius-xl: 1.5rem; /* A floating menu, around rows at the menu's padding, inside an edge as wide as the line between the sidebar and the panel. */ --radius-menu: calc(var(--radius-sm) + var(--menu-pad) + var(--menu-edge)); /* A card holding controls at an even inset, such as renaming a chat in the list. */ --radius-editor: calc(var(--radius-md) + 0.6rem + 1px); /* A secret to copy, nesting its Copy button at an even inset. */ --radius-secret: calc(var(--radius-md) + 0.35rem + 1px); /* The composer, nesting its round Send button in its corner, and a quote held in it, nesting in the composer's corner. */ --radius-composer: calc(var(--spacing-composer-button) / 2 + 0.55rem + 1px); --radius-quote: calc(var(--radius-composer) - 0.6rem - 1px); /* A file chip's picture, inset from the quote's corner by the chip's padding. */ --radius-thumb: calc(var(--radius-quote) - 0.375rem); /* A quote in your message's bubble, inset from its corner by the bubble's padding. */ --radius-bubble-quote: calc(var(--radius-xl) - 0.625rem); /* A chat's toolbar, around its buttons at a hairline inset. */ --radius-toolbar: calc(var(--radius-sm) + 0.125rem); /* A floating panel of controls, such as a chat's sharing, around them at its padding. */ --radius-panel: calc(var(--radius-md) + 0.875rem + var(--menu-edge));
/* A floating menu: a bevel under it and a soft shadow. */ --shadow-float: 0 1px 0 var(--color-bevel), 0 10px 30px -14px light-dark(#0000002e, #000a); /* A part of the sidebar lifted over it, such as the account menu: a soft shadow with no bevel, since its edge is the sidebar's own line. */ --shadow-lift: 0 12px 32px -16px light-dark(#0000002e, #000c);
/* Spacing is Tailwind's scale of quarter rems. These are the sizes the design names. */ --spacing: 0.25rem; /* Icons are 16px at the 15px design size, and grow with the text. */ --spacing-icon: calc(16rem / 15); --spacing-sidebar: 17.75rem; /* The space between the window's edge and the sidebar's rows, and the settings nav's. */ --spacing-sidebar-pad: 0.6rem; /* Where the text in every sidebar row starts, so rows, headings, and the search line up. */ --spacing-row-inset: 0.55rem; /* The sidebar as a drawer over the page on a narrow screen. */ --spacing-drawer: min(18rem, 85vw); /* The settings and admin nav beside the section on a wide screen. */ --spacing-settings-nav: 13.5rem; /* The height of a row in the sidebar, and of its search. */ --spacing-row: 2.2rem; /* The sidebar's top row and the chat's header share one height, so the app's name and the chat's title sit on one line across the window. */ --spacing-bar: 3.5rem; /* The square icon button that shows or hides the sidebar. */ --spacing-toggle: 2rem; /* The composer's round buttons, and how tall its box grows before it scrolls. */ --spacing-composer-button: 2rem; --spacing-composer-max: 40vh; /* The space beside the conversation and the settings, narrower on a phone. */ --spacing-gutter: 1.5rem; /* The space inside a settings card, the same on every side, around each of its rows. */ --spacing-card-pad: 1rem; /* A message's icon actions, square and as tall as the worded ones beside them. */ --spacing-action: calc(0.8rem * 1.55 + 0.36rem + 2px); /* The gap inside a turn, between your message and its reply, and the much larger one between turns, so each exchange reads as one unit. Each message's footer row adds about 1.85rem to both, which puts the gaps you see at about 2.5 to 1. */ --spacing-within: 0.25rem; --spacing-turn: 3.5rem;
/* The conversation's column, and every settings and admin section, so moving between them doesn't jump. */ --container-column: 44rem; --container-settings: 44rem; /* Below this a conversation or settings section lays itself out for a narrow panel. */ --container-narrow: 34rem; /* Below this the sidebar becomes a drawer. */ --breakpoint-wide: 45rem;
/* Motion eases out and never overshoots. */ --ease-out: cubic-bezier(0.25, 0.8, 0.25, 1); /* The sidebar sliding away or back starts quickly and settles slowly, so it glides to a stop. */ --ease-slide: cubic-bezier(0.32, 0.72, 0, 1); /* Two icons in one place, such as Copy and Copied, cross-fade as one gives way to the other. */ --ease-swap: cubic-bezier(0.2, 0, 0, 1); /* What arrives fades in; live work breathes at the slow pace. */ --animate-fade-in: fade-in var(--dur-enter) var(--ease-out) backwards; --animate-breathe: breathe var(--slow) ease-in-out infinite; /* A panel rising from the control below it, such as the account menu. */ --animate-panel-up: panel-up var(--dur-panel) var(--ease-out); /* What arrives only after a quiet wait, such as a reply's waiting header. */ --animate-fade-in-late: fade-in var(--dur-enter) var(--ease-out) var(--wait-quiet) backwards; /* A panel settling down from the control above it, such as the chat header's menu. */ --animate-panel-in: panel-in var(--dur-panel) var(--ease-out); /* A layer over the whole chat, like Jump to's, coming and going at a calmer pace, with what it holds settling into place. */ --animate-layer-in: fade-in var(--dur-layer) var(--ease-out); --animate-layer-out: fade-out var(--dur-layer) var(--ease-out) forwards; --animate-layer-settle: panel-in var(--dur-layer) var(--ease-out); /* The message landed on glows a moment, so the eye finds it. */ --animate-landed: landed 2s var(--ease-out); /* A reply has no bubble to ring, so it washes in a grey tint, as its row in Jump to is grey. */ --animate-landed-reply: landed-reply 2s var(--ease-out); /* Content on its way pulses gently in its skeleton, and a busy control's spinner turns. */ --animate-skeleton: skeleton var(--slow) ease-in-out infinite; --animate-spin: spin 0.9s linear infinite;
@keyframes fade-in { from { opacity: 0; } }
@keyframes fade-out { to { opacity: 0; } }
@keyframes panel-in { from { opacity: 0; transform: translateY(calc(-1 * var(--rise))); } }
@keyframes panel-up { from { opacity: 0; transform: translateY(var(--rise)); } }
@keyframes landed { from, 15% { box-shadow: 0 0 0 2px var(--color-accent-mark); } }
@keyframes landed-reply { from, 15% { background-color: var(--color-surface); } }
@keyframes breathe { 50% { opacity: 0.55; } }
@keyframes skeleton { 50% { opacity: 0.5; } }
@keyframes spin { to { rotate: 1turn; } }}
/* Values with no Tailwind namespace, used as utility-(--name). */@layer theme { :root { /* Feedback answers a touch, layout moves things into place, and enter fades in what arrives. A panel opening from a control arrives a little quicker, and a layer over the whole chat, like Jump to's, comes and goes at a calmer pace. */ --dur-feedback: 140ms; --dur-panel: 180ms; --dur-layout: 220ms; --dur-enter: 240ms; --dur-layer: 300ms; /* The sidebar, a whole column on the move, takes a little longer than a control does. */ --dur-slide: 300ms; --dur-swap: 300ms; /* How far a panel rises toward its place as it opens. */ --rise: 4px; /* How long a reply waits quietly before it shows that it is waiting, so a quick one never flashes a waiting state. */ --wait-quiet: 400ms; --slow: 2.8s; /* How far a link's underline sits below its text. */ --underline-offset: 0.2em; /* A floating menu's padding, the gap between New chat and the search button, and its edge. */ --menu-pad: 0.35rem; --menu-edge: 2px; /* A hairline: 1px, halved on a screen of twice the density or more, so one device pixel at 2x. */ --hairline: 1px; /* Icon strokes are drawn in screen pixels whatever the icon's size, at the weight of the text beside them: regular text, or the medium text of a control. */ --icon-stroke: 1.25; --icon-stroke-control: 1.75; /* A radio's ring. */ --control-edge: 1.5px; /* iOS zooms the page into a field whose text is under 16px, so on a touch screen fields never go below it. */ --input-floor: 0px; }
@media (resolution >= 2dppx) { :root { --hairline: 0.5px; } }
@media (pointer: coarse) { :root { --input-floor: 16px; } }
@media (width < 45rem) { :root { --spacing-gutter: 1rem; } }}