From 5c00c9f60b0a312e7b4f57191825cbf7bf6a6faa Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Fri, 12 Jun 2026 23:00:52 +0700 Subject: [PATCH] feat(ui): overhaul frontend with IMPHNEN visual identity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update CSS foundation: HSL→oklch color tokens, Poppins font, new utilities - Replace Three.js sakura particles with CSS glow orbs - Rebrand Header with IMPHNEN logo and gradient text - Update all UI components (Button, Card, Badge, Input, Select, Tabs, Toast) - Apply IMPHNEN design patterns (glass, gradient, grid) to layout - Remove all hardcoded #7EC8E3/#FFB7C5/#FCA5A5 references - Standardize rounded-xl across all components - Replace emoji toasts with Lucide icons Co-Authored-By: Claude --- services/frontend/index.html | 8 +- .../live/components/ActiveSpeakers.tsx | 4 +- .../live/components/AudioVisualizer.tsx | 8 +- .../live/components/MusicSubPanel.tsx | 12 +- .../features/live/components/NowPlaying.tsx | 10 +- .../live/components/RecordingsSubPanel.tsx | 6 +- .../live/components/ScreenSubPanel.tsx | 8 +- .../live/components/VoiceConnectionCard.tsx | 6 +- .../messages/components/ImageGrid.tsx | 4 +- .../messages/components/MessageCard.tsx | 6 +- .../frontend/src/features/messages/index.tsx | 8 +- .../tuner/components/CorrectionHistory.tsx | 52 +++-- .../tuner/components/CorrectionStats.tsx | 31 +-- .../tuner/components/SubmitCorrection.tsx | 25 ++- .../features/tuner/hooks/useCorrections.ts | 75 +++++--- .../frontend/src/features/tuner/index.tsx | 7 +- .../frontend/src/shared/ui/MobileTabBar.tsx | 5 +- services/frontend/src/shared/ui/badge.tsx | 6 +- services/frontend/src/shared/ui/button.tsx | 18 +- services/frontend/src/shared/ui/card.tsx | 2 +- services/frontend/src/shared/ui/input.tsx | 2 +- .../frontend/src/shared/ui/scroll-area.tsx | 2 +- services/frontend/src/shared/ui/select.tsx | 2 +- services/frontend/src/shared/ui/skeleton.tsx | 2 +- services/frontend/src/shared/ui/tabs.tsx | 2 +- services/frontend/src/shared/ui/toast.tsx | 34 ++-- services/frontend/src/styles.css | 97 ++++++---- .../frontend/src/widgets/DashboardLayout.tsx | 6 +- services/frontend/src/widgets/Header.tsx | 31 +-- services/frontend/src/widgets/Sidebar.tsx | 19 +- .../src/widgets/mascot/MascotChatbot.tsx | 12 +- .../src/widgets/mascot/MascotImage.tsx | 2 +- .../widgets/particles/ParticleBackground.tsx | 177 +----------------- services/frontend/tailwind.config.js | 46 +++-- 34 files changed, 341 insertions(+), 394 deletions(-) diff --git a/services/frontend/index.html b/services/frontend/index.html index a9dbcb1..3921464 100644 --- a/services/frontend/index.html +++ b/services/frontend/index.html @@ -1,10 +1,14 @@ - + - GMW — Discord Moderation Watcher + + IMPHNEN — Discord Moderation + + +
diff --git a/services/frontend/src/features/live/components/ActiveSpeakers.tsx b/services/frontend/src/features/live/components/ActiveSpeakers.tsx index 3e5117c..76fb79b 100644 --- a/services/frontend/src/features/live/components/ActiveSpeakers.tsx +++ b/services/frontend/src/features/live/components/ActiveSpeakers.tsx @@ -18,12 +18,12 @@ export function ActiveSpeakers({ speakers }: ActiveSpeakersProps) { return (
{s.username}
diff --git a/services/frontend/src/features/live/components/AudioVisualizer.tsx b/services/frontend/src/features/live/components/AudioVisualizer.tsx index 8a15710..e8bd971 100644 --- a/services/frontend/src/features/live/components/AudioVisualizer.tsx +++ b/services/frontend/src/features/live/components/AudioVisualizer.tsx @@ -21,10 +21,10 @@ export function AudioVisualizer({ levels }: AudioVisualizerProps) { const barWidth = width / levels.length; const maxBarHeight = height * 0.85; - // Sky-blue to pink gradient + // IMPHNEN blue gradient const gradient = ctx.createLinearGradient(0, 0, 0, height); - gradient.addColorStop(0, "#7EC8E3"); - gradient.addColorStop(1, "#FFB7C5"); + gradient.addColorStop(0, "#23a1eb"); + gradient.addColorStop(1, "#3eb0f2"); for (let i = 0; i < levels.length; i++) { const level = levels[i]; @@ -54,7 +54,7 @@ export function AudioVisualizer({ levels }: AudioVisualizerProps) { ref={canvasRef} width={512} height={128} - className="w-full rounded-xl bg-sky-50/30" + className="w-full rounded-lg bg-primary/5" style={{ height: "128px" }} />
diff --git a/services/frontend/src/features/live/components/MusicSubPanel.tsx b/services/frontend/src/features/live/components/MusicSubPanel.tsx index 337a6bc..e6b9bbb 100644 --- a/services/frontend/src/features/live/components/MusicSubPanel.tsx +++ b/services/frontend/src/features/live/components/MusicSubPanel.tsx @@ -43,7 +43,7 @@ export function MusicSubPanel({ }; return ( -
+
setSource(e.target.value)} @@ -51,7 +51,7 @@ export function MusicSubPanel({ placeholder="YouTube URL, Spotify track, or search terms" />
- + setDraftVolume(Number(e.target.value))} - className="h-2 w-full cursor-pointer accent-[#7EC8E3]" + className="h-2 w-full cursor-pointer accent-primary" /> {draftVolume}%
- diff --git a/services/frontend/src/features/messages/components/ImageGrid.tsx b/services/frontend/src/features/messages/components/ImageGrid.tsx index fe8161b..dfa7f4d 100644 --- a/services/frontend/src/features/messages/components/ImageGrid.tsx +++ b/services/frontend/src/features/messages/components/ImageGrid.tsx @@ -12,7 +12,7 @@ interface ImageItem { function kindBadge(kind: ImageItem["kind"]): string { switch (kind) { case "sticker": - return "bg-pink-100 text-pink-700 border-pink-200"; + return "bg-primary/10 text-primary border-primary/20"; case "attachment": return "bg-primary-soft text-primary border-primary/30"; case "embed": @@ -82,7 +82,7 @@ export function ImageGrid({ messages }: { messages: MessageRecord[] }) { href={image.url} target="_blank" rel="noreferrer" - className="group overflow-hidden rounded-2xl border border-primary/20 bg-white shadow-sm transition-all hover:border-primary/40 hover:shadow-md" + className="group overflow-hidden rounded-xl border border-primary/20 bg-white shadow-sm transition-all hover:border-primary/40 hover:shadow-md" >
{image.kind === "sticker" ? ( diff --git a/services/frontend/src/features/messages/components/MessageCard.tsx b/services/frontend/src/features/messages/components/MessageCard.tsx index 4951502..5d684ea 100644 --- a/services/frontend/src/features/messages/components/MessageCard.tsx +++ b/services/frontend/src/features/messages/components/MessageCard.tsx @@ -376,8 +376,8 @@ export function MessageCard({ messages, onReanalyze }: MessageCardProps) { return (
@@ -437,7 +437,7 @@ export function MessageCard({ messages, onReanalyze }: MessageCardProps) { export function MessageCardSkeleton() { return ( -
+
diff --git a/services/frontend/src/features/messages/index.tsx b/services/frontend/src/features/messages/index.tsx index 501e183..806a205 100644 --- a/services/frontend/src/features/messages/index.tsx +++ b/services/frontend/src/features/messages/index.tsx @@ -103,7 +103,7 @@ export function MessagesPanel({ animate="animate" > - + Messages {guildName && ( @@ -141,7 +141,7 @@ export function MessagesPanel({ {stats.flagged} flagged @@ -224,7 +224,7 @@ export function MessagesPanel({ setRetryingAll(false); } }} - className="rounded-xl bg-pink-100 text-pink-700 hover:bg-pink-200 border-pink-200" + className="rounded-xl bg-destructive/10 text-destructive hover:bg-destructive/20 border-destructive/20" > {f} diff --git a/services/frontend/src/features/tuner/components/CorrectionHistory.tsx b/services/frontend/src/features/tuner/components/CorrectionHistory.tsx index e4304e7..1452fb0 100644 --- a/services/frontend/src/features/tuner/components/CorrectionHistory.tsx +++ b/services/frontend/src/features/tuner/components/CorrectionHistory.tsx @@ -27,7 +27,14 @@ function CorrectionRow({ entry, index, }: { - entry: { id: string; created_at: number; original_flags: string; corrected_flags: string; content_snippet: string; correction_notes: string | null }; + entry: { + id: string; + created_at: number; + original_flags: string; + corrected_flags: string; + content_snippet: string; + correction_notes: string | null; + }; index: number; }) { const originalFlags = parseFlags(entry.original_flags); @@ -45,7 +52,11 @@ function CorrectionRow({
{originalFlags.map((f) => ( - + {f.replace(/_/g, " ")} ))} @@ -53,13 +64,20 @@ function CorrectionRow({ {isCleared ? ( - + Cleared ) : (
{correctedFlags.map((f) => ( - + {f.replace(/_/g, " ")} ))} @@ -77,23 +95,24 @@ function CorrectionRow({ } export function CorrectionHistoryContent() { - const { entries, loading, loadingMore, error, hasMore, loadMore, refetch } = useCorrectionHistory(); + const { entries, loading, loadingMore, error, hasMore, loadMore, refetch } = + useCorrectionHistory(); if (loading) { return (
- - - - - + + + + +
); } if (error) { return ( - +

{error}

@@ -112,11 +131,12 @@ export function CorrectionHistoryContent() { if (entries.length === 0) { return ( - +

- No corrections submitted yet. Use the Submit tab to record your first correction. + No corrections submitted yet. Use the Submit tab to record your + first correction.

@@ -124,7 +144,7 @@ export function CorrectionHistoryContent() { } return ( - + Correction History @@ -159,7 +179,9 @@ export function CorrectionHistoryContent() { disabled={loadingMore} className="gap-1.5 text-xs" > - + {loadingMore ? "Loading..." : "Load More"}
diff --git a/services/frontend/src/features/tuner/components/CorrectionStats.tsx b/services/frontend/src/features/tuner/components/CorrectionStats.tsx index dda344a..24f69a9 100644 --- a/services/frontend/src/features/tuner/components/CorrectionStats.tsx +++ b/services/frontend/src/features/tuner/components/CorrectionStats.tsx @@ -10,7 +10,15 @@ import { } from "../../../shared/ui"; import { EmptyStateMascot } from "../../../shared/ui"; -function FlagsBar({ flag, count, max }: { flag: string; count: number; max: number }) { +function FlagsBar({ + flag, + count, + max, +}: { + flag: string; + count: number; + max: number; +}) { const pct = max > 0 ? (count / max) * 100 : 0; return (
@@ -20,7 +28,7 @@ function FlagsBar({ flag, count, max }: { flag: string; count: number; max: numb
- - - + + +
); } if (error) { return ( - +

{error}

@@ -68,11 +76,12 @@ export function CorrectionStatsContent() { if (!stats || stats.total_corrections === 0) { return ( - +

- No corrections yet. When admins correct false positives, statistics will appear here. + No corrections yet. When admins correct false positives, statistics + will appear here.

@@ -97,7 +106,7 @@ export function CorrectionStatsContent() { animate: { opacity: 1, y: 0, transition: { duration: 0.4 } }, }} > - + Total Corrections @@ -117,7 +126,7 @@ export function CorrectionStatsContent() { animate: { opacity: 1, y: 0, transition: { duration: 0.4 } }, }} > - + Last 7 Days @@ -134,7 +143,7 @@ export function CorrectionStatsContent() { {/* Flags bar chart */} {stats.by_flag.length > 0 && ( - + Most Corrected Flags diff --git a/services/frontend/src/features/tuner/components/SubmitCorrection.tsx b/services/frontend/src/features/tuner/components/SubmitCorrection.tsx index 8a8b446..a25c373 100644 --- a/services/frontend/src/features/tuner/components/SubmitCorrection.tsx +++ b/services/frontend/src/features/tuner/components/SubmitCorrection.tsx @@ -67,7 +67,10 @@ export function SubmitCorrectionContent() { content_snippet: contentSnippet.trim(), }); - addToast("Correction submitted — the AI prompt will learn from this.", "success"); + addToast( + "Correction submitted — the AI prompt will learn from this.", + "success", + ); // Reset form setMessageId(""); @@ -86,14 +89,14 @@ export function SubmitCorrectionContent() { transition={{ duration: 0.4 }} className="max-w-2xl" > - + Submit Correction - Record a false positive — a message that was incorrectly flagged by AI moderation. - This helps the system learn and improve accuracy. + Record a false positive — a message that was incorrectly flagged by + AI moderation. This helps the system learn and improve accuracy. @@ -134,10 +137,20 @@ export function SubmitCorrectionContent() { placeholder="e.g. sexual_deviation" value={flagInput} onChange={(e) => setFlagInput(e.target.value)} - onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addFlag(); } }} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + addFlag(); + } + }} className="flex-1" /> -
diff --git a/services/frontend/src/features/tuner/hooks/useCorrections.ts b/services/frontend/src/features/tuner/hooks/useCorrections.ts index f3d6fe6..2ce68a0 100644 --- a/services/frontend/src/features/tuner/hooks/useCorrections.ts +++ b/services/frontend/src/features/tuner/hooks/useCorrections.ts @@ -27,7 +27,9 @@ export function useCorrectionStats() { } }, []); - useEffect(() => { fetch().catch(() => undefined); }, [fetch]); + useEffect(() => { + fetch().catch(() => undefined); + }, [fetch]); return { stats, loading, error, refetch: fetch }; } @@ -51,7 +53,9 @@ export function useCorrectionHistory() { cursorRef.current = result.nextCursor; hasMoreRef.current = result.nextCursor !== null; } catch (err) { - setError(err instanceof Error ? err.message : "Failed to load corrections"); + setError( + err instanceof Error ? err.message : "Failed to load corrections", + ); } finally { setLoading(false); } @@ -61,7 +65,10 @@ export function useCorrectionHistory() { if (!cursorRef.current || loadingMore) return; setLoadingMore(true); try { - const result = await listCorrections({ limit: 20, cursor: cursorRef.current }); + const result = await listCorrections({ + limit: 20, + cursor: cursorRef.current, + }); setEntries((prev) => [...prev, ...result.data]); cursorRef.current = result.nextCursor; hasMoreRef.current = result.nextCursor !== null; @@ -72,9 +79,19 @@ export function useCorrectionHistory() { } }, [loadingMore]); - useEffect(() => { fetchInitial().catch(() => undefined); }, [fetchInitial]); + useEffect(() => { + fetchInitial().catch(() => undefined); + }, [fetchInitial]); - return { entries, loading, loadingMore, error, hasMore: hasMoreRef.current, loadMore, refetch: fetchInitial }; + return { + entries, + loading, + loadingMore, + error, + hasMore: hasMoreRef.current, + loadMore, + refetch: fetchInitial, + }; } // ─── Submit ───────────────────────────────────────────────────────────────── @@ -84,28 +101,32 @@ export function useSubmitCorrection() { const [error, setError] = useState(null); const [success, setSuccess] = useState(null); - const submit = useCallback(async (data: { - message_id: string; - original_flags: string[]; - corrected_flags: string[]; - correction_notes?: string; - content_snippet: string; - }) => { - setSubmitting(true); - setError(null); - setSuccess(null); - try { - const result = await submitCorrection(data); - setSuccess(result); - return result; - } catch (err) { - const msg = err instanceof Error ? err.message : "Failed to submit correction"; - setError(msg); - throw err; - } finally { - setSubmitting(false); - } - }, []); + const submit = useCallback( + async (data: { + message_id: string; + original_flags: string[]; + corrected_flags: string[]; + correction_notes?: string; + content_snippet: string; + }) => { + setSubmitting(true); + setError(null); + setSuccess(null); + try { + const result = await submitCorrection(data); + setSuccess(result); + return result; + } catch (err) { + const msg = + err instanceof Error ? err.message : "Failed to submit correction"; + setError(msg); + throw err; + } finally { + setSubmitting(false); + } + }, + [], + ); const reset = useCallback(() => { setError(null); diff --git a/services/frontend/src/features/tuner/index.tsx b/services/frontend/src/features/tuner/index.tsx index b9fdfdc..e276db4 100644 --- a/services/frontend/src/features/tuner/index.tsx +++ b/services/frontend/src/features/tuner/index.tsx @@ -1,9 +1,4 @@ -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "../../shared/ui"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../shared/ui"; import { CorrectionStatsContent } from "./components/CorrectionStats"; import { CorrectionHistoryContent } from "./components/CorrectionHistory"; import { SubmitCorrectionContent } from "./components/SubmitCorrection"; diff --git a/services/frontend/src/shared/ui/MobileTabBar.tsx b/services/frontend/src/shared/ui/MobileTabBar.tsx index 8aa4b92..1b8a9f5 100644 --- a/services/frontend/src/shared/ui/MobileTabBar.tsx +++ b/services/frontend/src/shared/ui/MobileTabBar.tsx @@ -15,7 +15,7 @@ interface MobileTabBarProps { export function MobileTabBar({ activeTab, onTabChange }: MobileTabBarProps) { return ( -