/** * Chat color tokens — single source of truth. * * Every role-conditional class for chat surfaces lives here. * Components consume via `useChatBubbleStyles` / `useChatRoleStyles`, * never via raw Tailwind literals. * * Why centralize: * 1. One file to edit when the design system changes (e.g. light-theme * contrast tweaks, palette swap). * 2. Eliminates the "first-token-on-bg-primary-was-text-white" class * of bugs where each call site picks its own foreground. * 3. Lets us snapshot-test color decisions later. */ /** Bubble surface classes (background + text), keyed by message state. */ export const BUBBLE_SURFACE = { /** User-authored bubble — solid brand color. */ user: 'bg-primary text-primary-foreground rounded-tr-md', /** Assistant bubble in normal state — neutral muted surface. */ assistant: 'bg-muted text-foreground rounded-tl-md', /** Assistant bubble when the turn failed — destructive tint. */ error: 'bg-destructive/10 text-destructive rounded-tl-md border border-destructive/30', } as const; /** * Shared media-block surface — the single frame every non-prose * `message.blocks` renderer sits in (image, video, gallery, map, audio, * code, json, mermaid). Owning the frame here (instead of each * renderer inventing its own) guarantees one consistent corner radius, * hairline border and soft shadow across all block kinds — the calm, * uniform media-card look of Telegram / WhatsApp. * * `rounded-2xl` (16px) — large, soft messenger corner. * `border-border` — hairline, theme-aware. * `shadow-sm` — gentle lift off the transcript background. * `overflow-hidden` — clips inner content (maps, images) to the radius. * `bg-card` — neutral surface so a renderer with a transparent * body (json tree) still reads as a card. */ export const BLOCK_SURFACE = 'overflow-hidden rounded-2xl border border-border bg-card shadow-sm' as const; /** * Anchor (link) classes for markdown content rendered inside a bubble. * * On `bg-primary` the link MUST stay legible against the cyan/brand fill — * `text-primary-foreground` matches the bubble's foreground token, so * contrast tracks the design system automatically. * * On the neutral assistant bubble we keep the brand-primary color so links * still pop without competing with the body text. */ export const ANCHOR = { user: 'text-primary-foreground underline decoration-primary-foreground/60 underline-offset-2 ' + 'hover:decoration-primary-foreground transition-colors break-all', assistant: 'text-primary underline hover:text-primary/80 transition-colors break-all', } as const; /** Inline secondary action (e.g. "Show more / less"). Same logic as anchors. */ export const TOGGLE = { user: 'text-primary-foreground/80 hover:text-primary-foreground', assistant: 'text-primary hover:text-primary/80', } as const; /** Destructive surface — used by ErrorBanner and the delete action. */ export const DESTRUCTIVE_SURFACE = { /** Banner / card variant: border + tint + text. */ banner: 'border border-destructive/40 bg-destructive/10 text-destructive', /** Subtle hover for destructive buttons inside the banner / menu. */ hover: 'hover:bg-destructive/15', /** Strong-hover variant (e.g. trash overlay on attachments). */ hoverStrong: 'hover:bg-destructive hover:text-destructive-foreground', /** Inline destructive text utility. */ text: 'text-destructive', /** Hover style for menu items that delete data. */ menuItem: 'text-destructive focus:text-destructive hover:bg-destructive/15 hover:text-destructive', } as const; /** Tool-call result text. */ export const TOOL_CALL = { errorText: 'text-destructive', } as const; export type ChatBubbleSurface = keyof typeof BUBBLE_SURFACE;