diff --git a/.claude/rules/semantic-tokens.md b/.claude/rules/semantic-tokens.md
new file mode 100644
index 00000000000..09163c95345
--- /dev/null
+++ b/.claude/rules/semantic-tokens.md
@@ -0,0 +1,867 @@
+# EMCN Design System Reference
+
+Complete reference for the EMCN component library, semantic design tokens, and Tailwind configuration. Use `@/components/emcn` as the single import path for all components.
+
+---
+
+## Table of Contents
+
+1. [Semantic Tokens](#semantic-tokens)
+2. [Tailwind Configuration](#tailwind-configuration)
+3. [Component Reference](#component-reference)
+4. [Icons](#icons)
+
+---
+
+## Semantic Tokens
+
+All color values are CSS custom properties defined in `app/_styles/globals.css` under `:root` (light) and `.dark` selectors. Reference them with `var(--token-name)` in CSS or `[var(--token-name)]` in Tailwind arbitrary values.
+
+### Surface / Background
+
+| Token | Light | Dark | Usage |
+|---|---|---|---|
+| `--bg` | `#fefefe` | `#1b1b1b` | Main canvas background |
+| `--surface-1` | `#f9f9f9` | `#1e1e1e` | Sidebar, panels |
+| `--surface-2` | `#ffffff` | `#232323` | Blocks, cards, modals |
+| `--surface-3` | `#f7f7f7` | `#242424` | Popovers, headers |
+| `--surface-4` | `#f5f5f5` | `#292929` | Button base, badge backgrounds |
+| `--surface-5` | `#f3f3f3` | `#363636` | Inputs, form elements |
+| `--surface-6` | `#e5e5e5` | `#454545` | Elevated surfaces, hover states |
+| `--surface-7` | `#d9d9d9` | `#505050` | Strong hover states |
+| `--surface-active` | `#ececec` | `#2c2c2c` | Active/pressed state, skeleton bg |
+
+### Text
+
+| Token | Light | Dark | Usage |
+|---|---|---|---|
+| `--text-primary` | `#1a1a1a` | `#e6e6e6` | Headings, primary content |
+| `--text-secondary` | `#525252` | `#cccccc` | Secondary content, descriptions |
+| `--text-tertiary` | `#5c5c5c` | `#b3b3b3` | Tertiary labels, breadcrumbs |
+| `--text-muted` | `#707070` | `#787878` | Muted text, placeholders in components |
+| `--text-subtle` | `#8c8c8c` | `#7d7d7d` | Very low-emphasis text |
+| `--text-body` | `#3b3b3b` | `#cdcdcd` | Body text, popover items |
+| `--text-icon` | `#5e5e5e` | `#939393` | Icon default color |
+| `--text-inverse` | `#ffffff` | `#1b1b1b` | Text on dark/light inverted backgrounds |
+| `--text-muted-inverse` | `#a0a0a0` | `#b3b3b3` | Muted text on inverted backgrounds |
+| `--text-error` | `#ef4444` | `#ef4444` | Error messages, required indicators |
+| `--text-placeholder` | `#8d8d8d` | `#8d8d8d` | Input placeholder text |
+| `--text-success` | `#22c55e` | `#22c55e` | Success text |
+
+### Border / Divider
+
+| Token | Light | Dark | Usage |
+|---|---|---|---|
+| `--border` | `#dedede` | `#333333` | Primary border (default for all `*` elements) |
+| `--border-1` | `#e0e0e0` | `#3d3d3d` | Stronger border, input borders, active states |
+| `--border-muted` | `#e4e4e4` | `#424242` | Subtle borders |
+| `--border-success` | `#e0e0e0` | `#575757` | Success state borders |
+| `--divider` | `#ededed` | `#393939` | Section dividers |
+
+### Brand / State
+
+| Token | Light | Dark | Usage |
+|---|---|---|---|
+| `--brand-secondary` | `#33b4ff` | `#33b4ff` | Selection highlights, secondary brand |
+| `--brand-accent` | `#33c482` | `#33c482` | Tertiary button, accent green |
+| `--selection` | `#1a5cf6` | `#4b83f7` | Text selection |
+| `--warning` | `#ea580c` | `#ff6600` | Warning state |
+
+### Semantic Status
+
+| Token | Light | Dark | Usage |
+|---|---|---|---|
+| `--error` | `#dc2626` | `#f87171` | Error indicators |
+| `--error-muted` | `#fecaca` | `#f6d2d2` | Muted error backgrounds |
+| `--error-emphasis` | `#b91c1c` | `#b91c1c` | Strong error emphasis |
+| `--caution` | `#f59e0b` | `#f59e0b` | Warning/caution indicators |
+| `--success` | `#22c55e` | `#22c55e` | Success indicators |
+
+### Badge Colors
+
+Each badge color has `-bg` and `-text` variants. Used by the Badge component's color variants.
+
+| Base | Light BG | Light Text | Dark BG | Dark Text |
+|---|---|---|---|---|
+| `success` | `#bbf7d0` | `#15803d` | `rgba(34,197,94,0.2)` | `#86efac` |
+| `error` | `#fecaca` | `#dc2626` | `#551a1a` | `#fca5a5` |
+| `gray` | `#e7e5e4` | `#57534e` | `#3a3a3a` | `#a8a8a8` |
+| `blue` | `#bfdbfe` | `#1d4ed8` | `rgba(59,130,246,0.2)` | `#93c5fd` |
+| `blue-secondary` | `#bae6fd` | `#0369a1` | `rgba(51,180,255,0.2)` | `#7dd3fc` |
+| `purple` | `#e9d5ff` | `#7c3aed` | `rgba(168,85,247,0.2)` | `#d8b4fe` |
+| `orange` | `#fed7aa` | `#c2410c` | `rgba(249,115,22,0.2)` | `#fdba74` |
+| `amber` | `#fde68a` | `#a16207` | `rgba(245,158,11,0.2)` | `#fcd34d` |
+| `teal` | `#99f6e4` | `#0f766e` | `rgba(20,184,166,0.2)` | `#5eead4` |
+| `cyan` | `#cffafe` | `#0891b2` | `rgba(14,165,233,0.2)` | `#7dd3fc` |
+| `pink` | `#fbcfe8` | `#be185d` | `rgba(236,72,153,0.2)` | `#f9a8d4` |
+
+### Font Weights
+
+Weights are theme-aware to compensate for light-on-dark rendering differences.
+
+| Token | Light | Dark | Tailwind Class |
+|---|---|---|---|
+| `--font-weight-base` | `420` | `420` | `font-base` |
+| `--font-weight-medium` | `440` | `480` | `font-medium` |
+| `--font-weight-semibold` | `500` | `550` | `font-semibold` |
+
+### Shadows
+
+Defined in `:root` and overridden for `.dark` where applicable.
+
+| Token | Value | Tailwind Class |
+|---|---|---|
+| `--shadow-subtle` | `0 2px 4px 0 rgba(0,0,0,0.08)` | `shadow-subtle` |
+| `--shadow-medium` | `0 4px 12px rgba(0,0,0,0.1)` | `shadow-medium` |
+| `--shadow-overlay` | `0 16px 48px rgba(0,0,0,0.15)` | `shadow-overlay` |
+| `--shadow-kbd` | `0 4px 0 0 rgba(48,48,48,1)` | `shadow-kbd` |
+| `--shadow-kbd-sm` | `0 2px 0 0 rgba(48,48,48,1)` | `shadow-kbd-sm` |
+| `--shadow-brand-inset` | `inset 0 1.25px 2.5px 0 #9b77ff` | `shadow-brand-inset` |
+| `--shadow-card` | `0 1px 3px rgba(0,0,0,0.04)` | `shadow-card` |
+
+### Z-Index Scale
+
+| Token | Value | Usage |
+|---|---|---|
+| `--z-dropdown` | `100` | Dropdown menus |
+| `--z-modal` | `200` | Modal overlays |
+| `--z-popover` | `300` | Popovers (above modals for nested use) |
+| `--z-tooltip` | `400` | Tooltips |
+| `--z-toast` | `500` | Toast notifications |
+
+### Indicators
+
+| Token | Value | Usage |
+|---|---|---|
+| `--indicator-online` | `#33c482` | Online status dot |
+| `--indicator-active` | `#4ade80` | Active state |
+| `--indicator-inactive` | `#b7b7b7` | Inactive/offline |
+| `--indicator-seat-filled` | `#34b5ff` | Occupied seat indicator |
+
+### Code Editor
+
+| Token | Light | Dark |
+|---|---|---|
+| `--code-bg` | `#f5f5f5` | `#1f1f1f` |
+| `--code-foreground` | `#1a1a1a` | `#eeeeee` |
+| `--code-line-number` | `#737373` | `#a8a8a8` |
+
+---
+
+## Tailwind Configuration
+
+Defined in `tailwind.config.ts`. Key extensions to the default theme:
+
+### Dark Mode
+
+```ts
+darkMode: ['class'] // Toggle via .dark class on
+```
+
+### Font Families
+
+| Class | Stack |
+|---|---|
+| `font-season` | Custom variable font (`var(--font-season)`) |
+| `font-body` | System sans-serif stack |
+| `font-mono` | Martian Mono + system monospace fallbacks |
+
+### Font Sizes
+
+| Class | Size | Usage |
+|---|---|---|
+| `text-micro` | `10px` | Micro labels |
+| `text-xs` | `11px` | Small labels, button text `sm` |
+| `text-caption` | `12px` | Captions, button group text, badge `md`/`lg` |
+| `text-small` | `13px` | Labels, tag text |
+| `text-base` | `15px` | Body text, branded buttons |
+| `text-md` | `16px` | Larger body text |
+
+### Border Radius
+
+| Class | Value |
+|---|---|
+| `rounded-xs` | `2px` |
+| `rounded-sm` | `calc(var(--radius) - 4px)` = `4px` |
+| `rounded-md` | `calc(var(--radius) - 2px)` = `6px` |
+| `rounded-lg` | `var(--radius)` = `8px` |
+
+### Custom Variant: `hover-hover`
+
+Applies hover styles only on devices with fine pointers and hover capability. Prevents sticky hover on touch devices.
+
+```tsx
+// Usage
+
+```
+
+### Animations
+
+| Class | Duration | Usage |
+|---|---|---|
+| `animate-caret-blink` | `1.25s` | OTP input caret |
+| `animate-slide-left` | `80s` | Infinite slide left (landing) |
+| `animate-slide-right` | `80s` | Infinite slide right (landing) |
+| `animate-placeholder-pulse` | `1.5s` | Loading placeholder opacity |
+| `animate-ring-pulse` | `1.5s` | Border success pulse |
+| `animate-stream-fade-in` | `300ms` | Streaming content fade-in |
+| `animate-thinking-block` | `1.6s` | AI thinking indicator |
+| `animate-slide-in-right` | `350ms` | Panel slide-in |
+| `animate-slide-in-bottom` | `400ms` | Content slide-in from bottom |
+| `animate-tour-tooltip-in` | `200ms` | Tour tooltip entrance |
+| `animate-collapsible-down` | `300ms` | Expandable open |
+| `animate-collapsible-up` | `300ms` | Expandable close |
+
+---
+
+## Component Reference
+
+All components are imported from `@/components/emcn`. Never import from subpaths (except CSS files).
+
+```tsx
+import { Button, Input, Badge, Tooltip } from '@/components/emcn'
+```
+
+### Button
+
+Versatile button with 12 visual variants and 3 sizes.
+
+**Variants:** `default` | `active` | `3d` | `outline` | `primary` | `destructive` | `secondary` | `tertiary` | `ghost` | `subtle` | `ghost-secondary` | `branded`
+
+**Sizes:** `sm` (11px) | `md` (12px, default) | `branded` (login form)
+
+```tsx
+
+
+
+
+
+```
+
+| Variant | Background | Text | Border | Notes |
+|---|---|---|---|---|
+| `default` | `--surface-4` | `--text-secondary` | `--border` | Neutral, most common |
+| `active` | `--surface-5` | `--text-primary` | `--border-1` | Selected/active state |
+| `3d` | transparent | `--text-tertiary` | `--border-1` | Raised shadow effect |
+| `outline` | transparent | `--text-secondary` | `--text-muted` | Bordered, no fill |
+| `primary` | `--text-primary` | `--text-inverse` | none | High emphasis CTA |
+| `destructive` | `--text-error` | white | none | Danger actions |
+| `secondary` | `--brand-secondary` | `--text-primary` | none | Brand blue |
+| `tertiary` | `--brand-accent` | `--text-inverse` | none | Brand green |
+| `ghost` | transparent | `--text-secondary` | none | Minimal, text only |
+| `subtle` | transparent | `--text-body` | none | Subtle hover background |
+| `ghost-secondary` | transparent | `--text-muted` | none | Extra-low emphasis |
+| `branded` | via CSS class | white | theme | Requires `branded-button-gradient` or `branded-button-custom` class |
+
+**Exports:** `Button`, `ButtonProps`, `buttonVariants`
+
+---
+
+### ButtonGroup
+
+Connected toggle group where one item is selected at a time.
+
+**Gap:** `none` | `sm` (default)
+
+```tsx
+
+ cURL
+ Python
+
+```
+
+**Exports:** `ButtonGroup`, `ButtonGroupItem`, `ButtonGroupProps`, `ButtonGroupItemProps`, `buttonGroupVariants`, `buttonGroupItemVariants`
+
+---
+
+### Input
+
+Text input with variant and size support.
+
+**Variants:** `default` | `error` | `ghost`
+
+**Sizes:** `sm` (12px) | `md` (14px, default)
+
+```tsx
+
+
+
+
+```
+
+| Variant | Background | Border | Focus |
+|---|---|---|---|
+| `default` | `--surface-5` | `--border-1` | border -> `--text-muted` |
+| `error` | `--surface-5` | `--text-error` | stays `--text-error` |
+| `ghost` | transparent | transparent | border -> `--border-1`, bg -> `--surface-5` |
+
+**Exports:** `Input`, `InputProps`, `inputVariants`
+
+---
+
+### Textarea
+
+Multi-line text input with variant support.
+
+**Variants:** `default` | `error` | `ghost`
+
+```tsx
+
+
+
+```
+
+**Exports:** `Textarea`, `TextareaProps`, `textareaVariants`
+
+---
+
+### Badge
+
+Inline status label with extensive color variants.
+
+**Variants:** `default` | `outline` | `type` | `green` | `red` | `gray` | `blue` | `blue-secondary` | `purple` | `orange` | `amber` | `teal` | `cyan` | `pink` | `gray-secondary`
+
+**Sizes:** `sm` | `md` (default) | `lg`
+
+**Props:** `dot?: boolean` (color variants only), `icon?: ComponentType`
+
+```tsx
+Active
+Error
+Info
+```
+
+**Exports:** `Badge`, `BadgeProps`, `badgeVariants`
+
+---
+
+### Checkbox
+
+Radix-based checkbox with size variants.
+
+**Sizes:** `sm` (14px) | `md` (16px, default) | `lg` (20px)
+
+```tsx
+
+
+
+
+```
+
+**Exports:** `Checkbox`, `CheckboxProps`, `checkboxVariants`, `checkboxIconVariants`
+
+---
+
+### Switch
+
+Toggle switch with size variants. Uses `--text-primary` for checked state.
+
+**Sizes:** `sm` (14px height) | `md` (20px, default) | `lg` (24px)
+
+```tsx
+
+
+
+
+```
+
+**Exports:** `Switch`, `SwitchProps`, `switchVariants`, `switchThumbVariants`
+
+---
+
+### Label
+
+Form label with size variants and required indicator.
+
+**Sizes:** `sm` (11px) | `md` (13px, default) | `lg` (15px)
+
+**Props:** `required?: boolean` (shows red asterisk)
+
+```tsx
+
+
+
+```
+
+**Exports:** `Label`, `LabelProps`, `labelVariants`
+
+---
+
+### FormField
+
+Labeled field wrapper with error display.
+
+**Props:** `label`, `htmlFor?`, `optional?`, `error?`, `children`
+
+```tsx
+
+
+
+
+
+
+```
+
+**Exports:** `FormField`, `FormFieldProps`
+
+---
+
+### Slider
+
+Range slider with size variants.
+
+**Sizes:** `sm` | `md` (default) | `lg`
+
+```tsx
+
+
+
+```
+
+**Exports:** `Slider`, `SliderProps`
+
+---
+
+### Avatar
+
+User avatar with image, fallback, and status indicator.
+
+**Sizes:** `xs` (14px) | `sm` (24px) | `md` (32px, default) | `lg` (40px)
+
+**Status:** `online` | `offline` | `busy` | `away`
+
+```tsx
+
+
+ JD
+
+```
+
+**Exports:** `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarProps`, `avatarVariants`, `avatarStatusVariants`
+
+---
+
+### Skeleton
+
+Loading placeholder with shape variants.
+
+**Variants:** `line` (default, rounded-md) | `circle` (rounded-full) | `rectangle` (rounded-sm)
+
+```tsx
+
+
+
+```
+
+**Exports:** `Skeleton`, `SkeletonProps`, `skeletonVariants`
+
+---
+
+### Banner
+
+Full-width notification banner with semantic variants.
+
+**Variants:** `default` | `destructive` | `warning` | `info` | `success`
+
+**Props:** `text?`, `actionLabel?`, `onAction?`, `actionVariant?`, `children?`
+
+```tsx
+
+
+```
+
+**Exports:** `Banner`, `BannerProps`
+
+---
+
+### TagInput
+
+Input for managing tag lists with validation.
+
+**Tag Variants:** `default` (blue) | `secondary` (bordered) | `invalid` (red)
+
+**Props:** `items`, `onAdd`, `onRemove`, `tagVariant?`, `fileInputOptions?`, `triggerKeys?`
+
+```tsx
+ { addTag(v); return isValid(v) }}
+ onRemove={(v, i) => removeTag(i)}
+ tagVariant="secondary"
+/>
+```
+
+**Exports:** `Tag`, `TagInput`, `TagItem`, `TagProps`, `TagInputProps`, `tagVariants`, `tagInputVariants`, `FileInputOptions`
+
+---
+
+### Combobox
+
+Dropdown select with search, multi-select, and editable modes.
+
+**Sizes:** `sm` | `md` (default)
+
+**Modes:** Standard, `searchable`, `editable`, `multiSelect`
+
+```tsx
+
+ {}} searchable size="sm" />
+
+```
+
+**Exports:** `Combobox`, `ComboboxOption`, `ComboboxOptionGroup`
+
+---
+
+### DatePicker
+
+Calendar dropdown for date and date-range selection.
+
+**Sizes:** `default` | `sm`
+
+**Modes:** `single` (default), `range`, `inline`
+
+```tsx
+
+
+
+```
+
+**Exports:** `DatePicker`, `DatePickerProps`, `datePickerVariants`
+
+---
+
+### TimePicker
+
+Time selection dropdown with 12h display.
+
+**Sizes:** `default` | `sm`
+
+```tsx
+
+
+```
+
+**Exports:** `TimePicker`, `TimePickerProps`, `timePickerVariants`
+
+---
+
+### Tooltip
+
+Compound tooltip component with shortcut and preview support.
+
+**Sub-components:** `Tooltip.Root`, `Tooltip.Trigger`, `Tooltip.Content`, `Tooltip.Provider`, `Tooltip.Shortcut`, `Tooltip.Preview`
+
+```tsx
+
+
+
+
+
+
+ Save
+
+
+
+```
+
+**Exports:** `Tooltip`
+
+---
+
+### Popover
+
+Navigation popover with folder drill-down, search, and scroll area.
+
+**Sizes:** `sm` | `md` (default)
+
+**Color Schemes:** `default` | `inverted`
+
+**Variants:** `default` | `secondary`
+
+**Sub-components:** `Popover`, `PopoverTrigger`, `PopoverAnchor`, `PopoverContent`, `PopoverItem`, `PopoverFolder`, `PopoverBackButton`, `PopoverSearch`, `PopoverSection`, `PopoverDivider`, `PopoverScrollArea`
+
+```tsx
+
+
+
+
+
+
+
+ Item 1
+ Selected Item
+
+
+
+```
+
+---
+
+### DropdownMenu
+
+Full-featured dropdown menu built on Radix UI primitives.
+
+**Sub-components:** `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioItem`, `DropdownMenuRadioGroup`, `DropdownMenuLabel`, `DropdownMenuSeparator`, `DropdownMenuShortcut`, `DropdownMenuGroup`, `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent`, `DropdownMenuPortal`, `DropdownMenuSearchInput`
+
+```tsx
+
+
+
+
+
+ Actions
+
+ Edit
+ Delete
+
+
+```
+
+---
+
+### Modal
+
+Dialog component with optional tabs. Uses Radix Dialog primitives.
+
+**Content Sizes:** `sm` | `md` | `lg` | `xl` | `full`
+
+**Sub-components:** `Modal`, `ModalTrigger`, `ModalContent`, `ModalHeader`, `ModalBody`, `ModalFooter`, `ModalClose`, `ModalOverlay`, `ModalPortal`, `ModalTitle`, `ModalDescription`, `ModalTabs`, `ModalTabsList`, `ModalTabsTrigger`, `ModalTabsContent`
+
+```tsx
+
+
+
+
+
+ Title
+ Content
+
+
+
+
+
+```
+
+---
+
+### SModal (Sidebar Modal)
+
+Dialog with sidebar navigation for settings-style UIs.
+
+**Sub-components:** `SModal`, `SModalTrigger`, `SModalContent`, `SModalSidebar`, `SModalSidebarHeader`, `SModalSidebarSection`, `SModalSidebarSectionTitle`, `SModalSidebarItem`, `SModalMain`, `SModalMainHeader`, `SModalMainBody`, `SModalClose`, `SModalTabs`, `SModalTabsList`, `SModalTabsTrigger`, `SModalTabsContent`, `SModalTabsBody`
+
+```tsx
+
+
+
+
+
+
+ Settings
+
+ Account
+ } active>Profile
+
+
+
+ Profile
+ Content
+
+
+
+```
+
+---
+
+### Code
+
+Syntax-highlighted code viewer with line numbers, search, and JSON folding.
+
+**Compound object:** `Code.Container`, `Code.Content`, `Code.Gutter`, `Code.Placeholder`, `Code.Viewer` (standard + virtualized)
+
+```tsx
+// Simple viewer
+
+
+// With text wrapping
+
+```
+
+**Supported languages:** JavaScript, TypeScript, JSON, Python, HTML, CSS, Markdown, Bash, YAML, SQL, and more via PrismJS.
+
+**Exports:** `Code`, `CODE_LINE_HEIGHT_PX`, `calculateGutterWidth`, `getCodeEditorProps`, `highlight`, `languages`
+
+---
+
+### Expandable
+
+Animated collapsible content using Radix Collapsible.
+
+```tsx
+
+
+ Animated content
+
+
+```
+
+**Exports:** `Expandable`, `ExpandableContent`
+
+---
+
+### Breadcrumb
+
+Navigation breadcrumb with link and text items.
+
+```tsx
+
+```
+
+**Exports:** `Breadcrumb`, `BreadcrumbItem`, `BreadcrumbProps`
+
+---
+
+### Table
+
+Standard HTML table with consistent styling.
+
+**Sub-components:** `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`, `TableCell`, `TableCaption`
+
+```tsx
+
+
+
+ Name
+ Status
+
+
+
+
+ Alice
+ Active
+
+
+
+```
+
+---
+
+### InputOTP
+
+OTP verification input using `input-otp` library.
+
+```tsx
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**Exports:** `InputOTP`, `InputOTPGroup`, `InputOTPSeparator`, `InputOTPSlot`
+
+---
+
+### Toast
+
+Imperative toast notification system.
+
+```tsx
+// Wrap app with provider
+
+
+// Call from anywhere
+import { toast } from '@/components/emcn'
+
+toast('Changes saved', {
+ type: 'success',
+ duration: 3000,
+})
+```
+
+**Exports:** `ToastProvider`, `toast`, `useToast`, `CountdownRing`
+
+---
+
+### TourTooltip
+
+Positioned tooltip for product tours.
+
+```tsx
+
+```
+
+**Exports:** `TourTooltip`, `TourTooltipProps`, `TourTooltipPlacement`, `TourCard`, `TourCardProps`
+
+---
+
+## Icons
+
+Custom SVG icons are imported from `@/components/emcn`. They accept standard `SVGProps`.
+
+### Available Icons
+
+`BubbleChatClose`, `BubbleChatPreview`, `Card`, `ChevronDown`, `Connections`, `Copy`, `Cursor`, `DocumentAttachment`, `Download`, `Duplicate`, `Expand`, `Eye`, `FolderCode`, `FolderPlus`, `Hand`, `HexSimple`, `Key`, `Layout`, `Library`, `Loader`, `MoreHorizontal`, `NoWrap`, `PanelLeft`, `Play`, `PlayOutline`, `Redo`, `Rocket`, `Trash`, `Trash2`, `Undo`, `Wrap`, `ZoomIn`, `ZoomOut`
+
+Some icons have CSS module animations: `Copy`, `Download`, `Layout`, `Loader`.
+
+```tsx
+import { Copy, Loader, Play } from '@/components/emcn'
+
+
+```
+
+---
+
+## Styling Guidelines
+
+1. **Always use semantic tokens** (`var(--text-primary)`) over raw colors
+2. **Use `hover-hover:`** instead of `hover:` for interactive states (prevents sticky hover on touch)
+3. **Use `cn()`** from `@/lib/core/utils/cn` for conditional classes
+4. **Use `font-medium`** (not `font-normal`) as the base weight - it maps to theme-aware `--font-weight-medium`
+5. **Use `rounded-sm`** (`4px`) for inputs and small elements, `rounded-[5px]` for buttons
+6. **Use `text-caption`** (`12px`) as the default UI text size, `text-small` (`13px`) for labels
+7. **Use `transition-colors`** for interactive hover/active states
+8. **Respect `motion-reduce:animate-none`** on all animations
+9. **Use `data-[disabled]`** for disabled states on Radix components, `disabled:` for native elements
+
+### Token Usage Patterns
+
+```tsx
+// Surface hierarchy: bg < surface-1 < surface-2 < surface-3 < surface-4 < surface-5 < surface-6 < surface-7
+// Text hierarchy: text-primary > text-secondary > text-tertiary > text-muted > text-subtle
+// Border hierarchy: border < border-1 < border-muted
+
+// Interactive element pattern
+className="bg-[var(--surface-4)] text-[var(--text-secondary)] border-[var(--border)]
+ hover-hover:bg-[var(--surface-6)] hover-hover:text-[var(--text-primary)] hover-hover:border-[var(--border-1)]"
+
+// Input pattern
+className="border-[var(--border-1)] bg-[var(--surface-5)] text-[var(--text-primary)]
+ placeholder:text-[var(--text-muted)] focus-visible:border-[var(--text-muted)]"
+
+// Disabled pattern
+className="disabled:cursor-not-allowed disabled:opacity-50"
+// or for Radix
+className="data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
+```
diff --git a/apps/sim/app/playground/page.tsx b/apps/sim/app/playground/page.tsx
index bd5e6b7b5c2..789ee4ae23a 100644
--- a/apps/sim/app/playground/page.tsx
+++ b/apps/sim/app/playground/page.tsx
@@ -8,6 +8,7 @@ import {
AvatarFallback,
AvatarImage,
Badge,
+ Banner,
Breadcrumb,
BubbleChatClose,
BubbleChatPreview,
@@ -30,6 +31,7 @@ import {
Eye,
FolderCode,
FolderPlus,
+ FormField,
Hand,
HexSimple,
Input,
@@ -64,6 +66,7 @@ import {
PopoverTrigger,
Redo,
Rocket,
+ Skeleton,
Slider,
SModal,
SModalContent,
@@ -228,6 +231,9 @@ export default function PlaygroundPage() {
+
+
+
@@ -337,6 +343,9 @@ export default function PlaygroundPage() {
Gray
+
+ Pink
+
Gray Secondary
@@ -352,6 +361,18 @@ export default function PlaygroundPage() {
+
+
+
+
+
+
+
+
+
+
+
+
@@ -417,15 +438,85 @@ export default function PlaygroundPage() {
{/* Textarea */}
{/* Label */}
+
+ {/* FormField */}
+
{/* Switch */}
@@ -436,6 +527,22 @@ export default function PlaygroundPage() {
{switchValue ? 'On' : 'Off'}
+
+
+ Small
+
+
+
+ Medium (default)
+
+
+
+ Large
+
+
+
+
+
{/* Checkbox */}
@@ -472,6 +579,18 @@ export default function PlaygroundPage() {
{sliderValue[0]}
+
+
+
+
+ Small
+
+
+
+
+
+ Large
+
@@ -995,6 +1114,45 @@ export default function PlaygroundPage() {
+ {/* Banner */}
+
+
+ {/* Skeleton */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{/* Icons */}
diff --git a/apps/sim/components/emcn/components/banner/banner.tsx b/apps/sim/components/emcn/components/banner/banner.tsx
index 452cfb928f5..5452f0f7fe7 100644
--- a/apps/sim/components/emcn/components/banner/banner.tsx
+++ b/apps/sim/components/emcn/components/banner/banner.tsx
@@ -5,11 +5,27 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { Button, type ButtonProps } from '@/components/emcn/components/button/button'
import { cn } from '@/lib/core/utils/cn'
+/**
+ * Variant styles for the Banner component.
+ *
+ * @remarks
+ * Supports semantic variants:
+ * - **default** - Neutral surface background for informational banners
+ * - **destructive** - Red background for error/danger messages
+ * - **warning** - Amber/orange background for caution messages
+ * - **info** - Blue background for informational highlights
+ * - **success** - Green background for positive confirmations
+ */
+// TODO: Replace raw Tailwind palette colors with semantic tokens once
+// muted banner background tokens (e.g. --banner-destructive-bg) are added to globals.css.
const bannerVariants = cva('shrink-0 px-6 py-2.5', {
variants: {
variant: {
default: 'bg-[var(--surface-active)]',
destructive: 'bg-red-50 dark:bg-red-950/30',
+ warning: 'bg-amber-50 dark:bg-amber-950/30',
+ info: 'bg-blue-50 dark:bg-blue-950/30',
+ success: 'bg-green-50 dark:bg-green-950/30',
},
},
defaultVariants: {
diff --git a/apps/sim/components/emcn/components/combobox/combobox.tsx b/apps/sim/components/emcn/components/combobox/combobox.tsx
index 05760852633..2cf3e6422b0 100644
--- a/apps/sim/components/emcn/components/combobox/combobox.tsx
+++ b/apps/sim/components/emcn/components/combobox/combobox.tsx
@@ -97,7 +97,7 @@ export interface ComboboxProps
/** Additional input props for editable mode */
inputProps?: Omit<
React.InputHTMLAttributes
,
- 'value' | 'onChange' | 'disabled' | 'placeholder'
+ 'value' | 'onChange' | 'disabled' | 'placeholder' | 'size'
>
/** Ref for the input element in editable mode */
inputRef?: React.RefObject
diff --git a/apps/sim/components/emcn/components/index.ts b/apps/sim/components/emcn/components/index.ts
index f7a6fe5c82d..f813a40e293 100644
--- a/apps/sim/components/emcn/components/index.ts
+++ b/apps/sim/components/emcn/components/index.ts
@@ -60,7 +60,7 @@ export { Expandable, ExpandableContent } from './expandable/expandable'
export { FormField, type FormFieldProps } from './form-field/form-field'
export { Input, type InputProps, inputVariants } from './input/input'
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './input-otp/input-otp'
-export { Label } from './label/label'
+export { Label, type LabelProps, labelVariants } from './label/label'
export {
MODAL_SIZES,
Modal,
@@ -122,9 +122,9 @@ export {
SModalTabsTrigger,
SModalTrigger,
} from './s-modal/s-modal'
-export { Skeleton } from './skeleton/skeleton'
+export { Skeleton, type SkeletonProps, skeletonVariants } from './skeleton/skeleton'
export { Slider, type SliderProps } from './slider/slider'
-export { Switch } from './switch/switch'
+export { Switch, type SwitchProps, switchThumbVariants, switchVariants } from './switch/switch'
export {
Table,
TableBody,
@@ -145,7 +145,7 @@ export {
tagInputVariants,
tagVariants,
} from './tag-input/tag-input'
-export { Textarea } from './textarea/textarea'
+export { Textarea, type TextareaProps, textareaVariants } from './textarea/textarea'
export { TimePicker, type TimePickerProps, timePickerVariants } from './time-picker/time-picker'
export { CountdownRing } from './toast/countdown-ring'
export { ToastProvider, toast, useToast } from './toast/toast'
diff --git a/apps/sim/components/emcn/components/input/input.tsx b/apps/sim/components/emcn/components/input/input.tsx
index b3649a34e86..172981c1dbc 100644
--- a/apps/sim/components/emcn/components/input/input.tsx
+++ b/apps/sim/components/emcn/components/input/input.tsx
@@ -23,18 +23,33 @@ import { cn } from '@/lib/core/utils/cn'
/**
* Variant styles for the Input component.
- * Currently supports a 'default' variant.
+ *
+ * @remarks
+ * Supports visual variants for different contexts:
+ * - **default** - Standard bordered input with surface background
+ * - **error** - Red-tinted border and focus ring for validation errors
+ * - **ghost** - Transparent background, border only on focus/hover
*/
const inputVariants = cva(
- 'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50',
+ 'flex w-full touch-manipulation rounded-sm border font-medium font-sans text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50',
{
variants: {
variant: {
- default: '',
+ default:
+ 'border-[var(--border-1)] bg-[var(--surface-5)] focus-visible:border-[var(--text-muted)]',
+ error:
+ 'border-[var(--text-error)] bg-[var(--surface-5)] focus-visible:border-[var(--text-error)]',
+ ghost:
+ 'border-transparent bg-transparent hover-hover:bg-[var(--surface-4)] focus-visible:border-[var(--border-1)] focus-visible:bg-[var(--surface-5)]',
+ },
+ size: {
+ sm: 'px-1.5 py-1 text-caption',
+ md: 'px-2 py-1.5 text-sm',
},
},
defaultVariants: {
variant: 'default',
+ size: 'md',
},
}
)
@@ -44,19 +59,31 @@ const inputVariants = cva(
* Extends native input attributes with variant support.
*/
export interface InputProps
- extends React.InputHTMLAttributes,
+ extends Omit, 'size'>,
VariantProps {}
/**
* Minimal input component matching the textarea styling.
* Uses consistent emcn design patterns.
+ *
+ * @example
+ * ```tsx
+ * // Error state
+ *
+ *
+ * // Ghost variant (transparent until focused)
+ *
+ *
+ * // Small size
+ *
+ * ```
*/
const Input = React.forwardRef(
- ({ className, variant, type = 'text', ...props }, ref) => {
+ ({ className, variant, size, type = 'text', ...props }, ref) => {
return (
diff --git a/apps/sim/components/emcn/components/label/label.tsx b/apps/sim/components/emcn/components/label/label.tsx
index aad8b54b381..e210e5db019 100644
--- a/apps/sim/components/emcn/components/label/label.tsx
+++ b/apps/sim/components/emcn/components/label/label.tsx
@@ -1,9 +1,40 @@
'use client'
import * as LabelPrimitive from '@radix-ui/react-label'
+import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/core/utils/cn'
-export interface LabelProps extends React.ComponentPropsWithoutRef {}
+/**
+ * Variant styles for the Label component.
+ *
+ * @remarks
+ * Supports size variants for different contexts:
+ * - **sm** - Compact labels for dense forms (11px)
+ * - **md** - Default label size (13px)
+ * - **lg** - Larger labels for prominent form sections (15px)
+ */
+const labelVariants = cva(
+ 'inline-flex items-center font-medium text-[var(--text-primary)] leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
+ {
+ variants: {
+ size: {
+ sm: 'text-xs',
+ md: 'text-small',
+ lg: 'text-base',
+ },
+ },
+ defaultVariants: {
+ size: 'md',
+ },
+ }
+)
+
+export interface LabelProps
+ extends React.ComponentPropsWithoutRef,
+ VariantProps {
+ /** Displays a red asterisk after the label text */
+ required?: boolean
+}
/**
* EMCN Label component built on Radix UI Label primitive.
@@ -12,27 +43,27 @@ export interface LabelProps extends React.ComponentPropsWithoutRefEmail Address
+ *
+ * // Required label with asterisk
+ *
+ *
+ * // Small label
+ *
* ```
*/
-function Label({ className, ...props }: LabelProps) {
+function Label({ className, size, required, children, ...props }: LabelProps) {
return (
-
+
+ {children}
+ {required ? * : null}
+
)
}
Label.displayName = LabelPrimitive.Root.displayName
-export { Label }
+export { Label, labelVariants }
diff --git a/apps/sim/components/emcn/components/skeleton/skeleton.tsx b/apps/sim/components/emcn/components/skeleton/skeleton.tsx
index 7dbee5edc01..c3c2e25a53d 100644
--- a/apps/sim/components/emcn/components/skeleton/skeleton.tsx
+++ b/apps/sim/components/emcn/components/skeleton/skeleton.tsx
@@ -1,19 +1,52 @@
+import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/core/utils/cn'
+/**
+ * Variant styles for the Skeleton component.
+ *
+ * @remarks
+ * Supports shape variants for different placeholder types:
+ * - **line** - Rounded rectangle for text lines (default)
+ * - **circle** - Perfect circle for avatars and icons
+ * - **rectangle** - Sharp-cornered rectangle for images and cards
+ */
+const skeletonVariants = cva(
+ 'animate-pulse bg-[var(--surface-active)] motion-reduce:animate-none',
+ {
+ variants: {
+ variant: {
+ line: 'rounded-md',
+ circle: 'rounded-full',
+ rectangle: 'rounded-sm',
+ },
+ },
+ defaultVariants: {
+ variant: 'line',
+ },
+ }
+)
+
+export interface SkeletonProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
/**
* Placeholder loading skeleton with a subtle pulse animation.
- * @param props - Standard div attributes including className for sizing.
+ *
+ * @example
+ * ```tsx
+ * // Text line skeleton
+ *
+ *
+ * // Avatar skeleton
+ *
+ *
+ * // Image skeleton
+ *
+ * ```
*/
-function Skeleton({ className, ...props }: React.HTMLAttributes) {
- return (
-
- )
+function Skeleton({ className, variant, ...props }: SkeletonProps) {
+ return
}
-export { Skeleton }
+export { Skeleton, skeletonVariants }
diff --git a/apps/sim/components/emcn/components/slider/slider.tsx b/apps/sim/components/emcn/components/slider/slider.tsx
index 021fe9c056a..713d625f2de 100644
--- a/apps/sim/components/emcn/components/slider/slider.tsx
+++ b/apps/sim/components/emcn/components/slider/slider.tsx
@@ -4,7 +4,21 @@ import * as React from 'react'
import * as SliderPrimitive from '@radix-ui/react-slider'
import { cn } from '@/lib/core/utils/cn'
-export interface SliderProps extends React.ComponentPropsWithoutRef {}
+/**
+ * Track height and thumb size per slider size variant.
+ */
+const SLIDER_SIZES = {
+ sm: { track: 'h-1', thumb: 'h-2.5 w-2.5', hitArea: 'before:inset-[-12px]' },
+ md: { track: 'h-[6px]', thumb: 'h-[14px] w-[14px]', hitArea: 'before:inset-[-15px]' },
+ lg: { track: 'h-2', thumb: 'h-[18px] w-[18px]', hitArea: 'before:inset-[-15px]' },
+} as const
+
+type SliderSize = keyof typeof SLIDER_SIZES
+
+export interface SliderProps extends React.ComponentPropsWithoutRef {
+ /** Size variant of the slider */
+ size?: SliderSize
+}
/**
* EMCN Slider component built on Radix UI Slider primitive.
@@ -12,27 +26,49 @@ export interface SliderProps extends React.ComponentPropsWithoutRef
+ *
+ * // Small size for compact UIs
+ *
+ *
+ * // Large size for prominent controls
+ *
* ```
*/
const Slider = React.forwardRef, SliderProps>(
- ({ className, disabled, ...props }, ref) => (
-
-
-
-
-
-
- )
+ ({ className, disabled, size = 'md', ...props }, ref) => {
+ const sizeConfig = SLIDER_SIZES[size]
+
+ return (
+
+
+
+
+
+
+ )
+ }
)
Slider.displayName = SliderPrimitive.Root.displayName
diff --git a/apps/sim/components/emcn/components/switch/switch.tsx b/apps/sim/components/emcn/components/switch/switch.tsx
index 0db43d07024..6fa6994a4f5 100644
--- a/apps/sim/components/emcn/components/switch/switch.tsx
+++ b/apps/sim/components/emcn/components/switch/switch.tsx
@@ -2,31 +2,88 @@
import * as React from 'react'
import * as SwitchPrimitives from '@radix-ui/react-switch'
+import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/core/utils/cn'
+/**
+ * Variant styles for the Switch root element.
+ *
+ * @remarks
+ * Supports multiple sizes:
+ * - **sm** - Compact switch (14px height) for dense UIs like tables
+ * - **md** - Default switch (20px height)
+ * - **lg** - Larger switch (24px height) for prominent toggles
+ */
+const switchVariants = cva(
+ 'peer relative inline-flex shrink-0 cursor-pointer items-center rounded-full bg-[var(--border-1)] transition-colors before:absolute before:inset-[-12px] before:content-[""] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)] data-[disabled]:cursor-not-allowed data-[state=checked]:bg-[var(--text-primary)] data-[disabled]:opacity-50',
+ {
+ variants: {
+ size: {
+ sm: 'h-3.5 w-6',
+ md: 'h-5 w-9',
+ lg: 'h-6 w-11',
+ },
+ },
+ defaultVariants: {
+ size: 'md',
+ },
+ }
+)
+
+/**
+ * Variant styles for the Switch thumb element.
+ */
+const switchThumbVariants = cva(
+ 'pointer-events-none block rounded-full bg-[var(--surface-2)] shadow-sm ring-0 transition-transform',
+ {
+ variants: {
+ size: {
+ sm: 'h-2.5 w-2.5 data-[state=checked]:translate-x-[12px] data-[state=unchecked]:translate-x-0.5',
+ md: 'h-4 w-4 data-[state=checked]:translate-x-[18px] data-[state=unchecked]:translate-x-0.5',
+ lg: 'h-5 w-5 data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-0.5',
+ },
+ },
+ defaultVariants: {
+ size: 'md',
+ },
+ }
+)
+
+export interface SwitchProps
+ extends React.ComponentPropsWithoutRef,
+ VariantProps {}
+
/**
* Switch component styled to match Sim's design system.
* Uses brand color for checked state, neutral border for unchecked.
+ *
+ * @example
+ * ```tsx
+ * // Default size
+ *
+ *
+ * // Small size for tables
+ *
+ *
+ * // Large size
+ *
+ * ```
*/
const Switch = React.memo(
- React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
- >(({ className, disabled, ...props }, ref) => (
-
-
-
- ))
+ React.forwardRef, SwitchProps>(
+ ({ className, size, disabled, ...props }, ref) => (
+
+
+
+ )
+ )
)
Switch.displayName = 'Switch'
-export { Switch }
+export { Switch, switchVariants, switchThumbVariants }
diff --git a/apps/sim/components/emcn/components/textarea/textarea.tsx b/apps/sim/components/emcn/components/textarea/textarea.tsx
index a92cee7edf7..b04bca3ec3f 100644
--- a/apps/sim/components/emcn/components/textarea/textarea.tsx
+++ b/apps/sim/components/emcn/components/textarea/textarea.tsx
@@ -2,12 +2,26 @@ import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/core/utils/cn'
+/**
+ * Variant styles for the Textarea component.
+ *
+ * @remarks
+ * Supports visual variants for different contexts:
+ * - **default** - Standard bordered textarea with surface background
+ * - **error** - Red-tinted border for validation errors
+ * - **ghost** - Transparent background, border only on focus/hover
+ */
const textareaVariants = cva(
- 'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-2 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] resize-none overflow-auto disabled:cursor-not-allowed disabled:opacity-50',
+ 'flex w-full touch-manipulation rounded-sm border px-2 py-2 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none resize-none overflow-auto disabled:cursor-not-allowed disabled:opacity-50',
{
variants: {
variant: {
- default: '',
+ default:
+ 'border-[var(--border-1)] bg-[var(--surface-5)] focus-visible:border-[var(--text-muted)]',
+ error:
+ 'border-[var(--text-error)] bg-[var(--surface-5)] focus-visible:border-[var(--text-error)]',
+ ghost:
+ 'border-transparent bg-transparent hover-hover:bg-[var(--surface-4)] focus-visible:border-[var(--border-1)] focus-visible:bg-[var(--surface-5)]',
},
},
defaultVariants: {
@@ -22,7 +36,15 @@ export interface TextareaProps
/**
* Minimal textarea component matching the user-input styling.
- * Features a resize handle in the bottom right corner.
+ *
+ * @example
+ * ```tsx
+ * // Error state
+ *
+ *
+ * // Ghost variant (transparent until focused)
+ *
+ * ```
*/
const Textarea = React.forwardRef(
({ className, variant, ...props }, ref) => {