diff --git a/public/blog/qr_master_cover.png b/public/blog/qr_master_cover.png new file mode 100644 index 0000000..a77174a Binary files /dev/null and b/public/blog/qr_master_cover.png differ diff --git a/public/blog/qr_master_profile.png b/public/blog/qr_master_profile.png new file mode 100644 index 0000000..39082e1 Binary files /dev/null and b/public/blog/qr_master_profile.png differ diff --git a/src/components/marketing/AnimatedBackground.tsx b/src/components/marketing/AnimatedBackground.tsx new file mode 100644 index 0000000..958005b --- /dev/null +++ b/src/components/marketing/AnimatedBackground.tsx @@ -0,0 +1,77 @@ +'use client'; + +import React from 'react'; + +/** + * Modern animated grid background for Hero section + * Replaces the overused "blob" animations with a more premium, tech-forward aesthetic + */ +export const AnimatedBackground: React.FC = () => { + return ( +
+ {/* Subtle gradient overlay */} +
+ + {/* Animated grid pattern */} + + + {/* Gradient for grid lines */} + + + + + + + {/* Pattern definition */} + + {/* Vertical line */} + + {/* Horizontal line */} + + + + + {/* Grid overlay */} + + + + {/* Floating gradient orbs (subtle, as accents) */} +
+
+ + {/* Noise texture overlay for depth */} +
+
+ ); +}; diff --git a/src/components/marketing/BentoGrid.tsx b/src/components/marketing/BentoGrid.tsx new file mode 100644 index 0000000..d900fd8 --- /dev/null +++ b/src/components/marketing/BentoGrid.tsx @@ -0,0 +1,50 @@ +'use client'; + +import React from 'react'; +import { motion } from 'framer-motion'; + +interface BentoGridProps { + children: React.ReactNode[]; +} + +/** + * Modern Bento Box Grid Layout + * Features are displayed in different sizes based on importance + */ +export const BentoGrid: React.FC = ({ children }) => { + return ( +
+ {children} +
+ ); +}; + +interface BentoItemProps { + children: React.ReactNode; + size?: 'small' | 'medium' | 'large'; + className?: string; +} + +export const BentoItem: React.FC = ({ + children, + size = 'medium', + className = '', +}) => { + const sizeClasses = { + small: 'md:col-span-2', + medium: 'md:col-span-3', + large: 'md:col-span-6', + }; + + return ( + + {children} + + ); +}; diff --git a/src/components/marketing/FeatureAnalyticsDemo.tsx b/src/components/marketing/FeatureAnalyticsDemo.tsx new file mode 100644 index 0000000..dd91df0 --- /dev/null +++ b/src/components/marketing/FeatureAnalyticsDemo.tsx @@ -0,0 +1,95 @@ +'use client'; + +import React from 'react'; +import { motion } from 'framer-motion'; +import { TrendingUp, Eye, MousePointer } from 'lucide-react'; + +export const FeatureAnalyticsDemo: React.FC = () => { + const data = [30, 45, 35, 60, 50, 75, 65, 85]; + + return ( +
+ {/* Background Pattern */} +
+ + + + + + +
+ +
+ {/* Header */} +
+
+ +

Analytics

+
+

Real-time tracking and insights

+
+ + {/* Animated Chart */} +
+ {data.map((value, index) => ( + + + {value} + + + ))} +
+ + {/* Stats */} +
+
+
+ + Total Scans +
+ + 12,547 + +
+
+
+ + Click Rate +
+ + 87% + +
+
+
+
+ ); +}; diff --git a/src/components/marketing/FeatureBulkDemo.tsx b/src/components/marketing/FeatureBulkDemo.tsx new file mode 100644 index 0000000..33b434a --- /dev/null +++ b/src/components/marketing/FeatureBulkDemo.tsx @@ -0,0 +1,107 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { QRCodeSVG } from 'qrcode.react'; +import { FileSpreadsheet, ArrowRight } from 'lucide-react'; + +export const FeatureBulkDemo: React.FC = () => { + const [showQRs, setShowQRs] = useState(false); + + useEffect(() => { + const interval = setInterval(() => { + setShowQRs((prev) => !prev); + }, 4000); + + return () => clearInterval(interval); + }, []); + + const qrCodes = [ + { id: 1, url: 'product-1', delay: 0 }, + { id: 2, url: 'product-2', delay: 0.1 }, + { id: 3, url: 'product-3', delay: 0.2 }, + { id: 4, url: 'product-4', delay: 0.3 }, + { id: 5, url: 'product-5', delay: 0.4 }, + { id: 6, url: 'product-6', delay: 0.5 }, + ]; + + return ( +
+
+ {/* Header */} +
+
+ +

Bulk Creation

+
+

Generate hundreds at once

+
+ + {/* Animation */} +
+ {/* CSV Icon */} + +
+ +
+

products.csv

+
+ + {/* Arrow */} + + + + + {/* QR Codes Grid */} +
+ {qrCodes.map((qr) => ( + + + + ))} +
+
+ + {/* Counter */} + +

+ ✓ 6 QR codes generated +

+
+
+
+ ); +}; diff --git a/src/components/marketing/FeatureCustomizationDemo.tsx b/src/components/marketing/FeatureCustomizationDemo.tsx new file mode 100644 index 0000000..db20475 --- /dev/null +++ b/src/components/marketing/FeatureCustomizationDemo.tsx @@ -0,0 +1,82 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { QRCodeSVG } from 'qrcode.react'; +import { Palette } from 'lucide-react'; + +const colorPresets = [ + { fg: '#000000', bg: '#FFFFFF' }, + { fg: '#0ea5e9', bg: '#e0f2fe' }, + { fg: '#8b5cf6', bg: '#f3e8ff' }, + { fg: '#f59e0b', bg: '#fef3c7' }, +]; + +export const FeatureCustomizationDemo: React.FC = () => { + const [activeIndex, setActiveIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setActiveIndex((prev) => (prev + 1) % colorPresets.length); + }, 2500); + + return () => clearInterval(interval); + }, []); + + const currentPreset = colorPresets[activeIndex]; + + return ( +
+
+ {/* Header */} +
+
+ +

Customization

+
+

Brand your QR codes

+
+ + {/* QR Code with Morphing */} +
+ + + + + +
+ + {/* Color Dots Indicator */} +
+ {colorPresets.map((preset, index) => ( + setActiveIndex(index)} + className={`w-3 h-3 rounded-full transition-all ${ + index === activeIndex ? 'scale-125' : 'scale-100 opacity-50' + }`} + style={{ + background: `linear-gradient(135deg, ${preset.fg}, ${preset.bg})` + }} + whileHover={{ scale: 1.3 }} + whileTap={{ scale: 0.9 }} + /> + ))} +
+
+
+ ); +}; diff --git a/src/components/marketing/Features.tsx b/src/components/marketing/Features.tsx index d701366..1a779c5 100644 --- a/src/components/marketing/Features.tsx +++ b/src/components/marketing/Features.tsx @@ -2,83 +2,139 @@ import React from 'react'; import { motion } from 'framer-motion'; -import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; +import { BentoGrid, BentoItem } from './BentoGrid'; +import { FeatureAnalyticsDemo } from './FeatureAnalyticsDemo'; +import { FeatureCustomizationDemo } from './FeatureCustomizationDemo'; +import { FeatureBulkDemo } from './FeatureBulkDemo'; +import { Infinity as InfinityIcon, Clock, Shield } from 'lucide-react'; interface FeaturesProps { t: any; // i18n translation function } export const Features: React.FC = ({ t }) => { - const features = [ - { - key: 'analytics', - icon: ( - - ), - color: 'text-blue-600 bg-blue-100', - }, - { - key: 'customization', - icon: ( - - ), - color: 'text-purple-600 bg-purple-100', - }, - { - key: 'unlimited', - icon: ( - - ), - color: 'text-green-600 bg-green-100', - }, - ]; - return ( -
+
+ {/* Section Header */} -

+

{t.features.title}

+

+ Everything you need to create, manage, and track QR codes at scale +

-
- {features.map((feature, index) => ( - - - -
- {feature.icon} -
- {t.features[feature.key].title} -
- -

- {t.features[feature.key].description} -

-
-
-
- ))} -
+ {/* Bento Grid */} + + {/* Analytics - Large */} + + + + + {/* Customization - Medium */} + + + + + {/* Bulk Creation - Medium */} + + + + + {/* Unlimited - Small */} + +
+
+ +

+ {t.features.unlimited.title} +

+

+ {t.features.unlimited.description} +

+
+ + + +
+
+ + {/* Reliable - Small */} + +
+
+ +

+ 24/7 Reliable +

+

+ 99.9% uptime guarantee with enterprise-grade infrastructure +

+
+ +
+ 99.9% +
+
+
+
+ + {/* Secure - Small */} + +
+
+ +

+ Enterprise Security +

+

+ Bank-level encryption and GDPR compliant data handling +

+
+ + + +
+
+
); diff --git a/src/components/marketing/Hero.tsx b/src/components/marketing/Hero.tsx index 4b2d519..904ab19 100644 --- a/src/components/marketing/Hero.tsx +++ b/src/components/marketing/Hero.tsx @@ -4,152 +4,131 @@ import React from 'react'; import Link from 'next/link'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; -import { Card } from '@/components/ui/Card'; import { motion } from 'framer-motion'; -import { Globe, User, MapPin, Phone, CheckCircle2, ArrowRight } from 'lucide-react'; +import { CheckCircle2, ArrowRight, Zap } from 'lucide-react'; +import { AnimatedBackground } from './AnimatedBackground'; +import { HeroQRInteractive } from './HeroQRInteractive'; +import { StatsCounter, defaultStats } from './StatsCounter'; +import { slideUp, slideRight, staggerContainer, staggerItem } from '@/lib/animations'; interface HeroProps { t: any; // i18n translation function } export const Hero: React.FC = ({ t }) => { - const templateCards = [ - { title: 'URL/Website', color: 'bg-blue-500/10 text-blue-600', icon: Globe }, - { title: 'Contact Card', color: 'bg-purple-500/10 text-purple-600', icon: User }, - { title: 'Location', color: 'bg-green-500/10 text-green-600', icon: MapPin }, - { title: 'Phone Number', color: 'bg-pink-500/10 text-pink-600', icon: Phone }, - ]; - - const containerjs = { - hidden: { opacity: 0 }, - show: { - opacity: 1, - transition: { - staggerChildren: 0.1 - } - } - }; - - const itemjs = { - hidden: { opacity: 0, y: 20 }, - show: { opacity: 1, y: 0 } - }; - return ( -
- {/* Animated Background Orbs */} -
- {/* Orb 1 - Blue (top-left) */} -
- - {/* Orb 2 - Purple (top-right) */} -
- - {/* Orb 3 - Pink (bottom-left) */} -
- - {/* Orb 4 - Cyan (center-right) */} -
-
+
+ {/* Modern Animated Background */} +
-
- {/* 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 }} + > +
+
+ {preset.name} +
+ + ))} +
+ +
+
+ + {/* 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" - /> - -
-
- -
- setForegroundColor(e.target.value)} - className="w-14 h-12 rounded border border-gray-300 cursor-pointer" - aria-label="Foreground color picker" - /> - setForegroundColor(e.target.value)} - className="flex-1" - aria-label="Foreground color hex value" - /> -
-
- -
- -
- setBackgroundColor(e.target.value)} - className="w-14 h-12 rounded border border-gray-300 cursor-pointer" - aria-label="Background color picker" - /> - setBackgroundColor(e.target.value)} - className="flex-1" - aria-label="Background color hex value" - /> -
-
-
- -
-
- - -
- -
- - setSize(Number(e.target.value))} - className="w-full accent-primary-600" - aria-label={`QR code size: ${size} pixels`} - /> - -
+ + {tab.label} + + ))}
-
- - {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" + /> -
- - + +
+
- - - {/* Right Preview */} + {/* Right: Preview */} - {/* Artistic Curved Lines Background */} -
- - - - - - - - - - - - - - - -
+ {/* Large QR Preview */} +
+
+
+ +
+
- {/* Decorative Orbs */} -
-
- -
-

{t.generator.live_preview}

-
- {url ? ( -
- -
- ) : ( -
- Enter URL -
- )} -
-
- {url || 'https://example.com'} -
-
{t.generator.demo_note}
-
+ {/* Phone Mockup Preview */} +
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 */} +
+
+ 9:41 +
+ + + +
+
+
+ + {/* 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) => ( + + ))} +
+ + {/* 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 */