Spaces:
Running
Running
| "use client"; | |
| import { useRef, useLayoutEffect, useEffect, useState, useCallback } from "react"; | |
| import gsap from "gsap"; | |
| import { ScrollTrigger } from "gsap/ScrollTrigger"; | |
| if (typeof window !== "undefined") { | |
| gsap.registerPlugin(ScrollTrigger); | |
| } | |
| export default function AboutSection() { | |
| const sectionRef = useRef<HTMLElement>(null); | |
| const clippedContentRef = useRef<HTMLDivElement>(null); | |
| const headerWrapperRef = useRef<HTMLDivElement>(null); | |
| const heroLine1Ref = useRef<HTMLDivElement>(null); | |
| const heroLine2Ref = useRef<HTMLDivElement>(null); | |
| const storyBlock1Ref = useRef<HTMLDivElement>(null); | |
| const storyBlock2Ref = useRef<HTMLDivElement>(null); | |
| const stripsWrapperRef = useRef<HTMLDivElement>(null); | |
| const [mounted, setMounted] = useState(false); | |
| const [isMobile, setIsMobile] = useState(false); | |
| // --- Hover reveal circle --- | |
| const revealAreaRef = useRef<HTMLDivElement>(null); | |
| const revealRef = useRef<HTMLDivElement>(null); | |
| const circlePos = useRef({ x: -300, y: -300 }); | |
| const currentPos = useRef({ x: -300, y: -300 }); | |
| const isHovering = useRef(false); | |
| const circleSize = 300; | |
| const handleMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => { | |
| const rect = revealAreaRef.current?.getBoundingClientRect(); | |
| if (!rect) return; | |
| circlePos.current = { | |
| x: e.clientX - rect.left, | |
| y: e.clientY - rect.top, | |
| }; | |
| isHovering.current = true; | |
| }, []); | |
| const handleMouseLeave = useCallback(() => { | |
| isHovering.current = false; | |
| }, []); | |
| // Smooth animation loop for the reveal circle | |
| useEffect(() => { | |
| let animId: number; | |
| const animate = () => { | |
| const reveal = revealRef.current; | |
| if (!reveal) { animId = requestAnimationFrame(animate); return; } | |
| currentPos.current.x += (circlePos.current.x - currentPos.current.x) * 0.12; | |
| currentPos.current.y += (circlePos.current.y - currentPos.current.y) * 0.12; | |
| const x = currentPos.current.x; | |
| const y = currentPos.current.y; | |
| const r = isHovering.current ? circleSize / 2 : 0; | |
| reveal.style.clipPath = `circle(${r}px at ${x}px ${y}px)`; | |
| animId = requestAnimationFrame(animate); | |
| }; | |
| animId = requestAnimationFrame(animate); | |
| return () => cancelAnimationFrame(animId); | |
| }, []); | |
| useEffect(() => { | |
| setMounted(true); | |
| const checkMobile = () => setIsMobile(window.innerWidth < 768); | |
| checkMobile(); | |
| window.addEventListener('resize', checkMobile); | |
| return () => window.removeEventListener('resize', checkMobile); | |
| }, []); | |
| useLayoutEffect(() => { | |
| if (!mounted) return; | |
| const isMobile = window.innerWidth < 768; | |
| const insetX = isMobile ? "6%" : "14%"; | |
| const radius = isMobile ? "16px" : "28px"; | |
| const ctx = gsap.context(() => { | |
| gsap.fromTo( | |
| clippedContentRef.current, | |
| { | |
| clipPath: `inset(0 ${insetX} 0 ${insetX} round ${radius})`, | |
| }, | |
| { | |
| clipPath: "inset(0 0% 0 0% round 0px)", | |
| ease: "power2.inOut", | |
| scrollTrigger: { | |
| trigger: sectionRef.current, | |
| start: "top 85%", | |
| end: "top 5%", | |
| scrub: 0.6, | |
| }, | |
| } | |
| ); | |
| gsap.to(".about-header-marquee", { | |
| xPercent: -50, | |
| repeat: -1, | |
| duration: 20, | |
| ease: "linear", | |
| }); | |
| gsap.to(".about-strip-1-marquee", { | |
| xPercent: -50, | |
| repeat: -1, | |
| duration: 25, | |
| ease: "linear", | |
| }); | |
| gsap.fromTo(".about-strip-2-marquee", | |
| { xPercent: -50 }, | |
| { | |
| xPercent: 0, | |
| repeat: -1, | |
| duration: 25, | |
| ease: "linear", | |
| } | |
| ); | |
| gsap.set(headerWrapperRef.current, { opacity: 0, y: 30 }); | |
| gsap.set(heroLine1Ref.current, { opacity: 0, y: 100, filter: "blur(10px)" }); | |
| gsap.set(heroLine2Ref.current, { opacity: 0, y: 100, filter: "blur(10px)" }); | |
| gsap.set(storyBlock1Ref.current, { opacity: 0, x: -100, filter: "blur(8px)" }); | |
| gsap.set(storyBlock2Ref.current, { opacity: 0, x: 100, filter: "blur(8px)" }); | |
| gsap.set(stripsWrapperRef.current, { opacity: 0, y: 60 }); | |
| gsap.to(headerWrapperRef.current, { | |
| opacity: 1, | |
| y: 0, | |
| ease: "power2.out", | |
| scrollTrigger: { | |
| trigger: sectionRef.current, | |
| start: "top 90%", | |
| end: "top 50%", | |
| scrub: 0.3, | |
| }, | |
| }); | |
| const tl = gsap.timeline({ | |
| scrollTrigger: { | |
| trigger: sectionRef.current, | |
| start: "top top", | |
| end: isMobile ? "+=900" : "+=1500", | |
| scrub: 1, | |
| pin: true, | |
| anticipatePin: 1, | |
| pinSpacing: true, | |
| }, | |
| }); | |
| tl.to(heroLine1Ref.current, { opacity: 1, y: 0, filter: "blur(0px)", duration: 0.25, ease: "power3.out" }, 0); | |
| tl.to(heroLine2Ref.current, { opacity: 1, y: 0, filter: "blur(0px)", duration: 0.25, ease: "power3.out" }, 0.15); | |
| tl.to(storyBlock1Ref.current, { opacity: 1, x: 0, filter: "blur(0px)", duration: 0.25, ease: "power3.out" }, 0.35); | |
| tl.to(storyBlock2Ref.current, { opacity: 1, x: 0, filter: "blur(0px)", duration: 0.25, ease: "power3.out" }, 0.50); | |
| tl.to(stripsWrapperRef.current, { opacity: 1, y: 0, duration: 0.25, ease: "power3.out" }, 0.70); | |
| }, sectionRef); | |
| return () => ctx.revert(); | |
| }, [mounted]); | |
| const marqueeText = "DATA SCIENTIST ✦ PYTHON BACKEND DEVELOPER ✦ AI ENTHUSIAST ✦ PROBLEM SOLVER ✦ "; | |
| const strip1Text = "★ Driven by Curiosity, Powered by Data ★ Machine Learning Solutions ★ Predictive Analytics ★ AI-Driven Insights ★ "; | |
| const strip2Text = "★ Data Quality ★ Model Accuracy ★ Scalable Pipelines ★ Statistical Analysis ★ Deep Learning ★ Python Engineering ★ "; | |
| return ( | |
| <section | |
| id="about" | |
| ref={sectionRef} | |
| className="relative bg-[#050505] overflow-visible" | |
| > | |
| {/* Clipped content — clip-path creates the expanding box effect */} | |
| <div | |
| ref={clippedContentRef} | |
| className="relative" | |
| style={{ | |
| background: "linear-gradient(180deg, #0c0c0c 0%, #0a0a0a 50%, #080808 100%)", | |
| }} | |
| > | |
| {/* Subtle top edge glow inside the clipped box */} | |
| <div | |
| className="absolute top-0 left-[10%] right-[10%] h-px pointer-events-none z-20" | |
| style={{ | |
| background: "linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.12) 50%, transparent 100%)", | |
| }} | |
| /> | |
| {/* Content */} | |
| <div className="relative z-10 min-h-screen pt-16"> | |
| {/* Large Header Marquee Banner */} | |
| <div ref={headerWrapperRef} className="py-3 md:py-4 overflow-hidden border-b border-white/5"> | |
| <div className="about-header-marquee flex whitespace-nowrap" style={{ width: "200%" }}> | |
| <span className="text-2xl md:text-4xl lg:text-6xl font-black tracking-tight text-white/90 uppercase"> | |
| {marqueeText.repeat(6)} | |
| </span> | |
| </div> | |
| </div> | |
| {/* Hero + Story area — this is where the reveal mask lives */} | |
| <div | |
| ref={revealAreaRef} | |
| className="relative" | |
| onMouseMove={isMobile ? undefined : handleMouseMove} | |
| onMouseLeave={isMobile ? undefined : handleMouseLeave} | |
| > | |
| {/* Normal layer (white text on dark bg) */} | |
| <div className="px-6 md:px-12 lg:px-20 xl:px-32 py-8 md:py-12"> | |
| <div className="mb-16 md:mb-24"> | |
| <div ref={heroLine1Ref}> | |
| <h2 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-extralight text-white tracking-tighter leading-[0.95]"> | |
| I extract insights that | |
| </h2> | |
| </div> | |
| <div ref={heroLine2Ref}> | |
| <h2 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl tracking-tighter leading-[0.95]"> | |
| <span className="font-black text-white">drive</span> | |
| <span className="font-extralight text-white/50"> real </span> | |
| <span className="font-black text-transparent bg-clip-text bg-gradient-to-r from-white via-neutral-300 to-white/70">decisions</span> | |
| </h2> | |
| </div> | |
| </div> | |
| <div className="grid md:grid-cols-2 gap-8 md:gap-16 mb-16 md:mb-24"> | |
| <div ref={storyBlock1Ref} className="space-y-4"> | |
| <p className="text-white/30 text-xs uppercase tracking-[0.3em] font-medium">My Journey</p> | |
| <p className="text-lg md:text-xl lg:text-2xl text-white/80 font-light leading-relaxed"> | |
| Started as a <span className="text-white font-medium">curious teenager</span> exploring datasets, | |
| I've evolved into a data scientist who obsesses over every feature | |
| and every decimal of model accuracy. | |
| </p> | |
| </div> | |
| <div ref={storyBlock2Ref} className="space-y-4"> | |
| <p className="text-white/30 text-xs uppercase tracking-[0.3em] font-medium">My Approach</p> | |
| <p className="text-lg md:text-xl lg:text-2xl text-white/80 font-light leading-relaxed"> | |
| I believe the best data solutions are the ones that | |
| <span className="text-white font-medium"> speak for themselves</span>—clear insights, | |
| actionable results, and decisions backed by evidence. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Reveal layer (black text on white bg) — different alternate text */} | |
| <div | |
| ref={revealRef} | |
| className="absolute inset-0 z-30 bg-white pointer-events-none" | |
| style={{ clipPath: "circle(0px at -300px -300px)" }} | |
| > | |
| <div className="px-6 md:px-12 lg:px-20 xl:px-32 py-8 md:py-12"> | |
| <div className="mb-16 md:mb-24"> | |
| <div> | |
| <h2 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-extralight text-black tracking-tighter leading-[0.95]"> | |
| I build models that | |
| </h2> | |
| </div> | |
| <div> | |
| <h2 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl tracking-tighter leading-[0.95]"> | |
| <span className="font-black text-black">transform</span> | |
| <span className="font-extralight text-black/50"> raw </span> | |
| <span className="font-black text-transparent bg-clip-text bg-gradient-to-r from-black via-neutral-600 to-black/70">data</span> | |
| </h2> | |
| </div> | |
| </div> | |
| <div className="grid md:grid-cols-2 gap-8 md:gap-16 mb-16 md:mb-24"> | |
| <div className="space-y-4"> | |
| <p className="text-black/30 text-xs uppercase tracking-[0.3em] font-medium">The Spark</p> | |
| <p className="text-lg md:text-xl lg:text-2xl text-black/80 font-light leading-relaxed"> | |
| What started as <span className="text-black font-medium">late-night experiments</span> turned into a lifelong | |
| obsession with uncovering patterns, building models, and | |
| letting data tell its story. | |
| </p> | |
| </div> | |
| <div className="space-y-4"> | |
| <p className="text-black/30 text-xs uppercase tracking-[0.3em] font-medium">The Philosophy</p> | |
| <p className="text-lg md:text-xl lg:text-2xl text-black/80 font-light leading-relaxed"> | |
| Great analysis is <span className="text-black font-medium">invisible</span>—it removes noise, | |
| reveals clarity, and makes complex data feel | |
| wonderfully simple. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Diagonal Crossing Strips */} | |
| <div ref={stripsWrapperRef} className="relative h-20 md:h-32"> | |
| <div | |
| className="absolute w-[300vw] left-[-100vw] bg-white py-5 md:py-6 shadow-2xl z-10" | |
| style={{ transform: "rotate(-4deg)", top: "5%" }} | |
| > | |
| <div className="about-strip-1-marquee whitespace-nowrap" style={{ display: "inline-block", width: "200%" }}> | |
| <span className="text-xl md:text-2xl lg:text-3xl font-black text-[#050505] tracking-wide inline-block uppercase"> | |
| {strip1Text.repeat(15)} | |
| </span> | |
| </div> | |
| </div> | |
| <div | |
| className="absolute w-[300vw] left-[-100vw] bg-[#151515] py-5 md:py-6 shadow-2xl border-y border-white/5 z-20" | |
| style={{ transform: "rotate(4deg)", top: "50%" }} | |
| > | |
| <div className="about-strip-2-marquee whitespace-nowrap" style={{ display: "inline-block", width: "200%" }}> | |
| <span className="text-xl md:text-2xl lg:text-3xl font-black text-white/90 tracking-wide inline-block uppercase"> | |
| {strip2Text.repeat(15)} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| ); | |
| } | |