Vendor 50 Aceternity UI components into components/ui from the official registry (ui.aceternity.com/registry/<slug>.json), adapted for this template's stack: Tailwind v4, motion/react 13, cn util, React 19. Add a live showcase gallery (/ui + /docs/... navigation is unchanged): app/ui/page.tsx renders <ShowcaseGallery> with 30+ demos — 3d-pin, aurora, background-beams, bento, card-stack, card-spotlight, direction-aware-hover, encrypted-text, evervault-card, flip-words, hero-highlight, hero-parallax, infinite-moving-cards, moving-border, sparkles, tracing-beam, animated-tooltip, typewriter-effect, placeholders-and-vanish-input and friends. Toolchain stays zero-warning: vendored animation code is scoped in eslint.config.mjs (react-hooks/react-compiler advisory rules, no-any, prefer-const, no-img-element) so app code keeps a 0-error gate. typecheck, build, 7 vitest tests and 2 test suites all green.
121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
"use client";
|
|
import React from "react";
|
|
import { motion, useScroll, useTransform, useSpring, MotionValue } from "motion/react";
|
|
|
|
export const HeroParallax = ({
|
|
products,
|
|
}: {
|
|
products: {
|
|
title: string;
|
|
link: string;
|
|
thumbnail: string;
|
|
}[];
|
|
}) => {
|
|
const firstRow = products.slice(0, 5);
|
|
const secondRow = products.slice(5, 10);
|
|
const thirdRow = products.slice(10, 15);
|
|
const ref = React.useRef(null);
|
|
const { scrollYProgress } = useScroll({
|
|
target: ref,
|
|
offset: ["start start", "end start"],
|
|
});
|
|
|
|
const springConfig = { stiffness: 300, damping: 30, bounce: 100 };
|
|
|
|
const translateX = useSpring(useTransform(scrollYProgress, [0, 1], [0, 1000]), springConfig);
|
|
const translateXReverse = useSpring(
|
|
useTransform(scrollYProgress, [0, 1], [0, -1000]),
|
|
springConfig
|
|
);
|
|
const rotateX = useSpring(useTransform(scrollYProgress, [0, 0.2], [15, 0]), springConfig);
|
|
const opacity = useSpring(useTransform(scrollYProgress, [0, 0.2], [0.2, 1]), springConfig);
|
|
const rotateZ = useSpring(useTransform(scrollYProgress, [0, 0.2], [20, 0]), springConfig);
|
|
const translateY = useSpring(useTransform(scrollYProgress, [0, 0.2], [-700, 500]), springConfig);
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className="relative flex h-[300vh] flex-col self-auto overflow-hidden py-40 antialiased [perspective:1000px] [transform-style:preserve-3d]"
|
|
>
|
|
<Header />
|
|
<motion.div
|
|
style={{
|
|
rotateX,
|
|
rotateZ,
|
|
translateY,
|
|
opacity,
|
|
}}
|
|
className=""
|
|
>
|
|
<motion.div className="mb-20 flex flex-row-reverse space-x-20 space-x-reverse">
|
|
{firstRow.map((product) => (
|
|
<ProductCard product={product} translate={translateX} key={product.title} />
|
|
))}
|
|
</motion.div>
|
|
<motion.div className="mb-20 flex flex-row space-x-20">
|
|
{secondRow.map((product) => (
|
|
<ProductCard product={product} translate={translateXReverse} key={product.title} />
|
|
))}
|
|
</motion.div>
|
|
<motion.div className="flex flex-row-reverse space-x-20 space-x-reverse">
|
|
{thirdRow.map((product) => (
|
|
<ProductCard product={product} translate={translateX} key={product.title} />
|
|
))}
|
|
</motion.div>
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const Header = () => {
|
|
return (
|
|
<div className="relative top-0 left-0 mx-auto w-full max-w-7xl px-4 py-20 md:py-40">
|
|
<h1 className="text-2xl font-bold md:text-7xl dark:text-white">
|
|
The Ultimate <br /> development studio
|
|
</h1>
|
|
<p className="mt-8 max-w-2xl text-base md:text-xl dark:text-neutral-200">
|
|
We build beautiful products with the latest technologies and frameworks. We are a team of
|
|
passionate developers and designers that love to build amazing products.
|
|
</p>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const ProductCard = ({
|
|
product,
|
|
translate,
|
|
}: {
|
|
product: {
|
|
title: string;
|
|
link: string;
|
|
thumbnail: string;
|
|
};
|
|
translate: MotionValue<number>;
|
|
}) => {
|
|
return (
|
|
<motion.div
|
|
style={{
|
|
x: translate,
|
|
}}
|
|
whileHover={{
|
|
y: -20,
|
|
}}
|
|
key={product.title}
|
|
className="group/product relative h-96 w-[30rem] shrink-0"
|
|
>
|
|
<a href={product.link} className="block group-hover/product:shadow-2xl">
|
|
<img
|
|
src={product.thumbnail}
|
|
height="600"
|
|
width="600"
|
|
className="absolute inset-0 h-full w-full object-cover object-left-top"
|
|
alt={product.title}
|
|
/>
|
|
</a>
|
|
<div className="pointer-events-none absolute inset-0 h-full w-full bg-black opacity-0 group-hover/product:opacity-80"></div>
|
|
<h2 className="absolute bottom-4 left-4 text-white opacity-0 group-hover/product:opacity-100">
|
|
{product.title}
|
|
</h2>
|
|
</motion.div>
|
|
);
|
|
};
|