Redesign landing page and add Caddy deployment for nomadro.com
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
53
components/ui/aurora-background.tsx
Normal file
53
components/ui/aurora-background.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
export function AuroraBackground() {
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
{/* Base gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-[#0a0a1a] via-[#06060f] to-[#0d1117]" />
|
||||
|
||||
{/* Aurora layers */}
|
||||
<div
|
||||
className="animate-aurora absolute -left-1/4 top-0 h-[60%] w-[150%] opacity-40"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse at 30% 0%, rgba(139, 92, 246, 0.35) 0%, transparent 60%)",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="animate-aurora absolute -right-1/4 top-[10%] h-[50%] w-[120%] opacity-30"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse at 70% 20%, rgba(34, 211, 238, 0.3) 0%, transparent 55%)",
|
||||
animationDelay: "-4s",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="animate-aurora absolute left-0 top-[30%] h-[40%] w-full opacity-25"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse at 50% 50%, rgba(217, 70, 239, 0.25) 0%, transparent 50%)",
|
||||
animationDelay: "-8s",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Grid overlay */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03]"
|
||||
style={{
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(255,255,255,0.5) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.5) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: "60px 60px",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Scan line */}
|
||||
<div
|
||||
className="absolute left-0 right-0 h-px bg-gradient-to-r from-transparent via-cyan-400/20 to-transparent"
|
||||
style={{ animation: "scan-line 8s linear infinite" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,25 +2,21 @@ import * as React from "react";
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: "default" | "outline" | "ghost" | "link";
|
||||
variant?: "default" | "outline" | "ghost";
|
||||
size?: "default" | "sm" | "lg";
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className = "", variant = "default", size = "default", ...props }, ref) => {
|
||||
const base =
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";
|
||||
"inline-flex items-center justify-center rounded-xl font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 disabled:pointer-events-none disabled:opacity-50";
|
||||
const variants = {
|
||||
default:
|
||||
"bg-neutral-900 text-white hover:bg-neutral-800 focus-visible:ring-neutral-900",
|
||||
outline:
|
||||
"border border-neutral-200 bg-transparent hover:bg-neutral-50 focus-visible:ring-neutral-400",
|
||||
ghost: "hover:bg-neutral-100 focus-visible:ring-neutral-400",
|
||||
link: "text-neutral-900 underline-offset-4 hover:underline focus-visible:ring-neutral-400",
|
||||
default: "bg-violet-600 text-white hover:bg-violet-700 active:bg-violet-800",
|
||||
outline: "border border-neutral-200 text-neutral-700 hover:bg-neutral-50",
|
||||
ghost: "text-neutral-600 hover:bg-neutral-100",
|
||||
};
|
||||
const sizes = {
|
||||
default: "h-10 px-4 py-2 text-sm",
|
||||
default: "h-10 px-4 text-sm",
|
||||
sm: "h-8 px-3 text-xs",
|
||||
lg: "h-12 px-6 text-base",
|
||||
};
|
||||
|
||||
37
components/ui/floating-emojis.tsx
Normal file
37
components/ui/floating-emojis.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { FLOATING_EMOJIS } from "~/lib/data";
|
||||
|
||||
const POSITIONS = [
|
||||
{ left: "8%", top: "18%", size: "text-2xl", delay: 0 },
|
||||
{ left: "85%", top: "12%", size: "text-3xl", delay: 1 },
|
||||
{ left: "15%", top: "65%", size: "text-xl", delay: 2 },
|
||||
{ left: "78%", top: "55%", size: "text-2xl", delay: 0.5 },
|
||||
{ left: "5%", top: "42%", size: "text-lg", delay: 1.5 },
|
||||
{ left: "92%", top: "38%", size: "text-xl", delay: 2.5 },
|
||||
{ left: "25%", top: "8%", size: "text-lg", delay: 3 },
|
||||
{ left: "65%", top: "78%", size: "text-2xl", delay: 1.2 },
|
||||
{ left: "45%", top: "88%", size: "text-lg", delay: 0.8 },
|
||||
{ left: "55%", top: "5%", size: "text-xl", delay: 2.2 },
|
||||
] as const;
|
||||
|
||||
export function FloatingEmojis() {
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
{POSITIONS.map((pos, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={`absolute select-none opacity-[0.15] ${pos.size}`}
|
||||
style={{
|
||||
left: pos.left,
|
||||
top: pos.top,
|
||||
animation: `bounce-gentle ${4 + (i % 3)}s ease-in-out infinite`,
|
||||
animationDelay: `${pos.delay}s`,
|
||||
}}
|
||||
>
|
||||
{FLOATING_EMOJIS[i]}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
117
components/ui/globe-animation.tsx
Normal file
117
components/ui/globe-animation.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
"use client";
|
||||
|
||||
export function GlobeAnimation() {
|
||||
return (
|
||||
<div className="pointer-events-none absolute right-[-5%] top-[15%] hidden opacity-20 lg:block xl:right-[5%] xl:opacity-30">
|
||||
<div className="relative h-72 w-72 xl:h-96 xl:w-96">
|
||||
{/* Orbit ring 1 */}
|
||||
<div className="animate-orbit absolute inset-0">
|
||||
<svg viewBox="0 0 200 200" className="h-full w-full">
|
||||
<ellipse
|
||||
cx="100"
|
||||
cy="100"
|
||||
rx="95"
|
||||
ry="35"
|
||||
fill="none"
|
||||
stroke="url(#orbitGrad1)"
|
||||
strokeWidth="0.5"
|
||||
strokeDasharray="4 6"
|
||||
/>
|
||||
<circle cx="195" cy="100" r="3" fill="#22d3ee" opacity="0.8">
|
||||
<animate attributeName="opacity" values="0.4;1;0.4" dur="2s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<defs>
|
||||
<linearGradient id="orbitGrad1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#8b5cf6" stopOpacity="0" />
|
||||
<stop offset="50%" stopColor="#8b5cf6" stopOpacity="0.6" />
|
||||
<stop offset="100%" stopColor="#22d3ee" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Orbit ring 2 */}
|
||||
<div className="animate-orbit-reverse absolute inset-4">
|
||||
<svg viewBox="0 0 200 200" className="h-full w-full">
|
||||
<ellipse
|
||||
cx="100"
|
||||
cy="100"
|
||||
rx="80"
|
||||
ry="80"
|
||||
fill="none"
|
||||
stroke="url(#orbitGrad2)"
|
||||
strokeWidth="0.3"
|
||||
strokeDasharray="2 8"
|
||||
transform="rotate(-30 100 100)"
|
||||
/>
|
||||
<circle cx="100" cy="20" r="2.5" fill="#d946ef" opacity="0.7">
|
||||
<animate attributeName="opacity" values="0.3;0.9;0.3" dur="3s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<defs>
|
||||
<linearGradient id="orbitGrad2" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#d946ef" stopOpacity="0.2" />
|
||||
<stop offset="100%" stopColor="#34d399" stopOpacity="0.2" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Globe */}
|
||||
<div className="absolute inset-[25%] flex items-center justify-center">
|
||||
<svg viewBox="0 0 120 120" className="h-full w-full">
|
||||
<defs>
|
||||
<radialGradient id="globeGrad" cx="35%" cy="30%">
|
||||
<stop offset="0%" stopColor="#22d3ee" stopOpacity="0.3" />
|
||||
<stop offset="50%" stopColor="#8b5cf6" stopOpacity="0.15" />
|
||||
<stop offset="100%" stopColor="#06060f" stopOpacity="0.05" />
|
||||
</radialGradient>
|
||||
<linearGradient id="globeLine" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#22d3ee" stopOpacity="0.1" />
|
||||
<stop offset="50%" stopColor="#8b5cf6" stopOpacity="0.3" />
|
||||
<stop offset="100%" stopColor="#22d3ee" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="60" cy="60" r="55" fill="url(#globeGrad)" stroke="url(#globeLine)" strokeWidth="0.8" />
|
||||
{/* Latitude lines */}
|
||||
{[30, 45, 60, 75, 90].map((lat) => {
|
||||
const ry = Math.sin((lat * Math.PI) / 180) * 55;
|
||||
return (
|
||||
<ellipse
|
||||
key={lat}
|
||||
cx="60"
|
||||
cy="60"
|
||||
rx="55"
|
||||
ry={ry}
|
||||
fill="none"
|
||||
stroke="#22d3ee"
|
||||
strokeWidth="0.3"
|
||||
opacity="0.2"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* Longitude lines */}
|
||||
{[0, 30, 60, 90, 120, 150].map((lon) => (
|
||||
<ellipse
|
||||
key={lon}
|
||||
cx="60"
|
||||
cy="60"
|
||||
rx={Math.cos((lon * Math.PI) / 180) * 8 + 8}
|
||||
ry="55"
|
||||
fill="none"
|
||||
stroke="#8b5cf6"
|
||||
strokeWidth="0.3"
|
||||
opacity="0.15"
|
||||
transform={`rotate(${lon} 60 60)`}
|
||||
/>
|
||||
))}
|
||||
{/* Glow dot */}
|
||||
<circle cx="42" cy="38" r="4" fill="#22d3ee" opacity="0.6">
|
||||
<animate attributeName="r" values="3;5;3" dur="4s" repeatCount="indefinite" />
|
||||
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="4s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,20 @@
|
||||
"use client";
|
||||
|
||||
const PARTICLES = Array.from({ length: 50 }, (_, i) => ({
|
||||
size: 4 + (i % 6),
|
||||
const COLOR_MAP = {
|
||||
cyan: "from-cyan-400/60 to-cyan-300/30",
|
||||
violet: "from-violet-400/60 to-violet-300/30",
|
||||
fuchsia: "from-fuchsia-400/60 to-fuchsia-300/30",
|
||||
} as const;
|
||||
|
||||
type ParticleColor = keyof typeof COLOR_MAP;
|
||||
|
||||
const PARTICLES = Array.from({ length: 60 }, (_, i) => ({
|
||||
size: 2 + (i % 5),
|
||||
left: (i * 17.3) % 100,
|
||||
top: (i * 23.7) % 100,
|
||||
duration: 10 + (i % 8),
|
||||
delay: (i * 0.3) % 5,
|
||||
duration: 12 + (i % 10),
|
||||
delay: (i * 0.4) % 6,
|
||||
color: (i % 3 === 0 ? "cyan" : i % 3 === 1 ? "violet" : "fuchsia") as ParticleColor,
|
||||
}));
|
||||
|
||||
export function Particles() {
|
||||
@@ -14,13 +23,13 @@ export function Particles() {
|
||||
{PARTICLES.map((p, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute rounded-full bg-gradient-to-br from-violet-400/50 via-fuchsia-400/40 to-cyan-400/50 blur-[1px]"
|
||||
className={`absolute rounded-full bg-gradient-to-br ${COLOR_MAP[p.color]} blur-[0.5px]`}
|
||||
style={{
|
||||
width: `${p.size}px`,
|
||||
height: `${p.size}px`,
|
||||
left: `${p.left}%`,
|
||||
top: `${p.top}%`,
|
||||
animation: `float ${p.duration}s ease-in-out infinite`,
|
||||
animation: `float ${p.duration}s ease-in-out infinite, twinkle ${3 + (i % 4)}s ease-in-out infinite`,
|
||||
animationDelay: `${p.delay}s`,
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user