Initial commit

This commit is contained in:
User
2026-08-14 14:17:48 +02:00
commit 52dc765da4
46 changed files with 6318 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import React, { useEffect, useState, useRef } from 'react';
interface AnimatedCounterProps {
end: number;
duration?: number;
prefix?: string;
suffix?: string;
className?: string;
}
export const AnimatedCounter: React.FC<AnimatedCounterProps> = ({
end,
duration = 2000,
prefix = '',
suffix = '',
className = ''
}) => {
const [count, setCount] = useState(0);
const counterRef = useRef<HTMLSpanElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
}
},
{ threshold: 0.2 }
);
if (counterRef.current) {
observer.observe(counterRef.current);
}
return () => observer.disconnect();
}, []);
useEffect(() => {
if (!isVisible) return;
let start = 0;
const increment = end / (duration / 16);
const timer = setInterval(() => {
start += increment;
if (start >= end) {
setCount(end);
clearInterval(timer);
} else {
setCount(Math.floor(start));
}
}, 16);
return () => clearInterval(timer);
}, [end, duration, isVisible]);
return (
<span ref={counterRef} className={className}>
{prefix}
{count.toLocaleString()}
{suffix}
</span>
);
};
+64
View File
@@ -0,0 +1,64 @@
import React from 'react';
interface BadgeProps {
children: React.ReactNode;
variant?: 'emerald' | 'indigo' | 'cyan' | 'purple' | 'amber' | 'neutral' | 'tangerine' | 'sky';
showDot?: boolean;
className?: string;
}
export const Badge: React.FC<BadgeProps> = ({
children,
variant = 'tangerine',
showDot = true,
className = ''
}) => {
const styles = {
tangerine: {
bg: 'bg-orange-950/70 text-orange-300 border-orange-500/40',
dot: 'bg-orange-400'
},
sky: {
bg: 'bg-sky-950/70 text-sky-300 border-sky-500/40',
dot: 'bg-sky-400'
},
emerald: {
bg: 'bg-sky-950/70 text-sky-300 border-sky-500/40',
dot: 'bg-sky-400'
},
indigo: {
bg: 'bg-orange-950/70 text-orange-300 border-orange-500/40',
dot: 'bg-orange-400'
},
cyan: {
bg: 'bg-sky-950/70 text-sky-300 border-sky-500/40',
dot: 'bg-sky-400'
},
purple: {
bg: 'bg-sky-950/70 text-sky-300 border-sky-500/40',
dot: 'bg-sky-400'
},
amber: {
bg: 'bg-orange-950/70 text-orange-300 border-orange-500/40',
dot: 'bg-amber-400'
},
neutral: {
bg: 'bg-slate-800/80 text-slate-300 border-slate-700/60',
dot: 'bg-slate-400'
}
};
const current = styles[variant];
return (
<span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium border backdrop-blur-md shadow-inner tracking-wide ${current.bg} ${className}`}>
{showDot && (
<span className="relative flex h-2 w-2">
<span className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${current.dot}`} />
<span className={`relative inline-flex rounded-full h-2 w-2 ${current.dot}`} />
</span>
)}
{children}
</span>
);
};
+55
View File
@@ -0,0 +1,55 @@
import React from 'react';
import { motion } from 'motion/react';
import { Loader2 } from 'lucide-react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'glow';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
icon?: React.ReactNode;
iconPosition?: 'left' | 'right';
children: React.ReactNode;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
isLoading = false,
icon,
iconPosition = 'right',
children,
className = '',
disabled,
...props
}) => {
const baseStyles = 'inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-orange-500/50 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer whitespace-nowrap rounded-xl';
const sizeStyles = {
sm: 'px-3.5 py-1.5 text-xs gap-1.5',
md: 'px-5 py-2.5 text-sm gap-2',
lg: 'px-7 py-3.5 text-base gap-2.5 shadow-lg shadow-orange-950/20'
};
const variantStyles = {
primary: 'bg-orange-600 hover:bg-orange-500 text-white shadow-lg shadow-orange-600/25 border border-orange-500/30 hover:border-orange-400/50 hover:shadow-orange-500/35 active:scale-[0.98]',
secondary: 'bg-slate-800/90 hover:bg-slate-700/90 text-slate-100 border border-slate-700/60 hover:border-slate-600 active:scale-[0.98]',
outline: 'bg-transparent text-slate-200 border border-slate-700 hover:border-orange-500/50 hover:bg-orange-950/20 active:scale-[0.98]',
ghost: 'bg-transparent text-slate-300 hover:text-white hover:bg-slate-800/60 active:scale-[0.98]',
glow: 'relative bg-gradient-to-r from-orange-500 via-amber-500 to-sky-400 text-slate-950 font-extrabold shadow-xl shadow-orange-500/25 hover:shadow-orange-500/40 border border-amber-200/40 hover:scale-[1.02] active:scale-[0.98]'
};
return (
<motion.button
whileHover={{ y: -1 }}
whileTap={{ scale: 0.98 }}
className={`${baseStyles} ${sizeStyles[size]} ${variantStyles[variant]} ${className}`}
disabled={disabled || isLoading}
{...props}
>
{isLoading && <Loader2 className="w-4 h-4 animate-spin text-current" />}
{!isLoading && icon && iconPosition === 'left' && <span className="inline-flex">{icon}</span>}
<span>{children}</span>
{!isLoading && icon && iconPosition === 'right' && <span className="inline-flex">{icon}</span>}
</motion.button>
);
};
+37
View File
@@ -0,0 +1,37 @@
import React from 'react';
import { motion } from 'motion/react';
interface GlassCardProps {
children: React.ReactNode;
className?: string;
hoverEffect?: boolean;
gradientBorder?: boolean;
onClick?: () => void;
id?: string;
}
export const GlassCard: React.FC<GlassCardProps> = ({
children,
className = '',
hoverEffect = true,
gradientBorder = false,
onClick,
id
}) => {
return (
<motion.div
id={id}
whileHover={hoverEffect ? { y: -4, transition: { duration: 0.2 } } : undefined}
onClick={onClick}
className={`relative rounded-2xl bg-slate-900/70 backdrop-blur-xl border border-slate-800/80 p-6 shadow-xl shadow-black/40 overflow-hidden transition-all duration-300 ${
hoverEffect ? 'hover:border-orange-500/40 hover:shadow-2xl hover:shadow-orange-950/30' : ''
} ${
gradientBorder ? 'before:absolute before:inset-0 before:p-[1px] before:bg-gradient-to-b before:from-orange-500/40 before:via-sky-500/20 before:to-transparent before:rounded-2xl before:-z-10' : ''
} ${className}`}
>
{/* Subtle top glow line */}
<div className="absolute top-0 left-0 right-0 h-[1px] bg-gradient-to-r from-transparent via-orange-500/30 to-transparent opacity-60 pointer-events-none" />
{children}
</motion.div>
);
};
+51
View File
@@ -0,0 +1,51 @@
import React from 'react';
import { Badge } from './Badge';
interface SectionHeadingProps {
badgeText?: string;
badgeVariant?: 'emerald' | 'indigo' | 'cyan' | 'purple' | 'amber' | 'neutral' | 'tangerine' | 'sky';
title: string;
gradientTitle?: string;
subtitle?: string;
align?: 'left' | 'center' | 'right';
className?: string;
}
export const SectionHeading: React.FC<SectionHeadingProps> = ({
badgeText,
badgeVariant = 'tangerine',
title,
gradientTitle,
subtitle,
align = 'center',
className = ''
}) => {
const alignmentClass = {
left: 'text-left items-start',
center: 'text-center items-center',
right: 'text-right items-end'
}[align];
return (
<div className={`flex flex-col mb-12 ${alignmentClass} ${className}`}>
{badgeText && (
<div className="mb-4">
<Badge variant={badgeVariant}>{badgeText}</Badge>
</div>
)}
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight text-white max-w-3xl leading-tight">
{title}{' '}
{gradientTitle && (
<span className="bg-gradient-to-r from-orange-400 via-amber-300 to-sky-400 bg-clip-text text-transparent">
{gradientTitle}
</span>
)}
</h2>
{subtitle && (
<p className="mt-4 text-base sm:text-lg text-slate-400 max-w-2xl leading-relaxed font-normal">
{subtitle}
</p>
)}
</div>
);
};