@import "tailwindcss"; @import "tw-animate-css"; @import "@fontsource-variable/geist"; @custom-variant dark (&:is(.dark *)); /* ══════════════════════════════════════════════════════════════════════════════ DESIGN TOKEN LAYER Structure: Primitive → Semantic → Component Philosophy: Linear/Vercel dual-color (neutral monochrome + single indigo accent) ══════════════════════════════════════════════════════════════════════════════ */ /* ── Layer 1: Primitive tokens ─────────────────────────────────────────────── */ /* These are never used directly in components — always reference via semantic */ /* Light primitives */ @layer primitives { :root { /* Neutral scale (achromatic gray ramp) */ --p-gray-50: oklch(0.995 0 0); --p-gray-100: oklch(0.985 0 0); --p-gray-200: oklch(0.967 0 0); --p-gray-300: oklch(0.91 0 0); --p-gray-400: oklch(0.70 0 0); --p-gray-500: oklch(0.55 0 0); --p-gray-600: oklch(0.40 0 0); --p-gray-700: oklch(0.30 0 0); --p-gray-800: oklch(0.20 0 0); --p-gray-900: oklch(0.135 0 0); --p-gray-950: oklch(0.11 0 0); /* Brand accent (single hue: indigo) */ --p-accent-50: oklch(0.95 0.05 265); --p-accent-100: oklch(0.88 0.08 265); --p-accent-200: oklch(0.78 0.11 265); --p-accent-300: oklch(0.65 0.14 265); --p-accent-400: oklch(0.55 0.17 265); --p-accent-500: oklch(0.42 0.19 265); --p-accent-600: oklch(0.35 0.20 265); --p-accent-700: oklch(0.30 0.21 265); --p-accent-800: oklch(0.22 0.17 265); --p-accent-900: oklch(0.14 0.12 265); /* Status */ --p-success: oklch(0.55 0.14 160); /* green */ --p-warning: oklch(0.72 0.14 75); /* yellow */ --p-error: oklch(0.55 0.22 25); /* red */ /* Surface luminance — base for background */ --p-surface-light: oklch(0.995 0 0); --p-surface-dark: oklch(0.13 0 0); /* Shadows */ --p-shadow-sm: 0 1px 2px oklch(0 0 0 / 0.04), 0 1px 3px oklch(0 0 0 / 0.06); --p-shadow-md: 0 4px 6px oklch(0 0 0 / 0.04), 0 2px 4px oklch(0 0 0 / 0.06); --p-shadow-lg: 0 10px 15px oklch(0 0 0 / 0.05), 0 4px 6px oklch(0 0 0 / 0.06); --p-shadow-xl: 0 8px 30px oklch(0 0 0 / 0.10), 0 2px 8px oklch(0 0 0 / 0.06); --p-shadow-focus-light: 0 0 0 3px oklch(0.42 0.19 265 / 15%); --p-shadow-focus-dark: 0 0 0 3px oklch(0.60 0.17 265 / 25%); /* Radius base */ --p-radius-sm: 0.25rem; --p-radius-md: 0.375rem; --p-radius-lg: 0.5rem; --p-radius-xl: 0.75rem; } } /* ── Layer 2: Semantic tokens ─────────────────────────────────────────────── */ /* Maps primitives to roles. Theme-switching happens HERE only. */ @layer semantic { :root { /* Background / foreground */ --bg: var(--p-gray-50); --fg: var(--p-gray-900); --fg-muted: var(--p-gray-500); --fg-subtle: var(--p-gray-400); /* Surface hierarchy */ --surface-1: var(--p-gray-50); /* page bg */ --surface-2: var(--p-gray-100); /* cards */ --surface-3: var(--p-gray-200); /* hover, input bg */ /* Border */ --border: var(--p-gray-300); --border-2: var(--p-gray-200); /* subtle dividers */ /* Brand accent */ --accent: var(--p-accent-500); --accent-hover: var(--p-accent-400); --accent-subtle: oklch(0.42 0.19 265 / 10%); --accent-fg: var(--p-gray-50); /* Destructive */ --destructive: var(--p-error); --destructive-fg: var(--p-gray-50); /* Status */ --success: var(--p-success); --success-subtle: oklch(0.55 0.14 160 / 12%); --warning: var(--p-warning); --warning-subtle: oklch(0.72 0.14 75 / 12%); --error: var(--p-error); --error-subtle: oklch(0.55 0.22 25 / 12%); /* Ring / focus */ --ring: var(--accent); --focus: var(--p-shadow-focus-light); /* Chart — monochrome ramp */ --chart-1: var(--p-gray-600); --chart-2: var(--p-gray-500); --chart-3: var(--p-gray-400); --chart-4: var(--p-gray-300); --chart-5: var(--p-gray-200); /* Radius */ --radius: var(--p-radius-md); --radius-sm: calc(var(--radius) * 0.6); --radius-md: calc(var(--radius) * 0.8); --radius-lg: calc(var(--radius) * 1.0); --radius-xl: calc(var(--radius) * 1.4); --radius-2xl: calc(var(--radius) * 1.8); --radius-3xl: calc(var(--radius) * 2.2); --radius-4xl: calc(var(--radius) * 2.6); /* Shadows */ --shadow-sm: var(--p-shadow-sm); --shadow-md: var(--p-shadow-md); --shadow-lg: var(--p-shadow-lg); --shadow-xl: var(--p-shadow-xl); /* Sidebar */ --sidebar-bg: var(--p-gray-100); --sidebar-fg: var(--p-gray-900); --sidebar-border: var(--p-gray-300); --sidebar-accent: var(--p-gray-200); --sidebar-accent-fg: var(--p-gray-700); --sidebar-primary: var(--accent); --sidebar-primary-fg: var(--p-gray-50); /* Room / chat */ --room-bg: var(--p-gray-50); --room-sidebar: var(--p-gray-100); --room-sidebar-fg: var(--p-gray-900); --room-hover: var(--p-gray-200); --room-border: var(--p-gray-300); --room-channel-active: var(--accent-subtle); --room-text: var(--p-gray-900); --room-text-secondary: var(--p-gray-600); --room-text-muted: var(--p-gray-500); --room-text-subtle: var(--p-gray-400); --room-accent: var(--accent); --room-accent-hover: var(--accent-hover); --room-mention-bg: var(--accent); --room-mention-fg: var(--p-gray-50); --room-online: var(--p-success); --room-away: var(--p-warning); --room-offline: var(--p-gray-400); } /* Dark theme — only semantic overrides, primitives stay the same */ .dark { --bg: var(--p-gray-950); --fg: var(--p-gray-50); --fg-muted: var(--p-gray-400); --fg-subtle: var(--p-gray-600); --surface-1: var(--p-gray-950); --surface-2: var(--p-gray-900); --surface-3: var(--p-gray-800); --border: oklch(1 0 0 / 8%); --border-2: oklch(1 0 0 / 5%); --accent: var(--p-accent-600); --accent-hover: var(--p-accent-500); --accent-subtle: oklch(0.60 0.17 265 / 14%); --accent-fg: var(--p-gray-950); --destructive: oklch(0.65 0.18 25); --destructive-fg: var(--p-gray-950); --success: var(--p-success); --success-subtle: oklch(0.65 0.13 160 / 15%); --warning: var(--p-warning); --warning-subtle: oklch(0.68 0.14 75 / 15%); --error: var(--p-error); --error-subtle: oklch(0.65 0.18 25 / 15%); --ring: var(--accent); --focus: var(--p-shadow-focus-dark); --chart-1: var(--p-gray-200); --chart-2: var(--p-gray-300); --chart-3: var(--p-gray-400); --chart-4: var(--p-gray-500); --chart-5: var(--p-gray-600); --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.30), 0 1px 3px oklch(0 0 0 / 0.40); --shadow-md: 0 4px 6px oklch(0 0 0 / 0.30), 0 2px 4px oklch(0 0 0 / 0.40); --shadow-lg: 0 10px 15px oklch(0 0 0 / 0.35), 0 4px 6px oklch(0 0 0 / 0.40); --shadow-xl: 0 8px 30px oklch(0 0 0 / 0.50), 0 2px 8px oklch(0 0 0 / 0.40); --sidebar-bg: var(--p-gray-950); --sidebar-fg: var(--p-gray-50); --sidebar-border: oklch(1 0 0 / 8%); --sidebar-accent: var(--p-gray-800); --sidebar-accent-fg: var(--p-gray-200); --sidebar-primary: var(--accent); --sidebar-primary-fg: var(--p-gray-950); --room-bg: var(--p-gray-950); --room-sidebar: var(--p-gray-900); --room-sidebar-fg: var(--p-gray-50); --room-hover: var(--p-gray-800); --room-border: oklch(1 0 0 / 8%); --room-channel-active: var(--accent-subtle); --room-text: var(--p-gray-50); --room-text-secondary: var(--p-gray-200); --room-text-muted: oklch(1 0 0 / 55%); --room-text-subtle: oklch(1 0 0 / 38%); --room-accent: var(--accent); --room-accent-hover: var(--accent-hover); --room-mention-bg: var(--accent); --room-mention-fg: var(--p-gray-950); --room-online: var(--p-success); --room-away: var(--p-warning); --room-offline: oklch(1 0 0 / 30%); } } /* ── Layer 3: Tailwind @theme inline bridge ──────────────────────────────── */ /* Maps semantic tokens → Tailwind utility classes */ @theme inline { --font-heading: var(--font-sans); --font-sans: 'Geist Variable', sans-serif; --color-background: var(--bg); --color-foreground: var(--fg); --color-card: var(--surface-2); --color-card-foreground: var(--fg); --color-popover: var(--surface-2); --color-popover-foreground: var(--fg); --color-primary: var(--accent); --color-primary-foreground: var(--accent-fg); --color-secondary: var(--surface-3); --color-secondary-foreground: var(--fg); --color-muted: var(--surface-3); --color-muted-foreground: var(--fg-muted); --color-accent: var(--surface-3); --color-accent-foreground: var(--fg); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-fg); --color-border: var(--border); --color-input: var(--border); --color-ring: var(--ring); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); --radius-sm: var(--radius-sm); --radius-md: var(--radius-md); --radius-lg: var(--radius-lg); --radius-xl: var(--radius-xl); --radius-2xl: var(--radius-2xl); --radius-3xl: var(--radius-3xl); --radius-4xl: var(--radius-4xl); /* Sidebar */ --color-sidebar: var(--sidebar-bg); --color-sidebar-foreground: var(--sidebar-fg); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-fg); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-fg); --color-sidebar-ring: var(--ring); /* Room (discord-layout) — maps to semantic tokens */ --color-discord-bg: var(--room-bg); --color-discord-sidebar: var(--room-sidebar); --color-discord-fg: var(--room-sidebar-fg); --color-discord-hover: var(--room-hover); --color-discord-border: var(--room-border); --color-discord-channel-active: var(--room-channel-active); --color-discord-text: var(--room-text); --color-discord-text-secondary: var(--room-text-secondary); --color-discord-text-muted: var(--room-text-muted); --color-discord-text-subtle: var(--room-text-subtle); --color-discord-accent: var(--room-accent); --color-discord-accent-hover: var(--room-accent-hover); --color-discord-mention-bg: var(--room-mention-bg); --color-discord-mention-fg: var(--room-mention-fg); --color-discord-online: var(--room-online); --color-discord-away: var(--room-away); --color-discord-offline: var(--room-offline); --color-discord-success: var(--success); --color-discord-warning: var(--warning); --color-discord-error: var(--error); } /* ── Layer 4: Base styles ──────────────────────────────────────────────────── */ @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground antialiased; } html { @apply font-sans; } } /* Placeholder support for contenteditable MentionInput */ [contenteditable][data-placeholder]:empty::before { content: attr(data-placeholder); color: var(--fg-muted); pointer-events: none; } /* ══════════════════════════════════════════════════════════════════════════════ COMPONENT STYLES — all reference semantic tokens via CSS vars or Tailwind ══════════════════════════════════════════════════════════════════════════════ */ /* ── Discord layout ─────────────────────────────────────────────────────────── */ .discord-layout { display: flex; height: 100%; width: 100%; overflow: hidden; background: var(--room-bg); color: var(--room-text); } .discord-server-sidebar { display: flex; flex-direction: column; width: 72px; background: var(--room-sidebar); padding: 12px 0; gap: 6px; align-items: center; border-right: 1px solid var(--room-border); flex-shrink: 0; overflow-y: auto; overflow-x: hidden; } .discord-server-icon { position: relative; width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; background: var(--room-hover); color: var(--room-sidebar-fg); transition: border-radius 200ms ease, background 200ms ease; font-weight: 700; font-size: 14px; text-transform: uppercase; letter-spacing: 0; user-select: none; } .discord-server-icon:hover, .discord-server-icon.active { border-radius: 16px; background: var(--room-accent); color: var(--room-mention-fg); } .discord-server-icon .home-icon { width: 28px; height: 28px; } .discord-channel-sidebar { display: flex; flex-direction: column; width: 240px; background: var(--room-sidebar); border-right: 1px solid var(--room-border); flex-shrink: 0; overflow: hidden; } .discord-channel-header { display: flex; align-items: center; height: 48px; padding: 0 16px; border-bottom: 1px solid var(--room-border); flex-shrink: 0; } .discord-channel-header-title { font-weight: 600; font-size: 15px; color: var(--room-sidebar-fg); flex: 1; } .discord-channel-list { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 8px 8px 8px 0; } .discord-channel-list::-webkit-scrollbar { width: 4px; } .discord-channel-list::-webkit-scrollbar-track { background: transparent; } .discord-channel-list::-webkit-scrollbar-thumb { background: var(--room-border); border-radius: 4px; } .discord-channel-category { margin-bottom: 4px; } .discord-channel-category-header { display: flex; align-items: center; gap: 4px; padding: 4px 8px; cursor: pointer; user-select: none; border-radius: 4px; margin-bottom: 2px; color: var(--room-text-muted); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em; transition: color 150ms; } .discord-channel-category-header:hover { color: var(--room-sidebar-fg); } .discord-channel-category-header svg { transition: transform 150ms; } .discord-channel-category-header.collapsed svg { transform: rotate(-90deg); } .discord-channel-item { display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 4px; cursor: pointer; color: var(--room-text-muted); font-size: 15px; font-weight: 400; transition: background 100ms, color 100ms; user-select: none; position: relative; } .discord-channel-item:hover { background: var(--room-hover); color: var(--room-text); } .discord-channel-item.active { background: var(--room-channel-active); color: var(--room-sidebar-fg); } .discord-channel-item.active::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 70%; background: var(--room-accent); border-radius: 0 2px 2px 0; } .discord-channel-hash { opacity: 0.7; width: 12px !important; height: 12px !important; flex-shrink: 0; } .discord-channel-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; } .discord-mention-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: var(--room-mention-bg); color: var(--room-mention-fg); font-size: 11px; font-weight: 700; line-height: 1; } .discord-add-channel-btn { display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 4px; cursor: pointer; color: var(--room-text-subtle); font-size: 14px; transition: color 150ms; margin-left: 16px; background: none; border: none; text-align: left; width: 100%; } .discord-add-channel-btn:hover { color: var(--room-text); } /* Member sidebar */ .discord-member-sidebar { display: flex; flex-direction: column; width: 220px; background: var(--room-sidebar); border-left: 1px solid var(--room-border); flex-shrink: 0; overflow: hidden; } .discord-member-header { display: flex; align-items: center; height: 48px; padding: 0 16px; border-bottom: 1px solid var(--room-border); flex-shrink: 0; } .discord-member-section-title { display: flex; align-items: center; gap: 4px; padding: 16px 12px 4px; color: var(--room-text-subtle); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em; } .discord-member-item { display: flex; align-items: center; gap: 10px; padding: 6px 12px; border-radius: 4px; cursor: pointer; color: var(--room-text-secondary); font-size: 14px; transition: background 100ms; user-select: none; } .discord-member-item:hover { background: var(--room-hover); } .discord-member-avatar-wrap { position: relative; flex-shrink: 0; } .discord-member-status-dot { position: absolute; bottom: 0; right: 0; width: 11px; height: 11px; border-radius: 50%; border: 2px solid var(--room-sidebar); } .discord-member-status-dot.online { background: var(--room-online); } .discord-member-status-dot.offline { background: var(--room-offline); } .discord-member-status-dot.idle { background: var(--room-away); } /* ── Message list ──────────────────────────────────────────────────────────── */ .discord-message-list { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 0 16px 8px; } .discord-message-list::-webkit-scrollbar { width: 8px; } .discord-message-list::-webkit-scrollbar-track { background: transparent; } .discord-message-list::-webkit-scrollbar-thumb { background: var(--room-border); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; } .discord-message-group { display: flex; flex-direction: column; } .discord-message-row { display: flex; align-items: flex-start; gap: 16px; padding: 1px 0; min-height: 26px; border-radius: 4px; position: relative; } .discord-message-row:hover .discord-message-actions { opacity: 1; } .discord-message-avatar { width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; overflow: hidden; margin-top: -2px; } .discord-message-body { flex: 1; min-width: 0; } .discord-message-avatar-spacer { width: 40px; flex-shrink: 0; } .discord-message-header { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; } .discord-message-author { font-size: 15px; font-weight: 600; color: var(--room-text); line-height: 1.2; cursor: pointer; } .discord-message-author:hover { text-decoration: underline; } .discord-message-time { font-size: 11px; color: var(--room-text-subtle); line-height: 1.2; } .discord-message-content { font-size: 15px; line-height: 1.4; color: var(--room-text); word-break: break-word; white-space: pre-wrap; } .discord-message-content .mention { background: var(--accent-subtle); color: var(--accent); padding: 0 4px; border-radius: 3px; font-weight: 500; } .discord-message-actions { position: absolute; top: -14px; right: 16px; display: flex; align-items: center; gap: 2px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 4px; padding: 2px; opacity: 0; transition: opacity 150ms; box-shadow: var(--shadow-md); } .discord-msg-action-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 4px; cursor: pointer; color: var(--room-text-muted); transition: background 100ms, color 100ms; background: none; border: none; } .discord-msg-action-btn:hover { background: var(--room-hover); color: var(--room-text); } .discord-date-divider { display: flex; align-items: center; gap: 16px; margin: 16px 0 8px; padding: 0 60px; } .discord-date-divider::before, .discord-date-divider::after { content: ''; flex: 1; height: 1px; background: var(--room-border); } .discord-date-divider-label { font-size: 12px; font-weight: 600; color: var(--room-text-subtle); white-space: nowrap; } /* Typing indicator */ .discord-typing-indicator { display: flex; align-items: center; gap: 8px; padding: 0 16px 4px; font-size: 12px; color: var(--room-text-subtle); } .discord-typing-dots { display: flex; gap: 3px; } .discord-typing-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--room-text-subtle); animation: typing-bounce 1.2s infinite ease-in-out; } .discord-typing-dots span:nth-child(2) { animation-delay: 0.2s; } .discord-typing-dots span:nth-child(3) { animation-delay: 0.4s; } @keyframes typing-bounce { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-4px); } } /* WebSocket status */ .discord-ws-status { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--room-text-muted); } .discord-ws-dot { width: 8px; height: 8px; border-radius: 50%; } .discord-ws-dot.connected { background: var(--room-online); } .discord-ws-dot.connecting { background: var(--room-away); animation: pulse 1.5s infinite; } .discord-ws-dot.disconnected { background: var(--error); } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } /* Chat input */ .discord-chat-input-area { display: flex; flex-direction: column; padding: 0 16px 24px; margin-top: 4px; } .discord-input-wrapper { display: flex; align-items: flex-end; gap: 4px; background: var(--room-hover); border: 1px solid var(--room-border); border-radius: 8px; padding: 4px 4px 4px 16px; transition: background 150ms, border-color 150ms; } .discord-input-wrapper:focus-within { background: var(--room-sidebar); border-color: var(--room-accent); } .discord-input-field { flex: 1; background: transparent; border: none; outline: none; color: var(--room-text); font-size: 14px; line-height: 1.4; padding: 8px 0; resize: none; max-height: 200px; overflow-y: auto; font-family: inherit; } .discord-input-field::placeholder { color: var(--room-text-muted); } .discord-input-field::-webkit-scrollbar { width: 0; } .discord-send-btn { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 6px; background: var(--room-accent); color: var(--room-mention-fg); cursor: pointer; border: none; transition: background 150ms; flex-shrink: 0; } .discord-send-btn:hover { background: var(--room-accent-hover); } .discord-send-btn:disabled { opacity: 0.5; cursor: default; } /* Reply preview */ .discord-reply-preview { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; padding: 6px 8px; border-radius: 4px; background: var(--room-hover); border-left: 2px solid var(--room-accent); font-size: 13px; color: var(--room-text-muted); } .discord-reply-preview-author { font-weight: 600; color: var(--room-text-secondary); } .discord-reply-preview-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Streaming cursor */ .discord-streaming-cursor { display: inline-block; width: 2px; height: 1em; background: var(--room-accent); margin-left: 1px; vertical-align: text-bottom; animation: cursor-blink 0.8s step-end infinite; } @keyframes cursor-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } /* Role dot */ .discord-role-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }