Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 46 additions & 12 deletions app/components/landing-hero.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,48 @@
'use client'

import { motion } from 'framer-motion'
import { useGSAP } from '@gsap/react'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { useRef } from 'react'
import { NetworkBackground } from './network-background'

gsap.registerPlugin(ScrollTrigger)

export function LandingHero() {
const heroRef = useRef<HTMLDivElement>(null)

useGSAP(() => {
const tl = gsap.timeline()

// Stagger entrance animation for child elements
tl.fromTo(
'.hero-element',
{ opacity: 0, y: 20 },
{
opacity: 1,
y: 0,
duration: 0.6,
ease: 'power2.out',
stagger: 0.1,
}
)

// Add ScrollTrigger for parallax effect
ScrollTrigger.create({
trigger: heroRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: 1,
onUpdate: (self) => {
const progress = self.progress
gsap.set(heroRef.current, {
y: -progress * 50, // Parallax effect
scale: 1 + progress * 0.05, // Slight zoom
})
},
Comment on lines +30 to +42
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ScrollTrigger's onUpdate callback runs on every scroll event and directly manipulates the DOM via gsap.set. Consider using GSAP's invalidateOnRefresh option or moving the animation property to the ScrollTrigger configuration itself using animation property for better performance.

Suggested change
// Add ScrollTrigger for parallax effect
ScrollTrigger.create({
trigger: heroRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: 1,
onUpdate: (self) => {
const progress = self.progress
gsap.set(heroRef.current, {
y: -progress * 50, // Parallax effect
scale: 1 + progress * 0.05, // Slight zoom
})
},
// Add ScrollTrigger for parallax effect using an attached tween
const parallaxTween = gsap.to(heroRef.current, {
y: -50, // Parallax effect
scale: 1.05, // Slight zoom
ease: 'none',
})
ScrollTrigger.create({
trigger: heroRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: 1,
animation: parallaxTween,
invalidateOnRefresh: true,

Copilot uses AI. Check for mistakes.
})
Comment on lines +31 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The ScrollTrigger instance is created within the useGSAP hook but is not being cleaned up. While useGSAP handles cleanup for animations created through its context, ScrollTrigger.create() is a direct call and its instances need to be manually killed to prevent memory leaks when the component unmounts. You can do this by returning a cleanup function from the useGSAP callback.

        const st = ScrollTrigger.create({
            trigger: heroRef.current,
            start: 'top bottom',
            end: 'bottom top',
            scrub: 1,
            onUpdate: (self) => {
                const progress = self.progress
                gsap.set(heroRef.current, {
                    y: -progress * 50, // Parallax effect
                    scale: 1 + progress * 0.05, // Slight zoom
                })
            },
        });

        return () => {
            st.kill();
        };

})
Comment on lines +14 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Missing cleanup for ScrollTrigger and potential scope issue with class selector.

Two concerns with this GSAP implementation:

  1. The useGSAP hook is called without a scope ref or dependency array. While @gsap/react's useGSAP handles cleanup automatically when given a scope, using a class selector like .hero-element without scoping may animate elements outside this component.

  2. The ScrollTrigger.create() instance should be killed on cleanup to prevent memory leaks if the component remounts.

♻️ Recommended fix: Scope animations and ensure proper cleanup
 export function LandingHero() {
     const heroRef = useRef<HTMLDivElement>(null)
+    const containerRef = useRef<HTMLElement>(null)

-    useGSAP(() => {
+    useGSAP(() => {
         const tl = gsap.timeline()

         // Stagger entrance animation for child elements
         tl.fromTo(
             '.hero-element',
             { opacity: 0, y: 20 },
             {
                 opacity: 1,
                 y: 0,
                 duration: 0.6,
                 ease: 'power2.out',
                 stagger: 0.1,
             }
         )

         // Add ScrollTrigger for parallax effect
-        ScrollTrigger.create({
+        const st = ScrollTrigger.create({
             trigger: heroRef.current,
             start: 'top bottom',
             end: 'bottom top',
             scrub: 1,
             onUpdate: (self) => {
                 const progress = self.progress
                 gsap.set(heroRef.current, {
                     y: -progress * 50, // Parallax effect
                     scale: 1 + progress * 0.05, // Slight zoom
                 })
             },
         })
-    })
+
+        return () => {
+            st.kill()
+        }
+    }, { scope: containerRef })

     return (
-        <section className="relative h-[800px] w-full overflow-hidden border-b border-border bg-background">
+        <section ref={containerRef} className="relative h-[800px] w-full overflow-hidden border-b border-border bg-background">
🤖 Prompt for AI Agents
In `@app/components/landing-hero.tsx` around lines 14 - 44, The GSAP block in
useGSAP (the timeline and ScrollTrigger.create) is unscoped and never cleaned
up; change the selector '.hero-element' to operate only within this component by
selecting elements from heroRef.current (e.g., const elems =
heroRef.current.querySelectorAll('.hero-element') or
gsap.utils.toArray(heroRef.current.querySelectorAll(...))) and use those elems
in tl.fromTo, store the ScrollTrigger instance and the timeline in local vars
(e.g., const tl = gsap.timeline(); const st = ScrollTrigger.create(...)) and
return a cleanup that kills both (st.kill(); tl.kill(); or
ScrollTrigger.getAll/filter+kill) inside the same useGSAP callback so the
ScrollTrigger and timeline are torn down on unmount/remount.


return (
<section className="relative h-[800px] w-full overflow-hidden border-b border-border bg-background">
{/* Interactive System Visualization */}
Expand All @@ -14,40 +53,35 @@ export function LandingHero() {

<div className="container relative z-10 mx-auto flex h-full flex-col items-center justify-center px-4 text-center">
{/* Main Content */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="mx-auto max-w-4xl"
>
<div className="mb-6 inline-flex items-center rounded-full border border-white/10 bg-black/50 px-3 py-1 text-sm backdrop-blur-md shadow-lg shadow-black/20">
<div ref={heroRef} className="mx-auto max-w-4xl">
<div className="mb-6 inline-flex items-center rounded-full border border-white/10 bg-black/50 px-3 py-1 text-sm backdrop-blur-md shadow-lg shadow-black/20 hero-element">
<span className="flex size-2 me-2 rounded-full bg-green-500 animate-pulse"></span>
<span className="text-white/80 font-mono text-xs tracking-wider">
SYSTEM OPERATIONAL
</span>
</div>

<h1 className="mb-8 text-5xl font-bold tracking-tight text-foreground sm:text-6xl lg:text-8xl drop-shadow-2xl">
<h1 className="mb-8 text-5xl font-bold tracking-tight text-foreground sm:text-6xl lg:text-8xl drop-shadow-2xl hero-element">
Agent Orchestration
<br />
<span className="text-transparent bg-clip-text bg-linear-to-b from-white to-white/50">
Interactive Swarm
</span>
</h1>

<p className="mx-auto mb-12 max-w-xl text-lg leading-relaxed text-muted-foreground sm:text-xl">
<p className="mx-auto mb-12 max-w-xl text-lg leading-relaxed text-muted-foreground sm:text-xl hero-element">
Visualizing real-time agent interactions. Hover over the
nodes to interact with the neural network.
</p>

<div className="flex items-center justify-center gap-4">
<div className="flex items-center justify-center gap-4 hero-element">
<div className="h-px w-24 bg-linear-to-r from-transparent via-border to-transparent" />
<span className="text-xs text-muted-foreground font-mono uppercase tracking-widest">
Live Simulation
</span>
<div className="h-px w-24 bg-linear-to-r from-transparent via-border to-transparent" />
</div>
</motion.div>
</div>
</div>
</section>
)
Expand Down
Loading
Loading