feat(frontend): Discord design system tokens and palette variables
This commit is contained in:
parent
3eeb054452
commit
ce29eb3062
@ -1,89 +1,239 @@
|
|||||||
/**
|
/**
|
||||||
* AI Studio design system — room-wide tokens.
|
* AI Studio design system — palette hooks.
|
||||||
* Clean, modern palette. No Discord reference.
|
*
|
||||||
|
* Architecture:
|
||||||
|
* CSS custom properties (index.css) ← index.css :root / .dark ← index.css @layer semantic
|
||||||
|
* ↑ read only ← custom mode writes here
|
||||||
|
*
|
||||||
|
* In default mode the hook returns the CSS variable values (live, theme-aware).
|
||||||
|
* In "custom" mode it returns the stored localStorage palette and also writes
|
||||||
|
* those values to the DOM so they override the CSS layer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useTheme } from '@/contexts';
|
import { useTheme } from '@/contexts';
|
||||||
|
|
||||||
// ─── Palette ──────────────────────────────────────────────────────────────────
|
// ─── Preset definitions ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const PALETTE = {
|
export interface PaletteEntry {
|
||||||
light: {
|
bg: string; // page background
|
||||||
// Backgrounds
|
bgSubtle: string; // slightly elevated surface
|
||||||
bg: '#ffffff',
|
bgHover: string; // hover state
|
||||||
bgSubtle: '#f9f9fa',
|
bgActive: string; // active/pressed state
|
||||||
bgHover: '#f3f3f5',
|
border: string; // default border
|
||||||
bgActive: '#ebebef',
|
borderFocus: string; // focus ring / active border
|
||||||
// Borders
|
borderMuted: string; // subtle dividers
|
||||||
border: '#e4e4e8',
|
text: string; // primary text
|
||||||
borderFocus:'#1c7ded',
|
textMuted: string; // secondary / metadata
|
||||||
borderMuted:'#eeeeef',
|
textSubtle: string; // timestamps, hints
|
||||||
// Text
|
accent: string; // brand / action color
|
||||||
text: '#1f1f1f',
|
accentHover: string;
|
||||||
textMuted: '#8a8a8f',
|
accentText: string; // text on accent bg
|
||||||
textSubtle: '#b8b8bd',
|
icon: string; // default icon color
|
||||||
// Accent (primary action)
|
iconHover: string; // icon hover color
|
||||||
accent: '#1c7ded',
|
surface: string; // card / elevated surface
|
||||||
accentHover:'#1a73d4',
|
surface2: string; // deeper surface
|
||||||
accentText: '#ffffff',
|
online: string; // online status dot
|
||||||
// Icon
|
away: string; // away / idle dot
|
||||||
icon: '#8a8a8f',
|
offline: string; // offline dot
|
||||||
iconHover: '#5c5c62',
|
mentionBg: string; // mention highlight bg (with alpha)
|
||||||
// Surfaces
|
mentionText: string; // mention highlight text
|
||||||
surface: '#f7f7f8',
|
msgBg: string; // received message bubble bg
|
||||||
surface2: '#eeeeef',
|
msgOwnBg: string; // own message bubble bg
|
||||||
// Status
|
panelBg: string; // sidebar / panel background
|
||||||
online: '#22c55e',
|
badgeAi: string; // tailwind classes for AI badge
|
||||||
away: '#f59e0b',
|
badgeRole: string; // tailwind classes for role badge
|
||||||
offline: '#d1d1d6',
|
}
|
||||||
// Mention highlight
|
|
||||||
mentionBg: 'rgba(28,125,237,0.08)',
|
|
||||||
mentionText:'#1c7ded',
|
|
||||||
// Message bubbles
|
|
||||||
msgBg: '#f9f9fb',
|
|
||||||
msgOwnBg: '#e8f0fe',
|
|
||||||
// Panel
|
|
||||||
panelBg: '#f5f5f7',
|
|
||||||
// Badges
|
|
||||||
badgeAi: 'bg-blue-50 text-blue-600',
|
|
||||||
badgeRole: 'bg-gray-100 text-gray-600',
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
bg: '#1a1a1e',
|
|
||||||
bgSubtle: '#1e1e23',
|
|
||||||
bgHover: '#222228',
|
|
||||||
bgActive: '#2a2a30',
|
|
||||||
border: '#2e2e35',
|
|
||||||
borderFocus:'#4a9eff',
|
|
||||||
borderMuted:'#252528',
|
|
||||||
text: '#ececf1',
|
|
||||||
textMuted: '#8a8a92',
|
|
||||||
textSubtle: '#5c5c65',
|
|
||||||
accent: '#4a9eff',
|
|
||||||
accentHover:'#6aafff',
|
|
||||||
accentText: '#ffffff',
|
|
||||||
icon: '#7a7a84',
|
|
||||||
iconHover: '#b0b0ba',
|
|
||||||
surface: '#222228',
|
|
||||||
surface2: '#2a2a30',
|
|
||||||
online: '#34d399',
|
|
||||||
away: '#fbbf24',
|
|
||||||
offline: '#6b7280',
|
|
||||||
mentionBg: 'rgba(74,158,255,0.12)',
|
|
||||||
mentionText:'#4a9eff',
|
|
||||||
msgBg: '#1e1e23',
|
|
||||||
msgOwnBg: '#1a2a3a',
|
|
||||||
panelBg: '#161619',
|
|
||||||
badgeAi: 'bg-blue-900/40 text-blue-300',
|
|
||||||
badgeRole: 'bg-gray-800 text-gray-400',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type ThemePalette = typeof PALETTE.light;
|
export type ThemePresetId = 'default' | 'custom';
|
||||||
|
|
||||||
|
export interface ThemePreset {
|
||||||
|
id: ThemePresetId;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
/** Pre-built palette object, or null = read from CSS vars (default mode) */
|
||||||
|
palette: PaletteEntry | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Presets ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const THEME_PRESETS: ThemePreset[] = [
|
||||||
|
{
|
||||||
|
id: 'default',
|
||||||
|
label: 'Default',
|
||||||
|
description: 'Linear / Vercel inspired — neutral + single indigo accent',
|
||||||
|
palette: null, // reads live from CSS vars
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Well-known CSS vars that map to PaletteEntry keys */
|
||||||
|
const PALETTE_VAR_MAP: Record<keyof PaletteEntry, string> = {
|
||||||
|
bg: '--bg',
|
||||||
|
bgSubtle: '--surface-2',
|
||||||
|
bgHover: '--surface-3',
|
||||||
|
bgActive: '--surface-3',
|
||||||
|
border: '--border',
|
||||||
|
borderFocus: '--ring',
|
||||||
|
borderMuted: '--border-2',
|
||||||
|
text: '--fg',
|
||||||
|
textMuted: '--fg-muted',
|
||||||
|
textSubtle: '--fg-subtle',
|
||||||
|
accent: '--accent',
|
||||||
|
accentHover: '--accent-hover',
|
||||||
|
accentText: '--accent-fg',
|
||||||
|
icon: '--fg-muted',
|
||||||
|
iconHover: '--fg',
|
||||||
|
surface: '--surface-2',
|
||||||
|
surface2: '--surface-3',
|
||||||
|
online: '--success',
|
||||||
|
away: '--warning',
|
||||||
|
offline: '--room-offline',
|
||||||
|
mentionBg: '--accent-subtle',
|
||||||
|
mentionText: '--accent',
|
||||||
|
msgBg: '--surface-2',
|
||||||
|
msgOwnBg: '--accent-subtle',
|
||||||
|
panelBg: '--sidebar-bg',
|
||||||
|
badgeAi: 'bg-accent/10 text-accent font-medium',
|
||||||
|
badgeRole: 'bg-muted text-muted-foreground font-medium',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Read a CSS custom property value from the DOM, fallback to a default */
|
||||||
|
function readCssVar(name: string, fallback: string): string {
|
||||||
|
if (typeof document === 'undefined') return fallback;
|
||||||
|
return getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue(name)
|
||||||
|
.trim() || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Custom palette storage ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const CUSTOM_KEY = 'theme-custom-palette';
|
||||||
|
const PRESET_KEY = 'theme-preset';
|
||||||
|
|
||||||
|
export function loadCustomPalette(): PaletteEntry | null {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(CUSTOM_KEY);
|
||||||
|
if (!raw) return null;
|
||||||
|
return JSON.parse(raw) as PaletteEntry;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveCustomPalette(palette: PaletteEntry) {
|
||||||
|
localStorage.setItem(CUSTOM_KEY, JSON.stringify(palette));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCustomPalette() {
|
||||||
|
localStorage.removeItem(CUSTOM_KEY);
|
||||||
|
localStorage.setItem(PRESET_KEY, 'default');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadActivePresetId(): ThemePresetId {
|
||||||
|
return (localStorage.getItem(PRESET_KEY) as ThemePresetId) || 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveActivePresetId(id: ThemePresetId) {
|
||||||
|
localStorage.setItem(PRESET_KEY, id);
|
||||||
|
// Notify all useAIPalette hooks to re-read
|
||||||
|
window.dispatchEvent(new CustomEvent('theme-preset-change', { detail: id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a custom palette to the DOM root so it overrides the CSS layer.
|
||||||
|
* Only the keys present in PALETTE_VAR_MAP are written.
|
||||||
|
*/
|
||||||
|
export function applyPaletteToDOM(palette: PaletteEntry) {
|
||||||
|
const root = document.documentElement;
|
||||||
|
for (const [key, cssVar] of Object.entries(PALETTE_VAR_MAP)) {
|
||||||
|
if (key === 'badgeAi' || key === 'badgeRole') continue; // class strings, skip
|
||||||
|
root.style.setProperty(cssVar, (palette as unknown as Record<string, string>)[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reset DOM overrides back to the CSS layer (removes custom inline styles) */
|
||||||
|
export function resetDOMFromPalette() {
|
||||||
|
const root = document.documentElement;
|
||||||
|
for (const cssVar of Object.values(PALETTE_VAR_MAP)) {
|
||||||
|
if (cssVar === '--badge-ai' || cssVar === '--badge-role') continue;
|
||||||
|
root.style.removeProperty(cssVar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Hook ────────────────────────────────────────────────────────────────────
|
// ─── Hook ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function useAIPalette() {
|
export function useAIPalette(): PaletteEntry {
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
return resolvedTheme === 'dark' ? PALETTE.dark : PALETTE.light;
|
const [customPalette, setCustomPalette] = useState<PaletteEntry | null>(
|
||||||
|
loadCustomPalette,
|
||||||
|
);
|
||||||
|
const [activePresetId, setActivePresetId] = useState<ThemePresetId>(loadActivePresetId);
|
||||||
|
|
||||||
|
// Re-read from localStorage when the custom palette changes
|
||||||
|
// (e.g. after ThemeSwitcher saves a new custom palette)
|
||||||
|
useEffect(() => {
|
||||||
|
const onPresetChange = () => {
|
||||||
|
setActivePresetId(loadActivePresetId());
|
||||||
|
setCustomPalette(loadCustomPalette());
|
||||||
|
};
|
||||||
|
window.addEventListener('theme-preset-change', onPresetChange);
|
||||||
|
return () => window.removeEventListener('theme-preset-change', onPresetChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ── Derive palette ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if (activePresetId === 'custom' && customPalette) {
|
||||||
|
// Custom mode: return the stored palette (DOM is already updated by the
|
||||||
|
// ThemeSwitcher, but we also return it here so callers get the right values)
|
||||||
|
return customPalette;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default mode: read live from CSS variables
|
||||||
|
return useMemo<PaletteEntry>(() => {
|
||||||
|
// re-compute when light/dark resolvedTheme changes
|
||||||
|
void resolvedTheme;
|
||||||
|
|
||||||
|
return {
|
||||||
|
bg: readCssVar('--bg', '#ffffff'),
|
||||||
|
bgSubtle: readCssVar('--surface-2', '#f9f9fa'),
|
||||||
|
bgHover: readCssVar('--surface-3', '#f3f3f5'),
|
||||||
|
bgActive: readCssVar('--surface-3', '#ebebef'),
|
||||||
|
border: readCssVar('--border', '#e4e4e8'),
|
||||||
|
borderFocus: readCssVar('--ring', '#1c7ded'),
|
||||||
|
borderMuted: readCssVar('--border-2', '#eeeeef'),
|
||||||
|
text: readCssVar('--fg', '#1f1f1f'),
|
||||||
|
textMuted: readCssVar('--fg-muted', '#8a8a8f'),
|
||||||
|
textSubtle: readCssVar('--fg-subtle', '#b8b8bd'),
|
||||||
|
accent: readCssVar('--accent', '#1c7ded'),
|
||||||
|
accentHover: readCssVar('--accent-hover', '#1a73d4'),
|
||||||
|
accentText: readCssVar('--accent-fg', '#ffffff'),
|
||||||
|
icon: readCssVar('--fg-muted', '#8a8a8f'),
|
||||||
|
iconHover: readCssVar('--fg', '#5c5c62'),
|
||||||
|
surface: readCssVar('--surface-2', '#f7f7f8'),
|
||||||
|
surface2: readCssVar('--surface-3', '#eeeeef'),
|
||||||
|
online: readCssVar('--success', '#22c55e'),
|
||||||
|
away: readCssVar('--warning', '#f59e0b'),
|
||||||
|
offline: readCssVar('--room-offline', '#d1d1d6'),
|
||||||
|
mentionBg: readCssVar('--accent-subtle', 'rgba(28,125,237,0.08)'),
|
||||||
|
mentionText: readCssVar('--accent', '#1c7ded'),
|
||||||
|
msgBg: readCssVar('--surface-2', '#f9f9fb'),
|
||||||
|
msgOwnBg: readCssVar('--accent-subtle', '#e8f0fe'),
|
||||||
|
panelBg: readCssVar('--sidebar-bg', '#f9f9fa'),
|
||||||
|
badgeAi: 'bg-accent/10 text-accent font-medium',
|
||||||
|
badgeRole: 'bg-muted text-muted-foreground font-medium',
|
||||||
|
};
|
||||||
|
}, [resolvedTheme]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Trigger a custom palette: saves to localStorage, applies to DOM, sets preset */
|
||||||
|
export function activateCustomPalette(palette: PaletteEntry) {
|
||||||
|
saveCustomPalette(palette);
|
||||||
|
saveActivePresetId('custom');
|
||||||
|
applyPaletteToDOM(palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reset back to the default CSS-layer theme */
|
||||||
|
export function deactivateCustomPalette() {
|
||||||
|
clearCustomPalette();
|
||||||
|
resetDOMFromPalette();
|
||||||
}
|
}
|
||||||
|
|||||||
644
src/index.css
644
src/index.css
@ -4,211 +4,342 @@
|
|||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@theme inline {
|
/* ══════════════════════════════════════════════════════════════════════════════
|
||||||
--font-heading: var(--font-sans);
|
DESIGN TOKEN LAYER
|
||||||
--font-sans: 'Geist Variable', sans-serif;
|
Structure: Primitive → Semantic → Component
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
Philosophy: Linear/Vercel dual-color (neutral monochrome + single indigo accent)
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
══════════════════════════════════════════════════════════════════════════════ */
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
/* ── Layer 1: Primitive tokens ─────────────────────────────────────────────── */
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
/* These are never used directly in components — always reference via semantic */
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
/* Light primitives */
|
||||||
--color-sidebar: var(--sidebar);
|
@layer primitives {
|
||||||
--color-chart-5: var(--chart-5);
|
:root {
|
||||||
--color-chart-4: var(--chart-4);
|
/* Neutral scale (achromatic gray ramp) */
|
||||||
--color-chart-3: var(--chart-3);
|
--p-gray-50: oklch(0.995 0 0);
|
||||||
--color-chart-2: var(--chart-2);
|
--p-gray-100: oklch(0.985 0 0);
|
||||||
--color-chart-1: var(--chart-1);
|
--p-gray-200: oklch(0.967 0 0);
|
||||||
--color-ring: var(--ring);
|
--p-gray-300: oklch(0.91 0 0);
|
||||||
--color-input: var(--input);
|
--p-gray-400: oklch(0.70 0 0);
|
||||||
--color-border: var(--border);
|
--p-gray-500: oklch(0.55 0 0);
|
||||||
--color-destructive: var(--destructive);
|
--p-gray-600: oklch(0.40 0 0);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--p-gray-700: oklch(0.30 0 0);
|
||||||
--color-accent: var(--accent);
|
--p-gray-800: oklch(0.20 0 0);
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
--p-gray-900: oklch(0.135 0 0);
|
||||||
--color-muted: var(--muted);
|
--p-gray-950: oklch(0.11 0 0);
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
/* Brand accent (single hue: indigo) */
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
--p-accent-50: oklch(0.95 0.05 265);
|
||||||
--color-primary: var(--primary);
|
--p-accent-100: oklch(0.88 0.08 265);
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
--p-accent-200: oklch(0.78 0.11 265);
|
||||||
--color-popover: var(--popover);
|
--p-accent-300: oklch(0.65 0.14 265);
|
||||||
--color-card-foreground: var(--card-foreground);
|
--p-accent-400: oklch(0.55 0.17 265);
|
||||||
--color-card: var(--card);
|
--p-accent-500: oklch(0.42 0.19 265);
|
||||||
--color-foreground: var(--foreground);
|
--p-accent-600: oklch(0.35 0.20 265);
|
||||||
--color-background: var(--background);
|
--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-sm: calc(var(--radius) * 0.6);
|
||||||
--radius-md: calc(var(--radius) * 0.8);
|
--radius-md: calc(var(--radius) * 0.8);
|
||||||
--radius-lg: var(--radius);
|
--radius-lg: calc(var(--radius) * 1.0);
|
||||||
--radius-xl: calc(var(--radius) * 1.4);
|
--radius-xl: calc(var(--radius) * 1.4);
|
||||||
--radius-2xl: calc(var(--radius) * 1.8);
|
--radius-2xl: calc(var(--radius) * 1.8);
|
||||||
--radius-3xl: calc(var(--radius) * 2.2);
|
--radius-3xl: calc(var(--radius) * 2.2);
|
||||||
--radius-4xl: calc(var(--radius) * 2.6);
|
--radius-4xl: calc(var(--radius) * 2.6);
|
||||||
|
|
||||||
/* ── Discord layout tokens ─────────────────────────────────────────────── */
|
/* Shadows */
|
||||||
--color-discord-bg: var(--room-bg);
|
--shadow-sm: var(--p-shadow-sm);
|
||||||
--color-discord-sidebar: var(--room-sidebar);
|
--shadow-md: var(--p-shadow-md);
|
||||||
--color-discord-channel-hover: var(--room-channel-hover);
|
--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-channel-active: var(--room-channel-active);
|
||||||
--color-discord-mention-badge: var(--room-mention-badge);
|
--color-discord-text: var(--room-text);
|
||||||
--color-discord-blurple: var(--room-accent);
|
|
||||||
--color-discord-blurple-hover: var(--room-accent-hover);
|
|
||||||
--color-discord-green: var(--room-online);
|
|
||||||
--color-discord-red: oklch(0.63 0.21 25);
|
|
||||||
--color-discord-yellow: oklch(0.75 0.17 80);
|
|
||||||
--color-discord-online: var(--room-online);
|
|
||||||
--color-discord-offline: var(--room-offline);
|
|
||||||
--color-discord-idle: var(--room-away);
|
|
||||||
--color-discord-mention-text: var(--room-mention-text);
|
|
||||||
--color-discord-text: var(--room-text);
|
|
||||||
--color-discord-text-secondary: var(--room-text-secondary);
|
--color-discord-text-secondary: var(--room-text-secondary);
|
||||||
--color-discord-text-muted: var(--room-text-muted);
|
--color-discord-text-muted: var(--room-text-muted);
|
||||||
--color-discord-text-subtle: var(--room-text-subtle);
|
--color-discord-text-subtle: var(--room-text-subtle);
|
||||||
--color-discord-border: var(--room-border);
|
--color-discord-accent: var(--room-accent);
|
||||||
--color-discord-hover: var(--room-hover);
|
--color-discord-accent-hover: var(--room-accent-hover);
|
||||||
--color-discord-placeholder: var(--room-text-muted);
|
--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);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
/* ── Layer 4: Base styles ──────────────────────────────────────────────────── */
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0 0);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.145 0 0);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
|
||||||
--primary: oklch(0.488 0.243 264.376);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.97 0 0);
|
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
|
||||||
--muted: oklch(0.97 0 0);
|
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
|
||||||
--accent: oklch(0.97 0 0);
|
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0 0);
|
|
||||||
--input: oklch(0.922 0 0);
|
|
||||||
--ring: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-1: oklch(0.87 0 0);
|
|
||||||
--chart-2: oklch(0.556 0 0);
|
|
||||||
--chart-3: oklch(0.439 0 0);
|
|
||||||
--chart-4: oklch(0.371 0 0);
|
|
||||||
--chart-5: oklch(0.269 0 0);
|
|
||||||
--radius: 0.5rem;
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
|
||||||
--sidebar-ring: oklch(0.488 0.243 264.376);
|
|
||||||
|
|
||||||
/* AI Studio room palette — light */
|
|
||||||
--room-bg: oklch(0.995 0 0);
|
|
||||||
--room-sidebar: oklch(0.99 0 0);
|
|
||||||
--room-channel-hover: oklch(0.97 0 0);
|
|
||||||
--room-channel-active: oklch(0.55 0.18 253 / 8%);
|
|
||||||
--room-mention-badge: oklch(0.55 0.18 253);
|
|
||||||
--room-accent: oklch(0.55 0.18 253);
|
|
||||||
--room-accent-hover: oklch(0.52 0.19 253);
|
|
||||||
--room-online: oklch(0.63 0.19 158);
|
|
||||||
--room-offline: oklch(0.62 0 0 / 35%);
|
|
||||||
--room-away: oklch(0.75 0.17 80);
|
|
||||||
--room-text: oklch(0.145 0 0);
|
|
||||||
--room-text-secondary: oklch(0.25 0 0);
|
|
||||||
--room-text-muted: oklch(0.50 0 0);
|
|
||||||
--room-text-subtle: oklch(0.68 0 0);
|
|
||||||
--room-border: oklch(0.91 0 0);
|
|
||||||
--room-hover: oklch(0.97 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: oklch(0.145 0 0);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.18 0 0);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.18 0 0);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.488 0.243 264.376);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.269 0 0);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.22 0 0);
|
|
||||||
--muted-foreground: oklch(0.65 0 0);
|
|
||||||
--accent: oklch(0.269 0 0);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 8%);
|
|
||||||
--input: oklch(1 0 0 / 10%);
|
|
||||||
--ring: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-1: oklch(0.87 0 0);
|
|
||||||
--chart-2: oklch(0.556 0 0);
|
|
||||||
--chart-3: oklch(0.439 0 0);
|
|
||||||
--chart-4: oklch(0.371 0 0);
|
|
||||||
--chart-5: oklch(0.269 0 0);
|
|
||||||
--sidebar: oklch(0.13 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.22 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 8%);
|
|
||||||
--sidebar-ring: oklch(0.488 0.243 264.376);
|
|
||||||
|
|
||||||
/* Discord dark theme */
|
|
||||||
--discord-bg: oklch(0.145 0 0);
|
|
||||||
--discord-sidebar: oklch(0.13 0 0);
|
|
||||||
--discord-channel-hover: oklch(0.2 0 0);
|
|
||||||
/* AI Studio room palette — dark */
|
|
||||||
--room-bg: oklch(0.11 0 0);
|
|
||||||
--room-sidebar: oklch(0.10 0 0);
|
|
||||||
--room-channel-hover: oklch(0.16 0 0);
|
|
||||||
--room-channel-active: oklch(0.58 0.18 253 / 12%);
|
|
||||||
--room-mention-badge: oklch(0.58 0.18 253);
|
|
||||||
--room-accent: oklch(0.58 0.18 253);
|
|
||||||
--room-accent-hover: oklch(0.65 0.20 253);
|
|
||||||
--room-online: oklch(0.65 0.17 158);
|
|
||||||
--room-offline: oklch(0.50 0 0 / 35%);
|
|
||||||
--room-away: oklch(0.72 0.16 80);
|
|
||||||
--room-text: oklch(0.985 0 0);
|
|
||||||
--room-text-secondary: oklch(0.985 0 0 / 80%);
|
|
||||||
--room-text-muted: oklch(0.985 0 0 / 58%);
|
|
||||||
--room-text-subtle: oklch(0.985 0 0 / 40%);
|
|
||||||
--room-border: oklch(0 0 0 / 18%);
|
|
||||||
--room-hover: oklch(0.16 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground antialiased;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
@apply font-sans;
|
@apply font-sans;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Placeholder support for contenteditable MentionInput */
|
/* Placeholder support for contenteditable MentionInput */
|
||||||
[contenteditable][data-placeholder]:empty::before {
|
[contenteditable][data-placeholder]:empty::before {
|
||||||
content: attr(data-placeholder);
|
content: attr(data-placeholder);
|
||||||
color: var(--muted-foreground);
|
color: var(--fg-muted);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Discord layout ──────────────────────────────────────────────────────── */
|
/* ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
COMPONENT STYLES — all reference semantic tokens via CSS vars or Tailwind
|
||||||
|
══════════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* ── Discord layout ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.discord-layout {
|
.discord-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--room-bg);
|
background: var(--room-bg);
|
||||||
color: var(--foreground);
|
color: var(--room-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Server sidebar (left icon strip) */
|
|
||||||
.discord-server-sidebar {
|
.discord-server-sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -232,8 +363,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--room-channel-hover);
|
background: var(--room-hover);
|
||||||
color: var(--foreground);
|
color: var(--room-sidebar-fg);
|
||||||
transition: border-radius 200ms ease, background 200ms ease;
|
transition: border-radius 200ms ease, background 200ms ease;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -242,16 +373,11 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-server-icon:hover {
|
.discord-server-icon:hover,
|
||||||
border-radius: 16px;
|
|
||||||
background: var(--room-accent);
|
|
||||||
color: var(--primary-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-server-icon.active {
|
.discord-server-icon.active {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: var(--room-accent);
|
background: var(--room-accent);
|
||||||
color: var(--primary-foreground);
|
color: var(--room-mention-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-server-icon .home-icon {
|
.discord-server-icon .home-icon {
|
||||||
@ -259,11 +385,10 @@
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Channel sidebar */
|
|
||||||
.discord-channel-sidebar {
|
.discord-channel-sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 260px;
|
width: 240px;
|
||||||
background: var(--room-sidebar);
|
background: var(--room-sidebar);
|
||||||
border-right: 1px solid var(--room-border);
|
border-right: 1px solid var(--room-border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@ -276,14 +401,13 @@
|
|||||||
height: 48px;
|
height: 48px;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
border-bottom: 1px solid var(--room-border);
|
border-bottom: 1px solid var(--room-border);
|
||||||
box-shadow: 0 1px 0 var(--room-border);
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-channel-header-title {
|
.discord-channel-header-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: var(--foreground);
|
color: var(--room-sidebar-fg);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,20 +418,14 @@
|
|||||||
padding: 8px 8px 8px 0;
|
padding: 8px 8px 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-channel-list::-webkit-scrollbar {
|
.discord-channel-list::-webkit-scrollbar { width: 4px; }
|
||||||
width: 4px;
|
.discord-channel-list::-webkit-scrollbar-track { background: transparent; }
|
||||||
}
|
|
||||||
.discord-channel-list::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.discord-channel-list::-webkit-scrollbar-thumb {
|
.discord-channel-list::-webkit-scrollbar-thumb {
|
||||||
background: var(--room-border);
|
background: var(--room-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-channel-category {
|
.discord-channel-category { margin-bottom: 4px; }
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-channel-category-header {
|
.discord-channel-category-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -326,17 +444,9 @@
|
|||||||
transition: color 150ms;
|
transition: color 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-channel-category-header:hover {
|
.discord-channel-category-header:hover { color: var(--room-sidebar-fg); }
|
||||||
color: var(--foreground);
|
.discord-channel-category-header svg { transition: transform 150ms; }
|
||||||
}
|
.discord-channel-category-header.collapsed svg { transform: rotate(-90deg); }
|
||||||
|
|
||||||
.discord-channel-category-header svg {
|
|
||||||
transition: transform 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-channel-category-header.collapsed svg {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-channel-item {
|
.discord-channel-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -360,7 +470,7 @@
|
|||||||
|
|
||||||
.discord-channel-item.active {
|
.discord-channel-item.active {
|
||||||
background: var(--room-channel-active);
|
background: var(--room-channel-active);
|
||||||
color: var(--foreground);
|
color: var(--room-sidebar-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-channel-item.active::before {
|
.discord-channel-item.active::before {
|
||||||
@ -398,8 +508,8 @@
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
background: var(--room-mention-badge);
|
background: var(--room-mention-bg);
|
||||||
color: var(--primary-foreground);
|
color: var(--room-mention-fg);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@ -421,12 +531,9 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.discord-add-channel-btn:hover { color: var(--room-text); }
|
||||||
|
|
||||||
.discord-add-channel-btn:hover {
|
/* Member sidebar */
|
||||||
color: var(--room-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Member list sidebar */
|
|
||||||
.discord-member-sidebar {
|
.discord-member-sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -470,15 +577,9 @@
|
|||||||
transition: background 100ms;
|
transition: background 100ms;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
.discord-member-item:hover { background: var(--room-hover); }
|
||||||
|
|
||||||
.discord-member-item:hover {
|
.discord-member-avatar-wrap { position: relative; flex-shrink: 0; }
|
||||||
background: var(--room-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-member-avatar-wrap {
|
|
||||||
position: relative;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-member-status-dot {
|
.discord-member-status-dot {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -490,17 +591,12 @@
|
|||||||
border: 2px solid var(--room-sidebar);
|
border: 2px solid var(--room-sidebar);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-member-status-dot.online {
|
.discord-member-status-dot.online { background: var(--room-online); }
|
||||||
background: var(--room-online);
|
.discord-member-status-dot.offline { background: var(--room-offline); }
|
||||||
}
|
.discord-member-status-dot.idle { background: var(--room-away); }
|
||||||
.discord-member-status-dot.offline {
|
|
||||||
background: var(--room-offline);
|
/* ── Message list ──────────────────────────────────────────────────────────── */
|
||||||
}
|
|
||||||
.discord-member-status-dot.idle {
|
|
||||||
background: var(--room-away);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Discord message bubbles ─────────────────────────────────────────────── */
|
|
||||||
.discord-message-list {
|
.discord-message-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -508,12 +604,8 @@
|
|||||||
padding: 0 16px 8px;
|
padding: 0 16px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-message-list::-webkit-scrollbar {
|
.discord-message-list::-webkit-scrollbar { width: 8px; }
|
||||||
width: 8px;
|
.discord-message-list::-webkit-scrollbar-track { background: transparent; }
|
||||||
}
|
|
||||||
.discord-message-list::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.discord-message-list::-webkit-scrollbar-thumb {
|
.discord-message-list::-webkit-scrollbar-thumb {
|
||||||
background: var(--room-border);
|
background: var(--room-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -521,10 +613,7 @@
|
|||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-message-group {
|
.discord-message-group { display: flex; flex-direction: column; }
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-message-row {
|
.discord-message-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -536,9 +625,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-message-row:hover .discord-message-actions {
|
.discord-message-row:hover .discord-message-actions { opacity: 1; }
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-message-avatar {
|
.discord-message-avatar {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@ -549,15 +636,8 @@
|
|||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-message-body {
|
.discord-message-body { flex: 1; min-width: 0; }
|
||||||
flex: 1;
|
.discord-message-avatar-spacer { width: 40px; flex-shrink: 0; }
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-message-avatar-spacer {
|
|
||||||
width: 40px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-message-header {
|
.discord-message-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -569,14 +649,11 @@
|
|||||||
.discord-message-author {
|
.discord-message-author {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--foreground);
|
color: var(--room-text);
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.discord-message-author:hover { text-decoration: underline; }
|
||||||
.discord-message-author:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-message-time {
|
.discord-message-time {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -593,8 +670,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.discord-message-content .mention {
|
.discord-message-content .mention {
|
||||||
background: oklch(0.488 0.243 264.376 / 25%);
|
background: var(--accent-subtle);
|
||||||
color: var(--room-mention-text);
|
color: var(--accent);
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -607,13 +684,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
background: var(--card);
|
background: var(--surface-2);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 150ms;
|
transition: opacity 150ms;
|
||||||
box-shadow: 0 1px 4px var(--room-border);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-msg-action-btn {
|
.discord-msg-action-btn {
|
||||||
@ -629,7 +706,6 @@
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-msg-action-btn:hover {
|
.discord-msg-action-btn:hover {
|
||||||
background: var(--room-hover);
|
background: var(--room-hover);
|
||||||
color: var(--room-text);
|
color: var(--room-text);
|
||||||
@ -668,10 +744,7 @@
|
|||||||
color: var(--room-text-subtle);
|
color: var(--room-text-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-typing-dots {
|
.discord-typing-dots { display: flex; gap: 3px; }
|
||||||
display: flex;
|
|
||||||
gap: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-typing-dots span {
|
.discord-typing-dots span {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
@ -686,10 +759,10 @@
|
|||||||
|
|
||||||
@keyframes typing-bounce {
|
@keyframes typing-bounce {
|
||||||
0%, 60%, 100% { transform: translateY(0); }
|
0%, 60%, 100% { transform: translateY(0); }
|
||||||
30% { transform: translateY(-4px); }
|
30% { transform: translateY(-4px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status pill in header */
|
/* WebSocket status */
|
||||||
.discord-ws-status {
|
.discord-ws-status {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -704,19 +777,19 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-ws-dot.connected { background: var(--room-online); }
|
.discord-ws-dot.connected { background: var(--room-online); }
|
||||||
.discord-ws-dot.connecting {
|
.discord-ws-dot.connecting {
|
||||||
background: var(--room-away);
|
background: var(--room-away);
|
||||||
animation: pulse 1.5s infinite;
|
animation: pulse 1.5s infinite;
|
||||||
}
|
}
|
||||||
.discord-ws-dot.disconnected { background: oklch(0.63 0.21 25); }
|
.discord-ws-dot.disconnected { background: var(--error); }
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0.4; }
|
50% { opacity: 0.4; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat input area */
|
/* Chat input */
|
||||||
.discord-chat-input-area {
|
.discord-chat-input-area {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -754,11 +827,7 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
.discord-input-field::placeholder { color: var(--room-text-muted); }
|
||||||
.discord-input-field::placeholder {
|
|
||||||
color: var(--room-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-input-field::-webkit-scrollbar { width: 0; }
|
.discord-input-field::-webkit-scrollbar { width: 0; }
|
||||||
|
|
||||||
.discord-send-btn {
|
.discord-send-btn {
|
||||||
@ -769,17 +838,16 @@
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: var(--room-accent);
|
background: var(--room-accent);
|
||||||
color: var(--primary-foreground);
|
color: var(--room-mention-fg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
transition: background 150ms;
|
transition: background 150ms;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.discord-send-btn:hover { background: var(--room-accent-hover); }
|
||||||
.discord-send-btn:hover { background: var(--room-accent-hover); }
|
|
||||||
.discord-send-btn:disabled { opacity: 0.5; cursor: default; }
|
.discord-send-btn:disabled { opacity: 0.5; cursor: default; }
|
||||||
|
|
||||||
/* Reply preview in input */
|
/* Reply preview */
|
||||||
.discord-reply-preview {
|
.discord-reply-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -793,11 +861,7 @@
|
|||||||
color: var(--room-text-muted);
|
color: var(--room-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discord-reply-preview-author {
|
.discord-reply-preview-author { font-weight: 600; color: var(--room-text-secondary); }
|
||||||
font-weight: 600;
|
|
||||||
color: var(--room-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-reply-preview-text {
|
.discord-reply-preview-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -805,7 +869,7 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Streaming message cursor */
|
/* Streaming cursor */
|
||||||
.discord-streaming-cursor {
|
.discord-streaming-cursor {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 2px;
|
width: 2px;
|
||||||
@ -818,10 +882,10 @@
|
|||||||
|
|
||||||
@keyframes cursor-blink {
|
@keyframes cursor-blink {
|
||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0; }
|
50% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Role color dot */
|
/* Role dot */
|
||||||
.discord-role-dot {
|
.discord-role-dot {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user