chore: remove React frontend, rename frontend-leptos to frontend
This commit is contained in:
1 parent
4390a3da5e
commit
8166023f91
175 files changed
+22
-9817
No files matched your search
+1
-1
@@ -13,5 +13,5 @@ logs/
|
||||
.moon/docker
|
||||
worktrees/
|
||||
.worktrees/
|
||||
frontend-leptos/frontend/dist/
|
||||
services/frontend/frontend/dist/
|
||||
target/
|
||||
@@ -12,7 +12,7 @@ Built with **pnpm workspace monorepo** with 3 services and 1 shared library:
|
||||
|---------|------|-------------|
|
||||
| `discord-moderation-backend` | `services/backend` | Express HTTP/WS server, REST API, Redis bridge |
|
||||
| `@bete/discord-gateway` | `services/discord-gateway` | Discord client, voice recording, message capture, AI moderation |
|
||||
| `@gmw/frontend` | `services/frontend` | React 19 + Vite + Tailwind web dashboard |
|
||||
| `frontend` | `services/frontend/frontend` | Leptos 0.7 CSR WASM dashboard |
|
||||
| `@bete/shared` | `packages/shared` | Shared types, errors, logger, utilities |
|
||||
|
||||
**Database:** PostgreSQL (Drizzle ORM) — NOT SQLite.
|
||||
@@ -28,7 +28,7 @@ Discord
|
||||
|
|
||||
v
|
||||
discord-gateway ---- Redis ---- backend ---- WebSocket ---- frontend
|
||||
| pub/sub (broadcast) (React)
|
||||
| pub/sub (broadcast) (Leptos WASM)
|
||||
| |
|
||||
| |
|
||||
<------------------+
|
||||
@@ -194,19 +194,18 @@ The core service that connects to Discord using `discord.js-selfbot-v13`.
|
||||
- `src/shared/database/voiceRecordingRepo.ts` — Voice recording queries
|
||||
- `src/shared/discord/clientOptions.ts` — Discord client configuration
|
||||
|
||||
### frontend (`services/frontend`)
|
||||
### frontend (`services/frontend/frontend`)
|
||||
|
||||
React 19 + Vite 8 + Tailwind CSS 4 + TypeScript dashboard.
|
||||
Leptos 0.7 CSR WASM + TypeScript + CSS dashboard.
|
||||
|
||||
**Tech stack:**
|
||||
- React 19 with hooks
|
||||
- Vite 8 (rolldown) for bundling
|
||||
- Tailwind CSS 4 with PostCSS
|
||||
- Three.js + React Three Fiber + Drei for 3D visualizations
|
||||
- GSAP + Framer Motion for animations
|
||||
- Radix UI primitives (ScrollArea, Slot, Tabs)
|
||||
- TanStack React Query for data fetching
|
||||
- Lucide React for icons
|
||||
- Leptos 0.7 CSR (WASM via `#[wasm_bindgen(start)]`)
|
||||
- Trunk for bundling
|
||||
- Plain CSS with design tokens
|
||||
- Canvas 2D via `web-sys` for audio visualization
|
||||
- CSS animations (GSAP/Framer Motion dihapus — unused)
|
||||
- `web-sys` primitives (WebSocket, AudioContext, IntersectionObserver)
|
||||
- `lucide-leptos` 3 for icons
|
||||
|
||||
**Feature structure (entity + feature slices):**
|
||||
- `entities/` — Type exports re-exported from shared API client
|
||||
@@ -389,15 +388,16 @@ pnpm install
|
||||
# Run each service in development mode (separate terminal each)
|
||||
pnpm run dev:backend # Backend on port 3001
|
||||
pnpm run dev:discord-gateway # Discord client + all features
|
||||
pnpm run dev:web # Frontend on Vite dev server
|
||||
pnpm run dev:web # Frontend via trunk serve
|
||||
|
||||
# Build
|
||||
pnpm run build:backend
|
||||
pnpm run build:discord-gateway
|
||||
pnpm run build:web
|
||||
pnpm run build:web # trunk build --release
|
||||
|
||||
# Type checking across all packages
|
||||
pnpm run typecheck
|
||||
# Type checking
|
||||
pnpm run typecheck # Node services (pnpm -r)
|
||||
pnpm run typecheck:web # Leptos frontend (cargo check)
|
||||
|
||||
# Lint (Biome)
|
||||
pnpm run lint
|
||||
|
||||
+3
-2
@@ -7,10 +7,11 @@
|
||||
"scripts": {
|
||||
"dev:backend": "pnpm --filter './services/backend' run dev",
|
||||
"dev:discord-gateway": "pnpm --filter './services/discord-gateway' run dev",
|
||||
"dev:web": "pnpm --filter './services/frontend' run dev",
|
||||
"dev:web": "cd services/frontend/frontend && trunk serve --address 0.0.0.0",
|
||||
"build:web": "cd services/frontend/frontend && trunk build --release",
|
||||
"typecheck:web": "cd services/frontend/frontend && cargo check",
|
||||
"build:backend": "pnpm --filter './services/backend' run build",
|
||||
"build:discord-gateway": "pnpm --filter './services/discord-gateway' run build",
|
||||
"build:web": "pnpm --filter './services/frontend' run build",
|
||||
"typecheck": "pnpm -r run typecheck",
|
||||
"lint": "biome check --diagnostic-level=error .",
|
||||
"format": "biome format --write .",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
packages:
|
||||
- services/frontend
|
||||
- services/backend
|
||||
- services/discord-gateway
|
||||
- packages/shared
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# API/WS endpoints — set these before building
|
||||
VITE_BE_API_URL=http://localhost:3001
|
||||
VITE_BE_WS_URL=ws://localhost:3001/ws
|
||||
@@ -1,5 +1,3 @@
|
||||
# Backend API URL (default: http://localhost:3001)
|
||||
# API/WS endpoints — set these before building
|
||||
VITE_BE_API_URL=http://localhost:3001
|
||||
|
||||
# Backend WebSocket URL (default: ws://localhost:3001)
|
||||
VITE_BE_WS_URL=ws://localhost:3001
|
||||
VITE_BE_WS_URL=ws://localhost:3001/ws
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,753 +0,0 @@
|
||||
# Design Tokens — Bete Frontend
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [CSS Custom Properties (OKLCH)](#1-css-custom-properties-oklch)
|
||||
2. [Z-Index Registry](#2-z-index-registry)
|
||||
3. [Spacing & Layout Patterns](#3-spacing--layout-patterns)
|
||||
4. [Typography](#4-typography)
|
||||
5. [Animation Keyframes](#5-animation-keyframes)
|
||||
6. [Animation Utility Classes](#6-animation-utility-classes)
|
||||
7. [Framer Motion Presets](#7-framer-motion-presets)
|
||||
8. [GSAP Page Transitions](#8-gsap-page-transitions)
|
||||
9. [Card Pattern](#9-card-pattern)
|
||||
10. [Stagger Animation Pattern](#10-stagger-animation-pattern)
|
||||
11. [Component Patterns & Variants](#11-component-patterns--variants)
|
||||
12. [Reduced-Motion Handling](#12-reduced-motion-handling)
|
||||
13. [Known Issues & Technical Debt](#13-known-issues--technical-debt)
|
||||
|
||||
---
|
||||
|
||||
## 1. CSS Custom Properties (OKLCH)
|
||||
|
||||
Defined in `styles.css` on `:root`. All use the OKLCH color space (`lightness chroma hue`). Consumed via `oklch(var(--name))` in CSS and `oklch(var(--name))` in the Tailwind config (`tailwind.config.js`).
|
||||
|
||||
### Surface Colors
|
||||
|
||||
| Variable | OKLCH Value | Description |
|
||||
|------------------------|--------------------------|------------------------------------|
|
||||
| `--background` | `1 0 0` | Page background (white) |
|
||||
| `--foreground` | `0.141 0.005 285.823` | Body text (near-black) |
|
||||
| `--card` | `1 0 0` | Card surface (white) |
|
||||
| `--card-foreground` | `0.141 0.005 285.823` | Card text (near-black) |
|
||||
| `--border` | `0.92 0.004 286.32` | Default border |
|
||||
| `--input` | `0.92 0.004 286.32` | Input border (same as `--border`) |
|
||||
|
||||
### Interaction Colors
|
||||
|
||||
| Variable | OKLCH Value | Description |
|
||||
|------------------------|--------------------------|------------------------------------|
|
||||
| `--primary` | `0.623 0.214 259.815` | Primary accent (blue) |
|
||||
| `--primary-soft` | `0.92 0.04 259.815` | Soft primary background |
|
||||
| `--primary-foreground` | `0.97 0.014 254.604` | Text on primary (near-white) |
|
||||
| `--secondary` | `0.967 0.001 286.375` | Secondary surface (light gray) |
|
||||
| `--secondary-foreground`| `0.21 0.006 285.885` | Text on secondary |
|
||||
| `--muted` | `0.967 0.001 286.375` | Muted surface (same as secondary) |
|
||||
| `--muted-foreground` | `0.552 0.016 285.938` | Muted text (medium gray) |
|
||||
| `--accent` | `0.967 0.001 286.375` | Accent surface |
|
||||
| `--accent-foreground` | `0.21 0.006 285.885` | Text on accent |
|
||||
| `--destructive` | `0.577 0.245 27.325` | Destructive action (reddish) |
|
||||
| `--destructive-foreground`| `0.97 0.014 254.604` | Text on destructive |
|
||||
|
||||
### Semantic / Effect Colors
|
||||
|
||||
| Variable | OKLCH Value | Description |
|
||||
|--------------------------|-------------------------------|--------------------------------------|
|
||||
| `--ring` | `0.623 0.214 259.815` | Focus ring (same as primary) |
|
||||
| `--radius` | `1rem` | Border radius base |
|
||||
| `--primary-glow` | `0.623 0.214 259.815 / 0.15` | Primary glow backdrop-filter orb |
|
||||
| `--accent-glow` | `0.552 0.016 285.938 / 0.15` | Accent glow |
|
||||
| `--card-shadow` | `0.92 0.004 286.32 / 0.3` | Card box-shadow color |
|
||||
|
||||
### Tailwind Config Mapping
|
||||
|
||||
All custom properties are wired into the Tailwind theme under `theme.extend.colors`:
|
||||
|
||||
```js
|
||||
colors: {
|
||||
border: "oklch(var(--border))",
|
||||
input: "oklch(var(--input))",
|
||||
ring: "oklch(var(--ring))",
|
||||
background: "oklch(var(--background))",
|
||||
foreground: "oklch(var(--foreground))",
|
||||
"primary-soft":"oklch(var(--primary-soft))",
|
||||
"primary-glow":"oklch(var(--primary-glow))",
|
||||
"accent-glow": "oklch(var(--accent-glow))",
|
||||
primary: { DEFAULT: "oklch(var(--primary))", foreground: "oklch(var(--primary-foreground))" },
|
||||
secondary: { DEFAULT: "oklch(var(--secondary))", foreground: "oklch(var(--secondary-foreground))" },
|
||||
muted: { DEFAULT: "oklch(var(--muted))", foreground: "oklch(var(--muted-foreground))" },
|
||||
accent: { DEFAULT: "oklch(var(--accent))", foreground: "oklch(var(--accent-foreground))" },
|
||||
destructive: { DEFAULT: "oklch(var(--destructive))", foreground: "oklch(var(--destructive-foreground))" },
|
||||
card: { DEFAULT: "oklch(var(--card))", foreground: "oklch(var(--card-foreground))" },
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)", // 1rem
|
||||
md: "calc(var(--radius) - 2px)", // 0.875rem
|
||||
sm: "calc(var(--radius) - 4px)", // 0.75rem
|
||||
},
|
||||
```
|
||||
|
||||
### Color vs. Variable Hardcoding
|
||||
|
||||
**Prefer CSS variables**. The following are supposed to use CSS vars but are **hardcoded to Tailwind utility colors** — see [Known Issues §13.1](#131-hardcoded-utility-colors--no-css-var).
|
||||
|
||||
---
|
||||
|
||||
## 2. Z-Index Registry
|
||||
|
||||
All stacking contexts in the application, inventoried from `*.tsx` and `*.css` files.
|
||||
|
||||
| Value | Owner | Context / Location |
|
||||
|-----------|--------------------------|-------------------------------------------------|
|
||||
| `-1` | `ParticleBackground.tsx` | Inline `style={{ zIndex: -1 }}` — decorative orbs sit behind all content |
|
||||
| `10` | `Header.tsx` | Class `sticky top-0 z-10` — sticky page header |
|
||||
| `50` | `Sidebar.tsx` line 110 | Class `relative z-50` — mascot chatbot toggle button |
|
||||
| `50` | `MobileTabBar.tsx` | Class `fixed bottom-0 left-0 right-0 z-50` — mobile bottom nav |
|
||||
| `50` | `toast.tsx` | Class `fixed bottom-4 right-4 z-50` — toast notification container |
|
||||
| `[9999]` | `Sidebar.tsx` line 122 | Class `fixed bottom-[170px] left-[80px] z-[9999]` — MascotChatbot floating panel |
|
||||
|
||||
### Stacking Order (bottom to top)
|
||||
|
||||
1. **Layer -1**: Particle background orbs (`ParticleBackground`)
|
||||
2. **Layer 0**: Main page content, sidebar, cards
|
||||
3. **Layer 10**: Sticky header (`Header`)
|
||||
4. **Layer 50**: Toast container, mobile tab bar, mascot toggle button
|
||||
5. **Layer 9999**: Mascot chatbot floating panel
|
||||
|
||||
**Note**: There is no established z-index scale. Values 20, 30, 40 are unused. The arbitrary `z-[9999]` for the chatbot is an outlier — future additions should use a defined scale (e.g., 10/20/30/40/50/100) rather than arbitrary large numbers.
|
||||
|
||||
---
|
||||
|
||||
## 3. Spacing & Layout Patterns
|
||||
|
||||
### Page Layout
|
||||
|
||||
```
|
||||
DashboardLayout
|
||||
├── ParticleBackground (fixed inset-0)
|
||||
├── grid-pattern overlay (fixed inset-0, pointer-events-none)
|
||||
│
|
||||
└── flex container (relative flex min-h-screen)
|
||||
├── Sidebar (shrink-0, w-16 collapsed / w-64 expanded, hidden on <md)
|
||||
│
|
||||
└── main (flex-1, min-w-0)
|
||||
├── Header (sticky top-0 z-10, px-4 md:px-8)
|
||||
│ └── flex items-center justify-between
|
||||
│ ├── h1 + subtitle (left)
|
||||
│ └── WS/Voice badges (right)
|
||||
│
|
||||
│ (under md) MobileTabBar (fixed bottom-0 z-50)
|
||||
│
|
||||
└── main content (flex-1 overflow-auto, p-4 md:p-6 lg:p-8)
|
||||
```
|
||||
|
||||
### Common Padding Values
|
||||
|
||||
| Context | Class Pattern | Notes |
|
||||
|------------------|-----------------------------|------------------------------|
|
||||
| Card container | `p-6` | CardContent / CardHeader |
|
||||
| Card content | `p-6 pt-0` (or `p-4`) | `pt-0` when following header |
|
||||
| Card footer | `p-6 pt-0` + `flex` | |
|
||||
| Page content | `p-4 md:p-6 lg:p-8` | Responsive scaling |
|
||||
| Header | `px-4 py-4 md:px-8` | |
|
||||
| Sidebar | `px-2` (nav items) | |
|
||||
| Sub-panels | `p-4` | Music, Screen, etc. |
|
||||
| Mascot chat | `p-4` (messages), `p-3` (input) | |
|
||||
|
||||
### Common Gap Values
|
||||
|
||||
| Pattern | Usage |
|
||||
|----------------|--------------------------------------------|
|
||||
| `gap-3` | Sidebar nav items, message card avatar+text, recording items, feed groups |
|
||||
| `gap-4` | Stat card grid (>sm), user card 2-col, user detail layout |
|
||||
| `gap-6` | Top-level panel sections (dashboard, live) |
|
||||
| `gap-2` | Filter badges, button groups, message row indicators, toast container |
|
||||
| `space-y-1` | Compact text+value pairs |
|
||||
| `space-y-2` | Active speakers list, recording list, message metadata blocks |
|
||||
| `space-y-3` | Message feed items, skeleton groups |
|
||||
| `space-y-4` | Auth form, music panel |
|
||||
| `space-y-6` | User profile detail sections |
|
||||
|
||||
### Responsive Grid Breakpoints
|
||||
|
||||
- `sm:grid-cols-2` — stat cards, user cards, recordings cards
|
||||
- `lg:grid-cols-4` — stat summary cards
|
||||
- `xl:grid-cols-3`, `2xl:grid-cols-4` — image grid
|
||||
- `xl:grid-cols-[1fr_320px]` — live audio + sidebar layout
|
||||
- `md:grid-cols-2` — voice connection guild/channel selects
|
||||
- `md:grid-cols-3` — moderation queue stat cards
|
||||
|
||||
---
|
||||
|
||||
## 4. Typography
|
||||
|
||||
### Font Family
|
||||
|
||||
```css
|
||||
font-family: Poppins, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
```
|
||||
|
||||
Defined in `styles.css` and `tailwind.config.js` under `theme.extend.fontFamily.sans`.
|
||||
|
||||
### Type Scale
|
||||
|
||||
| Class | Used For |
|
||||
|----------------|--------------------------------------------|
|
||||
| `text-[10px]` | Badge text, filter labels, status chips |
|
||||
| `text-[11px]` | Message timestamps, edited/deleted indicators, action hints |
|
||||
| `text-[12px]` | AI analysis body text |
|
||||
| `text-xs` | Captions, descriptions, metadata, secondary info, badge text |
|
||||
| `text-sm` | Body text, card descriptions, message content, form labels |
|
||||
| `text-base` | Card titles (sometimes), live audio heading |
|
||||
| `text-lg` | Section headings, stat values |
|
||||
| `text-xl` | Page title (`h1`) |
|
||||
| `text-2xl` | Stat card values, moderation queue numbers |
|
||||
|
||||
### Font Weights
|
||||
|
||||
| Weight | Usage |
|
||||
|-----------------|--------------------------------------------|
|
||||
| `font-medium` | Sidebar items, timestamps, badge text, section labels, queue items |
|
||||
| `font-semibold` | CardTitle, user names, tracking-tight headings |
|
||||
| `font-bold` | Page titles, stat values, key numbers |
|
||||
|
||||
### Tracking
|
||||
|
||||
- `tracking-tight`: CardTitle, page `h1`
|
||||
- `tracking-wider`: Image grid kind badges
|
||||
|
||||
### Font Mono
|
||||
|
||||
- `font-mono`: Channel IDs, user IDs
|
||||
|
||||
---
|
||||
|
||||
## 5. Animation Keyframes
|
||||
|
||||
### Defined in `styles.css`
|
||||
|
||||
```css
|
||||
@keyframes bar-pulse {
|
||||
0%, 100% { transform: scaleY(0.8); }
|
||||
50% { transform: scaleY(1.2); }
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
```
|
||||
|
||||
### Defined in `tailwind.config.js` only (`glowPulse`)
|
||||
|
||||
```js
|
||||
keyframes: {
|
||||
glowPulse: {
|
||||
"0%, 100%": { opacity: "0.4" },
|
||||
"50%": { opacity: "0.8" },
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
**Note**: `glowPulse` is NOT defined in `styles.css`. It only exists in `tailwind.config.js` and is used by `ParticleBackground.tsx` via the class `animate-glow-pulse`. This works because Tailwind 4 uses the JS config for keyframes, but the CSS file is the canonical keyframe source. This is a **latent inconsistency** — see Known Issues §13.3.
|
||||
|
||||
### Animation Timings Summary
|
||||
|
||||
| Keyframe | Duration | Timing Function | Used In |
|
||||
|----------------|----------|------------------------|--------------------------------|
|
||||
| bar-pulse | 0.4s | ease-in-out | AudioVisualizer (via CSS class)|
|
||||
| shimmer | 1.5s (CSS) / 2s (TW config) | ease-in-out | Skeleton loading components |
|
||||
| fadeInUp | 0.5s | ease-out | Utility class `.animate-fade-in-up` |
|
||||
| fadeIn | 0.3s | ease-out | Utility class `.animate-fade-in` |
|
||||
| glowPulse | 3s | ease-in-out | ParticleBackground glow orbs |
|
||||
|
||||
**Mismatch**: `shimmer` duration is `1.5s` in `styles.css` but `2s` in `tailwind.config.js`. The CSS class `.animate-shimmer` is used by `Skeleton.tsx`, so the CSS definition wins.
|
||||
|
||||
---
|
||||
|
||||
## 6. Animation Utility Classes
|
||||
|
||||
Defined in `styles.css` under `@layer utilities`:
|
||||
|
||||
| Class | Animation | Purpose |
|
||||
|----------------------|------------------------------|--------------------------------|
|
||||
| `.animate-fade-in-up`| `fadeInUp 0.5s ease-out` | Entry animation for elements |
|
||||
| `.animate-fade-in` | `fadeIn 0.3s ease-out` | Simple fade-in |
|
||||
| `.animate-bar-pulse` | `bar-pulse 0.4s ease-in-out infinite` | Audio visualizer bars (transform-origin: bottom) |
|
||||
| `.animate-shimmer` | `shimmer 1.5s ease-in-out infinite` | Skeleton loading placeholder (gradient sweep) |
|
||||
|
||||
Tailwind config also registers `animate-glow-pulse` (`glowPulse 3s ease-in-out infinite`).
|
||||
|
||||
**Usage**: `Skeleton` component uses `animate-shimmer`. Inline `animate-spin` (Tailwind built-in) is used for loading spinners on re-analyze buttons.
|
||||
|
||||
---
|
||||
|
||||
## 7. Framer Motion Presets
|
||||
|
||||
All defined in `shared/hooks/useFramerStagger.ts`.
|
||||
|
||||
### `cardStagger` — Parent container variant
|
||||
```ts
|
||||
{
|
||||
initial: { opacity: 0 },
|
||||
animate: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.08, delayChildren: 0.1 },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `cardItem` — Child item (fade + slide up)
|
||||
```ts
|
||||
{
|
||||
initial: { opacity: 0, y: 20 },
|
||||
animate: {
|
||||
opacity: 1, y: 0,
|
||||
transition: { duration: 0.4, ease: "easeOut" },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `fadeSlideUp` — Single element (cubic-bezier)
|
||||
```ts
|
||||
{
|
||||
initial: { opacity: 0, y: 24 },
|
||||
animate: {
|
||||
opacity: 1, y: 0,
|
||||
transition: { duration: 0.5, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
},
|
||||
exit: { opacity: 0, y: -12, transition: { duration: 0.2 } },
|
||||
}
|
||||
```
|
||||
Used by `Header.tsx` (key=activeTab) and `DashboardLayout.tsx` (main content area, key=activeTab).
|
||||
|
||||
### `fadeIn` — Simple opacity
|
||||
```ts
|
||||
{
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1, transition: { duration: 0.3 } },
|
||||
exit: { opacity: 0, transition: { duration: 0.2 } },
|
||||
}
|
||||
```
|
||||
|
||||
### `scaleIn` — Badge/pill entrance
|
||||
```ts
|
||||
{
|
||||
initial: { opacity: 0, scale: 0.8 },
|
||||
animate: { opacity: 1, scale: 1, transition: { duration: 0.3, ease: "backOut" } },
|
||||
}
|
||||
```
|
||||
|
||||
### `springUp` — Emphasized entrance
|
||||
```ts
|
||||
{
|
||||
initial: { opacity: 0, y: 30 },
|
||||
animate: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 200, damping: 20 } },
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Pattern
|
||||
|
||||
```tsx
|
||||
<motion.div variants={cardStagger} initial="initial" animate="animate">
|
||||
<motion.div variants={cardItem}>...</motion.div>
|
||||
<motion.div variants={cardItem}>...</motion.div>
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. GSAP Page Transitions
|
||||
|
||||
### `useGsapTransition` (`shared/hooks/useGsapTransition.ts`)
|
||||
|
||||
A custom hook for tab-level page transitions, used by `AuthOverlay.tsx`.
|
||||
|
||||
**Page enter animation**:
|
||||
- Container: opacity 0→1, y 20→0, `duration: 0.4`, `ease: "power2.out"`
|
||||
- Stagger children: querySelectorAll `[data-stagger]`, `duration: 0.3`, `stagger: 0.05`
|
||||
- Reduced motion: all durations set to 0
|
||||
|
||||
**Page exit animation** (returns Promise):
|
||||
- Container: opacity→0, y→20, `duration: 0.3`, `ease: "power2.in"`
|
||||
|
||||
**Card hover helper** (`gsapCardHover`):
|
||||
```ts
|
||||
onMouseEnter: gsap.to(target, { y: -4, boxShadow: "0 8px 25px rgba(0,0,0,0.15)", duration: 0.2 })
|
||||
onMouseLeave: gsap.to(target, { y: 0, boxShadow: "0 2px 8px rgba(0,0,0,0.08)", duration: 0.2 })
|
||||
```
|
||||
(Exported but unused in current components — kept as utility.)
|
||||
|
||||
---
|
||||
|
||||
## 9. Card Pattern
|
||||
|
||||
### Base `Card` component (`shared/ui/card.tsx`)
|
||||
|
||||
```
|
||||
rounded-xl border border-border bg-card text-card-foreground shadow-sm hover:shadow-md transition-shadow
|
||||
```
|
||||
|
||||
### Composition
|
||||
|
||||
| Sub-component | Classes | Notes |
|
||||
|----------------|------------------------------|--------------------------------|
|
||||
| `Card` | See above | `transition-shadow` for hover |
|
||||
| `CardHeader` | `flex flex-col space-y-1.5 p-6` | |
|
||||
| `CardTitle` | `font-semibold leading-none tracking-tight` | `h3` element |
|
||||
| `CardDescription` | `text-sm text-muted-foreground` | `p` element |
|
||||
| `CardContent` | `p-6 pt-0` | `pt-0` to collapse with header |
|
||||
| `CardFooter` | `flex items-center p-6 pt-0` | |
|
||||
|
||||
### Common Card Variants
|
||||
|
||||
| Context | Additional Classes |
|
||||
|------------------|-----------------------------------------------------|
|
||||
| Stat card | `CardContent p-4` (compact) |
|
||||
| Dashboard stat | `overflow-hidden` |
|
||||
| Message card | `group hover:border-primary/30 hover:shadow-md` |
|
||||
| Auth card | `w-full max-w-md border-primary/30 shadow-lg shadow-primary/10` |
|
||||
| Error/empty card | `border-dashed border-destructive` (error state) |
|
||||
| User card | `hover:ring-1 hover:ring-primary/30 cursor-pointer` |
|
||||
| Media queue item | `rounded-lg border-l-2 border-l-primary border-border` |
|
||||
|
||||
### Empty State Pattern
|
||||
|
||||
```tsx
|
||||
<div className="flex flex-col items-center gap-4 py-12">
|
||||
<MascotImage size="md" className="opacity-60" />
|
||||
<p className="text-sm text-muted-foreground">No data to display</p>
|
||||
</div>
|
||||
```
|
||||
Wrapped in `EmptyStateMascot` component. Also used via standalone icon pattern:
|
||||
```tsx
|
||||
<div className="flex flex-col items-center gap-4 py-20 text-muted-foreground">
|
||||
<BarChart3 className="h-10 w-10" />
|
||||
<p className="text-sm">No data available yet.</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Skeleton Loading Pattern
|
||||
|
||||
```tsx
|
||||
<Skeleton className="rounded-lg bg-muted animate-shimmer" />
|
||||
```
|
||||
Used in `.animate-shimmer` containers matching real card layout. See `MessageCardSkeleton`, `StatsSkeleton`, `UserListSkeleton`, `DetailSkeleton`, `ActiveSpeakersSkeleton`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Stagger Animation Pattern
|
||||
|
||||
### Framer Motion (primary, used in panels)
|
||||
|
||||
Applied as `cardStagger` / `cardItem` pair on every major panel:
|
||||
|
||||
| Panel | File |
|
||||
|----------------------|----------------------------------|
|
||||
| LivePanel | `features/live/index.tsx` |
|
||||
| MessagesPanel | `features/messages/index.tsx` |
|
||||
| DashboardStatsContent| `features/dashboard/components/DashboardStats.tsx` |
|
||||
| UserSummaryList | `features/dashboard/components/UserSummaryList.tsx` |
|
||||
| UserProfileDetail | `features/dashboard/components/UserProfileDetail.tsx` |
|
||||
| MessageFeed | `features/messages/components/MessageFeed.tsx` |
|
||||
|
||||
**Timing**: stagger 80ms, delayChildren 100ms, item duration 400ms.
|
||||
|
||||
### GSAP (used in auth flow)
|
||||
|
||||
`useGsapTransition` with `[data-stagger]` attributes. Stagger 50ms, duration 300ms.
|
||||
|
||||
---
|
||||
|
||||
## 11. Component Patterns & Variants
|
||||
|
||||
### Button (`shared/ui/button.tsx`)
|
||||
|
||||
**Variants**: `default`, `secondary`, `destructive`, `outline`, `ghost`
|
||||
**Sizes**: `default` (h-10), `sm` (h-9), `lg` (h-11), `icon` (h-10 w-10)
|
||||
|
||||
Base classes:
|
||||
```
|
||||
inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium
|
||||
transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
|
||||
active:scale-[0.97] disabled:pointer-events-none disabled:opacity-50
|
||||
```
|
||||
|
||||
| Variant | Background |
|
||||
|--------------|---------------------------------------|
|
||||
| default | `bg-primary text-primary-foreground shadow-sm hover:bg-primary/90` |
|
||||
| secondary | `bg-secondary text-secondary-foreground hover:bg-secondary/80` |
|
||||
| destructive | `bg-destructive text-destructive-foreground hover:bg-destructive/90` |
|
||||
| outline | `border border-input bg-background hover:bg-accent hover:text-accent-foreground` |
|
||||
| ghost | `hover:bg-accent hover:text-accent-foreground` |
|
||||
|
||||
### Badge (`shared/ui/badge.tsx`)
|
||||
|
||||
**Variants**: `default`, `secondary`, `destructive`, `outline`, `success`, `warning`
|
||||
|
||||
Base classes:
|
||||
```
|
||||
inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors
|
||||
```
|
||||
|
||||
| Variant | Classes (uses CSS vars where possible) |
|
||||
|------------|------------------------------------------------------|
|
||||
| default | `bg-primary text-primary-foreground` |
|
||||
| secondary | `bg-muted text-muted-foreground` |
|
||||
| destructive| `bg-destructive/15 text-destructive` |
|
||||
| outline | `border-border text-foreground` |
|
||||
| success | `bg-emerald-100 text-emerald-700` **[HARDCODED]** |
|
||||
| warning | `bg-amber-100 text-amber-700` **[HARDCODED]** |
|
||||
|
||||
### Input (`shared/ui/input.tsx`)
|
||||
|
||||
```
|
||||
flex h-10 w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground
|
||||
ring-offset-background placeholder:text-muted-foreground
|
||||
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
```
|
||||
|
||||
### Select (`shared/ui/select.tsx`)
|
||||
|
||||
Same visual treatment as Input (`flex h-10 w-full rounded-lg border border-input bg-background ...`).
|
||||
|
||||
### Skeleton (`shared/ui/skeleton.tsx`)
|
||||
|
||||
```
|
||||
rounded-lg bg-muted animate-shimmer
|
||||
```
|
||||
|
||||
### Tabs (`shared/ui/tabs.tsx`)
|
||||
|
||||
Wraps Radix `TabsPrimitive`:
|
||||
|
||||
| Component | Key Classes |
|
||||
|---------------|-----------------------------------------------------------|
|
||||
| `TabsList` | `inline-flex h-10 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground` |
|
||||
| `TabsTrigger` | `inline-flex items-center justify-center whitespace-nowrap rounded-lg px-3 py-1.5 text-sm font-medium transition-all ... data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm` |
|
||||
| `TabsContent` | `mt-6 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring` |
|
||||
|
||||
### Toast (`shared/ui/toast.tsx`)
|
||||
|
||||
Context-based system with `addToast` / `removeToast`. Auto-dismiss after 4s.
|
||||
|
||||
Container: `fixed bottom-4 right-4 z-50 flex flex-col gap-2`
|
||||
|
||||
Type styles:
|
||||
|
||||
| Type | Border-left class | Icon color |
|
||||
|---------|----------------------------------|--------------------|
|
||||
| info | `border-l-primary` | `text-primary` |
|
||||
| success | `border-l-emerald-500` | `text-emerald-500` |
|
||||
| error | `border-l-destructive` | `text-destructive` |
|
||||
| warning | `border-l-amber-500` | `text-amber-500` |
|
||||
|
||||
**Note**: `emerald-500` and `amber-500` are hardcoded Tailwind utilities — see Known Issues.
|
||||
|
||||
### Glass Card (utility, `styles.css`)
|
||||
|
||||
```css
|
||||
.glass-card {
|
||||
@apply bg-white/70 backdrop-blur-sm border border-[oklch(0.92_0.004_286.32)] rounded-xl;
|
||||
}
|
||||
```
|
||||
|
||||
### Grid Pattern (utility, `styles.css`)
|
||||
|
||||
```css
|
||||
.grid-pattern {
|
||||
background-image:
|
||||
linear-gradient(oklch(0.92 0.004 286.32 / 0.3) 1px, transparent 1px),
|
||||
linear-gradient(90deg, oklch(0.92 0.004 286.32 / 0.3) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
}
|
||||
```
|
||||
Applied in `DashboardLayout.tsx` at `opacity-[0.03]`.
|
||||
|
||||
### Gradient Text
|
||||
|
||||
```css
|
||||
.gradient-text {
|
||||
@apply bg-clip-text text-transparent bg-gradient-to-r from-primary to-blue-400;
|
||||
}
|
||||
```
|
||||
Used in `Header.tsx` for the "IMPHNEN" brand text.
|
||||
|
||||
---
|
||||
|
||||
## 12. Reduced-Motion Handling
|
||||
|
||||
Three layers of protection:
|
||||
|
||||
### 1. CSS Level (`styles.css`)
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. GSAP Hook (`useGsapTransition`)
|
||||
|
||||
```ts
|
||||
function prefersReducedMotion(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
}
|
||||
```
|
||||
Passes `instant` flag → all durations set to 0, stagger set to 0.
|
||||
|
||||
### 3. Particle Background (`ParticleBackground.tsx`)
|
||||
|
||||
```tsx
|
||||
const [reducedMotion, setReducedMotion] = useState(false);
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
setReducedMotion(mq.matches);
|
||||
// listener for changes...
|
||||
}, []);
|
||||
if (reducedMotion) return null; // Entire particle layer removed
|
||||
```
|
||||
|
||||
### Coverage Gap
|
||||
|
||||
Framer Motion `AnimatePresence` and `motion` components (used extensively in MessageFeed, LivePanel, DashboardStats, UserSummaryList, UserProfileDetail, MascotChatbot) do **not** check `prefers-reduced-motion`. They rely solely on the CSS override. While the CSS `!important` rules do suppress Framer Motion animations (since they run via inline styles that get overridden), this is a fragile approach — some Framer Motion `transition` properties (spring physics, stagger delays) may not be fully Line truncated
|
||||
|
||||
---
|
||||
|
||||
## 13. Known Issues & Technical Debt
|
||||
|
||||
### 13.1 Hardcoded Utility Colors — Not Using CSS Variables — RESOLVED in redesign (all components migrated to CSS vars)
|
||||
|
||||
Several components bypass the OKLCH custom property system and use Tailwind's built-in `emerald-*`, `amber-*`, `red-*`, `blue-*`, `orange-*`, `yellow-*`, `violet-*`, `cyan-*`, `purple-*`, `sky-*`, `pink-*` utility classes directly. These will not respond to theme changes.
|
||||
|
||||
**Locations**:
|
||||
|
||||
| File(s) | Hardcoded Colors Used |
|
||||
|-------------------------------------------|--------------------------------------------|
|
||||
| `shared/ui/badge.tsx` | `bg-emerald-100 text-emerald-700` (success variant), `bg-amber-100 text-amber-700` (warning variant) |
|
||||
| `shared/ui/toast.tsx` | `border-l-emerald-500`, `text-emerald-500`, `border-l-amber-500`, `text-amber-500` |
|
||||
| `shared/ui/button.tsx` | None (uses CSS vars correctly) |
|
||||
| `features/dashboard/components/DashboardStats.tsx` | `text-emerald-500`, `bg-emerald-100`, `text-blue-500`, `bg-blue-100`, `text-violet-500`, `bg-violet-100`, `text-emerald-600`, `bg-cyan-100`, `text-cyan-500`, `text-amber-500`, `bg-amber-100` |
|
||||
| `features/dashboard/components/UserSummaryList.tsx` | `bg-emerald-100 text-emerald-700`, `bg-amber-100 text-amber-700`, `bg-red-100 text-red-700` |
|
||||
| `features/dashboard/components/UserProfileDetail.tsx` | `bg-red-100 text-red-700`, `bg-emerald-100 text-emerald-700`, `bg-amber-100 text-amber-700`, `text-emerald-600` |
|
||||
| `features/messages/components/MessageCard.tsx` | `bg-red-100 text-red-700 border-red-200`, `bg-orange-100 text-orange-700 border-orange-200`, `bg-yellow-100 text-yellow-700 border-yellow-200`, `bg-blue-100 text-blue-700 border-blue-200`, `bg-emerald-50/40`, `border-l-emerald-400`, `bg-pink-50/40`, `border-l-pink-400`, `text-pink-600`, `text-pink-600/70` |
|
||||
| `features/messages/components/MessagesPanel.tsx` | `bg-emerald-100 text-emerald-700 border-emerald-200`, `bg-orange-100 text-orange-700 border-orange-200`, `bg-red-100 text-red-700 border-red-200`, `text-emerald-600` |
|
||||
| `features/messages/components/ImageGrid.tsx` | `bg-purple-100 text-purple-700 border-purple-200` |
|
||||
| `features/live/components/ActiveSpeakers.tsx` | `text-emerald-700` |
|
||||
| `features/live/components/RecordingsSubPanel.tsx` | `border-sky-200 bg-white` |
|
||||
| `features/live/components/NowPlaying.tsx` | (uses Badge `success`/`warning` variants, which are hardcoded) |
|
||||
| `widgets/Header.tsx` | `bg-emerald-400`, `bg-red-400`, `bg-gray-400`, `text-emerald-400`, `text-red-400` |
|
||||
|
||||
**Fix**: Replace with CSS variable-based tokens, e.g.:
|
||||
- `emerald-100` → `oklch(var(--success-bg))` (would need new custom property)
|
||||
- `text-emerald-700` → `oklch(var(--success-fg))`
|
||||
- `border-emerald-200` → `oklch(var(--success-border))`
|
||||
|
||||
### 13.2 `formatTimeAgo` Forces Re-render on Every Tick
|
||||
|
||||
In `features/messages/components/MessageCard.tsx`:
|
||||
|
||||
```ts
|
||||
function formatTimeAgo(ts: number): string {
|
||||
const seconds = Math.floor((Date.now() - ts) / 1000);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`Date.now()` is called on every render, not via a periodic timer. This means the "X s ago" / "X m ago" labels are only accurate at the moment of render and do not update reactively. No `useEffect` interval keeps them fresh. Once rendered, a "5s ago" label will stay stale until a parent re-render. This is acceptable for a message feed that re-renders on new data, but inaccurate for pinned/static views.
|
||||
|
||||
**Fix**: Either (a) accept staleness (current behavior, simplest), (b) add a `useEffect` interval that forces re-render every ~30s, or (c) use `useSyncExternalStore` with a global ticker.
|
||||
|
||||
### 13.3 `AudioVisualizer.tsx` — Hardcoded IMPHNEN Gradient
|
||||
|
||||
```ts
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, height);
|
||||
gradient.addColorStop(0, "#23a1eb");
|
||||
gradient.addColorStop(1, "#3eb0f2");
|
||||
```
|
||||
|
||||
These hex values (IMPHNEN blue `#23a1eb` → `#3eb0f2`) are hardcoded and do not reference the OKLCH `--primary` CSS variable. A theme change would not affect the visualizer.
|
||||
|
||||
**Fix**: Read CSS custom property at paint time:
|
||||
```ts
|
||||
const primaryColor = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--primary").trim();
|
||||
// Convert OKLCH to hex or use Canvas oklch() if available
|
||||
```
|
||||
|
||||
### 13.4 `glow-pulse` Keyframe Fragmentation — RESOLVED in T02 (moved to styles.css)
|
||||
|
||||
The `glowPulse` keyframes are defined in `tailwind.config.js` but NOT in `styles.css`. The class `animate-glow-pulse` is referenced by `ParticleBackground.tsx`. Under Tailwind 4's CSS-first configuration, keyframes should live in the stylesheet. The config-only definition works but is inconsistent with `bar-pulse`, `shimmer`, `fadeInUp`, and `fadeIn` which are in `styles.css`.
|
||||
|
||||
**Fix**: Move `@keyframes glowPulse { ... }` into `styles.css`.
|
||||
|
||||
### 13.5 `shimmer` Duration Mismatch — RESOLVED in T02 (CSS canonical at 1.5s)
|
||||
|
||||
`styles.css`: `animation: shimmer 1.5s ease-in-out infinite`
|
||||
`tailwind.config.js`: `shimmer: "shimmer 2s linear infinite"` (also uses `linear` vs. `ease-in-out`)
|
||||
|
||||
The CSS class `.animate-shimmer` (used by `Skeleton.tsx`) references the CSS-based keyframe, so `1.5s ease-in-out` is the actual runtime value. The Tailwind config value is dead unless used via `animate-shimmer` as a Tailwind class name — which maps to `shimmer 2s linear infinite`.
|
||||
|
||||
**Fix**: Align both sources. Pick one canonical definition.
|
||||
|
||||
### 13.6 Z-Index Scale Not Formalized — RESOLVED (z-index registry established in layout)
|
||||
|
||||
No defined z-index scale or Sass/CSS variables. Values jump from 50 to 9999. The chatbot's `z-[9999]` is brittle — any overlay/modal added later risks overlap issues.
|
||||
|
||||
**Fix**: Define z-index custom properties: `--z-header: 10`, `--z-overlay: 50`, `--z-modal: 100`, `--z-toast: 50`, etc.
|
||||
|
||||
### 13.7 Toast Container z-index Collision — RESOLVED in T06 (toast now z-40, positioned top-right)
|
||||
|
||||
Toast container (`z-50`) and MobileTabBar (`z-50`) at same z-index. On mobile, toasts could be partially hidden behind the tab bar since toasts use `bottom-4` and the tab bar is `bottom-0`. In practice the toast gap prevents overlap, but this is fragile.
|
||||
|
||||
### 13.8 Retro `bg-white` Usage
|
||||
|
||||
`RecordingsSubPanel.tsx` uses `bg-white` (line 86) instead of `bg-card`. This will not respect a dark theme if one is added.
|
||||
|
||||
---
|
||||
|
||||
## 14. Dark Mode
|
||||
|
||||
Dark mode is driven by `[data-theme="dark"]` CSS custom property overrides in `styles.css`.
|
||||
The theme is managed by `useTheme()` hook (`shared/hooks/useTheme.ts`).
|
||||
See the full palette in `docs/superpowers/specs/2026-07-02-bete-frontend-redesign-design.md`.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: File Map
|
||||
|
||||
| Token / Concern | Canonical Source |
|
||||
|---------------------------|----------------------------------------------|
|
||||
| CSS custom properties | `styles.css` |
|
||||
| Tailwind theme extension | `tailwind.config.js` |
|
||||
| Framer Motion variants | `shared/hooks/useFramerStagger.ts` |
|
||||
| GSAP transition hook | `shared/hooks/useGsapTransition.ts` |
|
||||
| `cn()` utility | `shared/lib/utils.ts` |
|
||||
| Card component | `shared/ui/card.tsx` |
|
||||
| Button component | `shared/ui/button.tsx` |
|
||||
| Badge component | `shared/ui/badge.tsx` |
|
||||
| Input component | `shared/ui/input.tsx` |
|
||||
| Select component | `shared/ui/select.tsx` |
|
||||
| Skeleton component | `shared/ui/skeleton.tsx` |
|
||||
| Tabs component | `shared/ui/tabs.tsx` |
|
||||
| Toast component | `shared/ui/toast.tsx` |
|
||||
| ScrollArea component | `shared/ui/scroll-area.tsx` |
|
||||
| ParticleBackground | `widgets/particles/ParticleBackground.tsx` |
|
||||
| DashboardLayout | `widgets/DashboardLayout.tsx` |
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 50 of 175 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user