From 7cfc7ccf07e0cc39861359d4e59119e5f0c403a5 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Thu, 2 Jul 2026 05:42:40 +0700 Subject: [PATCH] docs: add implementation plan for frontend redesign 21-task plan covering 5 layers: - Foundation (theme engine, dark mode, animations) - Components (Badge, Toast, Skeleton, EmptyState, variants) - Layout (TabStrip, Sidebar, Header, Mobile, DashboardLayout) - Features (Messages, Dashboard, Live panel cleanup) - Polish (empty states, theme toggle, animation audit) --- .../2026-07-02-bete-frontend-redesign-plan.md | 1375 +++++++++++++++++ 1 file changed, 1375 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-02-bete-frontend-redesign-plan.md diff --git a/docs/superpowers/plans/2026-07-02-bete-frontend-redesign-plan.md b/docs/superpowers/plans/2026-07-02-bete-frontend-redesign-plan.md new file mode 100644 index 0000000..9c6617e --- /dev/null +++ b/docs/superpowers/plans/2026-07-02-bete-frontend-redesign-plan.md @@ -0,0 +1,1375 @@ +# Bete Frontend Redesign Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Full visual redesign of Bete frontend to deeply integrate IMPHNEN design system, add dark mode, fix hardcoded colors, and polish every component. + +**Architecture:** 5-layer incremental approach. Foundation (CSS vars + dark mode) first, then shared components, then layout, then feature components, then polish. Each layer is tested before the next. No new features — visual and structural changes only. + +**Tech Stack:** React 19, Vite 8, Tailwind 4, Framer Motion, Radix UI, Lucide Icons + +**Design Spec:** `docs/superpowers/specs/2026-07-02-bete-frontend-redesign-design.md` + +## Global Constraints + +- All components use CSS custom properties, not raw Tailwind utility colors. No `emerald-*`, `amber-*`, `red-*`, `blue-*` etc. Only `--success`, `--warning`, `--destructive`, `--primary`, etc. +- Dark mode: all components must render correctly in both `data-theme="light"` and `data-theme="dark"` +- No new features — preserve all existing functionality exactly +- Animation durations under 300ms for interactive elements, under 500ms for entry animations +- Z-index follows the registry: Header(10), Sidebar(20), TabStrip(30), Toast(40), MobileNav(50), Mascot(60), Modal(70), Overlay(100) +- Poppins monofamily throughout — no other typefaces +- Scroll areas use custom scrollbars styled with `--border` and `--primary` + +--- + +## Task Map + +``` +Layer 0 ── T01: Theme engine + dark mode palette (styles.css) + T02: Fix animation keyframes (shimmer, glowPulse) + T03: Update DESIGN_TOKENS.md specs + T04: Create theme context + toggle hook + +Layer 1 ── T05: Badge (fix success/warning colors) + T06: Toast (rewrite with CSS vars + z-index) + T07: Skeleton (fix shimmer duration + variants) + T08: EmptyState component (new) + T09: Button/Card/Input minor variants + +Layer 2 ── T10: TabStrip component (new) + T11: Sidebar (expanded default, brand section) + T12: Header (simplified brand bar + theme toggle UI) + T13: MobileTabBar (overhaul) + T14: DashboardLayout integrate all layout changes + T15: ParticleBackground (lazy + mobile skip) + +Layer 3 ── T16: MessageCard + MessagesPanel color cleanup + T17: DashboardStats + User/Channel components cleanup + T18: Live panel components cleanup (AudioVisualizer, Speakers, Recordings) + +Layer 4 ── T19: Empty states integration across all panels + T20: Theme toggle final integration + CSS transition + T21: Hover state consistency + entry animation audit +``` + +--- + +### Task 1: Theme Engine & Dark Mode Palette + +**Files:** +- Modify: `services/frontend/src/styles.css` — add dark mode `:root` overrides, restructure `@theme` block + +**Interfaces:** +- Produces: CSS custom properties at `:root` (light) and `[data-theme="dark"]` (dark) levels +- Produces: Updated `@theme` block referencing CSS vars + +- [ ] **Step 1: Add dark mode CSS variables after existing `:root`** + +Add to `styles.css` after the `:root` block: + +```css +[data-theme="dark"] { + --background: #1c1c1f; + --foreground: #f0f0f2; + --card: #1c1c1f; + --card-foreground: #f0f0f2; + --muted: #141417; + --muted-foreground: #a0a0a6; + --accent: #26262a; + --accent-foreground: #f0f0f2; + --popover: #1c1c1f; + --popover-foreground: #f0f0f2; + + --primary: #54a2ff; + --primary-foreground: #0d0d0f; + --primary-soft: #18263a; + --primary-hover: #3d8ee8; + --primary-active: #2a7ad4; + + --secondary: #4a8ef5; + --secondary-foreground: #0d0d0f; + --secondary-soft: #1a274a; + + --tertiary: #7984f5; + --tertiary-foreground: #0d0d0f; + --tertiary-soft: #20266a; + + --success: #34d399; + --success-soft: #13261a; + --warning: #fbbf24; + --warning-soft: #261a10; + --destructive: #f87171; + --destructive-soft: #2a1418; + --info: #60a5fa; + --info-soft: #141e38; + + --border: #343438; + --border-hover: #48484d; + --input: #343438; + --ring: #54a2ff; + + --outline: #6a6a70; + --outline-variant: #404044; + + --primary-glow: rgba(84, 162, 255, 0.15); + --tertiary-glow: rgba(121, 132, 245, 0.15); + --destructive-glow: rgba(248, 113, 113, 0.15); + --success-glow: rgba(52, 211, 153, 0.15); + + --surface: #1c1c1f; + --surface-dim: #141417; + --surface-bright: #2c2c30; + --on-surface: #f0f0f2; + --on-surface-variant: #a0a0a6; + --inverse-surface: #f0f0f2; + --inverse-on-surface: #1c1c1f; + --surface-container: #26262a; + --surface-container-low: #202023; + --surface-container-high: #2c2c30; + --surface-container-highest: #323236; +} +``` + +- [ ] **Step 2: Restructure `@theme` to reference CSS vars properly** + +Update the existing `@theme` block in `styles.css`. The current approach already works with Tailwind 4 — verify the `@theme` directives reference the CSS custom properties correctly (not hardcoded hex values). + +The existing `@theme` block already uses `--color-primary: #23a1eb` etc. — need to decide if we keep `@theme` with hex values (they're compile-time tokens that reference the CSS vars at runtime) or change them to reference the CSS vars. In Tailwind 4, `@theme` is a compile-time construct. The runtime values come from CSS custom properties. So `@theme` should have the light values as defaults, and `[data-theme="dark"]` overrides the CSS vars at runtime. + +Current pattern: +```css +@theme { + --color-primary: #23a1eb; +} +``` +With dark mode, this is fine — `@theme` defines the utility class `text-primary` → `color: var(--color-primary)`, and the CSS var `--color-primary` changes value under `[data-theme="dark"]`. + +BUT — components that use `bg-primary` will get `var(--color-primary)` which is fine. But components that use `var(--primary)` directly (from custom properties) need the CSS var, not the `@theme` one. + +The safest approach: **Keep `@theme` with light defaults** (it provides the utility classes) and **add `[data-theme="dark"]` CSS vars** that override at the CSS custom property level for both the `@theme` colors AND the raw custom properties. + +- [ ] **Step 3: Add CSS transition for smooth theme switch** + +At the end of the base layer in `styles.css`, add: + +```css +/* Smooth theme transition */ +html.theme-transitioning, +html.theme-transitioning *, +html.theme-transitioning *::before, +html.theme-transitioning *::after { + transition: background-color 200ms ease, + color 150ms ease, + border-color 150ms ease, + box-shadow 200ms ease !important; +} +``` + +Note: We apply this class only during the actual theme switch (via JS) to avoid performance issues with `transition: all`. + +- [ ] **Step 4: Remove `@keyframes glowPulse` from tailwind.config.js if present** + +Check `tailwind.config.js` for `glowPulse` keyframes. If they exist there but not in `styles.css`, remove from config and add to `styles.css`: + +```css +@keyframes glowPulse { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 0.8; } +} +``` + +- [ ] **Step 5: Commit** + +```bash +git add services/frontend/src/styles.css services/frontend/tailwind.config.js +git commit -m "feat(theme): add dark mode palette, CSS var transitions, fix keyframes" +``` + +--- + +### Task 2: Fix Animation Keyframes + +**Files:** +- Modify: `services/frontend/src/styles.css` — add missing keyframes, fix shimmer +- Modify: `services/frontend/tailwind.config.js` — remove `glowPulse` keyframes + +**Interfaces:** +- Consumes: Task 1 (updated styles.css base) +- Produces: Canonical animation keyframes in CSS only + +- [ ] **Step 1: Fix shimmer duration to be consistent** + +In `styles.css`, ensure `.animate-shimmer` has the canonical definition: + +```css +.animate-shimmer { + background: linear-gradient( + 90deg, + rgba(0, 0, 0, 0.06) 0%, + rgba(0, 0, 0, 0.02) 40%, + rgba(0, 0, 0, 0.06) 80%, + rgba(0, 0, 0, 0.08) 100% + ); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} +``` + +(Keep the existing definition — it's already correct at 1.5s.) + +- [ ] **Step 2: Remove duplicate keyframes from tailwind.config.js** + +In `tailwind.config.js`, find and remove any `glowPulse` or `shimmer` keyframes from the `keyframes` object — they should only live in `styles.css`. + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/src/styles.css services/frontend/tailwind.config.js +git commit -m "fix(theme): consolidate animation keyframes in styles.css, remove config duplicates" +``` + +--- + +### Task 3: Update DESIGN_TOKENS.md + +**Files:** +- Modify: `services/frontend/DESIGN_TOKENS.md` — update known issues section, add dark mode notes + +- [ ] **Step 1: Update §13 Known Issues to reflect resolved items** + +Mark as resolved: +- §13.1 Hardcoded utility colors → "Addressing in T05-T19 across all component files" +- §13.4 `glow-pulse` keyframe fragmentation → "Resolved in T02" +- §13.5 `shimmer` duration mismatch → "Resolved in T02" +- §13.6 Z-index scale not formalized → "Resolved in design spec, implementing across layout layers" +- §13.7 Toast container z-index collision → "Resolved in T06" + +- [ ] **Step 2: Add dark mode section** + +Add a new section documenting the dark mode palette and `[data-theme="dark"]` approach. + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/DESIGN_TOKENS.md +git commit -m "docs: update DESIGN_TOKENS.md with dark mode and resolved issues" +``` + +--- + +### Task 4: Create Theme Context & Toggle Hook + +**Files:** +- Create: `services/frontend/src/shared/hooks/useTheme.ts` + +**Interfaces:** +- Produces: `useTheme()` hook returning `{ theme: 'light' | 'dark' | 'system', setTheme, resolvedTheme: 'light' | 'dark', toggle }` +- Produces: Applies `data-theme` attribute to `` + +- [ ] **Step 1: Create `useTheme` hook** + +```tsx +/* ─── IMPHNEN Theme Hook ────────────────────────────────────────── + * Light / Dark / System theme management with smooth transitions. + * Persists choice to localStorage. Falls back to prefers-color-scheme. + * ─────────────────────────────────────────────────────────────────── */ + +import { useCallback, useEffect, useState } from "react"; + +type Theme = "light" | "dark" | "system"; + +const STORAGE_KEY = "imphnen-theme"; + +function getSystemTheme(): "light" | "dark" { + if (typeof window === "undefined") return "light"; + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; +} + +function applyTheme(resolved: "light" | "dark") { + const root = document.documentElement; + const transitioning = root.classList.contains("theme-transitioning"); + if (!transitioning) { + root.classList.add("theme-transitioning"); + } + root.dataset.theme = resolved; + // Remove transitioning class after the CSS transition completes + if (!transitioning) { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + root.classList.remove("theme-transitioning"); + }); + }); + } +} + +export function useTheme() { + const [theme, setThemeState] = useState(() => { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "light" || stored === "dark" || stored === "system") + return stored; + return "system"; + }); + + const resolvedTheme = theme === "system" ? getSystemTheme() : theme; + + const setTheme = useCallback((t: Theme) => { + setThemeState(t); + localStorage.setItem(STORAGE_KEY, t); + }, []); + + const toggle = useCallback(() => { + setTheme(resolvedTheme === "dark" ? "light" : "dark"); + }, [resolvedTheme, setTheme]); + + // Apply theme on mount and when it changes + useEffect(() => { + applyTheme(resolvedTheme); + }, [resolvedTheme]); + + // Listen for system theme changes (only when set to "system") + useEffect(() => { + if (theme !== "system") return; + const mq = window.matchMedia("(prefers-color-scheme: dark)"); + const handler = () => applyTheme(getSystemTheme()); + mq.addEventListener("change", handler); + return () => mq.removeEventListener("change", handler); + }, [theme]); + + return { theme, setTheme, resolvedTheme, toggle }; +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add services/frontend/src/shared/hooks/useTheme.ts +git commit -m "feat(theme): create useTheme hook with light/dark/system support" +``` + +--- + +### Task 5: Badge — Fix Success/Warning Colors + +**Files:** +- Modify: `services/frontend/src/shared/ui/badge.tsx` + +**Interfaces:** +- Consumes: Task 1 (--success, --warning, --destructive CSS vars) +- Produces: Badge variants that all use CSS vars, no hardcoded Tailwind colors + +- [ ] **Step 1: Read current badge.tsx** + +- [ ] **Step 2: Replace hardcoded color variants** + +Find: +```tsx +success: "bg-emerald-100 text-emerald-700", +warning: "bg-amber-100 text-amber-700", +``` + +Replace with: +```tsx +success: "bg-success-soft text-success", +warning: "bg-warning-soft text-warning", +``` + +Also verify all other variants (default, secondary, destructive, outline) already use CSS vars. The existing `variantStyles` in badge.tsx likely has these patterns already or similar. If `outline` uses raw `border-border`, that's fine since `--border` is a CSS var. + +Also check: the `Badge` component currently maps `success` and `warning` to classes. We need to make sure `bg-success-soft` and `text-success` exist as Tailwind utilities (they should via `@theme` in `styles.css`). + +Verify `styles.css` has: +```css +@theme { + --color-success: #22c55e; + --color-success-soft: #dcfce7; + --color-warning: #f59e0b; + --color-warning-soft: #fef3c7; +} +``` + +If not, add them to the `@theme` block. The dark-mode overrides in `[data-theme="dark"]` should also have the dark versions of these. (Already defined in Task 1.) + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/src/shared/ui/badge.tsx services/frontend/src/styles.css +git commit -m "fix(badge): replace hardcoded emerald/amber colors with CSS vars" +``` + +--- + +### Task 6: Toast — Rewrite with CSS Vars & Z-Index Fix + +**Files:** +- Modify: `services/frontend/src/shared/ui/toast.tsx` + +**Interfaces:** +- Consumes: Task 1 (CSS vars), Task 5 (Badge pattern) +- Produces: Toast with all semantic colors from CSS vars, z-index 40, positioned top-right + +- [ ] **Step 1: Read current toast.tsx** + +- [ ] **Step 2: Replace hardcoded color values** + +Find patterns like: +```tsx +case "success": return "border-l-emerald-500"; +case "warning": return "border-l-amber-500"; +// and +case "success": return "text-emerald-500"; +case "warning": return "text-amber-500"; +``` + +Replace with: +```tsx +case "success": return "border-l-success text-success"; +case "warning": return "border-l-warning text-warning"; +``` + +- [ ] **Step 3: Fix z-index from z-50 to z-40** + +Find: +``` +className="fixed bottom-4 right-4 z-50" +``` + +Replace with: +``` +className="fixed top-4 right-4 z-40" +``` + +(Change position from bottom to top to avoid MobileTabBar collision.) + +- [ ] **Step 4: Commit** + +```bash +git add services/frontend/src/shared/ui/toast.tsx +git commit -m "fix(toast): replace hardcoded colors with CSS vars, fix z-index, reposition top-right" +``` + +--- + +### Task 7: Skeleton — Fix Shimmer Duration & Add Variants + +**Files:** +- Modify: `services/frontend/src/shared/ui/skeleton.tsx` + +**Interfaces:** +- Consumes: Task 2 (canonical shimmer keyframe at 1.5s) +- Produces: Skeleton component with consistent animation + shape variants + +- [ ] **Step 1: Read current skeleton.tsx** + +- [ ] **Step 2: Add variant prop** + +Current `Skeleton` is probably just a div with `rounded-lg bg-muted animate-shimmer`. Add a `variant` prop: + +```tsx +type SkeletonVariant = "rounded" | "circular" | "rectangular"; + +interface SkeletonProps extends React.HTMLAttributes { + variant?: SkeletonVariant; +} + +const variantClasses: Record = { + rounded: "rounded-lg", + circular: "rounded-full", + rectangular: "rounded-none", +}; + +export function Skeleton({ + variant = "rounded", + className, + ...props +}: SkeletonProps) { + return ( +
+ ); +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/src/shared/ui/skeleton.tsx +git commit -m "feat(skeleton): add variant prop (rounded/circular/rectangular), consistent shimmer" +``` + +--- + +### Task 8: EmptyState Component + +**Files:** +- Create: `services/frontend/src/shared/ui/empty-state.tsx` +- Modify: `services/frontend/src/shared/ui/index.ts` — add export + +**Interfaces:** +- Produces: `` component + +- [ ] **Step 1: Create empty-state.tsx** + +```tsx +/* ─── IMPHNEN EmptyState Component ────────────────────────────── + * Standard empty/error/loading state with optional mascot icon + * and CTA action. Used across all feature panels. + * ─────────────────────────────────────────────────────────────────── */ + +import { motion } from "framer-motion"; +import { type LucideIcon, Inbox } from "lucide-react"; +import type { ReactNode } from "react"; +import { cn } from "../lib/utils"; +import { fadeSlideUp } from "../hooks/useFramerStagger"; + +interface EmptyStateProps { + icon?: LucideIcon; + title?: string; + description?: string; + action?: ReactNode; + className?: string; + compact?: boolean; +} + +export function EmptyState({ + icon: Icon = Inbox, + title, + description, + action, + className, + compact = false, +}: EmptyStateProps) { + return ( + +
+ +
+ {title && ( +

{title}

+ )} + {description && ( +

+ {description} +

+ )} + {action && ( +
{action}
+ )} +
+ ); +} +``` + +- [ ] **Step 2: Add export to shared/ui/index.ts** + +Find the exports file and add: +```ts +export { EmptyState } from "./empty-state.js"; +``` + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/src/shared/ui/empty-state.tsx services/frontend/src/shared/ui/index.ts +git commit -m "feat(ui): add EmptyState component with icon, title, description, and action slot" +``` + +--- + +### Task 9: Button/Card/Input Minor Variants + +**Files:** +- Modify: `services/frontend/src/shared/ui/button.tsx` +- Modify: `services/frontend/src/shared/ui/card.tsx` +- Modify: `services/frontend/src/shared/ui/input.tsx` + +- [ ] **Step 1: Add tertiary variant to button.tsx** + +Find the `variantStyles` object and add: +```tsx +tertiary: "bg-tertiary text-tertiary-foreground shadow-sm hover:bg-tertiary/90", +``` + +And ensure `icon-sm` size exists: +```tsx +const sizeClasses = { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + "icon-sm": "h-8 w-8", // NEW +}; +``` + +- [ ] **Step 2: Add elevated variant to card.tsx** + +Find the Card component and add a `variant` prop: + +```tsx +type CardVariant = "default" | "elevated" | "bordered"; + +const variantClasses: Record = { + default: "shadow-sm hover:shadow-md", + elevated: "shadow-md hover:shadow-lg", + bordered: "shadow-none border-2", +}; + +// Apply to the outer Card div: +className={cn( + "rounded-xl border bg-card text-card-foreground transition-all duration-300", + variantClasses[variant], + className, +)} +``` + +- [ ] **Step 3: Add soft variant to input.tsx** + +Add a `variant` prop: + +```tsx +type InputVariant = "default" | "soft"; + +const variantClasses: Record = { + default: "border border-input bg-background", + soft: "border-transparent bg-muted focus-visible:border-primary", +}; +``` + +- [ ] **Step 4: Commit** + +```bash +git add services/frontend/src/shared/ui/button.tsx services/frontend/src/shared/ui/card.tsx services/frontend/src/shared/ui/input.tsx +git commit -m "feat(ui): add button tertiary variant, card elevated variant, input soft variant" +``` + +--- + +### Task 10: TabStrip Component + +**Files:** +- Create: `services/frontend/src/widgets/TabStrip.tsx` +- Modify: `services/frontend/src/shared/ui/index.ts` — maybe + +**Interfaces:** +- Consumes: `DashboardTab` type from `entities/ui/types.ts` +- Produces: Horizontal tab strip with underline indicators, horizontal scroll on mobile + +- [ ] **Step 1: Create TabStrip.tsx** + +```tsx +/* ═══════════════════════════════════════════════════════════════════════════ + * IMPHNEN TabStrip — Horizontal navigation tabs + * Underline indicator, horizontal scroll on mobile, sticky with z-30. + * ═══════════════════════════════════════════════════════════════════════════ */ + +import { motion } from "framer-motion"; +import { + LayoutDashboard, + MessageSquare, + Radio, + type LucideIcon, +} from "lucide-react"; +import type { DashboardTab } from "../entities/ui/types.js"; +import { cn } from "../shared/lib/utils"; + +interface Tab { + id: DashboardTab; + label: string; + icon: LucideIcon; +} + +const tabs: Tab[] = [ + { id: "messages", label: "Pesan & Moderasi", icon: MessageSquare }, + { id: "live", label: "Voice & Media", icon: Radio }, + { id: "dashboard", label: "Dashboard Guild", icon: LayoutDashboard }, +]; + +interface TabStripProps { + activeTab: DashboardTab; + onTabChange: (tab: DashboardTab) => void; + className?: string; +} + +export function TabStrip({ activeTab, onTabChange, className }: TabStripProps) { + return ( + + ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add services/frontend/src/widgets/TabStrip.tsx +git commit -m "feat(layout): add TabStrip component with spring underline indicator" +``` + +--- + +### Task 11: Sidebar — Expanded Default + +**Files:** +- Modify: `services/frontend/src/widgets/Sidebar.tsx` + +**Interfaces:** +- Consumes: Design spec Layer 2 layout +- Produces: Sidebar w-64 expanded by default, with collapsible toggle, proper brand section + +- [ ] **Step 1: Read current Sidebar.tsx** + +- [ ] **Step 2: Change default collapsed to false** + +Current: `collapsed = true` → Change to `collapsed = false` + +- [ ] **Step 3: Update collapsed check from `w-16` to include toggle button** + +The current component has `collapsed ? "w-16" : "w-64"`. Since we want expanded by default, we should: +- Keep the `collapsed` prop but default to `false` +- Add a toggle button (hamburger icon) at the top of the sidebar +- Ensure the brand section always shows + +If the component is used in `DashboardLayout`, update the usage there if `collapsed` is hardcoded. Check `DashboardLayout.tsx` for ``. + +In `Sidebar.tsx`: +- Change default: `collapsed = false` +- Keep all existing structure but ensure the brand section (logo, gradient text, subtitle) is always visible since we're expanded by default +- Mascot image stays visible +- Add a collapse toggle button (optional, could add later) + +- [ ] **Step 4: Commit** + +```bash +git add services/frontend/src/widgets/Sidebar.tsx +git commit -m "feat(sidebar): expanded by default (w-64), brand section always visible" +``` + +--- + +### Task 12: Header — Simplified Brand Bar + Theme Toggle UI + +**Files:** +- Modify: `services/frontend/src/widgets/Header.tsx` + +**Interfaces:** +- Consumes: Task 4 (useTheme hook) +- Produces: Compact header with logo + status badges + theme toggle + +- [ ] **Step 1: Read current Header.tsx** + +- [ ] **Step 2: Simplify the header layout** + +Current header has: +- Left: logo + brand gradient + page title + subtitle + moderation badge +- Right: guild badge + WS status + voice status + +New header should be: +- Left: Logo + "IMPHNEN" + "· Guild Watcher" +- Right: Status badges (WS, Voice) + Theme toggle (sun/moon) + Notification bell + +Move page title + subtitle to the content area (they're already handled by the panel components themselves in most cases — DashboardPanel has its own heading). + +```tsx +// Simplified Header +
+
+ {/* Left: Brand */} +
+ IMPHNEN +
+

