# Bete Frontend Redesign — IMPHNEN Design System Deep Integration **Date:** 2026-07-02 **Status:** Design Spec (draft) **Project:** Bete — Guild Moderation Watcher for IMPHNEN Discord **Approach:** "Deep Foundation" (Layer 0 → 4) --- ## Overview Bete is a real-time Discord moderation dashboard serving the IMPHNEN community. The frontend (React 19 + Vite 8 + Tailwind 4) already has the IMPHNEN design tokens partially applied, but suffers from: 1. **Fragmented color system** — many components bypass CSS variables with hardcoded Tailwind utility colors (emerald, amber, orange, red — documented in `DESIGN_TOKENS.md` §13) 2. **No dark mode** — the design system only defines light tokens 3. **Inconsistent brand expression** — Sidebar collapses brand assets, navigation feels detached from community identity 4. **Mobile experience is minimal** — only a basic `MobileTabBar` 5. **Technical debt** in animation keyframes, z-index scale, component variant consistency This design spec outlines a systematic redesign across 5 layers, executed top-to-bottom (foundation → components → layout → features → polish), with **light + dark mode support** baked in from the start. --- ## Layer 0: Design Tokens & Theme System ### Current State - CSS custom properties defined in `styles.css` under `:root` — light only - Tailwind config duplicates token definitions in JS — fragmentasi source of truth - `DESIGN_TOKENS.md` documents 13 known issues, mostly hardcoded hex colors bypassing the token system ### Proposed System #### Theme Engine CSS-first approach. Tailwind 4 `@theme` directive references CSS custom properties. Dark mode driven by `prefers-color-scheme` and manual `[data-theme="dark"]` attribute toggle. ```css /* styles.css */ :root { /* Light tokens */ --surface: #ffffff; --on-surface: #1a1a1a; --primary: #23a1eb; --primary-soft: #e1f0fd; --border: #e0e0e0; /* … */ } [data-theme="dark"] { --surface: #1c1c1f; --on-surface: #f0f0f2; --primary: #54a2ff; --primary-soft: #18263a; --border: #343438; /* … */ } @theme { --color-primary: var(--primary); --color-primary-soft: var(--primary-soft); /* … */ } ``` #### Dark Mode Palette | Token | Light | Dark | |-------|-------|------| | `--surface` | `#ffffff` | `#1c1c1f` | | `--surface-dim` | `#f5f5f5` | `#141417` | | `--surface-container` | `#e8e8e8` | `#26262a` | | `--surface-bright` | `#ffffff` | `#2c2c30` | | `--on-surface` | `#1a1a1a` | `#f0f0f2` | | `--on-surface-variant` | `#666666` | `#a0a0a6` | | `--inverse-surface` | `#1a1a1a` | `#f0f0f2` | | `--inverse-on-surface` | `#f5f5f5` | `#1a1a1a` | | `--primary` | `#23a1eb` | `#54a2ff` | | `--primary-soft` | `#e1f0fd` | `#18263a` | | `--primary-hover` | `#1a8fd9` | `#3d8ee8` | | `--secondary` | `#1877f2` | `#4a8ef5` | | `--secondary-soft` | `#e7f1ff` | `#1a274a` | | `--tertiary` | `#5865f2` | `#7984f5` | | `--tertiary-soft` | `#eef0ff` | `#20266a` | | `--border` | `#e0e0e0` | `#343438` | | `--border-hover` | `#cccccc` | `#48484d` | | `--outline` | `#999999` | `#6a6a70` | | `--outline-variant` | `#cccccc` | `#404044` | | `--success` | `#22c55e` | `#34d399` | | `--success-soft` | `#dcfce7` | `#13261a` | | `--warning` | `#f59e0b` | `#fbbf24` | | `--warning-soft` | `#fef3c7` | `#261a10` | | `--destructive` | `#e4405f` | `#f87171` | | `--destructive-soft` | `#ffebee` | `#2a1418` | | `--info` | `#3b82f6` | `#60a5fa` | | `--info-soft` | `#dbeafe` | `#141e38` | #### Theme Switching - **Default**: follow `prefers-color-scheme` - **Manual toggle**: `[data-theme="dark"]` attribute on ``, persisted to `localStorage` - **Transition**: CSS `transition: background-color 200ms, color 150ms` on `body` and key containers - All components respond without re-render since they reference CSS variables #### Migration Strategy (Hardcoded Hex → CSS Vars) All components documented in `DESIGN_TOKENS.md` §13 will be migrated: **Before** (example from `MessageCard.tsx`): ```tsx className="bg-red-100 text-red-700 border-red-200" ``` **After**: ```tsx className="bg-destructive-soft text-destructive border-destructive/20" ``` Where `--destructive-soft`, `--destructive`, `--destructive/20` are CSS variable–backed. #### Z-Index Registry Standardize z-index stack to avoid collisions: | Value | Component | |-------|-----------| | `10` | Sticky Header | | `20` | Sidebar | | `30` | Tab Strip (sticky if scrolled) | | `40` | Toast Container | | `50` | Mobile Bottom Nav | | `60` | Mascot Chatbot | | `70` | Modal / Dialog | | `100` | Overlay backdrop | --- ## Layer 1: Shared UI Components ### Guiding Principle Every component matches exactly **one** design token reference per visual property. No component uses raw Tailwind utility colors like `emerald-*`, `amber-*`, `red-*` — only CSS variables. ### Component Audit & Changes #### Button (`shared/ui/button.tsx`) **Status:** Mostly good — uses CSS vars already ✅ **Changes:** - Add `variant: "tertiary"` (#5865f2 / #7984f5 dark) for Discord-specific actions - Add `size: "icon-sm"` (h-8 w-8, h-4 w-4 icon) for compact icon buttons - Ensure `active:scale-[0.97]` is consistent across all variants #### Badge (`shared/ui/badge.tsx`) **Status:** Needs fix — `success` uses hardcoded `emerald-100 text-emerald-700` ❌ **Changes:** - Map `success` → `bg-success-soft text-success` - Map `warning` → `bg-warning-soft text-warning` - Add `variant: "tertiary"` for Discord brand accent - All variants reference CSS variables #### Card (`shared/ui/card.tsx`) **Status:** Good — `shadow-sm hover:shadow-md transition-shadow` ✅ **Changes:** - Add `variant: "elevated"` for modals/important containers (shadow-md default, shadow-lg hover) - Add `variant: "bordered"` for inline containers (border only, no shadow) #### Input (`shared/ui/input.tsx`) **Status:** Good — proper focus ring, border ✅ **Changes:** - Add `variant: "soft"` for search/filter inputs (muted background, no border on idle) - Add `disabled` styling that's distinct (not just opacity-50) #### Select (`shared/ui/select.tsx`) **Status:** Same as Input — needs soft variant ✅ **Changes:** - Same `variant: "soft"` for filters - Chevron icon should use primary color on focus #### Toast (`shared/ui/toast.tsx`) **Status:** Needs rewrite — `emerald-500`/`amber-500` hardcoded ❌ **Changes:** - Replace `border-l-emerald-500 text-emerald-500` → `border-l-success text-success` - Replace `border-l-amber-500 text-amber-500` → `border-l-warning text-warning` - Change z-index from `z-50` to `z-40` to avoid colliding with MobileTabBar - Stack animation: slide-in from right, fade-out #### Skeleton (`shared/ui/skeleton.tsx`) **Status:** `animate-shimmer` duration mismatch between CSS (1.5s) and Tailwind config (2s) ❌ **Changes:** - Pick **CSS as canonical** (1.5s ease-in-out) - Remove duplicate from Tailwind config - Add `variant` prop: `rounded`, `circular`, `rectangular` - Add `width`/`height` props with sensible defaults #### Tabs (`shared/ui/tabs.tsx`) **Status:** Good — Radix-based, uses CSS vars ✅ **Changes:** - Add `variant: "pills"` for filter-style tabs (used in Messages filter bar) - Add `variant: "underline"` for navigation tabs (used instead of sidebar tab switching) - TabsContent should have `mt-6` spacing consistent #### ScrollArea (`shared/ui/scroll-area.tsx`) **Status:** Good ✅ **Changes:** - Style scrollbar thumb with design system `--border` and `--primary` on hover ### New Components #### Switch (`shared/ui/switch.tsx`) - Radix `Switch` primitive - Colors: `--border` (off), `--primary` (on), thumb white - Dark mode aware via CSS vars - For settings toggles (future: retention policies, auto-delete config) #### Dialog (`shared/ui/dialog.tsx`) - Radix `Dialog` primitive - Overlay: `bg-black/40 backdrop-blur-sm` - Content: Card variant `elevated`, max-w-md, centered - Enter: scaleIn + fadeIn animations - For confirmations, warnings, and future settings panels #### DropdownMenu (`shared/ui/dropdown-menu.tsx`) - Radix `DropdownMenu` primitive - Trigger: ghost button - Content: card surface with shadow-lg, rounded-lg - Items: list-item pattern with hover state - For overflow actions (message actions, bulk operations) #### EmptyState (`shared/ui/empty-state.tsx`) Standard empty/error/loading state component: - Props: `icon` (Lucide), `title` (string), `description` (string), `action` ({label, onClick} | ReactNode) - Visual: centered flexbox, mascot-style illustration or icon, muted text - Animation: fade-in-up on mount - All feature panels use this — no more ad-hoc empty states #### IconButton (`shared/ui/icon-button.tsx`) - Sizes: `sm` (h-8 w-8), `md` (h-10 w-10), `lg` (h-12 w-12) - Variants: `ghost`, `outline`, `primary`, `destructive`, `tertiary` - Tooltip on hover (custom or Radix Tooltip) - For action buttons in MessageCard (reanalyze, moderate, etc.) --- ## Layer 2: Layout & Navigation ### Guiding Principle The layout is the "frame" for IMPHNEN's brand identity. Every structural decision reinforces the community-centered moderation tool narrative. ### Desktop Layout ``` ┌──────────────────────────────────────────────────────────────────┐ │ ┌─────────┐ ┌──────────────────────────────────────────────┐ │ │ │ │ │ [Logo] IMPHNEN ● ● ● │ │ │ │ SIDEBAR │ │ Guild Watcher 🟢 Online 🟡 Idle│ │ │ │ w-64 │ ├──────────────────────────────────────────────┤ │ │ │ fixed │ │ │ │ │ │ │ │ ┌── TAB STRIP ──────────────────────────┐ │ │ │ │ ● Logo │ │ │ [≡] Pesan Voice Dashboard │ │ │ │ │ "IMPH- │ │ └──────────────────────────────────────┘ │ │ │ │ NEN" │ │ │ │ │ │ Guild │ │ ┌── MAIN CONTENT (max-w-1280) ─────────┐ │ │ │ │ Watcher │ │ │ │ │ │ │ │ │ │ │ [Page title] │ │ │ │ │ ─────── │ │ │ [Subtitle] │ │ │ │ │ │ │ │ │ │ │ │ │ [💬] │ │ │ [Content — cards, lists, grids] │ │ │ │ │ Pesan │ │ │ │ │ │ │ │ [📡] │ │ │ │ │ │ │ │ Voice │ │ │ │ │ │ │ │ [📊] │ │ │ │ │ │ │ │ Guild │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────┘ │ │ │ ─────── │ │ │ │ │ │ 🐱 │ │ │ │ │ │ Mascot │ │ │ │ │ └─────────┘ └──────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────┘ ``` #### Sidebar - **Desktop**: `w-64`, **expanded by default** with mascot visible - Collapsible via hamburger toggle to `w-16` (icon-only) - **Brand section**: Logo + gradient "IMPHNEN" text + "Guild Watcher" subtitle + live dot - **Nav items**: Icon + label, active state with `--primary-soft` background - **Mascot section**: Small mascot image (clickable → chat), active status, at bottom - **Border-right**: `border-[--border]` - **Sticky**: Full height, fixed on scroll #### Header (Brand Bar) - **Sticky top-0**, `z-10` - **Left**: Logo + brand name (40% width) - **Right**: Status badges row (WS indicator, Voice indicator, Theme toggle, Notification bell) - **Background**: `bg-surface/70 backdrop-blur-md` - **Border-bottom**: `border-[--border]` - **Height**: `h-14` (56px) — more compact than current (h-16) #### Tab Strip - **Horizontal** row below header, between brand bar and content - **Tabs**: `[Messages] [Voice & Media] [Dashboard Guild]` - **Active indicator**: underline bar (selected) or filled pill - **Sticky** with `z-20` when content scrolls - On mobile: horizontal scroll with snap points #### Content Area - **Max-width**: `1280px` - **Padding**: `p-4 md:p-6 lg:p-8` - **Page title + subtitle** rendered inside content (not header), consistent across all 3 tabs - **Grid gap**: `gap-6` between sections, `gap-4` between cards in a grid ### Mobile Layout ``` ┌─────────────────────────┐ │ [←] IMPHNEN ☰ 🌙 │ ← Compact header (brand + toggle) ├─────────────────────────┤ │ Tab Strip (horizontal │ │ scroll, snap points) │ ├─────────────────────────┤ │ │ │ Content (single col, │ │ stacked vertically) │ │ │ │ │ ├─────────────────────────┤ │ 💬 📡 📊 🐱 │ ← Bottom Nav (safe area) └─────────────────────────┘ ``` - **Header**: Compact — brand + collapse/expand + theme toggle - **Tab Strip**: Horizontal scroll, no Sidebar - **Bottom Nav**: 4 tabs (Messages, Voice, Dashboard, Mascot) with active state indicator - **Safe area**: `pb-4 md:pb-0` for notched devices - **Particle background**: Skipped entirely on mobile ### Particle Background **Status:** Three.js canvas, always rendered ✅ but heavy **Changes:** - Only render when `prefers-reduced-motion: no-preference` - **Mobile**: Skip entirely (conditional render via `useMediaQuery`) - **Desktop**: Reduce particle count from current (default) to ~30 orbs - Use CSS `opacity` transition for mount/unmount - Color references `--primary` and `--tertiary` from CSS vars (currently hardcoded hex) --- ## Layer 3: Feature Components ### Guiding Principle Every feature panel uses the same design language — same spacing, same typography, same color semantics, same interaction patterns. A user switching between Messages and Dashboard should feel like the same app. ### MessageCard (`features/messages/components/MessageCard.tsx`) **Visual structure:** ``` ┌─────────────────────────────────────────────────────────┐ │ │ │ [Avatar] username [Badge] timestamp [actions] │ │ │ │ Message content (word-wrap, max-height truncated) │ │ │ │ [flag badge 1] [flag badge 2] [flag badge 3] │ │ │ │ ────────────────────────────────────────────── │ │ AI: Analysis summary (1-2 lines, collapsible) │ │ │ └─────────────────────────────────────────────────────────┘ ``` **Changes:** - Replace all hardcoded status colors with `Badge` component variants - `ai_status="clean"` → `` - `ai_status="flagged"` → `` - `ai_status="error"` → `` - `ai_status="pending"` → `` - AI analysis section collapsible (default collapsed, show first 80 chars) - Flag badges use `Badge` component (not inline spans) - Avatar + username row compact (h-8 avatar, text-sm) - Card hover: subtle shadow increase + border-highlight - Action buttons (reanalyze) → `IconButton` component ### Dashboard Stats (`features/dashboard/components/DashboardStats.tsx`) **Changes:** - 4 stat cards: Total, Clean, Flagged, Error — using semantic colors - `StatCard` component (internal or shared) with `variant` prop: - `variant="primary"` → icon + accent in primary color - `variant="success"` → icon + accent in success color - `variant="destructive"` → icon + accent in destructive color - `variant="warning"` → icon + accent in warning color - Replace all hardcoded `emerald-*`, `blue-*`, `violet-*` with variant classes - Grid: `sm:grid-cols-2 lg:grid-cols-4 gap-4` - Animation: stagger with `cardStagger` ### UserSummaryList (`features/dashboard/components/UserSummaryList.tsx`) **Changes:** - Each user card: avatar + name + message count + status badge - Status badges use semantic colors (clean → success, flagged → destructive) - List item pattern with hover state - Search input: `variant="soft"` - Infinite scroll / pagination consistent ### UserProfileDetail (`features/dashboard/components/UserProfileDetail.tsx`) **Changes:** - All hardcoded `emerald-*`, `red-*`, `amber-*` → semantic CSS vars - Detail sections with consistent spacing (`space-y-6`) - Back button → `IconButton` with arrow-left - Loading state → `Skeleton` with matching layout - Error state → `EmptyState` with retry action ### ChannelProfileDetail (same pattern as UserProfileDetail) ### VoiceConnectionCard (`features/live/components/VoiceConnectionCard.tsx`) **Changes:** - Compact layout: guild select + channel select + connect button in one row - Select dropdowns: `variant="soft"` - Status indicator: `Badge` with connected/disconnected state - Mic level meter: uses `--primary` as bar gradient ### AudioVisualizer (`features/live/components/AudioVisualizer.tsx`) **Changes:** - Read `--primary` CSS variable at paint time instead of hardcoded `#23a1eb` - ```ts const primaryColor = getComputedStyle(document.documentElement) .getPropertyValue('--primary').trim(); ``` - Bar color adapts to theme (lighter in dark mode) - Responsive height: `h-32 md:h-40` ### ActiveSpeakers (`features/live/components/ActiveSpeakers.tsx`) **Changes:** - Replace hardcoded `text-emerald-700` with `text-success` - Stacked avatar + name + speaking indicator dot - Speaking: `bg-success` dot with pulse animation - Idle: `bg-outline` dot - Empty state: "No active speakers" with `EmptyState` component ### RecordingsSubPanel (`features/live/components/RecordingsSubPanel.tsx`) **Changes:** - Replace `bg-white` → `bg-surface` (CSS var) - Replace `border-sky-200` → `border-outline-variant` - Recording item layout: icon + filename + size + date + action button ### MessagesPanel Search & Filters **Changes:** - Search input: `variant="soft"`, debounced input (300ms) - Filter pills: `Tabs variant="pills"` with `[All] [Clean] [Flagged] [Error] [Pending]` - Stat badges row at top: uses `Badge` with semantic variants (not inline hardcoded spans) --- ## Layer 4: Polish & Micro-interactions ### Entry Animations | Element | Animation | Timing | Source | |---------|-----------|--------|--------| | Page/panel enter | `fadeSlideUp` | 0.5s, [0.25,0.46,0.45,0.94] | `useFramerStagger` | | Card grid enter | `cardStagger` + `cardItem` | stagger 80ms, item 400ms | `useFramerStagger` | | List items enter | `cardStagger` + `cardItem` | stagger 50ms, item 300ms | `useFramerStagger` | | Modal/dialog enter | `scaleIn` | 0.3s, backOut spring | `useFramerStagger` | | Toast enter | slideInRight | 0.3s ease-out | Inline | | Theme switch | CSS transition | 200ms background, 150ms color | CSS global | | Sidebar collapse/expand | Spring physics | stiffness 300, damping 30 | Inline (existing) | ### Loading States All skeleton components use consistent `animate-shimmer` with `1.5s ease-in-out` (CSS-canonical): | Component | Skeleton Pattern | Width/Height | |-----------|-----------------|--------------| | MessageCard | Card shape: 3 lines + header blob | Full card | | StatCard | Rectangular blob + icon circle | h-24 | | User list item | Avatar circle + 2 text lines | h-14 | | Detail view | Sidebar + header + content blobs | Full view | ### Empty States | Context | Title | Description | Action | |---------|-------|-------------|--------| | Messages (no data) | "Belum Ada Pesan" | "Tunggu aktivitas di server. Pesan akan muncul secara real-time." | — | | Messages (no search results) | "Tidak Ditemukan" | "Tidak ada pesan yang cocok dengan pencarianmu." | [Clear Search] | | Voice (disconnected) | "Belum Connect" | "Pilih guild dan channel voice, lalu klik Connect." | — | | Voice (no speakers) | "Sunyi Senyap" | "Belum ada yang speak di channel ini." | — | | Dashboard (no data) | "Data Belum Siap" | "Data guild akan muncul setelah gateway aktif." | [Refresh] | | Dashboard (no users) | "Belum Ada Pengguna" | "Data pengguna akan terkumpul seiring aktivitas server." | — | | Recordings (empty) | "Belum Ada Rekaman" | "Rekaman voice akan muncul setelah ada sesi voice." | — | ### Toast Notification System - **Position**: `fixed top-4 right-4 z-40` (not bottom — avoids MobileTabBar collision) - **Stack**: gap-2, newest at top, max 5 visible - **Types**: `info`, `success`, `error`, `warning` — all using semantic CSS vars - **Auto-dismiss**: 4s (existing), with close button - **Entry**: slide from right - **Exit**: fade out + slide right - **Grouping**: consecutive same-type toasts from same source merge into one ### Theme Toggle - **Location**: Header (right side, near status badges) - **Icon**: Sun/Moon from Lucide, animated (rotate + scale on switch) - **Default**: Follow `prefers-color-scheme` media query - **Persistence**: `localStorage.setItem('theme', 'light' | 'dark' | 'system')` - **DOM attribute**: `document.documentElement.dataset.theme` - **Transition**: CSS `transition` on `body` and card surfaces for smooth cross-fade ### Mascot Chatbot - **Toggle**: Sidebar bottom button (existing) — playful bounce on click - **Panel**: Floating card (320px wide) anchored to sidebar, not `z-[9999]` - **Chat bubble**: Rounded-xl with primary-soft background, subtle shadow - **Input**: `variant="soft"`, compact, with send icon - **Empty state**: Mascot greeting in IMPHNEN voice: "Hai! Ada yang bisa dibantu?" - **Error state**: "Wah, lagi error nih. Coba lagi ya!" - **Reduced motion**: Chatbot itself has no animation, only fade mount ### Hover & Active States | Element | Hover | Active | Transition | |---------|-------|--------|------------| | Sidebar nav item | `bg-primary-soft/50` | `bg-primary-soft` | 200ms | | Card | `shadow-md border-primary/20` | — | 300ms | | Button | Brightness/shift | `scale-[0.97]` | 150ms | | List item | `bg-surface-container-high` | — | 150ms | | Badge | Cursor pointer (if clickable) | — | — | | Input | `border-hover` | `border-primary + ring` | 150ms | --- ## Spec Self-Review ### Placeholder Scan - All sections above are complete with specific values (hex codes, component shapes, animation timings). No "TBD" or "TODO" remains. ### Internal Consistency - Layer 0 (theme tokens) feeds directly into Layer 1 (components via CSS vars), Layer 2 (layout) uses same tokens. No contradiction. - Dark palette maintains IMPHNEN's primary/secondary/tertiary distinction — secondary stays social-platform, tertiary stays Discord-specific. - Z-index scale (z-10 through z-100) resolves existing collision between Toast (z-50) and MobileTabBar (z-50). - Animation timings across layers: entry animations (0.3–0.5s), hover (150–200ms), theme switch (150–200ms) — all within IMPHNEN's "snappy" < 300ms promise. ### Scope Check - Focused on visual redesign and brand cohesion of the existing React frontend. - No changes to backend, API, database, or WebSocket protocol. - No new features — all existing functionality is preserved, only the visual layer is redesigned. - Scope is appropriate for a single implementation plan (see below). ### Ambiguity Check - All design decisions are explicit: hex values, component variants, animation timings, layout measurements. - The "ParticleBackground" behavior on mobile is specifically defined (skipped). - Dark mode fallback is explicitly defined (system preference → manual toggle). --- ## Migration Sequence The redesign will be implemented in order (Layer 0 → 4), with testing at each layer before proceeding: 1. **Layer 0** — Theme tokens, dark mode palette, CSS variable migration, fix shimmer mismatch, fix z-index 2. **Layer 1** — Badge (fix colors), Toast (rewrite), Skeleton (fix shimmer), new primitives (Switch, Dialog, EmptyState, IconButton) 3. **Layer 2** — Sidebar (expanded default), Header (simplified brand bar), Tab Strip (new), Mobile layout (overhaul), Particle (lazy) 4. **Layer 3** — MessageCard (refine), Dashboard (stat/User/Channel components), Live panels (AudioVisualizer, Speakers, Recordings) 5. **Layer 4** — Entry animations audit, empty/loading states, theme toggle, toast positioning, mascot polish, hover state consistency Each layer is tested in both light and dark modes before proceeding to the next. --- ## Appendix: Feature Panel Map | File | Layer | Priority | Changes | |------|-------|----------|---------| | `tailwind.config.js` | 0 | P0 | Remove duplicate tokens, keep only what `@theme` can't cover | | `styles.css` | 0 | P0 | Add dark mode `:root` overrides, make canonical | | `DESIGN_TOKENS.md` | 0 | P0 | Update to reflect changes, remove resolved issues | | `shared/ui/badge.tsx` | 1 | P0 | Fix hardcoded success/warning colors | | `shared/ui/toast.tsx` | 1 | P0 | Rewrite with CSS vars, fix z-index, change position | | `shared/ui/skeleton.tsx` | 1 | P0 | Fix shimmer duration, add variants | | `shared/ui/button.tsx` | 1 | P1 | Add tertiary variant, icon-sm size | | `shared/ui/card.tsx` | 1 | P1 | Add elevated/bordered variants | | `shared/ui/input.tsx` | 1 | P1 | Add soft variant | | `shared/ui/empty-state.tsx` | 1 | P0 | New component | | `shared/ui/switch.tsx` | 1 | P2 | New component | | `shared/ui/dialog.tsx` | 1 | P2 | New component | | `shared/ui/icon-button.tsx` | 1 | P1 | New component | | `widgets/Sidebar.tsx` | 2 | P0 | Expanded default, better brand display | | `widgets/Header.tsx` | 2 | P0 | Simplified brand bar, theme toggle | | `widgets/DashboardLayout.tsx` | 2 | P0 | Add TabStrip, adjust layout | | `widgets/TabStrip.tsx` | 2 | P0 | New component | | `widgets/MobileTabBar.tsx` | 2 | P1 | Refine bottom nav, add safe area | | `widgets/ParticleBackground.tsx` | 2 | P1 | Lazy render, mobile skip, CSS var colors | | `features/messages/components/MessageCard.tsx` | 3 | P0 | Fix all hardcoded colors, use Badge | | `features/messages/components/MessagesPanel.tsx` | 3 | P0 | Fix hardcoded stat badges | | `features/messages/components/ImageGrid.tsx` | 3 | P1 | Fix hardcoded colors | | `features/dashboard/components/DashboardStats.tsx` | 3 | P0 | Semantic color cleanup | | `features/dashboard/components/UserSummaryList.tsx` | 3 | P0 | Fix hardcoded colors | | `features/dashboard/components/UserProfileDetail.tsx` | 3 | P0 | Fix hardcoded colors | | `features/dashboard/components/ChannelProfileDetail.tsx` | 3 | P1 | Fix hardcoded colors | | `features/live/components/AudioVisualizer.tsx` | 3 | P0 | Read CSS var, not hardcoded hex | | `features/live/components/ActiveSpeakers.tsx` | 3 | P1 | Fix hardcoded emerald | | `features/live/components/RecordingsSubPanel.tsx` | 3 | P1 | Fix bg-white, fix sky-200 | | `features/live/components/VoiceConnectionCard.tsx` | 3 | P2 | Compact layout | | `features/auth/index.tsx` | 2 | P1 | Use EmptyState, consistent spacing | | `App.tsx` | 2 | P0 | Add TabStrip state, theme toggle | | All feature hooks | 3 | P2 | No visual changes |