From 58394b4236865a5198a020a4cdc04920ece05770 Mon Sep 17 00:00:00 2001 From: PlatyPuspus <23h46.shovin@sjec.ac.in> Date: Sat, 21 Feb 2026 15:19:04 +0530 Subject: [PATCH] Fixed the Jerks of the Marquee effect --- app/components/CTA.tsx | 26 ++- app/components/Categories.tsx | 294 +++++++++++++++++++++++++--------- app/components/Hero.tsx | 33 ++-- next.config.ts | 9 +- 4 files changed, 258 insertions(+), 104 deletions(-) diff --git a/app/components/CTA.tsx b/app/components/CTA.tsx index 688cca7..c5ab417 100644 --- a/app/components/CTA.tsx +++ b/app/components/CTA.tsx @@ -6,13 +6,7 @@ import Link from "next/link"; export default function CTA() { return ( -
- {/* Background Elements */} -
-
-
-
- +
-
- +
+ LIMITED TIME
-

+

Ready to Showcase
- + Your Masterpiece?

-

+

Join hundreds of talented artists from SJEC in this creative celebration. Submit your artwork and let the world vote for your talent. @@ -49,7 +43,7 @@ export default function CTA() { VIEW RULES @@ -61,11 +55,11 @@ export default function CTA() { whileInView={{ opacity: 1 }} viewport={{ once: true }} transition={{ delay: 0.3 }} - className="mt-12 inline-flex items-center gap-3 bg-zinc-100 px-6 py-3 rounded-full" + className="mt-12 inline-flex items-center gap-3 bg-zinc-900 border border-zinc-800 px-6 py-3 rounded-full" > - - Submissions close on February 28, 2026 + + Submissions close on February 28, 2026 diff --git a/app/components/Categories.tsx b/app/components/Categories.tsx index ad6a9c8..aac3cbe 100644 --- a/app/components/Categories.tsx +++ b/app/components/Categories.tsx @@ -1,60 +1,235 @@ "use client"; +import { useEffect, useRef } from "react"; import { motion } from "framer-motion"; -import { Palette, Monitor, FileImage, ArrowRight } from "lucide-react"; +import { Palette, Monitor, FileImage, ArrowRight, LucideIcon } from "lucide-react"; import Link from "next/link"; +import gsap from "gsap"; const categories = [ { icon: Monitor, title: "Digital Art", description: - "Create stunning digital masterpieces using any software of your choice. From illustrations to photo manipulations.", + "Create stunning digital masterpieces using any software of your choice.", color: "from-violet-500 to-purple-600", - bgColor: "bg-violet-50", + bgColor: "bg-violet-500", }, { icon: Palette, title: "Paintings", description: - "Traditional paintings in any medium - oil, acrylic, watercolor. Capture the essence of classic artistry.", + "Traditional paintings in any medium - oil, acrylic, watercolor.", color: "from-orange-500 to-red-500", - bgColor: "bg-orange-50", + bgColor: "bg-orange-500", }, { icon: FileImage, title: "Poster Making", description: - "Design impactful posters that communicate powerful messages. Blend creativity with purpose.", + "Design impactful posters that communicate powerful messages.", color: "from-lime-500 to-green-500", - bgColor: "bg-lime-50", + bgColor: "bg-lime-500", }, ]; -const containerVariants = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { - staggerChildren: 0.2, - }, - }, -}; +// BounceCards Component for Category Cards +interface CategoryCard { + icon: LucideIcon; + title: string; + description: string; + color: string; + bgColor: string; +} -const itemVariants = { - hidden: { opacity: 0, y: 30 }, - visible: { - opacity: 1, - y: 0, - transition: { - duration: 0.6, - }, - }, -}; +interface BounceCardsProps { + className?: string; + cards: CategoryCard[]; + containerWidth?: number; + containerHeight?: number; + animationDelay?: number; + animationStagger?: number; + easeType?: string; + transformStyles?: string[]; + enableHover?: boolean; +} + +function BounceCards({ + className = "", + cards, + containerWidth = 500, + containerHeight = 400, + animationDelay = 0.5, + animationStagger = 0.08, + easeType = "elastic.out(1, 0.8)", + transformStyles = [ + "rotate(-8deg) translate(-200px)", + "rotate(0deg)", + "rotate(8deg) translate(200px)", + ], + enableHover = true, +}: BounceCardsProps) { + const containerRef = useRef(null); + + useEffect(() => { + const ctx = gsap.context(() => { + gsap.fromTo( + ".card", + { scale: 0 }, + { + scale: 1, + stagger: animationStagger, + ease: easeType, + delay: animationDelay, + } + ); + }, containerRef); + return () => ctx.revert(); + }, [animationDelay, animationStagger, easeType]); + + const getNoRotationTransform = (transformStr: string): string => { + const hasRotate = /rotate\([\s\S]*?\)/.test(transformStr); + if (hasRotate) { + return transformStr.replace(/rotate\([\s\S]*?\)/, "rotate(0deg)"); + } else if (transformStr === "none") { + return "rotate(0deg)"; + } else { + return `${transformStr} rotate(0deg)`; + } + }; + + const getPushedTransform = ( + baseTransform: string, + offsetX: number + ): string => { + const translateRegex = /translate\(([-0-9.]+)px\)/; + const match = baseTransform.match(translateRegex); + if (match) { + const currentX = parseFloat(match[1]); + const newX = currentX + offsetX; + return baseTransform.replace(translateRegex, `translate(${newX}px)`); + } else { + return baseTransform === "none" + ? `translate(${offsetX}px)` + : `${baseTransform} translate(${offsetX}px)`; + } + }; + + const pushSiblings = (hoveredIdx: number) => { + const q = gsap.utils.selector(containerRef); + if (!enableHover || !containerRef.current) return; + + cards.forEach((_, i) => { + const selector = q(`.card-${i}`); + gsap.killTweensOf(selector); + + const baseTransform = transformStyles[i] || "none"; + + if (i === hoveredIdx) { + const noRotation = getNoRotationTransform(baseTransform); + gsap.to(selector, { + transform: noRotation, + zIndex: 10, + duration: 0.4, + ease: "back.out(1.4)", + overwrite: "auto", + }); + } else { + const offsetX = i < hoveredIdx ? -80 : 80; + const pushedTransform = getPushedTransform(baseTransform, offsetX); + + const distance = Math.abs(hoveredIdx - i); + const delay = distance * 0.05; + + gsap.to(selector, { + transform: pushedTransform, + zIndex: 1, + duration: 0.4, + ease: "back.out(1.4)", + delay, + overwrite: "auto", + }); + } + }); + }; + + const resetSiblings = () => { + if (!enableHover || !containerRef.current) return; + const q = gsap.utils.selector(containerRef); + + cards.forEach((_, i) => { + const selector = q(`.card-${i}`); + gsap.killTweensOf(selector); + + const baseTransform = transformStyles[i] || "none"; + gsap.to(selector, { + transform: baseTransform, + zIndex: cards.length - i, + duration: 0.4, + ease: "back.out(1.4)", + overwrite: "auto", + }); + }); + }; + + return ( +

+ {cards.map((card, idx) => ( +
pushSiblings(idx)} + onMouseLeave={resetSiblings} + > + {/* Icon */} +
+ +
+ + {/* Title */} +

{card.title}

+ + {/* Description */} +

+ {card.description} +

+ + {/* Submit Link */} +
+ + Submit Now + + +
+
+ ))} +
+ ); +} export default function Categories() { + const transformStyles = [ + "rotate(-8deg) translate(-220px)", + "rotate(0deg)", + "rotate(8deg) translate(220px)", + ]; + return ( -
+
{/* Section Header */} CATEGORIES -

+

Three Ways to - - {" "} - Express - + Express

-

+

Choose your canvas, unleash your creativity. Each category offers a unique way to showcase your artistic vision.

- {/* Categories Grid */} + {/* BounceCards with Category Cards */} - {categories.map((category) => ( - - {/* Background Gradient on Hover */} -
- - {/* Icon */} -
- -
- - {/* Content */} -

- {category.title} -

-

- {category.description} -

- - {/* Link */} - - Submit Now - - - - ))} +
diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index d5f1a19..bccbbad 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -14,10 +14,12 @@ function TileGrid() { const handleMouseEnter = useCallback((index: number) => { const tile = tilesRef.current[index]; if (tile) { + gsap.killTweensOf(tile); gsap.to(tile, { rotateY: 180, - duration: 0.4, - ease: "power2.out", + duration: 0.5, + ease: "power2.inOut", + overwrite: true, }); } }, []); @@ -25,10 +27,12 @@ function TileGrid() { const handleMouseLeave = useCallback((index: number) => { const tile = tilesRef.current[index]; if (tile) { + gsap.killTweensOf(tile); gsap.to(tile, { rotateY: 0, - duration: 0.4, - ease: "power2.out", + duration: 0.5, + ease: "power2.inOut", + overwrite: true, }); } }, []); @@ -53,7 +57,10 @@ function TileGrid() {
{ tilesRef.current[index] = el; }} className="w-full h-full relative" - style={{ transformStyle: "preserve-3d" }} + style={{ + transformStyle: "preserve-3d", + willChange: "transform", + }} > {/* Front - Black */}
-

+

From canvas to screen, showcase your creativity and let your artwork speak to the world.

-
+
+ + {/* Scroll Indicator */} +
+ Scroll +
+
- - {/* Scroll Indicator - positioned above the marquee */} -
- Scroll -
-
{/* Bottom Marquee */} diff --git a/next.config.ts b/next.config.ts index e9ffa30..a9194aa 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,14 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "images.unsplash.com", + }, + ], + }, }; export default nextConfig;