portfolio / src /components /AchievementsSection.tsx
jashdoshi77's picture
changed content form web developemetn to data sceince
251578d
"use client";
import { useRef, useEffect, useState } from "react";
interface TextRevealItem {
title: string;
hoverTitle: string;
description: string;
}
const textItems: TextRevealItem[] = [
{
title: "DATA SCIENCE",
hoverTitle: "INSIGHT DRIVEN",
description: "Extracting actionable insights from complex datasets with statistical rigor.",
},
{
title: "MACHINE LEARNING",
hoverTitle: "DEEP LEARNING",
description: "Building predictive models that learn, adapt, and scale.",
},
{
title: "DATA ANALYSIS",
hoverTitle: "VISUALIZATION",
description: "Turning raw numbers into compelling visual narratives and dashboards.",
},
{
title: "AI / ML",
hoverTitle: "INTELLIGENT",
description: "Designing AI systems that solve real-world problems with precision.",
},
{
title: "PYTHON",
hoverTitle: "BACKEND",
description: "Engineering robust Python backends and scalable data pipelines.",
},
];
const baseFontStyle: React.CSSProperties = {
fontSize: "clamp(1.6rem, 7vw, 8.5rem)",
fontWeight: 800,
letterSpacing: "-0.04em",
lineHeight: 1,
textTransform: "uppercase",
margin: 0,
fontFamily: "var(--font-sans)",
};
export default function AchievementsSection() {
const sectionRef = useRef<HTMLElement>(null);
const textRefs = useRef<(HTMLHeadingElement | null)[]>([]);
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [tappedIndex, setTappedIndex] = useState<number | null>(null);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const checkMobile = () => setIsMobile(window.innerWidth < 768);
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);
// Native scroll-driven color sweep — guaranteed to work
useEffect(() => {
const section = sectionRef.current;
if (!section) return;
let ticking = false;
const onScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
const rect = section.getBoundingClientRect();
const vh = window.innerHeight;
// Slow sweep: takes ~2 viewport heights of scrolling
// Progress 0 → 1 mapped across a wider scroll range
const rawProgress = (vh - rect.top) / (vh * 1.8);
const progress = Math.max(0, Math.min(1, rawProgress));
textRefs.current.forEach((el, i) => {
if (!el) return;
// Small stagger per row for gradual cascade
const rowStart = i * 0.07;
const rowDuration = 0.6;
const rowEnd = rowStart + rowDuration;
let rowProgress: number;
if (progress <= rowStart) {
rowProgress = 0;
} else if (progress >= rowEnd) {
rowProgress = 1;
} else {
rowProgress = (progress - rowStart) / rowDuration;
}
// Move background position from 100% (dim) to 0% (bright)
const bgPos = 100 - rowProgress * 100;
el.style.backgroundPosition = `${bgPos}% 0%`;
});
ticking = false;
});
ticking = true;
}
};
// Listen on window for Lenis scroll events
window.addEventListener("scroll", onScroll, { passive: true });
// Also run once on mount
onScroll();
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
<section
id="clients"
ref={sectionRef}
className="relative overflow-hidden"
style={{
background: "#050505",
minHeight: isMobile ? "auto" : "100vh",
display: "flex",
flexDirection: "column",
justifyContent: "center",
paddingTop: isMobile ? "4vh" : "0",
paddingBottom: isMobile ? "4vh" : "15vh",
}}
>
{/* Text rows */}
<div style={{ padding: "0 clamp(20px, 5vw, 80px)" }}>
{textItems.map((item, index) => {
const isHovered = isMobile ? tappedIndex === index : hoveredIndex === index;
return (
<div
key={index}
className="text-reveal-row"
onMouseEnter={() => !isMobile && setHoveredIndex(index)}
onMouseLeave={() => !isMobile && setHoveredIndex(null)}
onClick={() => isMobile && setTappedIndex(tappedIndex === index ? null : index)}
style={{
position: "relative",
overflow: "hidden",
borderBottom:
index < textItems.length - 1
? "1px solid rgba(255,255,255,0.06)"
: "none",
touchAction: "manipulation",
}}
>
{/* Base text — gradient background-clip, position driven by scroll */}
<div
style={{
position: "relative",
padding: "clamp(8px, 1.4vw, 18px) 0",
display: "flex",
alignItems: "center",
}}
>
<h3
ref={(el) => { textRefs.current[index] = el; }}
style={{
...baseFontStyle,
background: "linear-gradient(to right, rgba(255,255,255,0.9) 50%, rgba(255,255,255,0.15) 50%)",
backgroundSize: "200% 100%",
backgroundPosition: "100% 0%",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
}}
>
{item.title}
</h3>
</div>
{/* Hover reveal layer */}
<div
style={{
position: "absolute",
inset: 0,
background: "#ffffff",
clipPath: isHovered
? "inset(0 0 0 0)"
: "inset(50% 0 50% 0)",
transition: "clip-path 0.45s cubic-bezier(0.4, 0, 0.2, 1)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: `clamp(8px, 1.4vw, 18px) clamp(20px, 5vw, 80px)`,
gap: "2rem",
zIndex: 10,
pointerEvents: "none",
}}
>
<h3 style={{ ...baseFontStyle, color: "#000000" }}>
{item.hoverTitle}
</h3>
<p
className="hidden md:block"
style={{
fontSize: "clamp(0.7rem, 1vw, 0.9rem)",
color: "rgba(0,0,0,0.55)",
maxWidth: "260px",
lineHeight: 1.5,
margin: 0,
textAlign: "right",
flexShrink: 0,
opacity: isHovered ? 1 : 0,
transition: "opacity 0.3s ease 0.1s",
}}
>
{item.description}
</p>
</div>
</div>
);
})}
</div>
{/* Bottom gradient fade */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: "15vh",
background: "linear-gradient(to bottom, transparent, #050505)",
pointerEvents: "none",
zIndex: 5,
}}
/>
</section>
);
}