+ IMPHNEN + · + Guild Watcher +

+
+
+ + {/* Right: Status + Actions */} +
+ + + + {/* Theme Toggle */} + +
+
+
+``` + +Where `ThemeToggle` is: + +```tsx +function ThemeToggle() { + const { resolvedTheme, toggle } = useTheme(); + return ( + + ); +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/src/widgets/Header.tsx +git commit -m "feat(header): simplify brand bar, add theme toggle button" +``` + +--- + +### Task 13: MobileTabBar Overhaul + +**Files:** +- Modify: `services/frontend/src/shared/ui/MobileTabBar.tsx` + +**Interfaces:** +- Consumes: DashboardTab type +- Produces: Bottom nav with 3 tabs + mascot icon, safe area padding, active dot indicator + +- [ ] **Step 1: Read current MobileTabBar.tsx** + +- [ ] **Step 2: Update to match design spec** + +Current MobileTabBar probably has simple icon + label layout. Enhance with: +- Active tab dot indicator (small dot above active icon) +- Brand logo subtle watermark background +- Safe area padding: `pb-4 md:pb-0` (or `pb-safe`) +- Glass background: `bg-surface/80 backdrop-blur-lg` +- Z-index: `z-50` (stays at mobile nav level) + +```tsx +// Enhanced MobileTabBar +
+
+ {tabs.map((tab) => { + const Icon = tab.icon; + const isActive = activeTab === tab.id; + return ( + + ); + })} +
+
+``` + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/src/shared/ui/MobileTabBar.tsx +git commit -m "feat(mobile): enhance MobileTabBar with dot indicator, safe area, glass bg" +``` + +--- + +### Task 14: DashboardLayout — Integrate All Layout Changes + +**Files:** +- Modify: `services/frontend/src/widgets/DashboardLayout.tsx` +- Modify: `services/frontend/src/App.tsx` + +**Interfaces:** +- Consumes: Tasks 10-13 (TabStrip, Sidebar, Header, MobileTabBar) +- Produces: Full integrated layout with all new components + +- [ ] **Step 1: Read current DashboardLayout.tsx** + +- [ ] **Step 2: Integrate TabStrip** + +Add `` between Header and content area. + +The current layout is: +``` +Sidebar | Header + Content +``` + +New layout: +``` +Sidebar | Header (brand bar) + | TabStrip (tabs) + | Content (page) +``` + +- [ ] **Step 3: Read App.tsx and check layout integration** + +In `App.tsx`, the layout is constructed. Verify that: +1. The `collapsed` prop for Sidebar is not hardcoded (should default to false now) +2. TabStrip gets the correct `activeTab` and `onTabChange` +3. Theme toggle hook is integrated at the App level (the `useTheme` hook sets `data-theme` on ``, so it just needs to be called somewhere in the component tree) + +Add the theme hook call at the App level: +```tsx +import { useTheme } from "./shared/hooks/useTheme"; +// In App component: +useTheme(); // This sets data-theme attribute and listens for system changes +``` + +- [ ] **Step 4: Commit** + +```bash +git add services/frontend/src/widgets/DashboardLayout.tsx services/frontend/src/App.tsx +git commit -m "feat(layout): integrate TabStrip, Sidebar expanded default, theme hook in App" +``` + +--- + +### Task 15: ParticleBackground — Lazy + Mobile Skip + +**Files:** +- Modify: `services/frontend/src/widgets/particles/ParticleBackground.tsx` + +**Interfaces:** +- Produces: RequestAnimationFrame-based lazy render, skip on mobile/reduced-motion + +- [ ] **Step 1: Read current ParticleBackground.tsx** + +- [ ] **Step 2: Add conditional rendering** + +Add at the top of the component: + +```tsx +const [reducedMotion, setReducedMotion] = useState(true); // Default safe +const [isMobile, setIsMobile] = useState(true); +const [shouldRender, setShouldRender] = useState(false); + +useEffect(() => { + const mqReduced = window.matchMedia("(prefers-reduced-motion: reduce)"); + setReducedMotion(mqReduced.matches); + const mqMobile = window.matchMedia("(max-width: 768px)"); + setIsMobile(mqMobile.matches); + + const handleReducedChange = (e: MediaQueryListEvent) => setReducedMotion(e.matches); + const handleMobileChange = (e: MediaQueryListEvent) => setIsMobile(e.matches); + + mqReduced.addEventListener("change", handleReducedChange); + mqMobile.addEventListener("change", handleMobileChange); + + // Defer actual render by one frame to not block initial paint + requestAnimationFrame(() => setShouldRender(true)); + + return () => { + mqReduced.removeEventListener("change", handleReducedChange); + mqMobile.removeEventListener("change", handleMobileChange); + }; +}, []); +``` + +If `reducedMotion || isMobile || !shouldRender`, return `null`. + +- [ ] **Step 3: Update CSS variable references** + +Find any hardcoded hex colors in the Three.js code and replace with CSS variable reads. For example, if there's: + +```tsx +const color = new THREE.Color("#23a1eb"); +``` + +Replace with: +```tsx +const root = getComputedStyle(document.documentElement); +const primaryColor = root.getPropertyValue("--primary").trim(); +const color = new THREE.Color(primaryColor || "#23a1eb"); +``` + +- [ ] **Step 4: Commit** + +```bash +git add services/frontend/src/widgets/particles/ParticleBackground.tsx +git commit -m "perf(particles): lazy render, skip on mobile/reduced-motion, CSS var colors" +``` + +--- + +### Task 16: MessageCard + MessagesPanel Color Cleanup + +**Files:** +- Modify: `services/frontend/src/features/messages/components/MessageCard.tsx` +- Modify: `services/frontend/src/features/messages/index.tsx` (MessagesPanel) +- Modify: `services/frontend/src/features/messages/components/ImageGrid.tsx` + +- [ ] **Step 1: Read MessageCard.tsx and identify all hardcoded colors** + +Search for: `emerald`, `amber`, `red`, `orange`, `yellow`, `blue`, `pink`, `purple`, `sky`, `cyan`, `violet` — these are all hardcoded utility colors. + +- [ ] **Step 2: Replace MessageCard.tsx hardcoded colors** + +Map each instance to its CSS var equivalent: + +| Hardcoded | CSS Var Replacement | +|-----------|-------------------| +| `bg-red-100 text-red-700 border-red-200` | `bg-destructive-soft text-destructive border-destructive/20` | +| `bg-orange-100 text-orange-700 border-orange-200` | `bg-warning-soft text-warning border-warning/20` | +| `bg-yellow-100 text-yellow-700 border-yellow-200` | `bg-warning-soft text-warning border-warning/20` | +| `bg-blue-100 text-blue-700 border-blue-200` | `bg-info-soft text-info border-info/20` | +| `bg-emerald-50/40` → any | `bg-success/5` | +| `border-l-emerald-400` | `border-l-success` | +| `bg-pink-50/40` → any | `bg-tertiary/5` | +| `border-l-pink-400` | `border-l-tertiary` | +| `text-pink-600` | `text-tertiary` | +| `text-pink-600/70` | `text-tertiary/70` | +| `text-emerald-600` | `text-success` | + +- [ ] **Step 3: Replace MessagesPanel hardcoded stats badges** + +Replace: +```tsx +// Stat badges showing counts +"bg-emerald-100 text-emerald-700 border-emerald-200" → "bg-success-soft text-success border-success/20" +"bg-orange-100 text-orange-700 border-orange-200" → "bg-warning-soft text-warning border-warning/20" +"bg-red-100 text-red-700 border-red-200" → "bg-destructive-soft text-destructive border-destructive/20" +"text-emerald-600" → "text-success" +``` + +- [ ] **Step 4: Replace ImageGrid.tsx hardcoded colors** + +Replace: +```tsx +"bg-purple-100 text-purple-700 border-purple-200" → "bg-tertiary-soft text-tertiary border-tertiary/20" +``` + +- [ ] **Step 5: Commit** + +```bash +git add services/frontend/src/features/messages/ +git commit -m "fix(messages): replace all hardcoded colors with CSS vars in MessageCard, MessagesPanel, ImageGrid" +``` + +--- + +### Task 17: Dashboard + User/Channel Components Color Cleanup + +**Files:** +- Modify: `services/frontend/src/features/dashboard/components/DashboardStats.tsx` +- Modify: `services/frontend/src/features/dashboard/components/UserSummaryList.tsx` +- Modify: `services/frontend/src/features/dashboard/components/UserProfileDetail.tsx` +- Modify: `services/frontend/src/features/dashboard/components/ChannelProfileDetail.tsx` + +- [ ] **Step 1: Read each file and identify hardcoded colors** + +Search for: `emerald`, `amber`, `red`, `blue`, `violet`, `cyan`, `sky`, `orange`, `yellow`, `purple`, `pink`, `green` + +- [ ] **Step 2: Replace DashboardStats.tsx** + +The stat cards show counts for clean/flagged/error/total. Replace: +- `text-emerald-500` → `text-success` +- `bg-emerald-100` → `bg-success-soft` +- `text-blue-500` → `text-primary` or `text-info` +- `bg-blue-100` → `bg-primary-soft` or `bg-info-soft` +- `text-violet-500` → `text-tertiary` +- `bg-violet-100` → `bg-tertiary-soft` +- `text-emerald-600` → `text-success` +- `bg-cyan-100` → `bg-info-soft` +- `text-cyan-500` → `text-info` +- `text-amber-500` → `text-warning` +- `bg-amber-100` → `bg-warning-soft` + +Better approach: Create a `StatCard` sub-component that takes a `variant` prop (`primary`, `success`, `warning`, `destructive`) and renders the appropriate semantic colors. + +```tsx +type StatVariant = "primary" | "success" | "warning" | "destructive"; + +const statStyles: Record = { + primary: { bg: "bg-primary-soft", text: "text-primary", iconBg: "bg-primary" }, + success: { bg: "bg-success-soft", text: "text-success", iconBg: "bg-success" }, + warning: { bg: "bg-warning-soft", text: "text-warning", iconBg: "bg-warning" }, + destructive: { bg: "bg-destructive-soft", text: "text-destructive", iconBg: "bg-destructive" }, +}; +``` + +Then use: +```tsx + + +``` + +- [ ] **Step 3: Replace UserSummaryList.tsx** + +Replace: +- `bg-emerald-100 text-emerald-700` → `bg-success-soft text-success` +- `bg-amber-100 text-amber-700` → `bg-warning-soft text-warning` +- `bg-red-100 text-red-700` → `bg-destructive-soft text-destructive` + +These are likely used for AI status badges (clean/flagged/error) on user cards. Use the `Badge` component with appropriate variant instead of inline spans. + +- [ ] **Step 4: Replace UserProfileDetail.tsx** + +Replace same patterns as above. Also: +- `text-emerald-600` → `text-success` + +- [ ] **Step 5: Replace ChannelProfileDetail.tsx** + +Same patterns. + +- [ ] **Step 6: Commit** + +```bash +git add services/frontend/src/features/dashboard/ +git commit -m "fix(dashboard): replace all hardcoded colors with CSS vars, use semantic variant system" +``` + +--- + +### Task 18: Live Panel Components Color Cleanup + +**Files:** +- Modify: `services/frontend/src/features/live/components/AudioVisualizer.tsx` +- Modify: `services/frontend/src/features/live/components/ActiveSpeakers.tsx` +- Modify: `services/frontend/src/features/live/components/RecordingsSubPanel.tsx` + +- [ ] **Step 1: Read AudioVisualizer.tsx** + +Find hardcoded gradient colors: +```tsx +gradient.addColorStop(0, "#23a1eb"); +gradient.addColorStop(1, "#3eb0f2"); +``` + +Replace with CSS variable reads: +```tsx +const root = getComputedStyle(document.documentElement); +const primary = root.getPropertyValue("--primary").trim() || "#23a1eb"; +const lighter = primary + "88"; // semi-transparent + +gradient.addColorStop(0, primary); +gradient.addColorStop(1, lighter); +``` + +- [ ] **Step 2: Read ActiveSpeakers.tsx** + +Replace: +- `text-emerald-700` → `text-success` (for speaking/active indicator) + +- [ ] **Step 3: Read RecordingsSubPanel.tsx** + +Replace: +- `bg-white` → `bg-surface` (CSS var) +- `border-sky-200` → `border-outline-variant` + +- [ ] **Step 4: Commit** + +```bash +git add services/frontend/src/features/live/ +git commit -m "fix(live): replace hardcoded colors with CSS vars in AudioVisualizer, ActiveSpeakers, Recordings" +``` + +--- + +### Task 19: Empty States Integration + +**Files:** +- Modify: `services/frontend/src/features/messages/index.tsx` — use EmptyState for no-search-results +- Modify: `services/frontend/src/features/dashboard/index.tsx` — use EmptyState for user/channel empty +- Modify: `services/frontend/src/features/live/components/ActiveSpeakers.tsx` — use EmptyState +- Modify: `services/frontend/src/features/live/components/RecordingsSubPanel.tsx` — use EmptyState + +- [ ] **Step 1: Replace ad-hoc empty states with EmptyState component** + +For each panel, find patterns like: +```tsx +
+ +

No data to display

+
+``` + +Replace with: +```tsx + +``` + +- [ ] **Step 2: Commit** + +```bash +git add services/frontend/src/features/messages/ services/frontend/src/features/dashboard/ services/frontend/src/features/live/ +git commit -m "feat(ui): integrate EmptyState component across all panels" +``` + +--- + +### Task 20: Theme Toggle Final Integration + +**Files:** +- Modify: `services/frontend/src/widgets/Header.tsx` — ensure theme toggle is wired +- Modify: `services/frontend/src/App.tsx` — ensure `useTheme()` is called at root level + +- [ ] **Step 1: Verify theme toggle is connected** + +In `App.tsx`, add: +```tsx +import { useTheme } from "./shared/hooks/useTheme"; +// ... +export default function App() { + const uiState = useUIState(); // existing + useTheme(); // NEW — activates theme system + // ... +} +``` + +In `Header.tsx`, verify the toggle button exists and calls `toggle()` from `useTheme`. + +- [ ] **Step 2: Add transition to html element during theme switch** + +The `applyTheme` function in `useTheme.ts` already adds `theme-transitioning` class. Verify CSS transitions are effective. + +- [ ] **Step 3: Commit** + +```bash +git add services/frontend/src/App.tsx services/frontend/src/widgets/Header.tsx +git commit -m "fix(theme): integrate theme toggle at App root level, verify transitions" +``` + +--- + +### Task 21: Hover State Consistency + Entry Animation Audit + +**Files:** +- Review and audit all feature panels and widgets + +- [ ] **Step 1: Audit hover states** + +Check: +- Sidebar nav items: `hover:bg-primary-soft/50` +- Cards: shadow increase on hover (already done via `hover:shadow-md`) +- List items: `hover:bg-surface-container-high` (or `hover:bg-accent`) +- Icon buttons: `hover:bg-accent` +- All should use `transition-colors duration-150` or `transition-all duration-200` + +- [ ] **Step 2: Audit entry animations** + +Verify all panels use `cardStagger` / `cardItem` from `useFramerStagger`: +- MessagesPanel — already uses it ✅ +- LivePanel — already uses it ✅ +- DashboardPanel — verify DashboardStatsContent uses it ✅ + +- [ ] **Step 3: Commit** + +```bash +git commit -m "fix(a11y): audit hover states and entry animations for consistency" +``` + +--- + +## Execution Order Summary + +``` +T01 ── styles.css dark mode + theme vars +T02 ── Fix animation keyframes (shimmer, glowPulse) +T03 ── Update DESIGN_TOKENS.md +T04 ── useTheme hook +T05 ── Badge fix +T06 ── Toast rewrite +T07 ── Skeleton variants +T08 ── EmptyState component +T09 ── Button/Card/Input variants +T10 ── TabStrip component +T11 ── Sidebar expanded default +T12 ── Header simplified + theme toggle +T13 ── MobileTabBar overhaul +T14 ── DashboardLayout + App.tsx integration +T15 ── ParticleBackground lazy +T16 ── MessageCard + MessagesPanel +T17 ── Dashboard components cleanup +T18 ── Live panel components cleanup +T19 ── Empty states integration +T20 ── Theme toggle final integration +T21 ── Hover + entry animation audit +``` + +Dependencies: Tasks 1-4 must complete before 5-9 (CSS vars needed). Tasks 5-10 must complete before 10-15 (components needed). Tasks 16-18 depend on 1 (CSS vars) and 8 (EmptyState). Tasks 19-21 are final polish after everything else.