From 611ba3997344f9e2d21eeca108e078632d273bb7 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Wed, 26 Aug 2026 19:43:57 +0700 Subject: [PATCH] refactor: replace GSAP animations with CSS animations across components - Updated RootLayout to include a noise overlay class. - Refactored LiveModerationFeed to use CSS animations for item entry. - Simplified AmbientCanvas by removing WebGL and using pure CSS for ambient effects. - Converted Chatbot to use CSS for floating window animations. - Updated Card component to include a gradient border. - Refactored PageTransition to use CSS for fade-scale animations. - Enhanced SectionHeader with reveal-up animation class. - Replaced GSAP animations in NavRail with CSS animations for item entry. - Refactored VoiceStage to use CSS for speaker node animations and pulse rings. - Removed GSAP dependencies from use-gsap-animation hook, implementing CSS-based stagger reveal. --- services/frontend/package.json | 5 +- services/frontend/pnpm-lock.yaml | 30 -- .../src/app/(dashboard)/analysis/view.tsx | 153 ++++---- .../src/app/(dashboard)/dashboard/view.tsx | 2 +- .../src/app/(dashboard)/messages/view.tsx | 8 +- .../src/app/(dashboard)/moderation/view.tsx | 6 - .../src/app/(dashboard)/recordings/view.tsx | 8 +- services/frontend/src/app/globals.css | 351 +++++++++++++++++- services/frontend/src/app/layout.tsx | 2 +- .../src/components/LiveModerationFeed.tsx | 59 ++- .../src/components/ambient/ambient-canvas.tsx | 268 ++++--------- .../src/components/chatbot/chatbot.tsx | 29 +- .../src/components/primitives/card.tsx | 2 +- .../src/components/shared/page-transition.tsx | 51 +-- .../src/components/shared/section.tsx | 2 +- .../src/components/shell/nav-rail.tsx | 55 ++- .../src/components/voice/voice-stage.tsx | 116 +++--- .../frontend/src/hooks/use-gsap-animation.ts | 100 ++--- 18 files changed, 698 insertions(+), 549 deletions(-) diff --git a/services/frontend/package.json b/services/frontend/package.json index dcc718ba..15210986 100644 --- a/services/frontend/package.json +++ b/services/frontend/package.json @@ -10,10 +10,8 @@ "format": "biome format --write" }, "dependencies": { - "@gsap/react": "^2.1.2", "@orpc/client": "1.15.0", "clsx": "^2.1.1", - "gsap": "^3.15.0", "lucide-react": "^1.33.0", "next": "16.3.2", "next-themes": "^0.4.6", @@ -21,8 +19,7 @@ "react": "19.2.8", "react-dom": "19.2.8", "swr": "^2.5.1", - "tailwind-merge": "^3.6.0", - "three": "^0.185.1" + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@biomejs/biome": "2.5.10", diff --git a/services/frontend/pnpm-lock.yaml b/services/frontend/pnpm-lock.yaml index 27dc38f4..5c560f5c 100644 --- a/services/frontend/pnpm-lock.yaml +++ b/services/frontend/pnpm-lock.yaml @@ -8,18 +8,12 @@ importers: .: dependencies: - '@gsap/react': - specifier: ^2.1.2 - version: 2.1.2(gsap@3.15.0)(react@19.2.8) '@orpc/client': specifier: 1.15.0 version: 1.15.0 clsx: specifier: ^2.1.1 version: 2.1.1 - gsap: - specifier: ^3.15.0 - version: 3.15.0 lucide-react: specifier: ^1.33.0 version: 1.33.0(react@19.2.8) @@ -44,9 +38,6 @@ importers: tailwind-merge: specifier: ^3.6.0 version: 3.6.0 - three: - specifier: ^0.185.1 - version: 0.185.1 devDependencies: '@biomejs/biome': specifier: 2.5.10 @@ -215,12 +206,6 @@ packages: '@emnapi/runtime@1.11.3': resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - '@gsap/react@2.1.2': - resolution: {integrity: sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==} - peerDependencies: - gsap: ^3.12.5 - react: '>=17' - '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} @@ -827,9 +812,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - gsap@3.15.0: - resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==} - jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -1091,9 +1073,6 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - three@0.185.1: - resolution: {integrity: sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1319,11 +1298,6 @@ snapshots: tslib: 2.8.1 optional: true - '@gsap/react@2.1.2(gsap@3.15.0)(react@19.2.8)': - dependencies: - gsap: 3.15.0 - react: 19.2.8 - '@img/colour@1.1.0': optional: true @@ -1753,8 +1727,6 @@ snapshots: graceful-fs@4.2.11: {} - gsap@3.15.0: {} - jiti@2.7.0: {} js-tokens@4.0.0: @@ -1998,8 +1970,6 @@ snapshots: tapable@2.3.3: {} - three@0.185.1: {} - tslib@2.8.1: {} type-fest@5.8.0: diff --git a/services/frontend/src/app/(dashboard)/analysis/view.tsx b/services/frontend/src/app/(dashboard)/analysis/view.tsx index 3b09aac1..ba47618f 100644 --- a/services/frontend/src/app/(dashboard)/analysis/view.tsx +++ b/services/frontend/src/app/(dashboard)/analysis/view.tsx @@ -1,7 +1,5 @@ "use client"; -import { useGSAP } from "@gsap/react"; -import gsap from "gsap"; import { Hash, Search, Sparkles, TrendingUp } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useAmbient } from "@/components/ambient/ambient-context"; @@ -16,10 +14,6 @@ import { renderMessageContent, } from "@/lib/format"; -if (typeof window !== "undefined") { - gsap.registerPlugin(useGSAP); -} - export function AnalysisView() { const [query, setQuery] = useState(""); const search = useMessageSearch(query, query.trim().length >= 2); @@ -44,88 +38,101 @@ export function AnalysisView() { ); }, [query, ambient]); - // Count-up + bar-fill reveal for the reactor leaderboard — HUD gauge motif. - useGSAP( - () => { - if (!reactorsRef.current) return; - const bars = reactorsRef.current.querySelectorAll(".reactor-bar-fill"); - const counters = reactorsRef.current.querySelectorAll(".reactor-count"); - if (bars.length === 0) return; - const prefersReduced = window.matchMedia( - "(prefers-reduced-motion: reduce)", - ).matches; - if (prefersReduced) { - gsap.set(bars, { scaleX: 1 }); - return; - } - gsap.fromTo( - bars, - { scaleX: 0 }, - { - scaleX: 1, - duration: 0.7, - stagger: 0.06, - ease: "power2.out", - transformOrigin: "left center", - }, - ); + // Bar-fill reveal for reactor leaderboard — CSS animation + useEffect(() => { + const container = reactorsRef.current; + if (!container) return; + const bars = container.querySelectorAll(".reactor-bar-fill"); + const counters = container.querySelectorAll(".reactor-count"); + if (bars.length === 0) return; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + bars.forEach((el) => (el.style.transform = "scaleX(1)")); counters.forEach((el) => { - const target = Number(el.getAttribute("data-target") ?? "0"); - const obj = { val: 0 }; - gsap.to(obj, { - val: target, - duration: 0.8, - ease: "power2.out", - onUpdate: () => { - el.textContent = `+${Math.round(obj.val)}`; - }, - }); + el.textContent = `+${el.getAttribute("data-target") ?? "0"}`; }); - }, - { scope: reactorsRef, dependencies: [reactors] }, - ); + return; + } - useGSAP( - () => { - if (!channelsRef.current) return; - const rows = channelsRef.current.querySelectorAll(".channel-row"); - if (rows.length === 0) return; - const prefersReduced = window.matchMedia( - "(prefers-reduced-motion: reduce)", - ).matches; - if (prefersReduced) { - gsap.set(rows, { opacity: 1, x: 0 }); - return; - } - gsap.fromTo( - rows, - { opacity: 0, x: -10 }, - { - opacity: 1, - x: 0, - duration: 0.35, - stagger: 0.04, - ease: "power2.out", - clearProps: "transform", - }, - ); - }, - { scope: channelsRef, dependencies: [channels] }, - ); + bars.forEach((el, i) => { + el.style.transformOrigin = "left center"; + el.style.animationFillMode = "forwards"; + el.style.animationTimingFunction = "ease-out"; + el.style.animationName = "bar-fill-in"; + el.style.animationDuration = "0.7s"; + el.style.animationDelay = `${i * 0.06}s`; + }); + + // Simple counter animation using rAF + counters.forEach((el) => { + const target = Number(el.getAttribute("data-target") ?? "0"); + const start = performance.now(); + const duration = 800; + const step = (now: number) => { + const progress = Math.min((now - start) / duration, 1); + const eased = 1 - (1 - progress) ** 3; + el.textContent = `+${Math.round(target * eased)}`; + if (progress < 1) requestAnimationFrame(step); + }; + requestAnimationFrame(step); + }); + + return () => { + bars.forEach((el) => { + el.style.removeProperty("animation-name"); + el.style.removeProperty("animation-duration"); + el.style.removeProperty("animation-delay"); + el.style.removeProperty("animation-fill-mode"); + el.style.removeProperty("animation-timing-function"); + el.style.removeProperty("transform-origin"); + }); + }; + }, [reactors]); + + // Channel rows stagger slide-in — CSS animation + useEffect(() => { + const container = channelsRef.current; + if (!container) return; + const rows = container.querySelectorAll(".channel-row"); + if (rows.length === 0) return; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; + + rows.forEach((el, i) => { + el.style.opacity = "0"; + el.style.animationFillMode = "forwards"; + el.style.animationTimingFunction = "ease-out"; + el.style.animationName = "stagger-slide-in"; + el.style.animationDuration = "0.35s"; + el.style.animationDelay = `${i * 0.04}s`; + }); + + return () => { + rows.forEach((el) => { + el.style.removeProperty("opacity"); + el.style.removeProperty("animation-name"); + el.style.removeProperty("animation-duration"); + el.style.removeProperty("animation-delay"); + el.style.removeProperty("animation-fill-mode"); + el.style.removeProperty("animation-timing-function"); + }); + }; + }, [channels]); return (
{/* Tactical HUD Header Bar */}
- +

Deep Scan · Semantic Search & Telemetry Analysis

ENGINE: - + = 2 ? "SCANNING" : "STANDBY"} + > {query.trim().length >= 2 ? "SCANNING" : "STANDBY"}
diff --git a/services/frontend/src/app/(dashboard)/dashboard/view.tsx b/services/frontend/src/app/(dashboard)/dashboard/view.tsx index c8875240..c6ab1f87 100644 --- a/services/frontend/src/app/(dashboard)/dashboard/view.tsx +++ b/services/frontend/src/app/(dashboard)/dashboard/view.tsx @@ -105,7 +105,7 @@ export function DashboardView({ {/* Tactical HUD Header Bar */}
- +

Telemetry Overview · Node 01

diff --git a/services/frontend/src/app/(dashboard)/messages/view.tsx b/services/frontend/src/app/(dashboard)/messages/view.tsx index a5e38220..a105e6b4 100644 --- a/services/frontend/src/app/(dashboard)/messages/view.tsx +++ b/services/frontend/src/app/(dashboard)/messages/view.tsx @@ -236,7 +236,7 @@ export function MessagesView({ {/* Tactical HUD Header Bar */}
- +

Chat Log Stream · Ingestion Stream

@@ -249,7 +249,10 @@ export function MessagesView({
MODE: - + {searching ? "SEARCH_ACTIVE" : viewMode.toUpperCase()}
@@ -407,6 +410,7 @@ export function MessagesView({ FETCHING EARLIER PACKETS... + ) : hasMore && loadedPages < MAX_OLDER_PAGES ? (