Something went wrong. Try again.
ATProto-based AI Assistant Chat interface.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416/* * 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. * * Every color is light-dark(<light>, <dark>), so it follows the page's color scheme: the system's, * or the one chosen in Preferences. Every color is a step of the Gray, Green, Red, or Amber palette * from oklch.fyi/color-palettes, the Radix scales in OKLCH, each with a light and a dark scale of * twelve steps. The reds are the Red scale turned to a warmer hue, with its ink retuned to a true * red. * * 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;
/* The window frame, the panel inside it, and the cards on the panel. */ --color-chrome: light-dark(oklch(0.982 0 0), oklch(0.162 0 0)); --color-panel: light-dark(oklch(0.993 0 0), oklch(0.195 0 0)); --color-sidebar: light-dark(oklch(0.982 0 0), oklch(0.162 0 0)); --color-surface: light-dark(oklch(0.94 0 0), oklch(0.254 0 0)); /* A faint tint for asides on the panel, a step below a card. */ --color-sunken: light-dark(oklch(0.982 0 0), oklch(0.254 0 0)); /* A card of settings: a grey tint with no edge, as in system settings, a touch deeper than the sunken tint in light so it shows on the panel. Muted ink clears 5.5:1 on it. */ --color-card: light-dark(oklch(0.975 0 0), oklch(0.225 0 0)); /* The well a quote sits in, a step darker than the sunken tint so it shows inside a card too. */ --color-well: light-dark(oklch(0.961 0 0), oklch(0.302 0 0)); --color-raised: light-dark(oklch(1 0 0), oklch(0.195 0 0)); --color-raised-hover: light-dark(oklch(0.993 0 0), oklch(0.254 0 0)); /* Every floating menu, a step lighter than the panel. */ --color-lifted: light-dark(oklch(1 0 0), oklch(0.254 0 0)); /* 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)); --color-hover: light-dark(oklch(0.918 0 0), oklch(0.302 0 0)); /* The shapes standing in for content on its way, which show on the sidebar, the panel, and a card alike. */ --color-skeleton: light-dark(oklch(0.935 0 0), oklch(0.27 0 0)); /* 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); /* 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-sidebar), light-dark(#000, #fff) 4%); /* A switch's sliding knob. */ --color-knob: light-dark(oklch(1 0 0), oklch(0.933 0 0)); /* The empty chat's illustration: a light face inside a slightly darker rim. */ --color-illo-face: light-dark(oklch(0.961 0 0), oklch(0.254 0 0)); --color-illo-rim: light-dark(oklch(0.918 0 0), oklch(0.302 0 0)); --color-illo-back: light-dark(oklch(0.94 0 0), oklch(0.302 0 0)); /* The logo's bubble letters: a face inside a rim. */ --color-logo-face: light-dark(#ffffff, #111110); --color-logo-rim: light-dark(#b9b8b0, #8c8b85);
/* Soft grey ink, never black. */ --color-ink: light-dark(oklch(0.375 0 0), oklch(0.933 0 0)); --color-ink-strong: light-dark(oklch(0.375 0 0), oklch(0.933 0 0)); --color-prose: light-dark(oklch(0.375 0 0), oklch(0.863 0 0)); /* Muted ink still clears 4.5:1 on the panel, the frame, the sunken tint, and the grey fills of badges and hovered rows. */ --color-muted: light-dark(oklch(0.503 0 0), oklch(0.72 0 0)); /* Quieter ink for text on the panel or a card alone, such as a reply's model name and the composer's model. It clears 4.5:1 on the panel, 4.56:1 light and 4.63:1 dark, but not on tints or fills. The light step sits between the Gray scale's steps 10 and 11, since step 10 falls short. */ --color-muted-quiet: light-dark(oklch(0.56 0 0), oklch(0.6 0 0)); /* For placeholders, markers, and dots, never for text someone has to read. An icon on a card may take it, since it still clears 3:1 there. */ --color-faint: light-dark(oklch(0.649 0 0), oklch(0.549 0 0)); /* Icon buttons that stay quiet until hovered, such as a message's actions. A control's icon needs 3:1, not text's 4.5:1, and this still clears 3.2:1 on the panel, the frame, the sunken tint, and a card. Never for text. */ --color-icon-quiet: light-dark(oklch(0.606 0 0), oklch(0.549 0 0)); /* 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: light-dark(oklch(0.47 0 0), oklch(0.8 0 0)); /* The list's section headings, such as Today and Tags, in the quietest grey that still clears 4.5:1 on the sidebar once rounded to sRGB: 4.5:1 light and 4.62:1 dark. */ --color-section-ink: light-dark(oklch(0.555 0 0), oklch(0.585 0 0)); /* The quietest text on the steps' sunken tint, such as a search's Show more, a visible step under muted ink that still clears 4.5:1 there once rounded to sRGB: 4.5:1 light and 4.61:1 dark. The tint is lighter than the sidebar in dark, so section ink falls short on it. */ --color-tint-quiet: light-dark(oklch(0.555 0 0), oklch(0.635 0 0));
--color-line: light-dark(oklch(0.894 0 0), oklch(0.302 0 0)); /* A field's border while you type in it. */ --color-line-strong: light-dark(oklch(0.865 0 0), oklch(0.348 0 0)); /* The line between the sidebar and the panel, wider and quieter than a card's border. */ --color-divider: light-dark(oklch(0.961 0 0), oklch(0.195 0 0)); /* The darker bottom edge under a floating menu. */ --color-bevel: light-dark(oklch(0.865 0 0), oklch(0.162 0 0)); /* 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: light-dark(oklch(0.762 0.153 145), oklch(0.843 0.107 145)); --color-accent-strong: light-dark(oklch(0.464 0.143 145), oklch(0.843 0.107 145)); --color-accent-soft: light-dark(oklch(0.931 0.062 145), oklch(0.322 0.103 145)); --color-accent-soft-hover: light-dark(oklch(0.902 0.082 145), oklch(0.367 0.125 145)); --color-accent-border: light-dark(oklch(0.868 0.102 145), oklch(0.417 0.145 145)); /* Green for small marks, such as live dots, dark enough to clear 3:1 on any panel. */ --color-accent-mark: light-dark(oklch(0.579 0.179 145), oklch(0.676 0.167 145)); /* Dark ink on an accent fill, in both modes. */ --color-on-accent: oklch(0.348 0.095 145); --color-focus: var(--color-accent-mark); /* A true red, warmer than the crimson the Red scale leans to, so it never reads as pink or maroon. Its ink clears 4.5:1 on every panel and on its own pale fills, hovered too. */ --color-danger: light-dark(oklch(0.52 0.19 25), oklch(0.7 0.16 25)); --color-danger-soft: light-dark(oklch(0.946 0.051 25), oklch(0.227 0.041 25)); --color-danger-soft-hover: light-dark(oklch(0.924 0.067 25), oklch(0.295 0.082 25)); --color-danger-border: light-dark(oklch(0.895 0.083 25), oklch(0.348 0.111 25)); /* A softer red for the detail under a red heading, still clearing 4.5:1 on the panel. */ --color-danger-muted: color-mix(in srgb, var(--color-danger) 90%, var(--color-panel)); /* Pale amber for search matches and notices, with amber-brown ink. */ --color-highlight: light-dark(oklch(0.973 0.052 75), oklch(0.288 0.072 75)); --color-highlight-ink: light-dark(oklch(0.534 0.141 75), oklch(0.894 0.11 75)); /* 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: body text clears 7:1 on it and muted text 4.5:1, in both modes. */ --color-selection: light-dark(oklch(0.91 0.06 250), oklch(0.345 0.09 258)); /* 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 every one clears 4.5:1 on the block's card in its mode. 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: light-dark(oklch(0.53 0 0), oklch(0.66 0 0)); --color-code-token-punctuation: light-dark(oklch(0.5 0 0), oklch(0.72 0 0)); --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. 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; /* Secondary text: hints, menus, step rows, and details under a name. */ --text-small: 0.86rem; /* The sidebar, titles in a bar, and the controls around a chat. */ --text-ui: 0.92rem; /* Messages, replies, and the composer. */ --text-body: 1rem; /* A section's heading. */ --text-heading: 1.25rem; /* The settings title beside its sections, and each section's own title. */ --text-title: 1.5rem; /* The app's name on the login page. */ --text-display: 1.75rem; /* 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;
/* 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;
/* 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-panel: 0.9rem; --radius-card: 0.65rem; --radius-small: 0.55rem; --radius-badge: 0.35rem; --radius-row: 0.4rem; /* 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-row) + var(--menu-pad) + var(--menu-edge)); /* Your message's bubble. */ --radius-bubble: 1.5rem; /* A card holding controls at an even inset, such as renaming a chat in the list. */ --radius-editor: calc(var(--radius-small) + 0.6rem + 1px); /* A secret to copy, nesting its Copy button at an even inset. */ --radius-secret: calc(var(--radius-small) + 0.35rem + 1px); /* A search match's tint. */ --radius-mark: 2px; /* 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 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); /* 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 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; /* 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, which needs 3:1 against the card it sits on. */ --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 (pointer: coarse) { :root { --input-floor: 16px; } }
@media (width < 45rem) { :root { --spacing-gutter: 1rem; } }}