From 3c6d75b6bb10fd039b0901a32bb712038d4ea554 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 19 Jan 2026 09:14:52 +0100 Subject: [PATCH] refactor: Cleaner landing page - focused hero & reduced animations **User Feedback:** - Hero Section: Too much going on, redundant generator - Animations: Too excessive throughout - Phone Mockup: Works great, keep it! **Hero Section - Major Cleanup:** - REMOVED: Interactive QR Generator (redundant with generator below) - NEW: QRTypesShowcase - 3x3 grid showing 9 QR code types - NEW: Auto-rotating phone mockup demonstrating each type - Shows variety instead of single interactive element - Much cleaner, more focused first impression **Animation Cleanup:** - FeatureCustomizationDemo: Cycles ONCE then stops - FeatureBulkDemo: Animates ONCE then stays static - Features.tsx: Removed all infinite animations (rotate, scale, etc.) - StatsCounter: Subtiler - smaller text, slower animation - No more animation overload! **Philosophy:** - CLEANER > overloaded - FOCUSED > excessive interaction - SUBTLE > flashy animations - Show variety > show everything **PhoneMockup Enhanced:** - Auto-rotates through 9 QR types every 5s - Shows scan animation for each type - Displays type name in notification - Clean demo of all capabilities **Components:** - NEW: QRTypesShowcase.tsx - Grid with 9 QR types - UPDATED: PhoneMockup.tsx - Auto-rotation logic - UPDATED: Hero.tsx - Uses showcase instead of generator - UPDATED: Features.tsx - Static icons, no infinite loops - UPDATED: StatsCounter.tsx - Subtiler appearance Result: Professional, clean, focused landing page without animation chaos! Co-Authored-By: Claude Sonnet 4.5 --- src/components/marketing/FeatureBulkDemo.tsx | 14 +- .../marketing/FeatureCustomizationDemo.tsx | 17 +- src/components/marketing/Features.tsx | 42 +---- src/components/marketing/Hero.tsx | 59 +++++-- src/components/marketing/PhoneMockup.tsx | 163 +++++++++--------- src/components/marketing/QRTypesShowcase.tsx | 143 +++++++++++++++ src/components/marketing/StatsCounter.tsx | 22 +-- 7 files changed, 311 insertions(+), 149 deletions(-) create mode 100644 src/components/marketing/QRTypesShowcase.tsx diff --git a/src/components/marketing/FeatureBulkDemo.tsx b/src/components/marketing/FeatureBulkDemo.tsx index 33b434a..73678bc 100644 --- a/src/components/marketing/FeatureBulkDemo.tsx +++ b/src/components/marketing/FeatureBulkDemo.tsx @@ -7,14 +7,18 @@ import { FileSpreadsheet, ArrowRight } from 'lucide-react'; export const FeatureBulkDemo: React.FC = () => { const [showQRs, setShowQRs] = useState(false); + const [hasAnimated, setHasAnimated] = useState(false); useEffect(() => { - const interval = setInterval(() => { - setShowQRs((prev) => !prev); - }, 4000); + if (hasAnimated) return; - return () => clearInterval(interval); - }, []); + const timer = setTimeout(() => { + setShowQRs(true); + setHasAnimated(true); + }, 500); + + return () => clearTimeout(timer); + }, [hasAnimated]); const qrCodes = [ { id: 1, url: 'product-1', delay: 0 }, diff --git a/src/components/marketing/FeatureCustomizationDemo.tsx b/src/components/marketing/FeatureCustomizationDemo.tsx index db20475..1211703 100644 --- a/src/components/marketing/FeatureCustomizationDemo.tsx +++ b/src/components/marketing/FeatureCustomizationDemo.tsx @@ -14,14 +14,25 @@ const colorPresets = [ export const FeatureCustomizationDemo: React.FC = () => { const [activeIndex, setActiveIndex] = useState(0); + const [hasAnimated, setHasAnimated] = useState(false); useEffect(() => { + if (hasAnimated) return; + const interval = setInterval(() => { - setActiveIndex((prev) => (prev + 1) % colorPresets.length); - }, 2500); + setActiveIndex((prev) => { + const next = prev + 1; + if (next >= colorPresets.length) { + clearInterval(interval); + setHasAnimated(true); + return 0; // Reset to first + } + return next; + }); + }, 1500); return () => clearInterval(interval); - }, []); + }, [hasAnimated]); const currentPreset = colorPresets[activeIndex]; diff --git a/src/components/marketing/Features.tsx b/src/components/marketing/Features.tsx index 1a779c5..f761144 100644 --- a/src/components/marketing/Features.tsx +++ b/src/components/marketing/Features.tsx @@ -61,19 +61,9 @@ export const Features: React.FC = ({ t }) => { {t.features.unlimited.description}

- +
- +
@@ -89,21 +79,11 @@ export const Features: React.FC = ({ t }) => { 99.9% uptime guarantee with enterprise-grade infrastructure

- +
99.9%
- +
@@ -119,19 +99,9 @@ export const Features: React.FC = ({ t }) => { Bank-level encryption and GDPR compliant data handling

- +
- +
diff --git a/src/components/marketing/Hero.tsx b/src/components/marketing/Hero.tsx index 904ab19..2d65469 100644 --- a/src/components/marketing/Hero.tsx +++ b/src/components/marketing/Hero.tsx @@ -7,9 +7,9 @@ import { Badge } from '@/components/ui/Badge'; import { motion } from 'framer-motion'; import { CheckCircle2, ArrowRight, Zap } from 'lucide-react'; import { AnimatedBackground } from './AnimatedBackground'; -import { HeroQRInteractive } from './HeroQRInteractive'; +import { QRTypesShowcase } from './QRTypesShowcase'; +import { PhoneMockup } from './PhoneMockup'; import { StatsCounter, defaultStats } from './StatsCounter'; -import { slideUp, slideRight, staggerContainer, staggerItem } from '@/lib/animations'; interface HeroProps { t: any; // i18n translation function @@ -26,13 +26,18 @@ export const Hero: React.FC = ({ t }) => {
{/* Left: Content */} {/* Badge */} - + {t.hero.badge} @@ -40,7 +45,12 @@ export const Hero: React.FC = ({ t }) => { {/* Headline */} - +

{t.hero.title}

@@ -51,13 +61,18 @@ export const Hero: React.FC = ({ t }) => {
{/* Feature List */} - + {t.hero.features.map((feature: string, index: number) => (
@@ -70,7 +85,9 @@ export const Hero: React.FC = ({ t }) => { {/* CTAs */} @@ -97,7 +114,7 @@ export const Hero: React.FC = ({ t }) => {
@@ -112,19 +129,27 @@ export const Hero: React.FC = ({ t }) => { - {/* Right: Interactive QR Generator */} + {/* Right: QR Types Showcase + Phone */} - + {/* QR Types Grid */} + + + {/* Phone Mockup with Auto-Rotation */} +
+ +
- {/* Stats Counter */} - + {/* Stats Counter - Subtiler */} +
+ +
{/* Smooth Gradient Fade Transition to next section */} diff --git a/src/components/marketing/PhoneMockup.tsx b/src/components/marketing/PhoneMockup.tsx index 546e9ef..ab162bf 100644 --- a/src/components/marketing/PhoneMockup.tsx +++ b/src/components/marketing/PhoneMockup.tsx @@ -4,24 +4,37 @@ import React, { useEffect, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { QRCodeSVG } from 'qrcode.react'; import { Wifi, Battery, Signal } from 'lucide-react'; +import { qrTypes } from './QRTypesShowcase'; interface PhoneMockupProps { - url: string; - foreground: string; - background: string; + url?: string; + foreground?: string; + background?: string; + autoRotate?: boolean; } export const PhoneMockup: React.FC = ({ url, - foreground, - background, + foreground = '#000000', + background = '#FFFFFF', + autoRotate = false, }) => { + const [currentTypeIndex, setCurrentTypeIndex] = useState(0); const [isScanning, setIsScanning] = useState(false); const [showResult, setShowResult] = useState(false); - // Auto-scan animation every 6 seconds + const currentType = autoRotate ? qrTypes[currentTypeIndex] : null; + const displayUrl = url || (currentType ? currentType.data : 'https://qrmaster.net'); + const displayName = currentType ? currentType.name : 'QR Master'; + + // Auto-rotate through QR types useEffect(() => { + if (!autoRotate) return; + const interval = setInterval(() => { + setCurrentTypeIndex((prev) => (prev + 1) % qrTypes.length); + + // Trigger scan animation setIsScanning(true); setTimeout(() => { setIsScanning(false); @@ -29,30 +42,32 @@ export const PhoneMockup: React.FC = ({ setTimeout(() => { setShowResult(false); }, 2000); - }, 1500); - }, 6000); + }, 1000); + }, 5000); return () => clearInterval(interval); - }, []); + }, [autoRotate]); - // Extract domain from URL for preview const getDomain = (urlString: string) => { try { - const urlObj = new URL(urlString); - return urlObj.hostname.replace('www.', ''); + if (urlString.startsWith('http')) { + const urlObj = new URL(urlString); + return urlObj.hostname.replace('www.', ''); + } + return urlString.substring(0, 20); } catch { return 'qrmaster.net'; } }; return ( -
+
{/* Phone Frame */}
{/* Phone outline */} -
+
{/* Screen */} -
+
{/* Status Bar */}
@@ -66,57 +81,59 @@ export const PhoneMockup: React.FC = ({
{/* Camera Notch */} -
+
{/* Screen Content */}
{/* Camera App Header */} -
-

QR Scanner

+
+

QR Scanner

{/* QR Code Display Area */}
- {/* QR Code */} - -
- -
+ + +
+ +
- {/* Scan Overlay */} - - {isScanning && ( - -
- - )} - + {/* Scan Overlay */} + + {isScanning && ( + +
+ + )} + - {/* Corner Brackets */} -
-
-
-
- + {/* Corner Brackets */} +
+
+
+
+ +
{/* Result Notification */} @@ -126,17 +143,17 @@ export const PhoneMockup: React.FC = ({ initial={{ y: 100, opacity: 0 }} animate={{ y: 0, opacity: 1 }} exit={{ y: 100, opacity: 0 }} - className="mb-4 p-4 bg-white rounded-2xl shadow-lg border border-gray-100" + className="mb-3 p-3 bg-white rounded-xl shadow-lg border border-gray-100" > -
-
- +
+
+
-

QR Code Scanned

-

{getDomain(url)}

+

{displayName}

+

{getDomain(displayUrl)}

@@ -144,9 +161,9 @@ export const PhoneMockup: React.FC = ({ {/* Instruction Text */} -
-

- {isScanning ? 'Scanning...' : 'Point camera at QR code'} +

+

+ {isScanning ? 'Scanning...' : autoRotate ? 'Auto-scanning demo' : 'Point camera at QR code'}

@@ -154,24 +171,14 @@ export const PhoneMockup: React.FC = ({
{/* Side buttons */} -
-
-
+
+
+
{/* Phone Shadow */} -
+
- - {/* Floating Label */} - - Live Scan Preview -
); }; diff --git a/src/components/marketing/QRTypesShowcase.tsx b/src/components/marketing/QRTypesShowcase.tsx new file mode 100644 index 0000000..15be244 --- /dev/null +++ b/src/components/marketing/QRTypesShowcase.tsx @@ -0,0 +1,143 @@ +'use client'; + +import React from 'react'; +import { QRCodeSVG } from 'qrcode.react'; +import { motion } from 'framer-motion'; +import { Globe, User, Wifi, Mail, MessageSquare, Phone, MapPin, Calendar, FileText } from 'lucide-react'; + +export interface QRType { + id: string; + name: string; + icon: React.ComponentType<{ className?: string }>; + data: string; + color: string; +} + +const qrTypes: QRType[] = [ + { + id: 'url', + name: 'URL/Website', + icon: Globe, + data: 'https://qrmaster.net', + color: 'text-blue-600 bg-blue-50' + }, + { + id: 'vcard', + name: 'vCard', + icon: User, + data: 'BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nEND:VCARD', + color: 'text-purple-600 bg-purple-50' + }, + { + id: 'wifi', + name: 'WiFi', + icon: Wifi, + data: 'WIFI:T:WPA;S:MyNetwork;P:password;;', + color: 'text-cyan-600 bg-cyan-50' + }, + { + id: 'email', + name: 'Email', + icon: Mail, + data: 'mailto:hello@qrmaster.net', + color: 'text-red-600 bg-red-50' + }, + { + id: 'sms', + name: 'SMS', + icon: MessageSquare, + data: 'SMSTO:+1234567890:Hello!', + color: 'text-green-600 bg-green-50' + }, + { + id: 'phone', + name: 'Phone', + icon: Phone, + data: 'tel:+1234567890', + color: 'text-emerald-600 bg-emerald-50' + }, + { + id: 'location', + name: 'Location', + icon: MapPin, + data: 'geo:37.7749,-122.4194', + color: 'text-pink-600 bg-pink-50' + }, + { + id: 'event', + name: 'Event', + icon: Calendar, + data: 'BEGIN:VEVENT\nSUMMARY:Meeting\nEND:VEVENT', + color: 'text-indigo-600 bg-indigo-50' + }, + { + id: 'menu', + name: 'Menu/PDF', + icon: FileText, + data: 'https://qrmaster.net/menu.pdf', + color: 'text-orange-600 bg-orange-50' + } +]; + +interface QRTypesShowcaseProps { + onTypeSelect?: (type: QRType) => void; +} + +export const QRTypesShowcase: React.FC = ({ onTypeSelect }) => { + return ( +
+ {/* Grid of QR Types */} +
+ {qrTypes.map((type, index) => ( + onTypeSelect?.(type)} + className="group relative p-4 bg-white rounded-2xl border-2 border-gray-100 hover:border-primary-200 hover:shadow-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {/* Icon Badge */} +
+ +
+ + {/* QR Code */} +
+
+ +
+
+ + {/* Label */} +

+ {type.name} +

+ + {/* Subtle glow on hover */} +
+ + ))} +
+ + {/* Caption */} +

+ Support for all major QR code types +

+
+ ); +}; + +export { qrTypes }; diff --git a/src/components/marketing/StatsCounter.tsx b/src/components/marketing/StatsCounter.tsx index cec6af5..4716e63 100644 --- a/src/components/marketing/StatsCounter.tsx +++ b/src/components/marketing/StatsCounter.tsx @@ -25,8 +25,8 @@ const AnimatedNumber: React.FC<{ const isInView = useInView(ref, { once: true, margin: '-100px' }); const motionValue = useMotionValue(0); const springValue = useSpring(motionValue, { - damping: 60, - stiffness: 100, + damping: 80, + stiffness: 60, }); const [displayValue, setDisplayValue] = React.useState('0'); @@ -45,7 +45,7 @@ const AnimatedNumber: React.FC<{ }, [springValue, decimals]); return ( - + {prefix} {displayValue} {suffix} @@ -57,16 +57,18 @@ export const StatsCounter: React.FC = ({ stats }) => { return ( {stats.map((stat, index) => (
@@ -77,7 +79,7 @@ export const StatsCounter: React.FC = ({ stats }) => { decimals={stat.decimals} />
-

{stat.label}

+

{stat.label}

))}