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,132 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useRef } from "react";
|
||||
|
||||
export const GlareCard = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
const isPointerInside = useRef(false);
|
||||
const refElement = useRef<HTMLDivElement>(null);
|
||||
const state = useRef({
|
||||
glare: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
background: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
rotate: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
const containerStyle = {
|
||||
"--m-x": "50%",
|
||||
"--m-y": "50%",
|
||||
"--r-x": "0deg",
|
||||
"--r-y": "0deg",
|
||||
"--bg-x": "50%",
|
||||
"--bg-y": "50%",
|
||||
"--duration": "300ms",
|
||||
"--foil-size": "100%",
|
||||
"--opacity": "0",
|
||||
"--radius": "48px",
|
||||
"--easing": "ease",
|
||||
"--transition": "var(--duration) var(--easing)",
|
||||
} as any;
|
||||
|
||||
const backgroundStyle = {
|
||||
"--step": "5%",
|
||||
"--foil-svg": `url("data:image/svg+xml,%3Csvg width='26' height='26' viewBox='0 0 26 26' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.99994 3.419C2.99994 3.419 21.6142 7.43646 22.7921 12.153C23.97 16.8695 3.41838 23.0306 3.41838 23.0306' stroke='white' stroke-width='5' stroke-miterlimit='3.86874' stroke-linecap='round' style='mix-blend-mode:darken'/%3E%3C/svg%3E")`,
|
||||
"--pattern": "var(--foil-svg) center/100% no-repeat",
|
||||
"--rainbow":
|
||||
"repeating-linear-gradient( 0deg,rgb(255,119,115) calc(var(--step) * 1),rgba(255,237,95,1) calc(var(--step) * 2),rgba(168,255,95,1) calc(var(--step) * 3),rgba(131,255,247,1) calc(var(--step) * 4),rgba(120,148,255,1) calc(var(--step) * 5),rgb(216,117,255) calc(var(--step) * 6),rgb(255,119,115) calc(var(--step) * 7) ) 0% var(--bg-y)/200% 700% no-repeat",
|
||||
"--diagonal":
|
||||
"repeating-linear-gradient( 128deg,#0e152e 0%,hsl(180,10%,60%) 3.8%,hsl(180,10%,60%) 4.5%,hsl(180,10%,60%) 5.2%,#0e152e 10%,#0e152e 12% ) var(--bg-x) var(--bg-y)/300% no-repeat",
|
||||
"--shade":
|
||||
"radial-gradient( farthest-corner circle at var(--m-x) var(--m-y),rgba(255,255,255,0.1) 12%,rgba(255,255,255,0.15) 20%,rgba(255,255,255,0.25) 120% ) var(--bg-x) var(--bg-y)/300% no-repeat",
|
||||
backgroundBlendMode: "hue, hue, hue, overlay",
|
||||
};
|
||||
|
||||
const updateStyles = () => {
|
||||
if (refElement.current) {
|
||||
console.log(state.current);
|
||||
const { background, rotate, glare } = state.current;
|
||||
refElement.current?.style.setProperty("--m-x", `${glare.x}%`);
|
||||
refElement.current?.style.setProperty("--m-y", `${glare.y}%`);
|
||||
refElement.current?.style.setProperty("--r-x", `${rotate.x}deg`);
|
||||
refElement.current?.style.setProperty("--r-y", `${rotate.y}deg`);
|
||||
refElement.current?.style.setProperty("--bg-x", `${background.x}%`);
|
||||
refElement.current?.style.setProperty("--bg-y", `${background.y}%`);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={containerStyle}
|
||||
className="relative isolate [aspect-ratio:17/21] w-[320px] transition-transform delay-[var(--delay)] duration-[var(--duration)] ease-[var(--easing)] will-change-transform [contain:layout_style] [perspective:600px]"
|
||||
ref={refElement}
|
||||
onPointerMove={(event) => {
|
||||
const rotateFactor = 0.4;
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const position = {
|
||||
x: event.clientX - rect.left,
|
||||
y: event.clientY - rect.top,
|
||||
};
|
||||
const percentage = {
|
||||
x: (100 / rect.width) * position.x,
|
||||
y: (100 / rect.height) * position.y,
|
||||
};
|
||||
const delta = {
|
||||
x: percentage.x - 50,
|
||||
y: percentage.y - 50,
|
||||
};
|
||||
|
||||
const { background, rotate, glare } = state.current;
|
||||
background.x = 50 + percentage.x / 4 - 12.5;
|
||||
background.y = 50 + percentage.y / 3 - 16.67;
|
||||
rotate.x = -(delta.x / 3.5);
|
||||
rotate.y = delta.y / 2;
|
||||
rotate.x *= rotateFactor;
|
||||
rotate.y *= rotateFactor;
|
||||
glare.x = percentage.x;
|
||||
glare.y = percentage.y;
|
||||
|
||||
updateStyles();
|
||||
}}
|
||||
onPointerEnter={() => {
|
||||
isPointerInside.current = true;
|
||||
if (refElement.current) {
|
||||
setTimeout(() => {
|
||||
if (isPointerInside.current) {
|
||||
refElement.current?.style.setProperty("--duration", "0s");
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}}
|
||||
onPointerLeave={() => {
|
||||
isPointerInside.current = false;
|
||||
if (refElement.current) {
|
||||
refElement.current.style.removeProperty("--duration");
|
||||
refElement.current?.style.setProperty("--r-x", `0deg`);
|
||||
refElement.current?.style.setProperty("--r-y", `0deg`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="grid h-full origin-center [transform:rotateY(var(--r-x))_rotateX(var(--r-y))] overflow-hidden rounded-[var(--radius)] border border-slate-800 transition-transform delay-[var(--delay)] duration-[var(--duration)] ease-[var(--easing)] will-change-transform hover:filter-none hover:[--duration:200ms] hover:[--easing:linear] hover:[--opacity:0.6]">
|
||||
<div className="grid h-full w-full mix-blend-soft-light [clip-path:inset(0_0_0_0_round_var(--radius))] [grid-area:1/1]">
|
||||
<div className={cn("h-full w-full bg-slate-950", className)}>{children}</div>
|
||||
</div>
|
||||
<div className="transition-background will-change-background grid h-full w-full opacity-[var(--opacity)] mix-blend-soft-light transition-opacity delay-[var(--delay)] duration-[var(--duration)] ease-[var(--easing)] [background:radial-gradient(farthest-corner_circle_at_var(--m-x)_var(--m-y),_rgba(255,255,255,0.8)_10%,_rgba(255,255,255,0.65)_20%,_rgba(255,255,255,0)_90%)] [clip-path:inset(0_0_1px_0_round_var(--radius))] [grid-area:1/1]" />
|
||||
<div
|
||||
className="will-change-background after:grid-area-[inherit] after:bg-repeat-[inherit] after:bg-attachment-[inherit] after:bg-origin-[inherit] after:bg-clip-[inherit] relative grid h-full w-full opacity-[var(--opacity)] [background-blend-mode:hue_hue_hue_overlay] mix-blend-color-dodge transition-opacity [background:var(--pattern),_var(--rainbow),_var(--diagonal),_var(--shade)] [clip-path:inset(0_0_1px_0_round_var(--radius))] [grid-area:1/1] after:bg-[inherit] after:[background-size:varLine truncated
|
||||
style={{ ...backgroundStyle }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user