lighthouse
This commit is contained in:
@@ -1,282 +1,282 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { motion } 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';
|
||||
|
||||
interface InstantGeneratorProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
|
||||
const [url, setUrl] = useState('https://example.com');
|
||||
const [foregroundColor, setForegroundColor] = useState('#000000');
|
||||
const [backgroundColor, setBackgroundColor] = useState('#FFFFFF');
|
||||
const [cornerStyle, setCornerStyle] = useState('square');
|
||||
const [size, setSize] = useState(200);
|
||||
|
||||
const contrast = calculateContrast(foregroundColor, backgroundColor);
|
||||
const hasGoodContrast = contrast >= 4.5;
|
||||
|
||||
const downloadQR = (format: 'svg' | 'png') => {
|
||||
const svg = document.querySelector('#instant-qr-preview svg');
|
||||
if (!svg || !url) return;
|
||||
|
||||
if (format === 'svg') {
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = 'qrcode.svg';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
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);
|
||||
|
||||
img.onload = () => {
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
if (ctx) {
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
ctx.drawImage(img, 0, 0, size, size);
|
||||
}
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = 'qrcode.png';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="pt-16 pb-32 bg-gray-50 border-t border-gray-100 relative">
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-r from-blue-50 to-white pointer-events-none"
|
||||
style={{ maskImage: 'linear-gradient(to bottom, transparent, black)', WebkitMaskImage: 'linear-gradient(to bottom, transparent, black)' }}
|
||||
/>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t.generator.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
|
||||
{/* Left Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card className="space-y-6 shadow-xl shadow-gray-200/50 border-gray-100">
|
||||
<Input
|
||||
label="URL"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder={t.generator.url_placeholder}
|
||||
className="transition-all focus:ring-2 focus:ring-primary-500/20"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="foreground-color" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.foreground}
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
id="foreground-color"
|
||||
type="color"
|
||||
value={foregroundColor}
|
||||
onChange={(e) => setForegroundColor(e.target.value)}
|
||||
className="w-14 h-12 rounded border border-gray-300 cursor-pointer"
|
||||
aria-label="Foreground color picker"
|
||||
/>
|
||||
<Input
|
||||
id="foreground-color-text"
|
||||
value={foregroundColor}
|
||||
onChange={(e) => setForegroundColor(e.target.value)}
|
||||
className="flex-1"
|
||||
aria-label="Foreground color hex value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="background-color" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.background}
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
id="background-color"
|
||||
type="color"
|
||||
value={backgroundColor}
|
||||
onChange={(e) => setBackgroundColor(e.target.value)}
|
||||
className="w-14 h-12 rounded border border-gray-300 cursor-pointer"
|
||||
aria-label="Background color picker"
|
||||
/>
|
||||
<Input
|
||||
id="background-color-text"
|
||||
value={backgroundColor}
|
||||
onChange={(e) => setBackgroundColor(e.target.value)}
|
||||
className="flex-1"
|
||||
aria-label="Background color hex value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="corner-style" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.corners}
|
||||
</label>
|
||||
<select
|
||||
id="corner-style"
|
||||
value={cornerStyle}
|
||||
onChange={(e) => setCornerStyle(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="square">Square</option>
|
||||
<option value="rounded">Rounded</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="qr-size" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.size}
|
||||
</label>
|
||||
<input
|
||||
id="qr-size"
|
||||
type="range"
|
||||
min="100"
|
||||
max="400"
|
||||
value={size}
|
||||
onChange={(e) => setSize(Number(e.target.value))}
|
||||
className="w-full accent-primary-600"
|
||||
aria-label={`QR code size: ${size} pixels`}
|
||||
/>
|
||||
<div className="text-sm text-gray-500 text-center mt-1" aria-hidden="true">{size}px</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Badge variant={hasGoodContrast ? 'success' : 'warning'}>
|
||||
{hasGoodContrast ? t.generator.contrast_good : 'Low contrast'}
|
||||
</Badge>
|
||||
<div className="text-sm text-gray-500">
|
||||
Contrast: {contrast.toFixed(1)}:1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<Button variant="outline" className="flex-1 hover:bg-gray-50" onClick={() => downloadQR('svg')}>
|
||||
{t.generator.download_svg}
|
||||
</Button>
|
||||
<Button variant="outline" className="flex-1 hover:bg-gray-50" onClick={() => downloadQR('png')}>
|
||||
{t.generator.download_png}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button className="w-full text-lg py-6 shadow-lg shadow-primary-500/20 hover:shadow-primary-500/40 transition-all" onClick={() => window.location.href = '/login'}>
|
||||
{t.generator.save_track}
|
||||
</Button>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Preview */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
className="flex flex-col items-center justify-center p-8 bg-gradient-to-br from-blue-50/50 to-purple-50/50 rounded-2xl border border-blue-100/50 shadow-lg shadow-blue-500/5 relative overflow-hidden backdrop-blur-sm"
|
||||
>
|
||||
{/* Artistic Curved Lines Background */}
|
||||
<div className="absolute inset-0 opacity-[0.4]">
|
||||
<svg className="h-full w-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#60a5fa" stopOpacity="0.4" />
|
||||
<stop offset="100%" stopColor="#c084fc" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradient2" x1="100%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#818cf8" stopOpacity="0.4" />
|
||||
<stop offset="100%" stopColor="#38bdf8" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M0 100 Q 25 30 50 70 T 100 0" fill="none" stroke="url(#gradient1)" strokeWidth="0.8" className="opacity-60" />
|
||||
<path d="M0 50 Q 40 80 70 30 T 100 50" fill="none" stroke="url(#gradient2)" strokeWidth="0.8" className="opacity-60" />
|
||||
<path d="M0 0 Q 30 60 60 20 T 100 80" fill="none" stroke="url(#gradient1)" strokeWidth="0.6" className="opacity-40" />
|
||||
</svg>
|
||||
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-15 brightness-100 contrast-150 mix-blend-overlay"></div>
|
||||
</div>
|
||||
|
||||
{/* Decorative Orbs */}
|
||||
<div className="absolute -top-20 -right-20 w-64 h-64 bg-purple-200/30 rounded-full blur-3xl animate-blob"></div>
|
||||
<div className="absolute -bottom-20 -left-20 w-64 h-64 bg-blue-200/30 rounded-full blur-3xl animate-blob animation-delay-2000"></div>
|
||||
|
||||
<div className="text-center w-full relative z-10">
|
||||
<h3 className="text-xl font-bold mb-8 text-gray-800">{t.generator.live_preview}</h3>
|
||||
<div id="instant-qr-preview" className="flex justify-center mb-8 transform hover:scale-105 transition-transform duration-300">
|
||||
{url ? (
|
||||
<div className={`${cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''} p-4 bg-white shadow-lg rounded-xl`}>
|
||||
<QRCodeSVG
|
||||
value={url}
|
||||
size={size}
|
||||
fgColor={foregroundColor}
|
||||
bgColor={backgroundColor}
|
||||
level="M"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="bg-gray-100 rounded-xl flex items-center justify-center text-gray-500 animate-pulse"
|
||||
style={{ width: 200, height: 200 }}
|
||||
>
|
||||
Enter URL
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2 bg-gray-50 py-2 px-4 rounded-full inline-block">
|
||||
{url || 'https://example.com'}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mt-2">{t.generator.demo_note}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { motion } 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';
|
||||
|
||||
interface InstantGeneratorProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const InstantGenerator: React.FC<InstantGeneratorProps> = ({ t }) => {
|
||||
const [url, setUrl] = useState('https://example.com');
|
||||
const [foregroundColor, setForegroundColor] = useState('#000000');
|
||||
const [backgroundColor, setBackgroundColor] = useState('#FFFFFF');
|
||||
const [cornerStyle, setCornerStyle] = useState('square');
|
||||
const [size, setSize] = useState(200);
|
||||
|
||||
const contrast = calculateContrast(foregroundColor, backgroundColor);
|
||||
const hasGoodContrast = contrast >= 4.5;
|
||||
|
||||
const downloadQR = (format: 'svg' | 'png') => {
|
||||
const svg = document.querySelector('#instant-qr-preview svg');
|
||||
if (!svg || !url) return;
|
||||
|
||||
if (format === 'svg') {
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = 'qrcode.svg';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
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);
|
||||
|
||||
img.onload = () => {
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
if (ctx) {
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
ctx.drawImage(img, 0, 0, size, size);
|
||||
}
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = 'qrcode.png';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="pt-16 pb-32 bg-gray-50 border-t border-gray-100 relative">
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-r from-blue-50 to-white pointer-events-none"
|
||||
style={{ maskImage: 'linear-gradient(to bottom, transparent, black)', WebkitMaskImage: 'linear-gradient(to bottom, transparent, black)' }}
|
||||
/>
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t.generator.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
|
||||
{/* Left Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card className="space-y-6 shadow-xl shadow-gray-200/50 border-gray-100">
|
||||
<Input
|
||||
label="URL"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder={t.generator.url_placeholder}
|
||||
className="transition-all focus:ring-2 focus:ring-primary-500/20"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="foreground-color" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.foreground}
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
id="foreground-color"
|
||||
type="color"
|
||||
value={foregroundColor}
|
||||
onChange={(e) => setForegroundColor(e.target.value)}
|
||||
className="w-14 h-12 rounded border border-gray-300 cursor-pointer"
|
||||
aria-label="Foreground color picker"
|
||||
/>
|
||||
<Input
|
||||
id="foreground-color-text"
|
||||
value={foregroundColor}
|
||||
onChange={(e) => setForegroundColor(e.target.value)}
|
||||
className="flex-1"
|
||||
aria-label="Foreground color hex value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="background-color" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.background}
|
||||
</label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
id="background-color"
|
||||
type="color"
|
||||
value={backgroundColor}
|
||||
onChange={(e) => setBackgroundColor(e.target.value)}
|
||||
className="w-14 h-12 rounded border border-gray-300 cursor-pointer"
|
||||
aria-label="Background color picker"
|
||||
/>
|
||||
<Input
|
||||
id="background-color-text"
|
||||
value={backgroundColor}
|
||||
onChange={(e) => setBackgroundColor(e.target.value)}
|
||||
className="flex-1"
|
||||
aria-label="Background color hex value"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="corner-style" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.corners}
|
||||
</label>
|
||||
<select
|
||||
id="corner-style"
|
||||
value={cornerStyle}
|
||||
onChange={(e) => setCornerStyle(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="square">Square</option>
|
||||
<option value="rounded">Rounded</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="qr-size" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{t.generator.size}
|
||||
</label>
|
||||
<input
|
||||
id="qr-size"
|
||||
type="range"
|
||||
min="100"
|
||||
max="400"
|
||||
value={size}
|
||||
onChange={(e) => setSize(Number(e.target.value))}
|
||||
className="w-full accent-primary-600"
|
||||
aria-label={`QR code size: ${size} pixels`}
|
||||
/>
|
||||
<div className="text-sm text-gray-500 text-center mt-1" aria-hidden="true">{size}px</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Badge variant={hasGoodContrast ? 'success' : 'warning'}>
|
||||
{hasGoodContrast ? t.generator.contrast_good : 'Low contrast'}
|
||||
</Badge>
|
||||
<div className="text-sm text-gray-500">
|
||||
Contrast: {contrast.toFixed(1)}:1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<Button variant="outline" className="flex-1 hover:bg-gray-50" onClick={() => downloadQR('svg')}>
|
||||
{t.generator.download_svg}
|
||||
</Button>
|
||||
<Button variant="outline" className="flex-1 hover:bg-gray-50" onClick={() => downloadQR('png')}>
|
||||
{t.generator.download_png}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button className="w-full text-lg py-6 shadow-lg shadow-primary-500/20 hover:shadow-primary-500/40 transition-all" onClick={() => window.location.href = '/login'}>
|
||||
{t.generator.save_track}
|
||||
</Button>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Preview */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
className="flex flex-col items-center justify-center p-8 bg-gradient-to-br from-blue-50/50 to-purple-50/50 rounded-2xl border border-blue-100/50 shadow-lg shadow-blue-500/5 relative overflow-hidden backdrop-blur-sm"
|
||||
>
|
||||
{/* Artistic Curved Lines Background */}
|
||||
<div className="absolute inset-0 opacity-[0.4]">
|
||||
<svg className="h-full w-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="#60a5fa" stopOpacity="0.4" />
|
||||
<stop offset="100%" stopColor="#c084fc" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradient2" x1="100%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#818cf8" stopOpacity="0.4" />
|
||||
<stop offset="100%" stopColor="#38bdf8" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M0 100 Q 25 30 50 70 T 100 0" fill="none" stroke="url(#gradient1)" strokeWidth="0.8" className="opacity-60" />
|
||||
<path d="M0 50 Q 40 80 70 30 T 100 50" fill="none" stroke="url(#gradient2)" strokeWidth="0.8" className="opacity-60" />
|
||||
<path d="M0 0 Q 30 60 60 20 T 100 80" fill="none" stroke="url(#gradient1)" strokeWidth="0.6" className="opacity-40" />
|
||||
</svg>
|
||||
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-15 brightness-100 contrast-150 mix-blend-overlay"></div>
|
||||
</div>
|
||||
|
||||
{/* Decorative Orbs */}
|
||||
<div className="absolute -top-20 -right-20 w-64 h-64 bg-purple-200/30 rounded-full blur-3xl animate-blob"></div>
|
||||
<div className="absolute -bottom-20 -left-20 w-64 h-64 bg-blue-200/30 rounded-full blur-3xl animate-blob animation-delay-2000"></div>
|
||||
|
||||
<div className="text-center w-full relative z-10">
|
||||
<h3 className="text-xl font-bold mb-8 text-gray-800">{t.generator.live_preview}</h3>
|
||||
<div id="instant-qr-preview" className="flex justify-center mb-8 transform hover:scale-105 transition-transform duration-300">
|
||||
{url ? (
|
||||
<div className={`${cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''} p-4 bg-white shadow-lg rounded-xl`}>
|
||||
<QRCodeSVG
|
||||
value={url}
|
||||
size={size}
|
||||
fgColor={foregroundColor}
|
||||
bgColor={backgroundColor}
|
||||
level="M"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="bg-gray-100 rounded-xl flex items-center justify-center text-gray-500 animate-pulse"
|
||||
style={{ width: 200, height: 200 }}
|
||||
>
|
||||
Enter URL
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-600 mb-2 bg-gray-50 py-2 px-4 rounded-full inline-block">
|
||||
{url || 'https://example.com'}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400 mt-2">{t.generator.demo_note}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user