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.
This commit is contained in:
1 parent
bc64b74a34
commit
bf75d9deef
53 files changed
+6781
-1
No files matched your search
@@ -0,0 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
export const ThreeDMarquee = ({ images, className }: { images: string[]; className?: string }) => {
|
||||
// Split the images array into 4 equal parts
|
||||
const chunkSize = Math.ceil(images.length / 4);
|
||||
const chunks = Array.from({ length: 4 }, (_, colIndex) => {
|
||||
const start = colIndex * chunkSize;
|
||||
return images.slice(start, start + chunkSize);
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className={cn("mx-auto block h-[600px] overflow-hidden rounded-2xl max-sm:h-100", className)}
|
||||
>
|
||||
<div className="flex size-full items-center justify-center">
|
||||
<div className="size-[1720px] shrink-0 scale-50 sm:scale-75 lg:scale-100">
|
||||
<div
|
||||
style={{
|
||||
transform: "rotateX(55deg) rotateY(0deg) rotateZ(-45deg)",
|
||||
}}
|
||||
className="relative top-96 right-[50%] grid size-full origin-top-left grid-cols-4 gap-8 transform-3d"
|
||||
>
|
||||
{chunks.map((subarray, colIndex) => (
|
||||
<motion.div
|
||||
animate={{ y: colIndex % 2 === 0 ? 100 : -100 }}
|
||||
transition={{
|
||||
duration: colIndex % 2 === 0 ? 10 : 15,
|
||||
repeat: Infinity,
|
||||
repeatType: "reverse",
|
||||
}}
|
||||
key={colIndex + "marquee"}
|
||||
className="flex flex-col items-start gap-8"
|
||||
>
|
||||
<GridLineVertical className="-left-4" offset="80px" />
|
||||
{subarray.map((image, imageIndex) => (
|
||||
<div className="relative" key={imageIndex + image}>
|
||||
<GridLineHorizontal className="-top-4" offset="20px" />
|
||||
<motion.img
|
||||
whileHover={{
|
||||
y: -10,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
key={imageIndex + image}
|
||||
src={image}
|
||||
alt={`Image ${imageIndex + 1}`}
|
||||
className="aspect-[970/700] rounded-lg object-cover ring ring-gray-950/5 hover:shadow-2xl"
|
||||
width={970}
|
||||
height={700}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GridLineHorizontal = ({ className, offset }: { className?: string; offset?: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--background": "#ffffff",
|
||||
"--color": "rgba(0, 0, 0, 0.2)",
|
||||
"--height": "1px",
|
||||
"--width": "5px",
|
||||
"--fade-stop": "90%",
|
||||
"--offset": offset || "200px", //-100px if you want to keep the line inside
|
||||
"--color-dark": "rgba(255, 255, 255, 0.2)",
|
||||
maskComposite: "exclude",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
"absolute left-[calc(var(--offset)/2*-1)] h-[var(--height)] w-[calc(100%+var(--offset))]",
|
||||
"bg-[linear-gradient(to_right,var(--color),var(--color)_50%,transparent_0,transparent)]",
|
||||
"[background-size:var(--width)_var(--height)]",
|
||||
"[mask:linear-gradient(to_left,var(--background)_var(--fade-stop),transparent),_linear-gradient(to_right,var(--background)_var(--fade-stop),transparent),_linear-gradient(black,black)]",
|
||||
"[mask-composite:exclude]",
|
||||
"z-30",
|
||||
"dark:bg-[linear-gradient(to_right,var(--color-dark),var(--color-dark)_50%,transparent_0,transparent)]",
|
||||
className
|
||||
)}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
const GridLineVertical = ({ className, offset }: { className?: string; offset?: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--background": "#ffffff",
|
||||
"--color": "rgba(0, 0, 0, 0.2)",
|
||||
"--height": "5px",
|
||||
"--width": "1px",
|
||||
"--fade-stop": "90%",
|
||||
"--offset": offset || "150px", //-100px if you want to keep the line inside
|
||||
"--color-dark": "rgba(255, 255, 255, 0.2)",
|
||||
maskComposite: "exclude",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
"absolute top-[calc(var(--offset)/2*-1)] h-[calc(100%+var(--offset))] w-[var(--width)]",
|
||||
"bg-[linear-gradient(to_bottom,var(--color),var(--color)_50%,transparent_0,transparent)]",
|
||||
"[background-size:var(--width)_var(--height)]",
|
||||
"[mask:linear-gradient(to_top,var(--background)_var(--fade-stop),transparent),_linear-gradient(to_bottom,var(--background)_var(--fade-stop),transparent),_linear-gradient(black,black)]",
|
||||
"[mask-composite:exclude]",
|
||||
"z-30",
|
||||
"dark:bg-[linear-gradient(to_bottom,var(--color-dark),var(--color-dark)_50%,transparent_0,transparent)]",
|
||||
className
|
||||
)}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user