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.
This commit is contained in:
@@ -1,21 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { useGSAP } from "@gsap/react";
|
||||
import gsap from "gsap";
|
||||
import { useRef } from "react";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
gsap.registerPlugin(useGSAP);
|
||||
// Set default Linear-style crisp easing & duration
|
||||
gsap.defaults({
|
||||
ease: "power2.out",
|
||||
duration: 0.35,
|
||||
});
|
||||
}
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* Reusable hook for staggered reveal animations on child elements.
|
||||
* Linear-style: crisp entry, very subtle vertical displacement, no layout shift.
|
||||
* CSS-based stagger reveal. Applies `.stagger-in` class with incremental
|
||||
* `animation-delay` to matching children when they enter the viewport.
|
||||
* No GSAP — pure CSS keyframes + IntersectionObserver.
|
||||
*
|
||||
* Add this to globals.css if not already present:
|
||||
* @keyframes stagger-in { from { opacity:0; transform:translateY(6px) } to { opacity:1; transform:translateY(0) } }
|
||||
* .stagger-in { opacity:0; animation: stagger-in 0.32s ease-out forwards }
|
||||
*/
|
||||
export function useStaggerReveal<T extends HTMLElement = HTMLDivElement>(
|
||||
selector: string,
|
||||
@@ -29,43 +23,51 @@ export function useStaggerReveal<T extends HTMLElement = HTMLDivElement>(
|
||||
) {
|
||||
const containerRef = useRef<T>(null);
|
||||
|
||||
useGSAP(
|
||||
() => {
|
||||
if (!containerRef.current) return;
|
||||
const elements = containerRef.current.querySelectorAll(selector);
|
||||
if (!elements || elements.length === 0) return;
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const prefersReduced = window.matchMedia(
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches;
|
||||
if (prefersReduced) {
|
||||
gsap.set(elements, { opacity: 1, y: 0 });
|
||||
return;
|
||||
}
|
||||
const prefersReduced = window.matchMedia(
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches;
|
||||
|
||||
gsap.fromTo(
|
||||
elements,
|
||||
{
|
||||
opacity: 0,
|
||||
y: options?.y ?? 8,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: options?.duration ?? 0.32,
|
||||
delay: options?.delay ?? 0.02,
|
||||
stagger: options?.stagger ?? 0.03,
|
||||
ease: "power2.out",
|
||||
clearProps: "transform",
|
||||
},
|
||||
);
|
||||
},
|
||||
{
|
||||
scope: containerRef,
|
||||
dependencies: options?.dependencies ?? [],
|
||||
revertOnUpdate: true,
|
||||
},
|
||||
);
|
||||
const elements = container.querySelectorAll<HTMLElement>(selector);
|
||||
if (!elements || elements.length === 0) return;
|
||||
|
||||
if (prefersReduced) {
|
||||
elements.forEach((el) => {
|
||||
el.style.opacity = "1";
|
||||
el.style.transform = "none";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const staggerSec = options?.stagger ?? 0.03;
|
||||
const durationSec = options?.duration ?? 0.32;
|
||||
const baseDelay = options?.delay ?? 0;
|
||||
|
||||
elements.forEach((el, i) => {
|
||||
el.style.opacity = "0";
|
||||
el.style.animationFillMode = "forwards";
|
||||
el.style.animationTimingFunction = "ease-out";
|
||||
el.style.animationName = "stagger-in";
|
||||
el.style.animationDuration = `${durationSec}s`;
|
||||
el.style.animationDelay = `${baseDelay + i * staggerSec}s`;
|
||||
});
|
||||
|
||||
// Cleanup: remove inline styles so next re-render can re-apply
|
||||
return () => {
|
||||
elements.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");
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, options?.dependencies ?? []);
|
||||
|
||||
return containerRef;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user