Files
asepharyana bf75d9deef feat(ui): vendor Aceternity registry components + showcase gallery
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.
2026-09-20 19:10:43 +07:00

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>
);
};