- {/* Animated Intro */}
-
-
- {/* Aurora Subheading */}
-
-
- {/* Description */}
-
- Upload your selfies and instantly generate{" "}
-
- AI-powered portraits
-
- ,{" "}
-
- headshots
-
- , and{" "}
-
- creative models
- .
-
-
- {/* Waitlist / Input */}
-
-
+ <>
+
+ {/* Fixed Navbar */}
+
+
+
+
+
-
-
+
+ {/* Hero Section */}
+
+ {/* Animated Intro */}
+
+
+
+
+ Upload your selfies and instantly generate{" "}
+
+ AI-powered portraits
+
+ ,{" "}
+
+ headshots
+
+ , and{" "}
+
+ creative models
+ .
+
+
+
+
+
+
+
+
+ {/* How it works section */}
+
+
+ How It Works
+
+
+ Transform your photos into stunning AI-powered portraits in three
+ simple steps
+
+
+
+
+
+ Upload Your Photo
+
+
+ Start by uploading any portrait photo you'd like to enhance
+
+
+
+
+
+
+ AI Magic
+
+
+ Our advanced AI transforms your photo into stunning portraits
+
+
+
+
+
+
+ Download & Share
+
+
+ Get your enhanced portraits in multiple styles instantly
+
+
+
+
+
+ {/* Animated Numbers */}
+
+
+
+
+ {/* FULL WIDTH Flickering Grid Section */}
+
+ {/* Fullscreen background */}
+
+
+
+
+
+
+
+
+
+
+
+ >
);
}
+
+const UploadIcon = () => (
+
+);
+
+const MagicIcon = () => (
+
+);
+
+const DownloadIcon = () => (
+
+);
diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx
deleted file mode 100644
index d2c7b26..0000000
--- a/app/pricing/page.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-"use client"
-
-import { ShineBorderDemo } from "@/components/magicui/pricing-page"
-
-export default function Pricingpage() {
- return (
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/app/pricing/pricingpage.tsx b/app/pricing/pricingpage.tsx
new file mode 100644
index 0000000..e5629fc
--- /dev/null
+++ b/app/pricing/pricingpage.tsx
@@ -0,0 +1,9 @@
+import { PricingPage } from "@/components/magicui/pricing-page";
+
+export function Pricing() {
+ return (
+
+ );
+}
diff --git a/components.json b/components.json
index 47edc21..a70fe6a 100644
--- a/components.json
+++ b/components.json
@@ -17,5 +17,8 @@
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
+ },
+ "registries": {
+ "@magicui": "https://magicui.design/r/{name}.json"
}
}
diff --git a/components/magicui/animated-card.tsx b/components/magicui/animated-card.tsx
new file mode 100644
index 0000000..61c7ac8
--- /dev/null
+++ b/components/magicui/animated-card.tsx
@@ -0,0 +1,57 @@
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { BorderBeam } from "@/registry/magicui/border-beam";
+import { Play, SkipBack, SkipForward } from "lucide-react";
+
+export function MusicPlayer() {
+ return (
+
+
+ Now Playing
+ Stairway to Heaven - Led Zeppelin
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/magicui/animated-numbers.tsx b/components/magicui/animated-numbers.tsx
new file mode 100644
index 0000000..e7553fe
--- /dev/null
+++ b/components/magicui/animated-numbers.tsx
@@ -0,0 +1,92 @@
+'use client';
+import { useEffect, useState, useRef } from 'react';
+import { NumberTicker } from '../ui/number-ticker';
+import { useIntersectionObserver } from '@/lib/hooks/use-intersection-observer';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
+import { motion } from 'framer-motion';
+
+const containerVariants = {
+ hidden: { opacity: 0 },
+ show: { opacity: 1, transition: { staggerChildren: 0.08 } },
+};
+
+const itemVariants = {
+ hidden: { opacity: 0, y: 12 },
+ show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 200, damping: 20 } },
+};
+
+export function AnimatedNumberBasic() {
+ const [hasAnimated, setHasAnimated] = useState(false);
+ const ref = useRef
(null);
+ const entry = useIntersectionObserver(ref, {});
+ const isVisible = !!entry?.isIntersecting;
+
+ useEffect(() => {
+ if (isVisible && !hasAnimated) setHasAnimated(true);
+ }, [isVisible, hasAnimated]);
+
+ const items = [
+ { initialValue: 100, suffix: 'K+', label: 'AI Portraits Generated' },
+ { initialValue: 50, suffix: 'K+', label: 'Happy Users' },
+ { initialValue: 98, suffix: '%', label: 'Satisfaction Rate' },
+ { initialValue: '24/7', suffix: '', label: 'AI Support' },
+ ];
+
+ return (
+
+
+
+ Trusted by Thousands Worldwide
+
+
+
+ {items.map((item, i) => (
+
+
+
+
+ {hasAnimated && typeof item.initialValue === 'number' ? (
+
+ ) : (
+
+ {item.initialValue}
+
+ )}
+
+ {item.suffix}
+
+
+
+
+
+
+ {item.label}
+
+
+
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/magicui/pricing-page.tsx b/components/magicui/pricing-page.tsx
index f17cfaf..c4352af 100644
--- a/components/magicui/pricing-page.tsx
+++ b/components/magicui/pricing-page.tsx
@@ -1,4 +1,4 @@
-
+"use client"
import {
Card,
CardContent,
@@ -16,7 +16,9 @@ const geistSans = Geist({
subsets: ['latin']
})
-export function ShineBorderDemo({ order }: { order: number }) {
+export function PricingCard({
+ order,
+}: { order: number }) {
const borderWidth = 1 + order;
const duration = 10 - order;
const shineColor = [
@@ -27,7 +29,7 @@ export function ShineBorderDemo({ order }: { order: number }) {
][order - 1];
return (
-
+
{(order === 2) && (
Popular
)}
@@ -148,3 +150,15 @@ export function ShineBorderDemo({ order }: { order: number }) {
);
}
+
+export function PricingPage() {
+ return (
+
+
+ {[1, 2, 3, 4].map((order) => (
+
+ ))}
+
+
+ );
+}
diff --git a/components/magicui/shining-border.tsx b/components/magicui/shining-border.tsx
index 984df0f..7925a2d 100644
--- a/components/magicui/shining-border.tsx
+++ b/components/magicui/shining-border.tsx
@@ -1,7 +1,16 @@
import { Input } from "@/components/ui/input";
import { ShineBorder } from "@/components/magicui/shine-border";
-export function ShineBorderDemo() {
+export function ShineBorderDemo({ order }: { order: number }) {
+ const borderWidth = 1 + order;
+ const duration = 10 - order;
+ const shineColor = [
+ ["#A07CFE", "#FE8FB5", "#FFBE7B"],
+ ["#00C6FF", "#0072FF", "#001CFF"],
+ ["#FFBE7B", "#FE8FB5", "#A07CFE"],
+ ["#24C6DC", "#514A9D", "#24C6DC"],
+ ][order - 1];
+
return (
diff --git a/components/ui/border-beam.tsx b/components/ui/border-beam.tsx
new file mode 100644
index 0000000..842c32b
--- /dev/null
+++ b/components/ui/border-beam.tsx
@@ -0,0 +1,106 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { motion, MotionStyle, Transition } from "motion/react";
+
+interface BorderBeamProps {
+ /**
+ * The size of the border beam.
+ */
+ size?: number;
+ /**
+ * The duration of the border beam.
+ */
+ duration?: number;
+ /**
+ * The delay of the border beam.
+ */
+ delay?: number;
+ /**
+ * The color of the border beam from.
+ */
+ colorFrom?: string;
+ /**
+ * The color of the border beam to.
+ */
+ colorTo?: string;
+ /**
+ * The motion transition of the border beam.
+ */
+ transition?: Transition;
+ /**
+ * The class name of the border beam.
+ */
+ className?: string;
+ /**
+ * The style of the border beam.
+ */
+ style?: React.CSSProperties;
+ /**
+ * Whether to reverse the animation direction.
+ */
+ reverse?: boolean;
+ /**
+ * The initial offset position (0-100).
+ */
+ initialOffset?: number;
+ /**
+ * The border width of the beam.
+ */
+ borderWidth?: number;
+}
+
+export const BorderBeam = ({
+ className,
+ size = 50,
+ delay = 0,
+ duration = 6,
+ colorFrom = "#ffaa40",
+ colorTo = "#9c40ff",
+ transition,
+ style,
+ reverse = false,
+ initialOffset = 0,
+ borderWidth = 1,
+}: BorderBeamProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/components/ui/canvas-reveal-effect.tsx b/components/ui/canvas-reveal-effect.tsx
new file mode 100644
index 0000000..0629e0f
--- /dev/null
+++ b/components/ui/canvas-reveal-effect.tsx
@@ -0,0 +1,308 @@
+"use client";
+import { cn } from "@/lib/utils";
+import { Canvas, useFrame, useThree } from "@react-three/fiber";
+import React, { useMemo, useRef } from "react";
+import * as THREE from "three";
+
+export const CanvasRevealEffect = ({
+ animationSpeed = 0.4,
+ opacities = [0.3, 0.3, 0.3, 0.5, 0.5, 0.5, 0.8, 0.8, 0.8, 1],
+ colors = [[0, 255, 255]],
+ containerClassName,
+ dotSize,
+ showGradient = true,
+}: {
+ /**
+ * 0.1 - slower
+ * 1.0 - faster
+ */
+ animationSpeed?: number;
+ opacities?: number[];
+ colors?: number[][];
+ containerClassName?: string;
+ dotSize?: number;
+ showGradient?: boolean;
+}) => {
+ return (
+
+
+
+
+ {showGradient && (
+
+ )}
+
+ );
+};
+
+interface DotMatrixProps {
+ colors?: number[][];
+ opacities?: number[];
+ totalSize?: number;
+ dotSize?: number;
+ shader?: string;
+ center?: ("x" | "y")[];
+}
+
+const DotMatrix: React.FC = ({
+ colors = [[0, 0, 0]],
+ opacities = [0.04, 0.04, 0.04, 0.04, 0.04, 0.08, 0.08, 0.08, 0.08, 0.14],
+ totalSize = 4,
+ dotSize = 2,
+ shader = "",
+ center = ["x", "y"],
+}) => {
+ const uniforms = React.useMemo(() => {
+ let colorsArray = [
+ colors[0],
+ colors[0],
+ colors[0],
+ colors[0],
+ colors[0],
+ colors[0],
+ ];
+ if (colors.length === 2) {
+ colorsArray = [
+ colors[0],
+ colors[0],
+ colors[0],
+ colors[1],
+ colors[1],
+ colors[1],
+ ];
+ } else if (colors.length === 3) {
+ colorsArray = [
+ colors[0],
+ colors[0],
+ colors[1],
+ colors[1],
+ colors[2],
+ colors[2],
+ ];
+ }
+
+ return {
+ u_colors: {
+ value: colorsArray.map((color) => [
+ color[0] / 255,
+ color[1] / 255,
+ color[2] / 255,
+ ]),
+ type: "uniform3fv",
+ },
+ u_opacities: {
+ value: opacities,
+ type: "uniform1fv",
+ },
+ u_total_size: {
+ value: totalSize,
+ type: "uniform1f",
+ },
+ u_dot_size: {
+ value: dotSize,
+ type: "uniform1f",
+ },
+ };
+ }, [colors, opacities, totalSize, dotSize]);
+
+ return (
+
+ );
+};
+
+type Uniforms = {
+ [key: string]: {
+ value: number[] | number[][] | number;
+ type: string;
+ };
+};
+const ShaderMaterial = ({
+ source,
+ uniforms,
+ maxFps = 60,
+}: {
+ source: string;
+ hovered?: boolean;
+ maxFps?: number;
+ uniforms: Uniforms;
+}) => {
+ const { size } = useThree();
+ const ref = useRef();
+ let lastFrameTime = 0;
+
+ useFrame(({ clock }) => {
+ if (!ref.current) return;
+ const timestamp = clock.getElapsedTime();
+ if (timestamp - lastFrameTime < 1 / maxFps) {
+ return;
+ }
+ lastFrameTime = timestamp;
+
+ const material: any = ref.current.material;
+ const timeLocation = material.uniforms.u_time;
+ timeLocation.value = timestamp;
+ });
+
+ const getUniforms = () => {
+ const preparedUniforms: any = {};
+
+ for (const uniformName in uniforms) {
+ const uniform: any = uniforms[uniformName];
+
+ switch (uniform.type) {
+ case "uniform1f":
+ preparedUniforms[uniformName] = { value: uniform.value, type: "1f" };
+ break;
+ case "uniform3f":
+ preparedUniforms[uniformName] = {
+ value: new THREE.Vector3().fromArray(uniform.value),
+ type: "3f",
+ };
+ break;
+ case "uniform1fv":
+ preparedUniforms[uniformName] = { value: uniform.value, type: "1fv" };
+ break;
+ case "uniform3fv":
+ preparedUniforms[uniformName] = {
+ value: uniform.value.map((v: number[]) =>
+ new THREE.Vector3().fromArray(v)
+ ),
+ type: "3fv",
+ };
+ break;
+ case "uniform2f":
+ preparedUniforms[uniformName] = {
+ value: new THREE.Vector2().fromArray(uniform.value),
+ type: "2f",
+ };
+ break;
+ default:
+ console.error(`Invalid uniform type for '${uniformName}'.`);
+ break;
+ }
+ }
+
+ preparedUniforms["u_time"] = { value: 0, type: "1f" };
+ preparedUniforms["u_resolution"] = {
+ value: new THREE.Vector2(size.width * 2, size.height * 2),
+ }; // Initialize u_resolution
+ return preparedUniforms;
+ };
+
+ // Shader material
+ const material = useMemo(() => {
+ const materialObject = new THREE.ShaderMaterial({
+ vertexShader: `
+ precision mediump float;
+ in vec2 coordinates;
+ uniform vec2 u_resolution;
+ out vec2 fragCoord;
+ void main(){
+ float x = position.x;
+ float y = position.y;
+ gl_Position = vec4(x, y, 0.0, 1.0);
+ fragCoord = (position.xy + vec2(1.0)) * 0.5 * u_resolution;
+ fragCoord.y = u_resolution.y - fragCoord.y;
+ }
+ `,
+ fragmentShader: source,
+ uniforms: getUniforms(),
+ glslVersion: THREE.GLSL3,
+ blending: THREE.CustomBlending,
+ blendSrc: THREE.SrcAlphaFactor,
+ blendDst: THREE.OneFactor,
+ });
+
+ return materialObject;
+ }, [size.width, size.height, source]);
+
+ return (
+
+
+
+
+ );
+};
+
+const Shader: React.FC = ({ source, uniforms, maxFps = 60 }) => {
+ return (
+
+ );
+};
+interface ShaderProps {
+ source: string;
+ uniforms: {
+ [key: string]: {
+ value: number[] | number[][] | number;
+ type: string;
+ };
+ };
+ maxFps?: number;
+}
diff --git a/components/ui/card-demo.tsx b/components/ui/card-demo.tsx
new file mode 100644
index 0000000..cee2412
--- /dev/null
+++ b/components/ui/card-demo.tsx
@@ -0,0 +1,53 @@
+import { CardSpotlight } from "@/components/ui/card-spotlight";
+
+export function CardSpotlightDemo() {
+ return (
+
+
+ Authentication steps
+
+
+ Follow these steps to secure your account:
+
+
+
+ Ensuring your account is properly secured helps protect your personal
+ information and data.
+
+
+ );
+}
+
+const Step = ({ title }: { title: string }) => {
+ return (
+
+
+ {title}
+
+ );
+};
+
+const CheckIcon = () => {
+ return (
+
+ );
+};
diff --git a/components/ui/card-spotlight.tsx b/components/ui/card-spotlight.tsx
new file mode 100644
index 0000000..e8f7372
--- /dev/null
+++ b/components/ui/card-spotlight.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import { useMotionValue, motion, useMotionTemplate } from "motion/react";
+import React, { MouseEvent as ReactMouseEvent, useState } from "react";
+import { CanvasRevealEffect } from "@/components/ui/canvas-reveal-effect";
+import { cn } from "@/lib/utils";
+
+export const CardSpotlight = ({
+ children,
+ radius = 350,
+ color = "#262626",
+ className,
+ ...props
+}: {
+ radius?: number;
+ color?: string;
+ children: React.ReactNode;
+} & React.HTMLAttributes) => {
+ const mouseX = useMotionValue(0);
+ const mouseY = useMotionValue(0);
+ function handleMouseMove({
+ currentTarget,
+ clientX,
+ clientY,
+ }: ReactMouseEvent) {
+ let { left, top } = currentTarget.getBoundingClientRect();
+
+ mouseX.set(clientX - left);
+ mouseY.set(clientY - top);
+ }
+
+ const [isHovering, setIsHovering] = useState(false);
+ const handleMouseEnter = () => setIsHovering(true);
+ const handleMouseLeave = () => setIsHovering(false);
+ return (
+
+
+ {isHovering && (
+
+ )}
+
+ {children}
+
+ );
+};
diff --git a/components/ui/data.ts b/components/ui/data.ts
new file mode 100644
index 0000000..4bba55c
--- /dev/null
+++ b/components/ui/data.ts
@@ -0,0 +1,20 @@
+
+export const testimonials = [
+ {
+ text: "They are so identical from the original, that someone cannot draw a line",
+ author: "Rakesh Jha",
+ role: "Founder",
+
+ },
+ {
+ text: "In my content creation journey, i am just addicted of using Magnified",
+ author: "John Watkins",
+ role: "UI/UX designer",
+
+ },
+ {
+ text: "Its really helps, when you are running out of funds and want a instant solution of your Video creation problems",
+ author: "Aidan Joy",
+ role: "Video Editor",
+ },
+];
diff --git a/components/ui/flickering-grid-layout.tsx b/components/ui/flickering-grid-layout.tsx
new file mode 100644
index 0000000..a2f7d61
--- /dev/null
+++ b/components/ui/flickering-grid-layout.tsx
@@ -0,0 +1,22 @@
+import { FlickeringGrid } from "../ui/flickering-grid";
+import { ReactNode } from "react";
+
+export function FlickeringGridDemo({ children, className }: { children?: ReactNode; className?: string }) {
+ return (
+
+ );
+}
diff --git a/components/ui/flickering-grid.tsx b/components/ui/flickering-grid.tsx
new file mode 100644
index 0000000..f303d4d
--- /dev/null
+++ b/components/ui/flickering-grid.tsx
@@ -0,0 +1,199 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+
+interface FlickeringGridProps extends React.HTMLAttributes {
+ squareSize?: number;
+ gridGap?: number;
+ flickerChance?: number;
+ color?: string;
+ width?: number;
+ height?: number;
+ className?: string;
+ maxOpacity?: number;
+}
+
+export const FlickeringGrid: React.FC = ({
+ squareSize = 4,
+ gridGap = 6,
+ flickerChance = 0.3,
+ color = "rgb(0, 0, 0)",
+ width,
+ height,
+ className,
+ maxOpacity = 0.3,
+ ...props
+}) => {
+ const canvasRef = useRef(null);
+ const containerRef = useRef(null);
+ const [isInView, setIsInView] = useState(false);
+ const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
+
+ const memoizedColor = useMemo(() => {
+ const toRGBA = (color: string) => {
+ if (typeof window === "undefined") {
+ return `rgba(0, 0, 0,`;
+ }
+ const canvas = document.createElement("canvas");
+ canvas.width = canvas.height = 1;
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return "rgba(255, 0, 0,";
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, 1, 1);
+ const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
+ return `rgba(${r}, ${g}, ${b},`;
+ };
+ return toRGBA(color);
+ }, [color]);
+
+ const setupCanvas = useCallback(
+ (canvas: HTMLCanvasElement, width: number, height: number) => {
+ const dpr = window.devicePixelRatio || 1;
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
+ canvas.style.width = `${width}px`;
+ canvas.style.height = `${height}px`;
+ const cols = Math.floor(width / (squareSize + gridGap));
+ const rows = Math.floor(height / (squareSize + gridGap));
+
+ const squares = new Float32Array(cols * rows);
+ for (let i = 0; i < squares.length; i++) {
+ squares[i] = Math.random() * maxOpacity;
+ }
+
+ return { cols, rows, squares, dpr };
+ },
+ [squareSize, gridGap, maxOpacity],
+ );
+
+ const updateSquares = useCallback(
+ (squares: Float32Array, deltaTime: number) => {
+ for (let i = 0; i < squares.length; i++) {
+ if (Math.random() < flickerChance * deltaTime) {
+ squares[i] = Math.random() * maxOpacity;
+ }
+ }
+ },
+ [flickerChance, maxOpacity],
+ );
+
+ const drawGrid = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ width: number,
+ height: number,
+ cols: number,
+ rows: number,
+ squares: Float32Array,
+ dpr: number,
+ ) => {
+ ctx.clearRect(0, 0, width, height);
+ ctx.fillStyle = "transparent";
+ ctx.fillRect(0, 0, width, height);
+
+ for (let i = 0; i < cols; i++) {
+ for (let j = 0; j < rows; j++) {
+ const opacity = squares[i * rows + j];
+ ctx.fillStyle = `${memoizedColor}${opacity})`;
+ ctx.fillRect(
+ i * (squareSize + gridGap) * dpr,
+ j * (squareSize + gridGap) * dpr,
+ squareSize * dpr,
+ squareSize * dpr,
+ );
+ }
+ }
+ },
+ [memoizedColor, squareSize, gridGap],
+ );
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ const container = containerRef.current;
+ if (!canvas || !container) return;
+
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+
+ let animationFrameId: number;
+ let gridParams: ReturnType;
+
+ const updateCanvasSize = () => {
+ const newWidth = width || container.clientWidth;
+ const newHeight = height || container.clientHeight;
+ setCanvasSize({ width: newWidth, height: newHeight });
+ gridParams = setupCanvas(canvas, newWidth, newHeight);
+ };
+
+ updateCanvasSize();
+
+ let lastTime = 0;
+ const animate = (time: number) => {
+ if (!isInView) return;
+
+ const deltaTime = (time - lastTime) / 1000;
+ lastTime = time;
+
+ updateSquares(gridParams.squares, deltaTime);
+ drawGrid(
+ ctx,
+ canvas.width,
+ canvas.height,
+ gridParams.cols,
+ gridParams.rows,
+ gridParams.squares,
+ gridParams.dpr,
+ );
+ animationFrameId = requestAnimationFrame(animate);
+ };
+
+ const resizeObserver = new ResizeObserver(() => {
+ updateCanvasSize();
+ });
+
+ resizeObserver.observe(container);
+
+ const intersectionObserver = new IntersectionObserver(
+ ([entry]) => {
+ setIsInView(entry.isIntersecting);
+ },
+ { threshold: 0 },
+ );
+
+ intersectionObserver.observe(canvas);
+
+ if (isInView) {
+ animationFrameId = requestAnimationFrame(animate);
+ }
+
+ return () => {
+ cancelAnimationFrame(animationFrameId);
+ resizeObserver.disconnect();
+ intersectionObserver.disconnect();
+ };
+ }, [setupCanvas, updateSquares, drawGrid, width, height, isInView]);
+
+ return (
+
+
+
+ );
+};
diff --git a/components/ui/marquee.tsx b/components/ui/marquee.tsx
new file mode 100644
index 0000000..073aab8
--- /dev/null
+++ b/components/ui/marquee.tsx
@@ -0,0 +1,73 @@
+import { cn } from "@/lib/utils";
+import { ComponentPropsWithoutRef } from "react";
+
+interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
+ /**
+ * Optional CSS class name to apply custom styles
+ */
+ className?: string;
+ /**
+ * Whether to reverse the animation direction
+ * @default false
+ */
+ reverse?: boolean;
+ /**
+ * Whether to pause the animation on hover
+ * @default false
+ */
+ pauseOnHover?: boolean;
+ /**
+ * Content to be displayed in the marquee
+ */
+ children: React.ReactNode;
+ /**
+ * Whether to animate vertically instead of horizontally
+ * @default false
+ */
+ vertical?: boolean;
+ /**
+ * Number of times to repeat the content
+ * @default 4
+ */
+ repeat?: number;
+}
+
+export function Marquee({
+ className,
+ reverse = false,
+ pauseOnHover = false,
+ children,
+ vertical = false,
+ repeat = 4,
+ ...props
+}: MarqueeProps) {
+ return (
+
+ {Array(repeat)
+ .fill(0)
+ .map((_, i) => (
+
+ {children}
+
+ ))}
+
+ );
+}
diff --git a/components/ui/navbar.tsx b/components/ui/navbar.tsx
index d8c3cb7..368411b 100644
--- a/components/ui/navbar.tsx
+++ b/components/ui/navbar.tsx
@@ -1,11 +1,11 @@
"use client";
import React, { useEffect, useState } from "react";
-import { Menu, X } from "lucide-react"; // use lucide-react icons (or Heroicons)
+import { Menu, X } from "lucide-react";
import { Geist } from "next/font/google";
const geistSans = Geist({
- subsets: ["latin"]
-})
+ subsets: ["latin"],
+});
export function Navbar() {
const [scrolled, setScrolled] = useState(false);
@@ -19,52 +19,50 @@ export function Navbar() {
return (