mirror of
https://github.com/neosubhamoy/neodlp-website.git
synced 2026-02-04 15:42:22 +05:30
(chore): initial MVP release
This commit is contained in:
59
src/components/ui/button.tsx
Normal file
59
src/components/ui/button.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
41
src/components/ui/command-snippet.tsx
Normal file
41
src/components/ui/command-snippet.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import { Copy, Terminal } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const CommandSnippet = ({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const handleCopy = () => {
|
||||
const command = children?.toString();
|
||||
if (command) {
|
||||
navigator.clipboard.writeText(command).then(() => {
|
||||
toast("Copied to clipboard!");
|
||||
}).catch((err) => {
|
||||
toast("Failed to copy to clipboard!");
|
||||
console.error("Failed to copy command:", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col rounded-lg border border-border overflow-hidden min-w-[20rem] md:min-w-[25rem]">
|
||||
<div className="flex items-center justify-between px-4 py-1 gap-2 bg-muted">
|
||||
<div className="flex items-center gap-2">
|
||||
<Terminal className="size-4" />
|
||||
<span className="text-xs text-muted-foreground">{title}</span>
|
||||
</div>
|
||||
<button className="flex items-center gap-2 cursor-pointer text-muted-foreground hover:text-primary" onClick={handleCopy}>
|
||||
<Copy className="size-4" />
|
||||
<span className="text-xs">Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
<code className="flex px-4 py-2">
|
||||
<p className="">{children}</p>
|
||||
</code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
282
src/components/ui/container-scroll-animation.tsx
Normal file
282
src/components/ui/container-scroll-animation.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
"use client";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useScroll, useTransform, motion, MotionValue } from "motion/react";
|
||||
import { ArrowRight, CircleArrowDown, Download, Puzzle } from "lucide-react";
|
||||
import { type LatestRelease } from "@/types/release";
|
||||
import { SlidingButton } from "@/components/ui/sliding-button";
|
||||
import { getOS } from "@/lib/utils";
|
||||
import type { OS } from "@/types/os";
|
||||
|
||||
export const ContainerScroll = ({
|
||||
titleComponent,
|
||||
children,
|
||||
release,
|
||||
}: {
|
||||
titleComponent: string | React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
release: LatestRelease;
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
});
|
||||
const [isMobile, setIsMobile] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
setIsMobile(window.innerWidth <= 768);
|
||||
};
|
||||
checkMobile();
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => {
|
||||
window.removeEventListener("resize", checkMobile);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const scaleDimensions = () => {
|
||||
return isMobile ? [0.7, 0.9] : [1.05, 1];
|
||||
};
|
||||
|
||||
const rotate = useTransform(scrollYProgress, [0, 1], [20, 0]);
|
||||
const scale = useTransform(scrollYProgress, [0, 1], scaleDimensions());
|
||||
const translate = useTransform(scrollYProgress, [0, 1], [0, -100]);
|
||||
|
||||
const [os, setOS] = useState<OS>('unknown');
|
||||
useEffect(() => {
|
||||
const detectedOS = getOS();
|
||||
setOS(detectedOS);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-[60rem] md:h-[80rem] flex items-center justify-center relative p-2 md:p-20"
|
||||
ref={containerRef}
|
||||
>
|
||||
<div className="absolute top-20 left-0 inset-x-auto w-full z-20 flex flex-col items-center justify-center">
|
||||
<button
|
||||
className="bg-slate-800 no-underline group cursor-pointer relative shadow-2xl shadow-zinc-900 rounded-full p-px text-xs font-semibold leading-6 text-white inline-block"
|
||||
onClick={() => {
|
||||
window.open("https://github.com/neosubhamoy/neodlp/releases/latest", "_blank");
|
||||
}}
|
||||
>
|
||||
<span className="absolute inset-0 overflow-hidden rounded-full">
|
||||
<span className="absolute inset-0 rounded-full bg-[image:radial-gradient(75%_100%_at_50%_0%,rgba(56,189,248,0.6)_0%,rgba(56,189,248,0)_75%)] opacity-0 transition-opacity duration-500 group-hover:opacity-100"></span>
|
||||
</span>
|
||||
<div className="relative flex space-x-2 items-center z-10 rounded-full bg-white dark:bg-zinc-950 text-black dark:text-white py-0.5 px-4 ring-1 ring-white/10 ">
|
||||
<span>{`✨ v${release?.version ?? 'Unknown'} released. explore what's new`}</span>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M10.75 8.75L14.25 12L10.75 15.25"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-emerald-400/0 via-emerald-400/90 to-emerald-400/0 transition-opacity duration-500 group-hover:opacity-40"></span>
|
||||
</button>
|
||||
<div className="flex justify-center items-center gap-4 mt-10">
|
||||
{/* <button className="px-4 py-2 rounded-md text-center relative overflow-hidden cursor-pointer bg-black dark:bg-white dark:text-black text-white flex justify-center group/windows-download-btn">
|
||||
<span className="group-hover/windows-download-btn:translate-x-60 text-center transition duration-500 flex flex-col justify-center items-center">
|
||||
<span className="font-semibold flex items-center gap-2">
|
||||
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z"/>
|
||||
</svg>
|
||||
Download for Windows
|
||||
</span>
|
||||
<span className="text-xs">(x64) EXE Installer</span>
|
||||
</span>
|
||||
<div className="-translate-x-60 group-hover/windows-download-btn:translate-x-0 flex items-center justify-center absolute inset-0 transition duration-500 text-white z-20">
|
||||
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
|
||||
<Download className="size-4" />
|
||||
<span>Download Now</span>
|
||||
</div>
|
||||
</div>
|
||||
</button> */}
|
||||
{os === "windows" && (
|
||||
<SlidingButton
|
||||
slidingContent={
|
||||
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
|
||||
<Download className="size-4" />
|
||||
<span>Download Now</span>
|
||||
</div>
|
||||
}
|
||||
as="a"
|
||||
href={release.platforms["windows-x86_64"].url}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="font-semibold flex items-center gap-2">
|
||||
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z"/>
|
||||
</svg>
|
||||
Download for Windows
|
||||
</span>
|
||||
<span className="text-xs">(x64) EXE Installer</span>
|
||||
</SlidingButton>
|
||||
)}
|
||||
{os === "macos" && (
|
||||
<>
|
||||
<SlidingButton
|
||||
slidingContent={
|
||||
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
|
||||
<Download className="size-4" />
|
||||
<span>Download Now</span>
|
||||
</div>
|
||||
}
|
||||
as="a"
|
||||
href={release.platforms["darwin-x86_64-dmg"].url}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="font-semibold flex items-center gap-2">
|
||||
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
|
||||
<path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/>
|
||||
</svg>
|
||||
Download for MacOS
|
||||
</span>
|
||||
<span className="text-xs">(x64) DMG Installer</span>
|
||||
</SlidingButton>
|
||||
<SlidingButton
|
||||
slidingContent={
|
||||
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
|
||||
<Download className="size-4" />
|
||||
<span>Download Now</span>
|
||||
</div>
|
||||
}
|
||||
as="a"
|
||||
href={release.platforms["darwin-aarch64-dmg"].url}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="font-semibold flex items-center gap-2">
|
||||
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
|
||||
<path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/>
|
||||
</svg>
|
||||
Download for MacOS
|
||||
</span>
|
||||
<span className="text-xs">(ARM64) DMG Installer</span>
|
||||
</SlidingButton>
|
||||
</>
|
||||
)}
|
||||
{os === "linux" && (
|
||||
<>
|
||||
<SlidingButton
|
||||
slidingContent={
|
||||
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
|
||||
<Download className="size-4" />
|
||||
<span>Download Now</span>
|
||||
</div>
|
||||
}
|
||||
as="a"
|
||||
href={release.platforms["linux-x86_64-deb"].url}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="font-semibold flex items-center gap-2">
|
||||
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M220.8 123.3c1 .5 1.8 1.7 3 1.7 1.1 0 2.8-.4 2.9-1.5 .2-1.4-1.9-2.3-3.2-2.9-1.7-.7-3.9-1-5.5-.1-.4 .2-.8 .7-.6 1.1 .3 1.3 2.3 1.1 3.4 1.7zm-21.9 1.7c1.2 0 2-1.2 3-1.7 1.1-.6 3.1-.4 3.5-1.6 .2-.4-.2-.9-.6-1.1-1.6-.9-3.8-.6-5.5 .1-1.3 .6-3.4 1.5-3.2 2.9 .1 1 1.8 1.5 2.8 1.4zM420 403.8c-3.6-4-5.3-11.6-7.2-19.7-1.8-8.1-3.9-16.8-10.5-22.4-1.3-1.1-2.6-2.1-4-2.9-1.3-.8-2.7-1.5-4.1-2 9.2-27.3 5.6-54.5-3.7-79.1-11.4-30.1-31.3-56.4-46.5-74.4-17.1-21.5-33.7-41.9-33.4-72C311.1 85.4 315.7 .1 234.8 0 132.4-.2 158 103.4 156.9 135.2c-1.7 23.4-6.4 41.8-22.5 64.7-18.9 22.5-45.5 58.8-58.1 96.7-6 17.9-8.8 36.1-6.2 53.3-6.5 5.8-11.4 14.7-16.6 20.2-4.2 4.3-10.3 5.9-17 8.3s-14 6-18.5 14.5c-2.1 3.9-2.8 8.1-2.8 12.4 0 3.9 .6 7.9 1.2 11.8 1.2 8.1 2.5 15.7 .8 20.8-5.2 14.4-5.9 24.4-2.2 31.7 3.8 7.3 11.4 10.5 20.1 12.3 17.3 3.6 40.8 2.7 59.3 12.5 19.8 10.4 39.9 14.1 55.9 10.4 11.6-2.6 21.1-9.6 25.9-20.2 12.5-.1 26.3-5.4 48.3-6.6 14.9-1.2 33.6 5.3 55.1 4.1 .6 2.3 1.4 4.6 2.5 6.7v.1c8.3 16.7 23.8 24.3 40.3 23 16.6-1.3 34.1-11 48.3-27.9 13.6-16.4 36-23.2 50.9-32.2 7.4-4.5 13.4-10.1 13.9-18.3 .4-8.2-4.4-17.3-15.5-29.7zM223.7 87.3c9.8-22.2 34.2-21.8 44-.4 6.5 14.2 3.6 30.9-4.3 40.4-1.6-.8-5.9-2.6-12.6-4.9 1.1-1.2 3.1-2.7 3.9-4.6 4.8-11.8-.2-27-9.1-27.3-7.3-.5-13.9 10.8-11.8 23-4.1-2-9.4-3.5-13-4.4-1-6.9-.3-14.6 2.9-21.8zM183 75.8c10.1 0 20.8 14.2 19.1 33.5-3.5 1-7.1 2.5-10.2 4.6 1.2-8.9-3.3-20.1-9.6-19.6-8.4 .7-9.8 21.2-1.8 28.1 1 .8 1.9-.2-5.9 5.5-15.6-14.6-10.5-52.1 8.4-52.1zm-13.6 60.7c6.2-4.6 13.6-10 14.1-10.5 4.7-4.4 13.5-14.2 27.9-14.2 7.1 0 15.6 2.3 25.9 8.9 6.3 4.1 11.3 4.4 22.6 9.3 8.4 3.5 13.7 9.7 10.5 18.2-2.6 7.1-11 14.4-22.7 18.1-11.1 3.6-19.8 16-38.2 14.9-3.9-.2-7-1-9.6-2.1-8-3.5-12.2-10.4-20-15-8.6-4.8-13.2-10.4-14.7-15.3-1.4-4.9 0-9 4.2-12.3zm3.3 334c-2.7 35.1-43.9 34.4-75.3 18-29.9-15.8-68.6-6.5-76.5-21.9-2.4-4.7-2.4-12.7 2.6-26.4v-.2c2.4-7.6 .6-16-.6-23.9-1.2-7.8-1.8-15 .9-20 3.5-6.7 8.5-9.1 14.8-11.3 10.3-3.7 11.8-3.4 19.6-9.9 5.5-5.7 9.5-12.9 14.3-18 5.1-5.5 10-8.1 17.7-6.9 8.1 1.2 15.1 6.8 21.9 16l19.6 35.6c9.5 19.9 43.1 48.4 41 68.9zm-1.4-25.9c-4.1-6.6-9.6-13.6-14.4-19.6 7.1 0 14.2-2.2 16.7-8.9 2.3-6.2 0-14.9-7.4-24.9-13.5-18.2-38.3-32.5-38.3-32.5-13.5-8.4-21.1-18.7-24.6-29.9s-3-23.3-.3-35.2c5.2-22.9 18.6-45.2 27.2-59.2 2.3-1.7 .8 3.2-8.7 20.8-8.5 16.1-24.4 53.3-2.6 82.4 .6-20.7 5.5-41.8 13.8-61.5 12-27.4 37.3-74.9 39.3-112.7 1.1 .8 4.6 3.2 6.2 4.1 4.6 2.7 8.1 6.7 12.6 10.3 12.4 10 28.5 9.2 42.4 1.2 6.2-3.5 11.2-7.5 15.9-9 9.9-3.1 17.8-8.6 22.3-15 7.7 30.4 25.7 74.3 37.2 95.7 6.1 11.4 18.3 35.5 23.6 64.6 3.3-.1 7 .4 10.9 1.4 13.8-35.7-11.7-74.2-23.3-84.9-4.7-4.6-4.9-6.6-2.6-6.5 12.6 11.2 29.2 33.7 35.2 59 2.8 11.6 3.3 23.7 .4 35.7 16.4 6.8 35.9 17.9 30.7 34.8-2.2-.1-3.2 0-4.2 0 3.2-10.1-3.9-17.6-22.8-26.1-19.6-8.6-36-8.6-38.3 12.5-12.1 4.2-18.3 14.7-21.4 27.3-2.8 11.2-3.6 24.7-4.4 39.9-.5 7.7-3.6 18-6.8 29-32.1 22.9-76.7 32.9-114.3 7.2zm257.4-11.5c-.9 16.8-41.2 19.9-63.2 46.5-13.2 15.7-29.4 24.4-43.6 25.5s-26.5-4.8-33.7-19.3c-4.7-11.1-2.4-23.1 1.1-36.3 3.7-14.2 9.2-28.8 9.9-40.6 .8-15.2 1.7-28.5 4.2-38.7 2.6-10.3 6.6-17.2 13.7-21.1 .3-.2 .7-.3 1-.5 .8 13.2 7.3 26.6 18.8 29.5 12.6 3.3 30.7-7.5 38.4-16.3 9-.3 15.7-.9 22.6 5.1 9.9 8.5 7.1 30.3 17.1 41.6 10.6 11.6 14 19.5 13.7 24.6zM173.3 148.7c2 1.9 4.7 4.5 8 7.1 6.6 5.2 15.8 10.6 27.3 10.6 11.6 0 22.5-5.9 31.8-10.8 4.9-2.6 10.9-7 14.8-10.4s5.9-6.3 3.1-6.6-2.6 2.6-6 5.1c-4.4 3.2-9.7 7.4-13.9 9.8-7.4 4.2-19.5 10.2-29.9 10.2s-18.7-4.8-24.9-9.7c-3.1-2.5-5.7-5-7.7-6.9-1.5-1.4-1.9-4.6-4.3-4.9-1.4-.1-1.8 3.7 1.7 6.5z"/>
|
||||
</svg>
|
||||
Download for Linux
|
||||
</span>
|
||||
<span className="text-xs">(x64) DEB Package</span>
|
||||
</SlidingButton>
|
||||
<SlidingButton
|
||||
slidingContent={
|
||||
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
|
||||
<Download className="size-4" />
|
||||
<span>Download Now</span>
|
||||
</div>
|
||||
}
|
||||
as="a"
|
||||
href={release.platforms["linux-x86_64-rpm"].url}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="font-semibold flex items-center gap-2">
|
||||
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M220.8 123.3c1 .5 1.8 1.7 3 1.7 1.1 0 2.8-.4 2.9-1.5 .2-1.4-1.9-2.3-3.2-2.9-1.7-.7-3.9-1-5.5-.1-.4 .2-.8 .7-.6 1.1 .3 1.3 2.3 1.1 3.4 1.7zm-21.9 1.7c1.2 0 2-1.2 3-1.7 1.1-.6 3.1-.4 3.5-1.6 .2-.4-.2-.9-.6-1.1-1.6-.9-3.8-.6-5.5 .1-1.3 .6-3.4 1.5-3.2 2.9 .1 1 1.8 1.5 2.8 1.4zM420 403.8c-3.6-4-5.3-11.6-7.2-19.7-1.8-8.1-3.9-16.8-10.5-22.4-1.3-1.1-2.6-2.1-4-2.9-1.3-.8-2.7-1.5-4.1-2 9.2-27.3 5.6-54.5-3.7-79.1-11.4-30.1-31.3-56.4-46.5-74.4-17.1-21.5-33.7-41.9-33.4-72C311.1 85.4 315.7 .1 234.8 0 132.4-.2 158 103.4 156.9 135.2c-1.7 23.4-6.4 41.8-22.5 64.7-18.9 22.5-45.5 58.8-58.1 96.7-6 17.9-8.8 36.1-6.2 53.3-6.5 5.8-11.4 14.7-16.6 20.2-4.2 4.3-10.3 5.9-17 8.3s-14 6-18.5 14.5c-2.1 3.9-2.8 8.1-2.8 12.4 0 3.9 .6 7.9 1.2 11.8 1.2 8.1 2.5 15.7 .8 20.8-5.2 14.4-5.9 24.4-2.2 31.7 3.8 7.3 11.4 10.5 20.1 12.3 17.3 3.6 40.8 2.7 59.3 12.5 19.8 10.4 39.9 14.1 55.9 10.4 11.6-2.6 21.1-9.6 25.9-20.2 12.5-.1 26.3-5.4 48.3-6.6 14.9-1.2 33.6 5.3 55.1 4.1 .6 2.3 1.4 4.6 2.5 6.7v.1c8.3 16.7 23.8 24.3 40.3 23 16.6-1.3 34.1-11 48.3-27.9 13.6-16.4 36-23.2 50.9-32.2 7.4-4.5 13.4-10.1 13.9-18.3 .4-8.2-4.4-17.3-15.5-29.7zM223.7 87.3c9.8-22.2 34.2-21.8 44-.4 6.5 14.2 3.6 30.9-4.3 40.4-1.6-.8-5.9-2.6-12.6-4.9 1.1-1.2 3.1-2.7 3.9-4.6 4.8-11.8-.2-27-9.1-27.3-7.3-.5-13.9 10.8-11.8 23-4.1-2-9.4-3.5-13-4.4-1-6.9-.3-14.6 2.9-21.8zM183 75.8c10.1 0 20.8 14.2 19.1 33.5-3.5 1-7.1 2.5-10.2 4.6 1.2-8.9-3.3-20.1-9.6-19.6-8.4 .7-9.8 21.2-1.8 28.1 1 .8 1.9-.2-5.9 5.5-15.6-14.6-10.5-52.1 8.4-52.1zm-13.6 60.7c6.2-4.6 13.6-10 14.1-10.5 4.7-4.4 13.5-14.2 27.9-14.2 7.1 0 15.6 2.3 25.9 8.9 6.3 4.1 11.3 4.4 22.6 9.3 8.4 3.5 13.7 9.7 10.5 18.2-2.6 7.1-11 14.4-22.7 18.1-11.1 3.6-19.8 16-38.2 14.9-3.9-.2-7-1-9.6-2.1-8-3.5-12.2-10.4-20-15-8.6-4.8-13.2-10.4-14.7-15.3-1.4-4.9 0-9 4.2-12.3zm3.3 334c-2.7 35.1-43.9 34.4-75.3 18-29.9-15.8-68.6-6.5-76.5-21.9-2.4-4.7-2.4-12.7 2.6-26.4v-.2c2.4-7.6 .6-16-.6-23.9-1.2-7.8-1.8-15 .9-20 3.5-6.7 8.5-9.1 14.8-11.3 10.3-3.7 11.8-3.4 19.6-9.9 5.5-5.7 9.5-12.9 14.3-18 5.1-5.5 10-8.1 17.7-6.9 8.1 1.2 15.1 6.8 21.9 16l19.6 35.6c9.5 19.9 43.1 48.4 41 68.9zm-1.4-25.9c-4.1-6.6-9.6-13.6-14.4-19.6 7.1 0 14.2-2.2 16.7-8.9 2.3-6.2 0-14.9-7.4-24.9-13.5-18.2-38.3-32.5-38.3-32.5-13.5-8.4-21.1-18.7-24.6-29.9s-3-23.3-.3-35.2c5.2-22.9 18.6-45.2 27.2-59.2 2.3-1.7 .8 3.2-8.7 20.8-8.5 16.1-24.4 53.3-2.6 82.4 .6-20.7 5.5-41.8 13.8-61.5 12-27.4 37.3-74.9 39.3-112.7 1.1 .8 4.6 3.2 6.2 4.1 4.6 2.7 8.1 6.7 12.6 10.3 12.4 10 28.5 9.2 42.4 1.2 6.2-3.5 11.2-7.5 15.9-9 9.9-3.1 17.8-8.6 22.3-15 7.7 30.4 25.7 74.3 37.2 95.7 6.1 11.4 18.3 35.5 23.6 64.6 3.3-.1 7 .4 10.9 1.4 13.8-35.7-11.7-74.2-23.3-84.9-4.7-4.6-4.9-6.6-2.6-6.5 12.6 11.2 29.2 33.7 35.2 59 2.8 11.6 3.3 23.7 .4 35.7 16.4 6.8 35.9 17.9 30.7 34.8-2.2-.1-3.2 0-4.2 0 3.2-10.1-3.9-17.6-22.8-26.1-19.6-8.6-36-8.6-38.3 12.5-12.1 4.2-18.3 14.7-21.4 27.3-2.8 11.2-3.6 24.7-4.4 39.9-.5 7.7-3.6 18-6.8 29-32.1 22.9-76.7 32.9-114.3 7.2zm257.4-11.5c-.9 16.8-41.2 19.9-63.2 46.5-13.2 15.7-29.4 24.4-43.6 25.5s-26.5-4.8-33.7-19.3c-4.7-11.1-2.4-23.1 1.1-36.3 3.7-14.2 9.2-28.8 9.9-40.6 .8-15.2 1.7-28.5 4.2-38.7 2.6-10.3 6.6-17.2 13.7-21.1 .3-.2 .7-.3 1-.5 .8 13.2 7.3 26.6 18.8 29.5 12.6 3.3 30.7-7.5 38.4-16.3 9-.3 15.7-.9 22.6 5.1 9.9 8.5 7.1 30.3 17.1 41.6 10.6 11.6 14 19.5 13.7 24.6zM173.3 148.7c2 1.9 4.7 4.5 8 7.1 6.6 5.2 15.8 10.6 27.3 10.6 11.6 0 22.5-5.9 31.8-10.8 4.9-2.6 10.9-7 14.8-10.4s5.9-6.3 3.1-6.6-2.6 2.6-6 5.1c-4.4 3.2-9.7 7.4-13.9 9.8-7.4 4.2-19.5 10.2-29.9 10.2s-18.7-4.8-24.9-9.7c-3.1-2.5-5.7-5-7.7-6.9-1.5-1.4-1.9-4.6-4.3-4.9-1.4-.1-1.8 3.7 1.7 6.5z"/>
|
||||
</svg>
|
||||
Download for Linux
|
||||
</span>
|
||||
<span className="text-xs">(x64) RPM Package</span>
|
||||
</SlidingButton>
|
||||
</>
|
||||
)}
|
||||
<SlidingButton
|
||||
slidingContent={
|
||||
<div className="flex items-center justify-center gap-2 text-white dark:text-black">
|
||||
<ArrowRight className="size-4" />
|
||||
<span>Goto All Downloads</span>
|
||||
</div>
|
||||
}
|
||||
as="a"
|
||||
href="/download"
|
||||
>
|
||||
<span className="font-semibold flex items-center gap-2">
|
||||
<CircleArrowDown className="size-4" />
|
||||
All Downloads
|
||||
</span>
|
||||
<span className="text-xs">Choose from all download options</span>
|
||||
</SlidingButton>
|
||||
</div>
|
||||
<div className="flex justify-center items-center gap-2 mt-5 text-sm text-muted-foreground">
|
||||
<Puzzle className=" size-4 text-emerald-500" /> Looking for browser integration? <a className="font-semibold underline transition-all duration-500 hover:text-white" href="/extension">Get Here</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="py-10 md:py-40 w-full relative"
|
||||
style={{
|
||||
perspective: "1000px",
|
||||
}}
|
||||
>
|
||||
<Header translate={translate} titleComponent={titleComponent} />
|
||||
<Card rotate={rotate} translate={translate} scale={scale}>
|
||||
{children}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Header = ({ translate, titleComponent }: any) => {
|
||||
return (
|
||||
<motion.div
|
||||
style={{
|
||||
translateY: translate,
|
||||
}}
|
||||
className="div max-w-5xl mx-auto text-center"
|
||||
>
|
||||
{titleComponent}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Card = ({
|
||||
rotate,
|
||||
scale,
|
||||
children,
|
||||
}: {
|
||||
rotate: MotionValue<number>;
|
||||
scale: MotionValue<number>;
|
||||
translate: MotionValue<number>;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<motion.div
|
||||
style={{
|
||||
rotateX: rotate,
|
||||
scale,
|
||||
boxShadow:
|
||||
"0 0 #0000004d, 0 9px 20px #0000004a, 0 37px 37px #00000042, 0 84px 50px #00000026, 0 149px 60px #0000000a, 0 233px 65px #00000003",
|
||||
}}
|
||||
className="max-w-5xl -mt-12 mx-auto aspect-video w-full border-4 border-[#6C6C6C] p-2 md:p-6 bg-[#222222] rounded-[30px] shadow-2xl"
|
||||
>
|
||||
<div className="w-full aspect-video overflow-hidden rounded-2xl bg-gray-100 dark:bg-zinc-900 md:rounded-2xl md:p-4 ">
|
||||
{children}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
255
src/components/ui/dropdown-menu.tsx
Normal file
255
src/components/ui/dropdown-menu.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function DropdownMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
}
|
||||
190
src/components/ui/glowing-effect.tsx
Normal file
190
src/components/ui/glowing-effect.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
"use client";
|
||||
|
||||
import { memo, useCallback, useEffect, useRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { animate } from "motion/react";
|
||||
|
||||
interface GlowingEffectProps {
|
||||
blur?: number;
|
||||
inactiveZone?: number;
|
||||
proximity?: number;
|
||||
spread?: number;
|
||||
variant?: "default" | "white";
|
||||
glow?: boolean;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
movementDuration?: number;
|
||||
borderWidth?: number;
|
||||
}
|
||||
const GlowingEffect = memo(
|
||||
({
|
||||
blur = 0,
|
||||
inactiveZone = 0.7,
|
||||
proximity = 0,
|
||||
spread = 20,
|
||||
variant = "default",
|
||||
glow = false,
|
||||
className,
|
||||
movementDuration = 2,
|
||||
borderWidth = 1,
|
||||
disabled = true,
|
||||
}: GlowingEffectProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const lastPosition = useRef({ x: 0, y: 0 });
|
||||
const animationFrameRef = useRef<number>(0);
|
||||
|
||||
const handleMove = useCallback(
|
||||
(e?: MouseEvent | { x: number; y: number }) => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
}
|
||||
|
||||
animationFrameRef.current = requestAnimationFrame(() => {
|
||||
const element = containerRef.current;
|
||||
if (!element) return;
|
||||
|
||||
const { left, top, width, height } = element.getBoundingClientRect();
|
||||
const mouseX = e?.x ?? lastPosition.current.x;
|
||||
const mouseY = e?.y ?? lastPosition.current.y;
|
||||
|
||||
if (e) {
|
||||
lastPosition.current = { x: mouseX, y: mouseY };
|
||||
}
|
||||
|
||||
const center = [left + width * 0.5, top + height * 0.5];
|
||||
const distanceFromCenter = Math.hypot(
|
||||
mouseX - center[0],
|
||||
mouseY - center[1]
|
||||
);
|
||||
const inactiveRadius = 0.5 * Math.min(width, height) * inactiveZone;
|
||||
|
||||
if (distanceFromCenter < inactiveRadius) {
|
||||
element.style.setProperty("--active", "0");
|
||||
return;
|
||||
}
|
||||
|
||||
const isActive =
|
||||
mouseX > left - proximity &&
|
||||
mouseX < left + width + proximity &&
|
||||
mouseY > top - proximity &&
|
||||
mouseY < top + height + proximity;
|
||||
|
||||
element.style.setProperty("--active", isActive ? "1" : "0");
|
||||
|
||||
if (!isActive) return;
|
||||
|
||||
const currentAngle =
|
||||
parseFloat(element.style.getPropertyValue("--start")) || 0;
|
||||
let targetAngle =
|
||||
(180 * Math.atan2(mouseY - center[1], mouseX - center[0])) /
|
||||
Math.PI +
|
||||
90;
|
||||
|
||||
const angleDiff = ((targetAngle - currentAngle + 180) % 360) - 180;
|
||||
const newAngle = currentAngle + angleDiff;
|
||||
|
||||
animate(currentAngle, newAngle, {
|
||||
duration: movementDuration,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
onUpdate: (value) => {
|
||||
element.style.setProperty("--start", String(value));
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[inactiveZone, proximity, movementDuration]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) return;
|
||||
|
||||
const handleScroll = () => handleMove();
|
||||
const handlePointerMove = (e: PointerEvent) => handleMove(e);
|
||||
|
||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
||||
document.body.addEventListener("pointermove", handlePointerMove, {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
}
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
document.body.removeEventListener("pointermove", handlePointerMove);
|
||||
};
|
||||
}, [handleMove, disabled]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none absolute -inset-px hidden rounded-[inherit] border opacity-0 transition-opacity",
|
||||
glow && "opacity-100",
|
||||
variant === "white" && "border-white",
|
||||
disabled && "!block"
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={
|
||||
{
|
||||
"--blur": `${blur}px`,
|
||||
"--spread": spread,
|
||||
"--start": "0",
|
||||
"--active": "0",
|
||||
"--glowingeffect-border-width": `${borderWidth}px`,
|
||||
"--repeating-conic-gradient-times": "5",
|
||||
"--gradient":
|
||||
variant === "white"
|
||||
? `repeating-conic-gradient(
|
||||
from 236.84deg at 50% 50%,
|
||||
var(--black),
|
||||
var(--black) calc(25% / var(--repeating-conic-gradient-times))
|
||||
)`
|
||||
: `radial-gradient(circle, #dd7bbb 10%, #dd7bbb00 20%),
|
||||
radial-gradient(circle at 40% 40%, #d79f1e 5%, #d79f1e00 15%),
|
||||
radial-gradient(circle at 60% 60%, #5a922c 10%, #5a922c00 20%),
|
||||
radial-gradient(circle at 40% 60%, #4c7894 10%, #4c789400 20%),
|
||||
repeating-conic-gradient(
|
||||
from 236.84deg at 50% 50%,
|
||||
#dd7bbb 0%,
|
||||
#d79f1e calc(25% / var(--repeating-conic-gradient-times)),
|
||||
#5a922c calc(50% / var(--repeating-conic-gradient-times)),
|
||||
#4c7894 calc(75% / var(--repeating-conic-gradient-times)),
|
||||
#dd7bbb calc(100% / var(--repeating-conic-gradient-times))
|
||||
)`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 rounded-[inherit] opacity-100 transition-opacity",
|
||||
glow && "opacity-100",
|
||||
blur > 0 && "blur-[var(--blur)] ",
|
||||
className,
|
||||
disabled && "!hidden"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"glow",
|
||||
"rounded-[inherit]",
|
||||
'after:content-[""] after:rounded-[inherit] after:absolute after:inset-[calc(-1*var(--glowingeffect-border-width))]',
|
||||
"after:[border:var(--glowingeffect-border-width)_solid_transparent]",
|
||||
"after:[background:var(--gradient)] after:[background-attachment:fixed]",
|
||||
"after:opacity-[var(--active)] after:transition-opacity after:duration-300",
|
||||
"after:[mask-clip:padding-box,border-box]",
|
||||
"after:[mask-composite:intersect]",
|
||||
"after:[mask-image:linear-gradient(#0000,#0000),conic-gradient(from_calc((var(--start)-var(--spread))*1deg),#00000000_0deg,#fff,#00000000_calc(var(--spread)*2deg))]"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
GlowingEffect.displayName = "GlowingEffect";
|
||||
|
||||
export { GlowingEffect };
|
||||
288
src/components/ui/resizable-navbar.tsx
Normal file
288
src/components/ui/resizable-navbar.tsx
Normal file
@@ -0,0 +1,288 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Menu as IconMenu, X as IconX } from "lucide-react";
|
||||
import {
|
||||
motion,
|
||||
AnimatePresence,
|
||||
useScroll,
|
||||
useMotionValueEvent,
|
||||
} from "motion/react";
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
|
||||
|
||||
interface NavbarProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface NavBodyProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
interface NavItemsProps {
|
||||
items: {
|
||||
name: string;
|
||||
link: string;
|
||||
}[];
|
||||
className?: string;
|
||||
onItemClick?: () => void;
|
||||
}
|
||||
|
||||
interface MobileNavProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
interface MobileNavHeaderProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface MobileNavMenuProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const Navbar = ({ children, className }: NavbarProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { scrollY } = useScroll({
|
||||
target: ref,
|
||||
offset: ["start start", "end start"],
|
||||
});
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
|
||||
useMotionValueEvent(scrollY, "change", (latest) => {
|
||||
if (latest > 100) {
|
||||
setVisible(true);
|
||||
} else {
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={ref}
|
||||
// IMPORTANT: Change this to class of `fixed` if you want the navbar to be fixed
|
||||
className={cn("sticky inset-x-0 top-4 lg:top-6 xl:top-8 z-40 w-full", className)}
|
||||
>
|
||||
{React.Children.map(children, (child) =>
|
||||
React.isValidElement(child)
|
||||
? React.cloneElement(
|
||||
child as React.ReactElement<{ visible?: boolean }>,
|
||||
{ visible },
|
||||
)
|
||||
: child,
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NavBody = ({ children, className, visible }: NavBodyProps) => {
|
||||
return (
|
||||
<motion.div
|
||||
animate={{
|
||||
backdropFilter: visible ? "blur(10px)" : "none",
|
||||
boxShadow: visible
|
||||
? "0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset"
|
||||
: "none",
|
||||
width: visible ? "40%" : "100%",
|
||||
y: visible ? 20 : 0,
|
||||
}}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 50,
|
||||
}}
|
||||
style={{
|
||||
minWidth: "800px",
|
||||
}}
|
||||
className={cn(
|
||||
"relative z-[60] mx-auto hidden w-full max-w-7xl flex-row items-center justify-between self-start rounded-full bg-transparent px-4 py-2 lg:flex dark:bg-transparent",
|
||||
visible && "bg-white/80 dark:bg-neutral-950/80",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NavItems = ({ items, className, onItemClick }: NavItemsProps) => {
|
||||
const [hovered, setHovered] = useState<number | null>(null);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
onMouseLeave={() => setHovered(null)}
|
||||
className={cn(
|
||||
"absolute inset-0 hidden flex-1 flex-row items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800 lg:flex lg:space-x-2",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{items.map((item, idx) => (
|
||||
<a
|
||||
onMouseEnter={() => setHovered(idx)}
|
||||
onClick={onItemClick}
|
||||
className="relative px-4 py-2 text-neutral-600 dark:text-neutral-300"
|
||||
key={`link-${idx}`}
|
||||
href={item.link}
|
||||
>
|
||||
{hovered === idx && (
|
||||
<motion.div
|
||||
layoutId="hovered"
|
||||
className="absolute inset-0 h-full w-full rounded-full bg-gray-100 dark:bg-neutral-800"
|
||||
/>
|
||||
)}
|
||||
<span className="relative z-20">{item.name}</span>
|
||||
</a>
|
||||
))}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileNav = ({ children, className, visible }: MobileNavProps) => {
|
||||
return (
|
||||
<motion.div
|
||||
animate={{
|
||||
backdropFilter: visible ? "blur(10px)" : "none",
|
||||
boxShadow: visible
|
||||
? "0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset"
|
||||
: "none",
|
||||
width: visible ? "90%" : "100%",
|
||||
paddingRight: visible ? "12px" : "0px",
|
||||
paddingLeft: visible ? "12px" : "0px",
|
||||
borderRadius: visible ? "4px" : "2rem",
|
||||
y: visible ? 20 : 0,
|
||||
}}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 50,
|
||||
}}
|
||||
className={cn(
|
||||
"relative z-50 mx-auto flex w-full max-w-[calc(100vw-2rem)] flex-col items-center justify-between bg-transparent px-0 py-2 lg:hidden",
|
||||
visible && "bg-white/80 dark:bg-neutral-950/80",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileNavHeader = ({
|
||||
children,
|
||||
className,
|
||||
}: MobileNavHeaderProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-row items-center justify-between",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileNavMenu = ({
|
||||
children,
|
||||
className,
|
||||
isOpen,
|
||||
onClose,
|
||||
}: MobileNavMenuProps) => {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className={cn(
|
||||
"absolute inset-x-0 top-16 z-50 flex w-full flex-col items-start justify-start gap-4 rounded-lg bg-white px-4 py-8 shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset] dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileNavToggle = ({
|
||||
isOpen,
|
||||
onClick,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
return isOpen ? (
|
||||
<IconX className="text-black dark:text-white" onClick={onClick} />
|
||||
) : (
|
||||
<IconMenu className="text-black dark:text-white" onClick={onClick} />
|
||||
);
|
||||
};
|
||||
|
||||
export const NavbarLogo = () => {
|
||||
return (
|
||||
<a
|
||||
href="/"
|
||||
className="relative z-20 mr-4 flex items-center space-x-4 px-2 py-1 text-sm font-normal text-black"
|
||||
>
|
||||
<img
|
||||
src="/neodlp.svg"
|
||||
alt="logo"
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
<span className="font-bold text-2xl text-black dark:text-white">NeoDLP</span>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export const NavbarButton = ({
|
||||
href,
|
||||
as: Tag = "a",
|
||||
children,
|
||||
className,
|
||||
variant = "primary",
|
||||
...props
|
||||
}: {
|
||||
href?: string;
|
||||
as?: React.ElementType;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
variant?: "primary" | "secondary" | "dark" | "gradient";
|
||||
} & (
|
||||
| React.ComponentPropsWithoutRef<"a">
|
||||
| React.ComponentPropsWithoutRef<"button">
|
||||
)) => {
|
||||
const baseStyles =
|
||||
"px-4 py-2 rounded-md bg-white button bg-white text-black text-sm font-bold relative cursor-pointer hover:-translate-y-0.5 transition duration-200 inline-block text-center";
|
||||
|
||||
const variantStyles = {
|
||||
primary:
|
||||
"shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]",
|
||||
secondary: "bg-transparent shadow-none dark:text-white",
|
||||
dark: "bg-black text-white shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]",
|
||||
gradient:
|
||||
"bg-gradient-to-b from-blue-500 to-blue-700 text-white shadow-[0px_2px_0px_0px_rgba(255,255,255,0.3)_inset]",
|
||||
};
|
||||
|
||||
return (
|
||||
<Tag
|
||||
href={href || undefined}
|
||||
className={cn(baseStyles, variantStyles[variant], className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
26
src/components/ui/separator.tsx
Normal file
26
src/components/ui/separator.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Separator({
|
||||
className,
|
||||
orientation = "horizontal",
|
||||
decorative = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||
return (
|
||||
<SeparatorPrimitive.Root
|
||||
data-slot="separator-root"
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Separator }
|
||||
52
src/components/ui/sliding-button.tsx
Normal file
52
src/components/ui/sliding-button.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { type ReactNode } from "react";
|
||||
|
||||
export const SlidingButton = ({
|
||||
children,
|
||||
slidingContent,
|
||||
as: Tag = "button",
|
||||
href,
|
||||
target,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
children: ReactNode;
|
||||
slidingContent: ReactNode;
|
||||
as?: React.ElementType;
|
||||
href?: string;
|
||||
target?: string;
|
||||
className?: string;
|
||||
} & (
|
||||
| React.ComponentPropsWithoutRef<"a">
|
||||
| React.ComponentPropsWithoutRef<"button">
|
||||
)) => {
|
||||
return (
|
||||
<Tag
|
||||
className={cn(
|
||||
"px-4 py-2 rounded-md bg-black dark:bg-white dark:text-black text-white text-center relative overflow-hidden cursor-pointer flex justify-center",
|
||||
`group/sliding-button`,
|
||||
className
|
||||
)}
|
||||
href={href}
|
||||
target={target}
|
||||
{...props}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'text-center transition duration-500 flex flex-col justify-center items-center',
|
||||
`group-hover/sliding-button:translate-x-60`
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center absolute inset-0 transition duration-500 text-white z-20',
|
||||
`-translate-x-60 group-hover/sliding-button:translate-x-0`
|
||||
)}
|
||||
>
|
||||
{slidingContent}
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
23
src/components/ui/sonner.tsx
Normal file
23
src/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
style={
|
||||
{
|
||||
"--normal-bg": "var(--popover)",
|
||||
"--normal-text": "var(--popover-foreground)",
|
||||
"--normal-border": "var(--border)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Toaster }
|
||||
78
src/components/ui/wobble-card.tsx
Normal file
78
src/components/ui/wobble-card.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import { motion } from "motion/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const WobbleCard = ({
|
||||
children,
|
||||
containerClassName,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
containerClassName?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
const handleMouseMove = (event: React.MouseEvent<HTMLElement>) => {
|
||||
const { clientX, clientY } = event;
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const x = (clientX - (rect.left + rect.width / 2)) / 20;
|
||||
const y = (clientY - (rect.top + rect.height / 2)) / 20;
|
||||
setMousePosition({ x, y });
|
||||
};
|
||||
return (
|
||||
<motion.section
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => {
|
||||
setIsHovering(false);
|
||||
setMousePosition({ x: 0, y: 0 });
|
||||
}}
|
||||
style={{
|
||||
transform: isHovering
|
||||
? `translate3d(${mousePosition.x}px, ${mousePosition.y}px, 0) scale3d(1, 1, 1)`
|
||||
: "translate3d(0px, 0px, 0) scale3d(1, 1, 1)",
|
||||
transition: "transform 0.1s ease-out",
|
||||
}}
|
||||
className={cn(
|
||||
"mx-auto w-full bg-indigo-800 relative rounded-2xl overflow-hidden",
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="relative h-full [background-image:radial-gradient(88%_100%_at_top,rgba(255,255,255,0.5),rgba(255,255,255,0))] sm:mx-0 sm:rounded-2xl overflow-hidden"
|
||||
style={{
|
||||
boxShadow:
|
||||
"0 10px 32px rgba(34, 42, 53, 0.12), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.05), 0 4px 6px rgba(34, 42, 53, 0.08), 0 24px 108px rgba(47, 48, 55, 0.10)",
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
style={{
|
||||
transform: isHovering
|
||||
? `translate3d(${-mousePosition.x}px, ${-mousePosition.y}px, 0) scale3d(1.03, 1.03, 1)`
|
||||
: "translate3d(0px, 0px, 0) scale3d(1, 1, 1)",
|
||||
transition: "transform 0.1s ease-out",
|
||||
}}
|
||||
className={cn("h-full px-4 py-20 sm:px-10", className)}
|
||||
>
|
||||
<Noise />
|
||||
{children}
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.section>
|
||||
);
|
||||
};
|
||||
|
||||
const Noise = () => {
|
||||
return (
|
||||
<div
|
||||
className="absolute inset-0 w-full h-full scale-[1.2] transform opacity-10 [mask-image:radial-gradient(#fff,transparent,75%)]"
|
||||
style={{
|
||||
backgroundImage: "url(/noise.webp)",
|
||||
backgroundSize: "30%",
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user