-
- {/* Left Content */}
-
-
- {t.hero.badge}
-
+ {/* Main Content Grid */}
+
+ {/* Left: Content */}
+
+ {/* Badge */}
+
+
+
+ {t.hero.badge}
+
+
-
-
+ {/* Headline */}
+
+
{t.hero.title}
-
+
{t.hero.subtitle}
-
-
- {t.hero.features.map((feature: string, index: number) => (
-
-
-
-
- {feature}
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
- {/* Right Preview Widget */}
-
-
- {templateCards.map((card, index) => (
-
-
-
-
-
- {card.title}
-
+ {/* Feature List */}
+
+ {t.hero.features.map((feature: string, index: number) => (
+
+
+
+
+ {feature}
))}
- {/* Floating Badge */}
+ {/* CTAs */}
-
-
-
-
- {t.hero.engagement_badge}
+
+
+
+
+
+
-
+
+ {/* Trust Indicator */}
+
+
+ {[1, 2, 3].map((i) => (
+
+ ))}
+
+ Trusted by 25,000+ professionals worldwide
+
+
+
+ {/* Right: Interactive QR Generator */}
+
+
+
+
+ {/* Stats Counter */}
+
- {/* Smooth Gradient Fade Transition */}
-
-
+ {/* Smooth Gradient Fade Transition to next section */}
+
+
);
};
diff --git a/src/components/marketing/HeroQRInteractive.tsx b/src/components/marketing/HeroQRInteractive.tsx
new file mode 100644
index 0000000..cbf76ab
--- /dev/null
+++ b/src/components/marketing/HeroQRInteractive.tsx
@@ -0,0 +1,201 @@
+'use client';
+
+import React, { useState, useCallback } from 'react';
+import { QRCodeSVG } from 'qrcode.react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Input } from '@/components/ui/Input';
+import { Sparkles } from 'lucide-react';
+
+interface ColorPreset {
+ name: string;
+ fg: string;
+ bg: string;
+ gradient?: string;
+}
+
+const colorPresets: ColorPreset[] = [
+ { name: 'Classic', fg: '#000000', bg: '#FFFFFF' },
+ { name: 'Ocean', fg: '#0ea5e9', bg: '#e0f2fe' },
+ { name: 'Forest', fg: '#059669', bg: '#d1fae5' },
+ { name: 'Sunset', fg: '#f59e0b', bg: '#fef3c7' },
+ { name: 'Purple', fg: '#8b5cf6', bg: '#f3e8ff' },
+ { name: 'Pink', fg: '#ec4899', bg: '#fce7f3' },
+];
+
+export const HeroQRInteractive: React.FC = () => {
+ const [url, setUrl] = useState('https://qrmaster.net');
+ const [foreground, setForeground] = useState('#000000');
+ const [background, setBackground] = useState('#FFFFFF');
+ const [selectedPreset, setSelectedPreset] = useState(0);
+
+ const handlePresetClick = useCallback((preset: ColorPreset, index: number) => {
+ setForeground(preset.fg);
+ setBackground(preset.bg);
+ setSelectedPreset(index);
+ }, []);
+
+ const handleUrlChange = (e: React.ChangeEvent
) => {
+ setUrl(e.target.value || 'https://qrmaster.net');
+ };
+
+ return (
+
+ {/* Main QR Container */}
+
+ {/* Glass card container */}
+
+ {/* Decorative corner accents */}
+
+
+
+ {/* QR Code Display */}
+
+
+ {/* QR Code with hover effect */}
+
+
+
+
+
+
+
+ {/* Scan indicator */}
+
+
+
+ {/* Live indicator */}
+
+
+
+
+
+ Live Preview
+
+
+
+
+ {/* Input Field */}
+
+
+
+
+
+
+
+ {/* Color Presets */}
+
+
+ Choose a style:
+
+
+ {colorPresets.map((preset, index) => (
+
handlePresetClick(preset, index)}
+ className={`relative px-4 py-2 rounded-xl font-medium text-sm transition-all ${
+ selectedPreset === index
+ ? 'bg-white shadow-md ring-2 ring-primary-500 ring-offset-2'
+ : 'bg-white/60 hover:bg-white hover:shadow-sm'
+ }`}
+ whileHover={{ scale: 1.05 }}
+ whileTap={{ scale: 0.95 }}
+ >
+
+
+ ))}
+
+
+
+
+
+ {/* Decorative floating elements */}
+
+
+
+ );
+};
diff --git a/src/components/marketing/InstantGenerator.tsx b/src/components/marketing/InstantGenerator.tsx
index 0e5f557..2c9f519 100644
--- a/src/components/marketing/InstantGenerator.tsx
+++ b/src/components/marketing/InstantGenerator.tsx
@@ -2,30 +2,48 @@
import React, { useState } from 'react';
import { QRCodeSVG } from 'qrcode.react';
-import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { calculateContrast } from '@/lib/utils';
-import AdBanner from '@/components/ads/AdBanner';
+import { PresetGallery, qrPresets, type QRPreset } from './PresetGallery';
+import { PhoneMockup } from './PhoneMockup';
+import { Download, Smartphone, Palette, Settings2, Sparkles } from 'lucide-react';
interface InstantGeneratorProps {
t: any; // i18n translation function
}
+type TabType = 'basic' | 'presets' | 'advanced';
+
export const InstantGenerator: React.FC = ({ t }) => {
- const [url, setUrl] = useState('https://example.com');
+ const [activeTab, setActiveTab] = useState('basic');
+ const [url, setUrl] = useState('https://qrmaster.net');
const [foregroundColor, setForegroundColor] = useState('#000000');
const [backgroundColor, setBackgroundColor] = useState('#FFFFFF');
+ const [selectedPreset, setSelectedPreset] = useState('bold-1');
const [cornerStyle, setCornerStyle] = useState('square');
- const [size, setSize] = useState(200);
+ const [size, setSize] = useState(256);
const contrast = calculateContrast(foregroundColor, backgroundColor);
const hasGoodContrast = contrast >= 4.5;
+ const tabs = [
+ { id: 'basic' as TabType, label: 'Basic', icon: Sparkles },
+ { id: 'presets' as TabType, label: 'Presets', icon: Palette },
+ { id: 'advanced' as TabType, label: 'Advanced', icon: Settings2 },
+ ];
+
+ const handlePresetSelect = (preset: QRPreset) => {
+ setForegroundColor(preset.fg);
+ setBackgroundColor(preset.bg);
+ setSelectedPreset(preset.id);
+ };
+
const downloadQR = (format: 'svg' | 'png') => {
- const svg = document.querySelector('#instant-qr-preview svg');
+ const svg = document.querySelector('#generator-qr-preview svg');
if (!svg || !url) return;
if (format === 'svg') {
@@ -40,13 +58,12 @@ export const InstantGenerator: React.FC = ({ t }) => {
document.body.removeChild(a);
URL.revokeObjectURL(downloadUrl);
} else {
- // Convert SVG to PNG using Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
const svgData = new XMLSerializer().serializeToString(svg);
const blob = new Blob([svgData], { type: 'image/svg+xml' });
- const url = URL.createObjectURL(blob);
+ const blobUrl = URL.createObjectURL(blob);
img.onload = () => {
canvas.width = size;
@@ -56,9 +73,9 @@ export const InstantGenerator: React.FC = ({ t }) => {
ctx.fillRect(0, 0, size, size);
ctx.drawImage(img, 0, 0, size, size);
}
- canvas.toBlob((blob) => {
- if (blob) {
- const downloadUrl = URL.createObjectURL(blob);
+ canvas.toBlob((canvasBlob) => {
+ if (canvasBlob) {
+ const downloadUrl = URL.createObjectURL(canvasBlob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = 'qrcode.png';
@@ -68,19 +85,16 @@ export const InstantGenerator: React.FC = ({ t }) => {
URL.revokeObjectURL(downloadUrl);
}
});
- URL.revokeObjectURL(url);
+ URL.revokeObjectURL(blobUrl);
};
- img.src = url;
+ img.src = blobUrl;
}
};
return (
-
-
+
+ {/* Section Header */}
= ({ t }) => {
transition={{ duration: 0.5 }}
className="text-center mb-12"
>
-
+
+
+ Try It Now
+
+
{t.generator.title}
+
+ Create and customize your QR code in seconds. No signup required.
+
-
- {/* Left Form */}
+
+ {/* Left: Controls */}
-
- setUrl(e.target.value)}
- placeholder={t.generator.url_placeholder}
- className="transition-all focus:ring-2 focus:ring-primary-500/20"
- />
-
-
-
-
-
-
-
-
-
- {hasGoodContrast ? t.generator.contrast_good : 'Low contrast'}
-
-
- Contrast: {contrast.toFixed(1)}:1
-
-
+ {/* Tab Content */}
+
+
+ {/* Basic Tab */}
+ {activeTab === 'basic' && (
+ <>
+ setUrl(e.target.value)}
+ placeholder="https://your-website.com"
+ />
-
-
-
diff --git a/src/components/marketing/PhoneMockup.tsx b/src/components/marketing/PhoneMockup.tsx
new file mode 100644
index 0000000..546e9ef
--- /dev/null
+++ b/src/components/marketing/PhoneMockup.tsx
@@ -0,0 +1,177 @@
+'use client';
+
+import React, { useEffect, useState } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { QRCodeSVG } from 'qrcode.react';
+import { Wifi, Battery, Signal } from 'lucide-react';
+
+interface PhoneMockupProps {
+ url: string;
+ foreground: string;
+ background: string;
+}
+
+export const PhoneMockup: React.FC = ({
+ url,
+ foreground,
+ background,
+}) => {
+ const [isScanning, setIsScanning] = useState(false);
+ const [showResult, setShowResult] = useState(false);
+
+ // Auto-scan animation every 6 seconds
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setIsScanning(true);
+ setTimeout(() => {
+ setIsScanning(false);
+ setShowResult(true);
+ setTimeout(() => {
+ setShowResult(false);
+ }, 2000);
+ }, 1500);
+ }, 6000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ // Extract domain from URL for preview
+ const getDomain = (urlString: string) => {
+ try {
+ const urlObj = new URL(urlString);
+ return urlObj.hostname.replace('www.', '');
+ } catch {
+ return 'qrmaster.net';
+ }
+ };
+
+ return (
+
+ {/* Phone Frame */}
+
+ {/* Phone outline */}
+
+ {/* Screen */}
+
+ {/* Status Bar */}
+
+
+ {/* Camera Notch */}
+
+
+ {/* Screen Content */}
+
+
+ {/* Camera App Header */}
+
+
QR Scanner
+
+
+ {/* QR Code Display Area */}
+
+ {/* QR Code */}
+
+
+
+
+
+ {/* Scan Overlay */}
+
+ {isScanning && (
+
+
+
+ )}
+
+
+ {/* Corner Brackets */}
+
+
+
+
+
+
+
+ {/* Result Notification */}
+
+ {showResult && (
+
+
+
+
+
QR Code Scanned
+
{getDomain(url)}
+
+
+
+ )}
+
+
+ {/* Instruction Text */}
+
+
+ {isScanning ? 'Scanning...' : 'Point camera at QR code'}
+
+
+
+
+
+
+ {/* Side buttons */}
+
+
+
+
+
+ {/* Phone Shadow */}
+
+
+
+ {/* Floating Label */}
+
+ Live Scan Preview
+
+
+ );
+};
diff --git a/src/components/marketing/PresetGallery.tsx b/src/components/marketing/PresetGallery.tsx
new file mode 100644
index 0000000..ba09437
--- /dev/null
+++ b/src/components/marketing/PresetGallery.tsx
@@ -0,0 +1,135 @@
+'use client';
+
+import React from 'react';
+import { motion } from 'framer-motion';
+import { QRCodeSVG } from 'qrcode.react';
+
+export interface QRPreset {
+ id: string;
+ name: string;
+ category: 'bold' | 'minimal' | 'gradient' | 'brand';
+ fg: string;
+ bg: string;
+ description?: string;
+}
+
+export const qrPresets: QRPreset[] = [
+ // Bold
+ { id: 'bold-1', name: 'Classic Black', category: 'bold', fg: '#000000', bg: '#FFFFFF', description: 'Timeless professional' },
+ { id: 'bold-2', name: 'Deep Ocean', category: 'bold', fg: '#0369a1', bg: '#e0f2fe', description: 'Trust and reliability' },
+ { id: 'bold-3', name: 'Forest Green', category: 'bold', fg: '#047857', bg: '#d1fae5', description: 'Growth and nature' },
+ { id: 'bold-4', name: 'Royal Purple', category: 'bold', fg: '#7c3aed', bg: '#f3e8ff', description: 'Premium and luxury' },
+
+ // Minimal
+ { id: 'minimal-1', name: 'Soft Gray', category: 'minimal', fg: '#6b7280', bg: '#f9fafb', description: 'Subtle elegance' },
+ { id: 'minimal-2', name: 'Gentle Blue', category: 'minimal', fg: '#60a5fa', bg: '#eff6ff', description: 'Calm and clean' },
+ { id: 'minimal-3', name: 'Muted Green', category: 'minimal', fg: '#34d399', bg: '#ecfdf5', description: 'Fresh and light' },
+ { id: 'minimal-4', name: 'Soft Pink', category: 'minimal', fg: '#f472b6', bg: '#fdf2f8', description: 'Friendly warmth' },
+
+ // Gradient-inspired
+ { id: 'gradient-1', name: 'Sunset', category: 'gradient', fg: '#f59e0b', bg: '#fef3c7', description: 'Warm energy' },
+ { id: 'gradient-2', name: 'Cotton Candy', category: 'gradient', fg: '#ec4899', bg: '#fce7f3', description: 'Playful fun' },
+ { id: 'gradient-3', name: 'Sky Blue', category: 'gradient', fg: '#0ea5e9', bg: '#e0f2fe', description: 'Open freedom' },
+ { id: 'gradient-4', name: 'Mint Fresh', category: 'gradient', fg: '#14b8a6', bg: '#ccfbf1', description: 'Modern clean' },
+];
+
+interface PresetGalleryProps {
+ selectedPreset: string;
+ onPresetSelect: (preset: QRPreset) => void;
+ url: string;
+}
+
+export const PresetGallery: React.FC = ({
+ selectedPreset,
+ onPresetSelect,
+ url,
+}) => {
+ const [activeCategory, setActiveCategory] = React.useState('all');
+
+ const categories = ['all', 'bold', 'minimal', 'gradient'] as const;
+
+ const filteredPresets = activeCategory === 'all'
+ ? qrPresets
+ : qrPresets.filter(p => p.category === activeCategory);
+
+ return (
+
+ {/* Category Filter */}
+
+ {categories.map((cat) => (
+ setActiveCategory(cat)}
+ className={`px-4 py-2 rounded-lg font-medium text-sm transition-all capitalize ${
+ activeCategory === cat
+ ? 'bg-primary-600 text-white shadow-md'
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+ }`}
+ >
+ {cat}
+
+ ))}
+
+
+ {/* Preset Grid */}
+
+ {filteredPresets.map((preset, index) => (
+
onPresetSelect(preset)}
+ initial={{ opacity: 0, scale: 0.9 }}
+ animate={{ opacity: 1, scale: 1 }}
+ transition={{ delay: index * 0.05, duration: 0.3 }}
+ className={`group relative p-4 rounded-xl border-2 transition-all ${
+ selectedPreset === preset.id
+ ? 'border-primary-500 bg-primary-50 shadow-md'
+ : 'border-gray-200 bg-white hover:border-primary-300 hover:shadow-sm'
+ }`}
+ >
+ {/* Mini QR Preview */}
+
+
+ {/* Preset Info */}
+
+
+ {preset.name}
+
+
+ {preset.description}
+
+
+
+ {/* Selected Indicator */}
+ {selectedPreset === preset.id && (
+
+
+
+ )}
+
+ {/* Hover Glow */}
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/marketing/Pricing.tsx b/src/components/marketing/Pricing.tsx
index 2655f6d..59603ed 100644
--- a/src/components/marketing/Pricing.tsx
+++ b/src/components/marketing/Pricing.tsx
@@ -31,19 +31,19 @@ export const Pricing: React.FC = ({ t }) => {
];
return (
-
+
-
+
{t.pricing.title}
-
+
{t.pricing.subtitle}
@@ -63,11 +63,15 @@ export const Pricing: React.FC
= ({ t }) => {
className="h-full"
>
+ {/* Gradient overlay for popular */}
+ {plan.popular && (
+
+ )}
{plan.popular && (
diff --git a/src/components/marketing/StatsCounter.tsx b/src/components/marketing/StatsCounter.tsx
new file mode 100644
index 0000000..cec6af5
--- /dev/null
+++ b/src/components/marketing/StatsCounter.tsx
@@ -0,0 +1,114 @@
+'use client';
+
+import React from 'react';
+import { motion, useInView, useMotionValue, useSpring } from 'framer-motion';
+
+interface Stat {
+ label: string;
+ value: number;
+ suffix?: string;
+ prefix?: string;
+ decimals?: number;
+}
+
+interface StatsCounterProps {
+ stats: Stat[];
+}
+
+const AnimatedNumber: React.FC<{
+ value: number;
+ prefix?: string;
+ suffix?: string;
+ decimals?: number;
+}> = ({ value, prefix = '', suffix = '', decimals = 0 }) => {
+ const ref = React.useRef(null);
+ const isInView = useInView(ref, { once: true, margin: '-100px' });
+ const motionValue = useMotionValue(0);
+ const springValue = useSpring(motionValue, {
+ damping: 60,
+ stiffness: 100,
+ });
+ const [displayValue, setDisplayValue] = React.useState('0');
+
+ React.useEffect(() => {
+ if (isInView) {
+ motionValue.set(value);
+ }
+ }, [isInView, motionValue, value]);
+
+ React.useEffect(() => {
+ const unsubscribe = springValue.on('change', (latest) => {
+ setDisplayValue(latest.toFixed(decimals));
+ });
+
+ return unsubscribe;
+ }, [springValue, decimals]);
+
+ return (
+
+ {prefix}
+ {displayValue}
+ {suffix}
+
+ );
+};
+
+export const StatsCounter: React.FC = ({ stats }) => {
+ return (
+
+ {stats.map((stat, index) => (
+
+
+ {stat.label}
+
+ ))}
+
+ );
+};
+
+/**
+ * Default stats - can be overridden with real data
+ * For now using generic/aspirational numbers
+ */
+export const defaultStats: Stat[] = [
+ {
+ label: 'QR Codes Created',
+ value: 850,
+ suffix: 'K+',
+ },
+ {
+ label: 'Total Scans',
+ value: 12.5,
+ suffix: 'M+',
+ decimals: 1,
+ },
+ {
+ label: 'Active Users',
+ value: 25,
+ suffix: 'K+',
+ },
+ {
+ label: 'Uptime',
+ value: 99.9,
+ suffix: '%',
+ decimals: 1,
+ },
+];
diff --git a/src/lib/animations.ts b/src/lib/animations.ts
new file mode 100644
index 0000000..a4301b6
--- /dev/null
+++ b/src/lib/animations.ts
@@ -0,0 +1,249 @@
+/**
+ * Centralized Animation Variants for Framer Motion
+ * Premium animation library for QR Master landing page
+ */
+
+import type { Variants } from 'framer-motion';
+
+// Custom easing curves
+export const easings = {
+ smooth: [0.25, 0.1, 0.25, 1],
+ snappy: [0.34, 1.56, 0.64, 1], // Bounce effect
+ elegant: [0.43, 0.13, 0.23, 0.96],
+} as const;
+
+// Fade & Scale animations
+export const fadeIn: Variants = {
+ initial: { opacity: 0 },
+ animate: {
+ opacity: 1,
+ transition: { duration: 0.5, ease: easings.smooth }
+ },
+};
+
+export const scaleIn: Variants = {
+ initial: { opacity: 0, scale: 0.95 },
+ animate: {
+ opacity: 1,
+ scale: 1,
+ transition: { duration: 0.3, ease: easings.smooth }
+ },
+};
+
+export const scaleInBounce: Variants = {
+ initial: { opacity: 0, scale: 0.8 },
+ animate: {
+ opacity: 1,
+ scale: 1,
+ transition: { duration: 0.5, ease: easings.snappy }
+ },
+};
+
+// Slide animations
+export const slideUp: Variants = {
+ initial: { opacity: 0, y: 30 },
+ animate: {
+ opacity: 1,
+ y: 0,
+ transition: { duration: 0.5, ease: easings.smooth }
+ },
+};
+
+export const slideUpBounce: Variants = {
+ initial: { opacity: 0, y: 30 },
+ animate: {
+ opacity: 1,
+ y: 0,
+ transition: { duration: 0.5, ease: easings.snappy }
+ },
+};
+
+export const slideDown: Variants = {
+ initial: { opacity: 0, y: -30 },
+ animate: {
+ opacity: 1,
+ y: 0,
+ transition: { duration: 0.5, ease: easings.smooth }
+ },
+};
+
+export const slideLeft: Variants = {
+ initial: { opacity: 0, x: 30 },
+ animate: {
+ opacity: 1,
+ x: 0,
+ transition: { duration: 0.5, ease: easings.smooth }
+ },
+};
+
+export const slideRight: Variants = {
+ initial: { opacity: 0, x: -30 },
+ animate: {
+ opacity: 1,
+ x: 0,
+ transition: { duration: 0.5, ease: easings.smooth }
+ },
+};
+
+// Container animations with stagger
+export const staggerContainer: Variants = {
+ initial: {},
+ animate: {
+ transition: {
+ staggerChildren: 0.1,
+ delayChildren: 0.1,
+ }
+ },
+};
+
+export const staggerContainerFast: Variants = {
+ initial: {},
+ animate: {
+ transition: {
+ staggerChildren: 0.05,
+ delayChildren: 0,
+ }
+ },
+};
+
+export const staggerItem: Variants = {
+ initial: { opacity: 0, y: 20 },
+ animate: {
+ opacity: 1,
+ y: 0,
+ transition: { duration: 0.4, ease: easings.smooth }
+ },
+};
+
+// Morph animations (for QR code transformations)
+export const colorMorph: Variants = {
+ initial: { scale: 1 },
+ animate: {
+ scale: [1, 1.02, 1],
+ transition: { duration: 0.4, ease: easings.smooth }
+ },
+};
+
+// Hover states
+export const hoverScale = {
+ scale: 1.05,
+ transition: { duration: 0.2, ease: easings.smooth }
+};
+
+export const hoverScaleSmall = {
+ scale: 1.02,
+ transition: { duration: 0.2, ease: easings.smooth }
+};
+
+export const hoverLift = {
+ y: -4,
+ transition: { duration: 0.2, ease: easings.smooth }
+};
+
+export const hoverGlow = {
+ boxShadow: '0 8px 32px rgba(99, 102, 241, 0.25)',
+ transition: { duration: 0.3, ease: easings.smooth }
+};
+
+// Rotate animations
+export const rotate360: Variants = {
+ initial: { rotate: 0 },
+ animate: {
+ rotate: 360,
+ transition: { duration: 0.6, ease: easings.smooth }
+ },
+};
+
+export const pulseGlow: Variants = {
+ initial: { opacity: 0.7 },
+ animate: {
+ opacity: [0.7, 1, 0.7],
+ transition: {
+ duration: 2,
+ repeat: Infinity,
+ ease: 'easeInOut'
+ }
+ },
+};
+
+// Advanced entrance animations
+export const revealFromBottom: Variants = {
+ initial: {
+ opacity: 0,
+ y: 50,
+ clipPath: 'inset(100% 0 0 0)'
+ },
+ animate: {
+ opacity: 1,
+ y: 0,
+ clipPath: 'inset(0% 0 0 0)',
+ transition: {
+ duration: 0.7,
+ ease: easings.elegant
+ }
+ },
+};
+
+// Scroll-triggered animation preset
+export const scrollReveal = {
+ initial: "initial",
+ whileInView: "animate",
+ viewport: { once: true, margin: "-100px" }
+} as const;
+
+// Interactive button animations
+export const buttonTap = {
+ scale: 0.95,
+ transition: { duration: 0.1 }
+};
+
+export const buttonHover = {
+ scale: 1.02,
+ boxShadow: '0 12px 32px rgba(99, 102, 241, 0.3)',
+ transition: { duration: 0.2 }
+};
+
+// Float animation (for decorative elements)
+export const float: Variants = {
+ initial: { y: 0 },
+ animate: {
+ y: [-10, 10, -10],
+ transition: {
+ duration: 4,
+ repeat: Infinity,
+ ease: 'easeInOut'
+ }
+ }
+};
+
+// Shimmer effect
+export const shimmer: Variants = {
+ initial: { backgroundPosition: '-200% 0' },
+ animate: {
+ backgroundPosition: '200% 0',
+ transition: {
+ duration: 2,
+ repeat: Infinity,
+ ease: 'linear'
+ }
+ }
+};
+
+// Drawing animation (for borders/lines)
+export const drawLine: Variants = {
+ initial: { pathLength: 0, opacity: 0 },
+ animate: {
+ pathLength: 1,
+ opacity: 1,
+ transition: { duration: 1.5, ease: easings.smooth }
+ }
+};
+
+// Count-up animation helper
+export const createCountUpVariant = (from: number, to: number, duration = 2): Variants => ({
+ initial: { value: from },
+ animate: {
+ value: to,
+ transition: { duration, ease: easings.smooth }
+ }
+});
diff --git a/src/styles/globals.css b/src/styles/globals.css
index cbc0615..cb81437 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -4,22 +4,21 @@
@layer utilities {
- /* Floating blob animation for hero background */
- @keyframes blob {
+ /* =======================
+ ANIMATIONS
+ ======================= */
- 0%,
- 100% {
+ /* Floating blob animation for hero background (DEPRECATED - use grid instead) */
+ @keyframes blob {
+ 0%, 100% {
transform: translate(0, 0) scale(1);
}
-
25% {
transform: translate(20px, -30px) scale(1.1);
}
-
50% {
transform: translate(-20px, 20px) scale(0.9);
}
-
75% {
transform: translate(30px, 10px) scale(1.05);
}
@@ -40,6 +39,147 @@
.animation-delay-6000 {
animation-delay: 6s;
}
+
+ /* Grid dash animation for modern background */
+ @keyframes dashMove {
+ 0% {
+ stroke-dashoffset: 0;
+ }
+ 100% {
+ stroke-dashoffset: 24;
+ }
+ }
+
+ .animate-dash {
+ animation: dashMove 20s linear infinite;
+ }
+
+ /* Shimmer effect for loading/highlights */
+ @keyframes shimmer {
+ 0% {
+ background-position: -200% 0;
+ }
+ 100% {
+ background-position: 200% 0;
+ }
+ }
+
+ .animate-shimmer {
+ background: linear-gradient(
+ 90deg,
+ transparent 0%,
+ rgba(255, 255, 255, 0.4) 50%,
+ transparent 100%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 2s infinite;
+ }
+
+ /* Pulse glow for interactive elements */
+ @keyframes pulseGlow {
+ 0%, 100% {
+ opacity: 0.7;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.05);
+ }
+ }
+
+ .animate-pulse-glow {
+ animation: pulseGlow 2s ease-in-out infinite;
+ }
+
+ /* Float animation for decorative elements */
+ @keyframes float {
+ 0%, 100% {
+ transform: translateY(0px);
+ }
+ 50% {
+ transform: translateY(-20px);
+ }
+ }
+
+ .animate-float {
+ animation: float 4s ease-in-out infinite;
+ }
+
+ /* =======================
+ GLASSMORPHISM
+ ======================= */
+
+ .glass {
+ background: rgba(255, 255, 255, 0.1);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
+ }
+
+ .glass-strong {
+ background: rgba(255, 255, 255, 0.25);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
+ }
+
+ .glass-dark {
+ background: rgba(0, 0, 0, 0.2);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ /* =======================
+ ADVANCED SHADOWS
+ ======================= */
+
+ .shadow-primary-sm {
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.1);
+ }
+
+ .shadow-primary-md {
+ box-shadow: 0 4px 16px rgba(99, 102, 241, 0.15);
+ }
+
+ .shadow-primary-lg {
+ box-shadow: 0 20px 40px rgba(99, 102, 241, 0.2);
+ }
+
+ .shadow-success {
+ box-shadow: 0 8px 24px rgba(16, 185, 129, 0.2);
+ }
+
+ .shadow-warning {
+ box-shadow: 0 8px 24px rgba(245, 158, 11, 0.2);
+ }
+
+ .shadow-elevation-1 {
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ }
+
+ .shadow-elevation-2 {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+ }
+
+ .shadow-elevation-3 {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+ }
+
+ .shadow-elevation-4 {
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
+ }
+
+ /* Colored inner glow */
+ .glow-primary {
+ box-shadow: inset 0 0 20px rgba(99, 102, 241, 0.3);
+ }
+
+ .glow-success {
+ box-shadow: inset 0 0 20px rgba(16, 185, 129, 0.3);
+ }
}
:root {
@@ -155,13 +295,82 @@ a {
@apply animate-pulse bg-gray-200 rounded;
}
-/* Gradient backgrounds */
+/* =======================
+ PREMIUM GRADIENTS
+ ======================= */
+
+/* Primary gradients */
.gradient-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
+.gradient-primary-vibrant {
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%);
+}
+
+.gradient-primary-subtle {
+ background: linear-gradient(135deg, #eef2ff 0%, #f5f3ff 100%);
+}
+
+/* Success gradients */
.gradient-success {
- background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+}
+
+.gradient-success-vibrant {
+ background: linear-gradient(135deg, #34d399 0%, #10b981 50%, #059669 100%);
+}
+
+/* Accent gradients */
+.gradient-accent {
+ background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%);
+}
+
+.gradient-warm {
+ background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);
+}
+
+.gradient-cool {
+ background: linear-gradient(135deg, #06b6d4 0%, #8b5cf6 100%);
+}
+
+/* Gradient text */
+.gradient-text {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.gradient-text-vibrant {
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+/* Gradient borders */
+.gradient-border {
+ border: 2px solid transparent;
+ background-clip: padding-box, border-box;
+ background-origin: padding-box, border-box;
+ background-image: linear-gradient(white, white), linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+/* Animated gradient background */
+@keyframes gradientShift {
+ 0%, 100% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+}
+
+.gradient-animated {
+ background: linear-gradient(270deg, #667eea, #764ba2, #8b5cf6);
+ background-size: 200% 200%;
+ animation: gradientShift 8s ease infinite;
}
/* Chart container */