lighthouse
This commit is contained in:
parent
d64459b200
commit
1f067e81f3
|
|
@ -209,7 +209,7 @@ export default function CryptoGenerator() {
|
|||
Wallet Direct
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-2">
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
{qrMode === 'universal'
|
||||
? "Works with any phone camera. Opens blockchain explorer."
|
||||
: "Requires scanning from a wallet app. Enables direct payment."}
|
||||
|
|
@ -319,7 +319,7 @@ export default function CryptoGenerator() {
|
|||
<Bitcoin className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate capitalize">{currency}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1 truncate px-2">
|
||||
<div className="text-xs text-slate-600 mt-1 truncate px-2">
|
||||
{address || 'Wallet Address'}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -344,7 +344,7 @@ export default function CryptoGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning copies the wallet address or opens a crypto app.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ export default function EmailGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
100% free. No signup required.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ export default function EventGenerator() {
|
|||
<span className="truncate">{title || 'Event Title'}</span>
|
||||
</h3>
|
||||
{(startDate) && (
|
||||
<div className="text-xs text-slate-500 mt-1 flex items-center justify-center gap-1">
|
||||
<div className="text-xs text-slate-600 mt-1 flex items-center justify-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
{new Date(startDate).toLocaleDateString()}
|
||||
</div>
|
||||
|
|
@ -304,7 +304,7 @@ export default function EventGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning adds the event to the user's calendar.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export default function FacebookGenerator() {
|
|||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1877F2] focus:ring-[#1877F2]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Paste the full link to your profile, page, group, or post.</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Paste the full link to your profile, page, group, or post.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ export default function FacebookGenerator() {
|
|||
<Facebook className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{url ? url.replace('https://', '') : 'facebook.com/...'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1">Opens in Facebook App</div>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in Facebook App</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ export default function FacebookGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to the Facebook profile or page.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export default function GeolocationGenerator() {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500">
|
||||
<p className="text-xs text-slate-600">
|
||||
Tip: You can copy-paste coordinates directly from Google Maps.
|
||||
(Right-click a location on standard Maps, then click the coordinates to copy).
|
||||
</p>
|
||||
|
|
@ -243,7 +243,7 @@ export default function GeolocationGenerator() {
|
|||
<MapPin className="w-4 h-4 text-[#10B981] shrink-0" />
|
||||
<span className="truncate">{latitude || 'Lat'}, {longitude || 'Long'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1">Google Maps Location</div>
|
||||
<div className="text-xs text-slate-600 mt-1">Google Maps Location</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -266,7 +266,7 @@ export default function GeolocationGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning opens the location directly in Google Maps.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ export default function GeolocationQRCodePage() {
|
|||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<MapPin className="w-8 h-8 text-red-500 drop-shadow-lg animate-bounce" />
|
||||
</div>
|
||||
<div className="absolute bottom-2 left-2 right-2 bg-white/90 p-2 rounded text-[10px] text-slate-500 font-mono text-center">
|
||||
<div className="absolute bottom-2 left-2 right-2 bg-white/90 p-2 rounded text-[10px] text-slate-600 font-mono text-center">
|
||||
40.7128° N, 74.0060° W
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export default function InstagramGenerator() {
|
|||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#E1306C] focus:ring-[#E1306C]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Enter your username (without @) or paste full profile link.</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter your username (without @) or paste full profile link.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ export default function InstagramGenerator() {
|
|||
<Instagram className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{username || '@username'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1">Opens in Instagram</div>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in Instagram</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ export default function InstagramGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to your Instagram profile.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ export default function PayPalGenerator() {
|
|||
onChange={(e) => setPaypalId(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#003087] focus:ring-[#003087]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
{inputType === 'username'
|
||||
? <>Find yours at <a href="https://paypal.me" target="_blank" rel="noopener noreferrer" className="text-[#003087] underline">paypal.me</a></>
|
||||
: 'The email address linked to your PayPal account'
|
||||
|
|
@ -293,7 +293,7 @@ export default function PayPalGenerator() {
|
|||
<span className="truncate">{paypalId || 'Your PayPal'}</span>
|
||||
</h3>
|
||||
{amount && (
|
||||
<p className="text-sm text-slate-500 mt-1">{amount} {currency}</p>
|
||||
<p className="text-sm text-slate-600 mt-1">{amount} {currency}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -317,7 +317,7 @@ export default function PayPalGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your PayPal link is encoded directly. Static and forever free.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ export default function PhoneGenerator() {
|
|||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#1A1265] focus:ring-[#1A1265]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Enter with country code for best results (e.g. +1).</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter with country code for best results (e.g. +1).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ export default function PhoneGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning initiates a call on any mobile phone.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export default function SMSGenerator() {
|
|||
onChange={(e) => setMessage(e.target.value)}
|
||||
maxLength={160}
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2 text-right">{message.length}/160</p>
|
||||
<p className="text-xs text-slate-600 mt-2 text-right">{message.length}/160</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -214,7 +214,7 @@ export default function SMSGenerator() {
|
|||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{phone || 'Number'}</span>
|
||||
</h3>
|
||||
<div className="flex items-center justify-center gap-2 mt-2 text-slate-500 text-xs">
|
||||
<div className="flex items-center justify-center gap-2 mt-2 text-slate-600 text-xs">
|
||||
<MessageSquare className="w-3 h-3" />
|
||||
<span className="italic truncate">{message || 'Your message...'}</span>
|
||||
</div>
|
||||
|
|
@ -240,7 +240,7 @@ export default function SMSGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Opens the messaging app with text pre-filled.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export default function TeamsGenerator() {
|
|||
onChange={(e) => setMeetingUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
Copy the meeting link from your Teams calendar invite.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -166,7 +166,7 @@ export default function TeamsGenerator() {
|
|||
onChange={(e) => setUserEmail(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#6264A7] focus:ring-[#6264A7]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">
|
||||
<p className="text-xs text-slate-600 mt-2">
|
||||
The person's work email to start a Teams chat.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -269,7 +269,7 @@ export default function TeamsGenerator() {
|
|||
{linkType === 'meeting' ? 'Teams Meeting' : (userEmail || 'Teams Chat')}
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
<p className="text-sm text-slate-600 mt-1">
|
||||
{linkType === 'meeting' ? 'Join Meeting' : 'Start Chat'}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -294,7 +294,7 @@ export default function TeamsGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Works with Microsoft Teams desktop and mobile apps.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ export default function TeamsQRCodePage() {
|
|||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
||||
<div className="text-xs text-slate-500">Daily at 9:00 AM</div>
|
||||
<div className="text-xs text-slate-600">Daily at 9:00 AM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ export default function TextGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your text stays on your device. Nothing is sent to servers.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export default function TiktokGenerator() {
|
|||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Enter your TikTok handle (e.g. @charlidamelio).</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter your TikTok handle (e.g. @charlidamelio).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ export default function TiktokGenerator() {
|
|||
<Music className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{username || '@username'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1">Opens in TikTok</div>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in TikTok</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ export default function TiktokGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to your TikTok profile.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export default function TwitterGenerator() {
|
|||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-black focus:ring-black"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Enter your X (Twitter) handle to create a profile link.</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Enter your X (Twitter) handle to create a profile link.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ export default function TwitterGenerator() {
|
|||
<Twitter className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{username || '@username'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1">Opens in X (Twitter)</div>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in X (Twitter)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ export default function TwitterGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to the X profile.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default function URLGenerator() {
|
|||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#4F46E5] focus:ring-[#4F46E5]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Include https:// for best results.</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Include https:// for best results.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -219,7 +219,7 @@ export default function URLGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your link is encoded directly. Static and forever free.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ export default function VCardGenerator() {
|
|||
<Contact className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{firstName || 'First'} {lastName || 'Last'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1 truncate">{company || 'Company Name'}</div>
|
||||
<div className="text-xs text-slate-600 mt-1 truncate">{company || 'Company Name'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -322,7 +322,7 @@ export default function VCardGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning adds this contact to the address book instantly.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export default function WhatsappGenerator() {
|
|||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#25D366] focus:ring-[#25D366]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Include country code (e.g. 1 for US, 44 for UK). No '+' symbol.</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Include country code (e.g. 1 for US, 44 for UK). No '+' symbol.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -218,7 +218,7 @@ export default function WhatsappGenerator() {
|
|||
<Phone className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{phone ? `+${phone}` : 'Number'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1">Starts WhatsApp Chat</div>
|
||||
<div className="text-xs text-slate-600 mt-1">Starts WhatsApp Chat</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ export default function WhatsappGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning starts a chat with this number instantly.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -210,12 +210,12 @@ export default function WhatsappQRCodePage() {
|
|||
{/* Chat Bubble Right */}
|
||||
<div className="bg-[#DCF8C6] p-2 rounded-lg self-end mb-2 max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
||||
Hi! I'd like to book an appointment.
|
||||
<div className="text-[8px] text-slate-500 text-right mt-0.5">10:42 AM <span className="text-blue-500">✓✓</span></div>
|
||||
<div className="text-[8px] text-slate-600 text-right mt-0.5">10:42 AM <span className="text-blue-500">✓✓</span></div>
|
||||
</div>
|
||||
{/* Chat Bubble Left */}
|
||||
<div className="bg-white p-2 rounded-lg self-start max-w-[80%] text-[10px] text-slate-800 shadow-sm">
|
||||
Sure! What time works for you?
|
||||
<div className="text-[8px] text-slate-500 text-right mt-0.5">10:43 AM</div>
|
||||
<div className="text-[8px] text-slate-600 text-right mt-0.5">10:43 AM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ export default function WiFiGenerator() {
|
|||
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your credentials stay on your device. Nothing is sent to servers.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default function YoutubeGenerator() {
|
|||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#FF0000] focus:ring-[#FF0000]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">Paste a link to any video, channel, or playlist.</p>
|
||||
<p className="text-xs text-slate-600 mt-2">Paste a link to any video, channel, or playlist.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ export default function YoutubeGenerator() {
|
|||
<Youtube className="w-4 h-4 text-slate-400 shrink-0" />
|
||||
<span className="truncate">{url ? 'YouTube Content' : 'youtube.com'}</span>
|
||||
</h3>
|
||||
<div className="text-xs text-slate-500 mt-1">Opens in YouTube App</div>
|
||||
<div className="text-xs text-slate-600 mt-1">Opens in YouTube App</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -219,7 +219,7 @@ export default function YoutubeGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Scanning redirects directly to the video or channel.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export default function ZoomGenerator() {
|
|||
onChange={(e) => setMeetingId(e.target.value.replace(/\D/g, ''))}
|
||||
className="h-12 text-base rounded-xl border-slate-200 focus:border-[#2D8CFF] focus:ring-[#2D8CFF]"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-2">The 10-11 digit meeting ID from your Zoom invite.</p>
|
||||
<p className="text-xs text-slate-600 mt-2">The 10-11 digit meeting ID from your Zoom invite.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -254,7 +254,7 @@ export default function ZoomGenerator() {
|
|||
<span className="truncate">{formatMeetingId(meetingId) || 'Meeting ID'}</span>
|
||||
</h3>
|
||||
{passcode && (
|
||||
<p className="text-sm text-slate-500 mt-1">Passcode: {passcode}</p>
|
||||
<p className="text-sm text-slate-600 mt-1">Passcode: {passcode}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -278,7 +278,7 @@ export default function ZoomGenerator() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-4 text-center">
|
||||
<p className="text-xs text-slate-600 mt-4 text-center">
|
||||
Your meeting ID is encoded directly. Static and forever free.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ export default function ZoomQRCodePage() {
|
|||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-slate-900 text-sm">Team Standup</div>
|
||||
<div className="text-xs text-slate-500">ID: 123 456 7890</div>
|
||||
<div className="text-xs text-slate-600">ID: 123 456 7890</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
|
|
|
|||
|
|
@ -1,216 +1,216 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Sparkles, Brain, TrendingUp, MessageSquare, Palette, ArrowRight, Mail, CheckCircle2, Lock } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const AIComingSoonBanner = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/newsletter/subscribe', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to subscribe');
|
||||
}
|
||||
|
||||
setSubmitted(true);
|
||||
setEmail('');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Something went wrong. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Brain,
|
||||
category: 'Smart QR Generation',
|
||||
items: [
|
||||
'AI-powered content optimization',
|
||||
'Intelligent design suggestions',
|
||||
'Auto-generate vCard from LinkedIn',
|
||||
'Smart URL shortening with SEO',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
category: 'Advanced Analytics & Insights',
|
||||
items: [
|
||||
'AI-powered scan predictions',
|
||||
'Anomaly detection',
|
||||
'Natural language analytics queries',
|
||||
'Automated insights reports',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: MessageSquare,
|
||||
category: 'Smart Content Management',
|
||||
items: [
|
||||
'AI chatbot for instant support',
|
||||
'Automated QR categorization',
|
||||
'Smart bulk QR generation',
|
||||
'Content recommendations',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Palette,
|
||||
category: 'Creative & Marketing',
|
||||
items: [
|
||||
'AI-generated custom QR designs',
|
||||
'Marketing copy generation',
|
||||
'A/B testing suggestions',
|
||||
'Campaign optimization',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden pt-12 pb-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
||||
{/* Animated Background Orbs (matching Hero) */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-400/20 rounded-full blur-3xl animate-blob" />
|
||||
<div className="absolute top-1/2 right-1/4 w-80 h-80 bg-purple-400/20 rounded-full blur-3xl animate-blob animation-delay-2000" />
|
||||
<div className="absolute bottom-0 left-1/2 w-96 h-96 bg-cyan-400/15 rounded-full blur-3xl animate-blob animation-delay-4000" />
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto relative z-10">
|
||||
{/* Header */}
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-blue-100 mb-4 animate-pulse">
|
||||
<Sparkles className="w-4 h-4 text-blue-600" />
|
||||
<span className="text-sm font-medium text-blue-700">
|
||||
Coming Soon
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-gray-900 mb-3">
|
||||
The Future of QR Codes is{' '}
|
||||
<span className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
AI-Powered
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<p className="text-gray-600 text-lg max-w-2xl mx-auto">
|
||||
Revolutionary AI features to transform how you create, manage, and optimize QR codes
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="bg-white/80 backdrop-blur rounded-xl p-6 border border-gray-100 hover:shadow-lg transition-all hover:scale-105"
|
||||
>
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-100 to-purple-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<feature.icon className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
|
||||
<h3 className="font-semibold text-gray-900 mb-3">
|
||||
{feature.category}
|
||||
</h3>
|
||||
|
||||
<ul className="space-y-2">
|
||||
{feature.items.map((item, itemIndex) => (
|
||||
<li key={itemIndex} className="flex items-start gap-2 text-sm text-gray-600">
|
||||
<div className="mt-1.5 w-1.5 h-1.5 rounded-full bg-blue-500 flex-shrink-0" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Email Capture */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
className="max-w-2xl mx-auto bg-gradient-to-br from-blue-50 to-purple-50 rounded-2xl p-8 border border-gray-100"
|
||||
>
|
||||
{!submitted ? (
|
||||
<>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 mb-3">
|
||||
<div className="flex-1 relative">
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none" />
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
setError('');
|
||||
}}
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
disabled={loading}
|
||||
className="w-full pl-12 pr-4 py-3 rounded-xl bg-white border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold rounded-xl transition-all disabled:opacity-50 whitespace-nowrap flex items-center justify-center gap-2 group"
|
||||
>
|
||||
{loading ? 'Subscribing...' : (
|
||||
<>
|
||||
Notify Me
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
{error && (
|
||||
<p className="text-sm text-red-600 mb-2">{error}</p>
|
||||
)}
|
||||
<p className="text-xs text-gray-500 text-center">
|
||||
Be the first to know when AI features launch
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-2 text-green-600">
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
<span className="font-medium">
|
||||
You're on the list! We'll notify you when AI features launch.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-b from-transparent to-white pointer-events-none" />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIComingSoonBanner;
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Sparkles, Brain, TrendingUp, MessageSquare, Palette, ArrowRight, Mail, CheckCircle2, Lock } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const AIComingSoonBanner = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/newsletter/subscribe', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to subscribe');
|
||||
}
|
||||
|
||||
setSubmitted(true);
|
||||
setEmail('');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Something went wrong. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Brain,
|
||||
category: 'Smart QR Generation',
|
||||
items: [
|
||||
'AI-powered content optimization',
|
||||
'Intelligent design suggestions',
|
||||
'Auto-generate vCard from LinkedIn',
|
||||
'Smart URL shortening with SEO',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
category: 'Advanced Analytics & Insights',
|
||||
items: [
|
||||
'AI-powered scan predictions',
|
||||
'Anomaly detection',
|
||||
'Natural language analytics queries',
|
||||
'Automated insights reports',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: MessageSquare,
|
||||
category: 'Smart Content Management',
|
||||
items: [
|
||||
'AI chatbot for instant support',
|
||||
'Automated QR categorization',
|
||||
'Smart bulk QR generation',
|
||||
'Content recommendations',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Palette,
|
||||
category: 'Creative & Marketing',
|
||||
items: [
|
||||
'AI-generated custom QR designs',
|
||||
'Marketing copy generation',
|
||||
'A/B testing suggestions',
|
||||
'Campaign optimization',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden pt-12 pb-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
||||
{/* Animated Background Orbs (matching Hero) */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-400/20 rounded-full blur-3xl animate-blob" />
|
||||
<div className="absolute top-1/2 right-1/4 w-80 h-80 bg-purple-400/20 rounded-full blur-3xl animate-blob animation-delay-2000" />
|
||||
<div className="absolute bottom-0 left-1/2 w-96 h-96 bg-cyan-400/15 rounded-full blur-3xl animate-blob animation-delay-4000" />
|
||||
</div>
|
||||
|
||||
<div className="max-w-6xl mx-auto relative z-10">
|
||||
{/* Header */}
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-blue-100 mb-4 animate-pulse">
|
||||
<Sparkles className="w-4 h-4 text-blue-600" />
|
||||
<span className="text-sm font-medium text-blue-700">
|
||||
Coming Soon
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-gray-900 mb-3">
|
||||
The Future of QR Codes is{' '}
|
||||
<span className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
AI-Powered
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<p className="text-gray-600 text-lg max-w-2xl mx-auto">
|
||||
Revolutionary AI features to transform how you create, manage, and optimize QR codes
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="bg-white/80 backdrop-blur rounded-xl p-6 border border-gray-100 hover:shadow-lg transition-all hover:scale-105"
|
||||
>
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-100 to-purple-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<feature.icon className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
|
||||
<h3 className="font-semibold text-gray-900 mb-3">
|
||||
{feature.category}
|
||||
</h3>
|
||||
|
||||
<ul className="space-y-2">
|
||||
{feature.items.map((item, itemIndex) => (
|
||||
<li key={itemIndex} className="flex items-start gap-2 text-sm text-gray-600">
|
||||
<div className="mt-1.5 w-1.5 h-1.5 rounded-full bg-blue-500 flex-shrink-0" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Email Capture */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
className="max-w-2xl mx-auto bg-gradient-to-br from-blue-50 to-purple-50 rounded-2xl p-8 border border-gray-100"
|
||||
>
|
||||
{!submitted ? (
|
||||
<>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 mb-3">
|
||||
<div className="flex-1 relative">
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none" />
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
setError('');
|
||||
}}
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
disabled={loading}
|
||||
className="w-full pl-12 pr-4 py-3 rounded-xl bg-white border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold rounded-xl transition-all disabled:opacity-50 whitespace-nowrap flex items-center justify-center gap-2 group"
|
||||
>
|
||||
{loading ? 'Subscribing...' : (
|
||||
<>
|
||||
Notify Me
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
{error && (
|
||||
<p className="text-sm text-red-600 mb-2">{error}</p>
|
||||
)}
|
||||
<p className="text-xs text-gray-500 text-center">
|
||||
Be the first to know when AI features launch
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-2 text-green-600">
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
<span className="font-medium">
|
||||
You're on the list! We'll notify you when AI features launch.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-b from-transparent to-white pointer-events-none" />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIComingSoonBanner;
|
||||
|
|
|
|||
|
|
@ -1,92 +1,92 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
interface FAQProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const FAQ: React.FC<FAQProps> = ({ t }) => {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
|
||||
const questions = [
|
||||
'account',
|
||||
'static_vs_dynamic',
|
||||
'forever',
|
||||
'file_type',
|
||||
'analytics',
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="faq" className="py-16 bg-gray-50">
|
||||
<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.faq.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{questions.map((key, index) => (
|
||||
<motion.div
|
||||
key={key}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Card className="cursor-pointer border-gray-200 hover:border-gray-300 transition-colors" onClick={() => setOpenIndex(openIndex === index ? null : index)}>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{t.faq.questions[key].question}
|
||||
</h3>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-500 transition-transform duration-300 ${openIndex === index ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{openIndex === index && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0, marginTop: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1, marginTop: 16 }}
|
||||
exit={{ height: 0, opacity: 0, marginTop: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="text-gray-600">
|
||||
{t.faq.questions[key].answer}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-8">
|
||||
<a href="/faq" className="text-primary-600 hover:text-primary-700 font-medium">
|
||||
View All Questions →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
|
||||
interface FAQProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const FAQ: React.FC<FAQProps> = ({ t }) => {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||
|
||||
const questions = [
|
||||
'account',
|
||||
'static_vs_dynamic',
|
||||
'forever',
|
||||
'file_type',
|
||||
'analytics',
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="faq" className="py-16 bg-gray-50">
|
||||
<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.faq.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="max-w-3xl mx-auto space-y-4">
|
||||
{questions.map((key, index) => (
|
||||
<motion.div
|
||||
key={key}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Card className="cursor-pointer border-gray-200 hover:border-gray-300 transition-colors" onClick={() => setOpenIndex(openIndex === index ? null : index)}>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{t.faq.questions[key].question}
|
||||
</h3>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-500 transition-transform duration-300 ${openIndex === index ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{openIndex === index && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0, marginTop: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1, marginTop: 16 }}
|
||||
exit={{ height: 0, opacity: 0, marginTop: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="text-gray-600">
|
||||
{t.faq.questions[key].answer}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-8">
|
||||
<a href="/faq" className="text-primary-600 hover:text-primary-700 font-medium">
|
||||
View All Questions →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,85 +1,85 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
|
||||
interface FeaturesProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Features: React.FC<FeaturesProps> = ({ t }) => {
|
||||
const features = [
|
||||
{
|
||||
key: 'analytics',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-blue-600 bg-blue-100',
|
||||
},
|
||||
{
|
||||
key: 'customization',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-purple-600 bg-purple-100',
|
||||
},
|
||||
{
|
||||
key: 'unlimited',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-green-600 bg-green-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50">
|
||||
<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.features.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.key}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Card hover className="h-full border-gray-100 hover:border-primary-100 hover:shadow-lg transition-all">
|
||||
<CardHeader>
|
||||
<div className={`w-12 h-12 rounded-lg ${feature.color} flex items-center justify-center mb-4`}>
|
||||
{feature.icon}
|
||||
</div>
|
||||
<CardTitle>{t.features[feature.key].title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600">
|
||||
{t.features[feature.key].description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
|
||||
interface FeaturesProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Features: React.FC<FeaturesProps> = ({ t }) => {
|
||||
const features = [
|
||||
{
|
||||
key: 'analytics',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-blue-600 bg-blue-100',
|
||||
},
|
||||
{
|
||||
key: 'customization',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-purple-600 bg-purple-100',
|
||||
},
|
||||
{
|
||||
key: 'unlimited',
|
||||
icon: (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
color: 'text-green-600 bg-green-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gray-50">
|
||||
<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.features.title}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.key}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Card hover className="h-full border-gray-100 hover:border-primary-100 hover:shadow-lg transition-all">
|
||||
<CardHeader>
|
||||
<div className={`w-12 h-12 rounded-lg ${feature.color} flex items-center justify-center mb-4`}>
|
||||
{feature.icon}
|
||||
</div>
|
||||
<CardTitle>{t.features[feature.key].title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600">
|
||||
{t.features[feature.key].description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const TOOLS = [
|
|||
name: 'Text',
|
||||
description: 'Display plain text',
|
||||
href: '/tools/text-qr-code',
|
||||
color: 'text-slate-500',
|
||||
color: 'text-slate-600',
|
||||
bg: 'bg-slate-50'
|
||||
},
|
||||
{
|
||||
|
|
@ -240,7 +240,7 @@ export function FreeToolsGrid() {
|
|||
<h3 className="text-base md:text-lg font-semibold text-slate-900 mb-0.5">
|
||||
{tool.name}
|
||||
</h3>
|
||||
<p className="text-xs md:text-sm text-slate-500 text-center">
|
||||
<p className="text-xs md:text-sm text-slate-600 text-center">
|
||||
{tool.description}
|
||||
</p>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,155 +1,155 @@
|
|||
'use client';
|
||||
|
||||
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';
|
||||
|
||||
interface HeroProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({ 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 (
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
|
||||
{/* Animated Background Orbs */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
{/* Orb 1 - Blue (top-left) */}
|
||||
<div className="absolute -top-24 -left-24 w-96 h-96 bg-blue-400/30 rounded-full blur-3xl animate-blob" />
|
||||
|
||||
{/* Orb 2 - Purple (top-right) */}
|
||||
<div className="absolute -top-12 -right-12 w-96 h-96 bg-purple-400/30 rounded-full blur-3xl animate-blob animation-delay-2000" />
|
||||
|
||||
{/* Orb 3 - Pink (bottom-left) */}
|
||||
<div className="absolute -bottom-24 -left-12 w-96 h-96 bg-pink-400/20 rounded-full blur-3xl animate-blob animation-delay-4000" />
|
||||
|
||||
{/* Orb 4 - Cyan (center-right) */}
|
||||
<div className="absolute top-1/2 -right-24 w-80 h-80 bg-cyan-400/20 rounded-full blur-3xl animate-blob animation-delay-6000" />
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
{/* Left Content */}
|
||||
<div className="space-y-8">
|
||||
<Badge variant="info" className="inline-flex items-center space-x-2">
|
||||
<span>{t.hero.badge}</span>
|
||||
</Badge>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
{t.hero.title}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed max-w-2xl">
|
||||
{t.hero.subtitle}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3 pt-2">
|
||||
{t.hero.features.map((feature: string, index: number) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.2 + (index * 0.1) }}
|
||||
className="flex items-center space-x-3"
|
||||
>
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-emerald-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
|
||||
</div>
|
||||
<span className="text-gray-700 font-medium">{feature}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="flex flex-col sm:flex-row gap-4 pt-4"
|
||||
>
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-6 w-full sm:w-auto shadow-lg shadow-blue-500/25 hover:shadow-blue-500/40 transition-all duration-300">
|
||||
{t.hero.cta_primary}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/#pricing">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-6 w-full sm:w-auto backdrop-blur-sm bg-white/50 border-gray-200 hover:bg-white/80 transition-all duration-300">
|
||||
{t.hero.cta_secondary}
|
||||
</Button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Right Preview Widget */}
|
||||
<div className="relative">
|
||||
<motion.div
|
||||
variants={containerjs}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="grid grid-cols-2 gap-4"
|
||||
>
|
||||
{templateCards.map((card, index) => (
|
||||
<motion.div key={index} variants={itemjs}>
|
||||
<Card className={`backdrop-blur-xl bg-white/70 border-white/50 shadow-xl shadow-gray-200/50 p-6 text-center hover:scale-105 transition-all duration-300 group cursor-pointer`}>
|
||||
<div className={`w-12 h-12 mx-auto mb-4 rounded-xl ${card.color} flex items-center justify-center group-hover:scale-110 transition-transform duration-300`}>
|
||||
<card.icon className="w-6 h-6" />
|
||||
</div>
|
||||
<p className="font-semibold text-gray-800 group-hover:text-gray-900">{card.title}</p>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.8 }}
|
||||
className="absolute -top-4 -right-4 bg-gradient-to-r from-success-500 to-emerald-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg shadow-success-500/30 flex items-center gap-2"
|
||||
>
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||
</span>
|
||||
{t.hero.engagement_badge}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Smooth Gradient Fade Transition */}
|
||||
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
|
||||
</section >
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
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';
|
||||
|
||||
interface HeroProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({ 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 (
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 pt-12 pb-20">
|
||||
{/* Animated Background Orbs */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
{/* Orb 1 - Blue (top-left) */}
|
||||
<div className="absolute -top-24 -left-24 w-96 h-96 bg-blue-400/30 rounded-full blur-3xl animate-blob" />
|
||||
|
||||
{/* Orb 2 - Purple (top-right) */}
|
||||
<div className="absolute -top-12 -right-12 w-96 h-96 bg-purple-400/30 rounded-full blur-3xl animate-blob animation-delay-2000" />
|
||||
|
||||
{/* Orb 3 - Pink (bottom-left) */}
|
||||
<div className="absolute -bottom-24 -left-12 w-96 h-96 bg-pink-400/20 rounded-full blur-3xl animate-blob animation-delay-4000" />
|
||||
|
||||
{/* Orb 4 - Cyan (center-right) */}
|
||||
<div className="absolute top-1/2 -right-24 w-80 h-80 bg-cyan-400/20 rounded-full blur-3xl animate-blob animation-delay-6000" />
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
{/* Left Content */}
|
||||
<div className="space-y-8">
|
||||
<Badge variant="info" className="inline-flex items-center space-x-2">
|
||||
<span>{t.hero.badge}</span>
|
||||
</Badge>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
{t.hero.title}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed max-w-2xl">
|
||||
{t.hero.subtitle}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3 pt-2">
|
||||
{t.hero.features.map((feature: string, index: number) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.2 + (index * 0.1) }}
|
||||
className="flex items-center space-x-3"
|
||||
>
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-emerald-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
|
||||
</div>
|
||||
<span className="text-gray-700 font-medium">{feature}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="flex flex-col sm:flex-row gap-4 pt-4"
|
||||
>
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-6 w-full sm:w-auto shadow-lg shadow-blue-500/25 hover:shadow-blue-500/40 transition-all duration-300">
|
||||
{t.hero.cta_primary}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/#pricing">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-6 w-full sm:w-auto backdrop-blur-sm bg-white/50 border-gray-200 hover:bg-white/80 transition-all duration-300">
|
||||
{t.hero.cta_secondary}
|
||||
</Button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Right Preview Widget */}
|
||||
<div className="relative">
|
||||
<motion.div
|
||||
variants={containerjs}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="grid grid-cols-2 gap-4"
|
||||
>
|
||||
{templateCards.map((card, index) => (
|
||||
<motion.div key={index} variants={itemjs}>
|
||||
<Card className={`backdrop-blur-xl bg-white/70 border-white/50 shadow-xl shadow-gray-200/50 p-6 text-center hover:scale-105 transition-all duration-300 group cursor-pointer`}>
|
||||
<div className={`w-12 h-12 mx-auto mb-4 rounded-xl ${card.color} flex items-center justify-center group-hover:scale-110 transition-transform duration-300`}>
|
||||
<card.icon className="w-6 h-6" />
|
||||
</div>
|
||||
<p className="font-semibold text-gray-800 group-hover:text-gray-900">{card.title}</p>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Floating Badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.8 }}
|
||||
className="absolute -top-4 -right-4 bg-gradient-to-r from-success-500 to-emerald-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg shadow-success-500/30 flex items-center gap-2"
|
||||
>
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
|
||||
</span>
|
||||
{t.hero.engagement_badge}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Smooth Gradient Fade Transition */}
|
||||
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-b from-transparent to-gray-50 pointer-events-none" />
|
||||
</section >
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,141 +1,141 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { BillingToggle } from '@/components/ui/BillingToggle';
|
||||
|
||||
interface PricingProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Pricing: React.FC<PricingProps> = ({ t }) => {
|
||||
const [billingPeriod, setBillingPeriod] = useState<'month' | 'year'>('month');
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 'free',
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
key: 'pro',
|
||||
popular: true,
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
popular: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="pricing" className="py-16">
|
||||
<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.pricing.title}
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
{t.pricing.subtitle}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex justify-center mb-8">
|
||||
<BillingToggle value={billingPeriod} onChange={setBillingPeriod} />
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
{plans.map((plan, index) => (
|
||||
<motion.div
|
||||
key={plan.key}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="h-full"
|
||||
>
|
||||
<Card
|
||||
className={`h-full flex flex-col ${plan.popular
|
||||
? 'border-primary-500 shadow-xl relative scale-105 z-10'
|
||||
: 'border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all'
|
||||
}`}
|
||||
>
|
||||
{plan.popular && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 w-full text-center">
|
||||
<Badge variant="info" className="px-4 py-1.5 shadow-sm">
|
||||
{t.pricing[plan.key].badge}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="text-center pb-8">
|
||||
<CardTitle className="text-2xl mb-4">
|
||||
{t.pricing[plan.key].title}
|
||||
</CardTitle>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-baseline justify-center">
|
||||
<span className="text-4xl font-bold">
|
||||
{plan.key === 'free'
|
||||
? t.pricing[plan.key].price
|
||||
: billingPeriod === 'month'
|
||||
? t.pricing[plan.key].price
|
||||
: plan.key === 'pro'
|
||||
? '€90'
|
||||
: '€290'}
|
||||
</span>
|
||||
<span className="text-gray-600 ml-2">
|
||||
{plan.key === 'free'
|
||||
? t.pricing[plan.key].period
|
||||
: billingPeriod === 'month'
|
||||
? t.pricing[plan.key].period
|
||||
: 'per year'}
|
||||
</span>
|
||||
</div>
|
||||
{billingPeriod === 'year' && plan.key !== 'free' && (
|
||||
<Badge variant="success" className="mt-2">
|
||||
Save 16%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-8 flex-1 flex flex-col">
|
||||
<ul className="space-y-3 flex-1">
|
||||
{t.pricing[plan.key].features.map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<svg className="w-5 h-5 text-success-500 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-100">
|
||||
<Link href="/signup">
|
||||
<Button
|
||||
variant={plan.popular ? 'primary' : 'outline'}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { BillingToggle } from '@/components/ui/BillingToggle';
|
||||
|
||||
interface PricingProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const Pricing: React.FC<PricingProps> = ({ t }) => {
|
||||
const [billingPeriod, setBillingPeriod] = useState<'month' | 'year'>('month');
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 'free',
|
||||
popular: false,
|
||||
},
|
||||
{
|
||||
key: 'pro',
|
||||
popular: true,
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
popular: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="pricing" className="py-16">
|
||||
<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.pricing.title}
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600">
|
||||
{t.pricing.subtitle}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex justify-center mb-8">
|
||||
<BillingToggle value={billingPeriod} onChange={setBillingPeriod} />
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
{plans.map((plan, index) => (
|
||||
<motion.div
|
||||
key={plan.key}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="h-full"
|
||||
>
|
||||
<Card
|
||||
className={`h-full flex flex-col ${plan.popular
|
||||
? 'border-primary-500 shadow-xl relative scale-105 z-10'
|
||||
: 'border-gray-200 hover:border-gray-300 hover:shadow-lg transition-all'
|
||||
}`}
|
||||
>
|
||||
{plan.popular && (
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 w-full text-center">
|
||||
<Badge variant="info" className="px-4 py-1.5 shadow-sm">
|
||||
{t.pricing[plan.key].badge}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="text-center pb-8">
|
||||
<CardTitle className="text-2xl mb-4">
|
||||
{t.pricing[plan.key].title}
|
||||
</CardTitle>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-baseline justify-center">
|
||||
<span className="text-4xl font-bold">
|
||||
{plan.key === 'free'
|
||||
? t.pricing[plan.key].price
|
||||
: billingPeriod === 'month'
|
||||
? t.pricing[plan.key].price
|
||||
: plan.key === 'pro'
|
||||
? '€90'
|
||||
: '€290'}
|
||||
</span>
|
||||
<span className="text-gray-600 ml-2">
|
||||
{plan.key === 'free'
|
||||
? t.pricing[plan.key].period
|
||||
: billingPeriod === 'month'
|
||||
? t.pricing[plan.key].period
|
||||
: 'per year'}
|
||||
</span>
|
||||
</div>
|
||||
{billingPeriod === 'year' && plan.key !== 'free' && (
|
||||
<Badge variant="success" className="mt-2">
|
||||
Save 16%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-8 flex-1 flex flex-col">
|
||||
<ul className="space-y-3 flex-1">
|
||||
{t.pricing[plan.key].features.map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<svg className="w-5 h-5 text-success-500 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-100">
|
||||
<Link href="/signup">
|
||||
<Button
|
||||
variant={plan.popular ? 'primary' : 'outline'}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,98 +1,98 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
|
||||
interface StaticVsDynamicProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const StaticVsDynamic: React.FC<StaticVsDynamicProps> = ({ t }) => {
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900 sm:text-4xl mb-4">
|
||||
{t.static_vs_dynamic.title}
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
{t.static_vs_dynamic.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-8 max-w-6xl mx-auto">
|
||||
{/* Static QR Codes */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card className="relative h-full border-gray-200 shadow-sm hover:shadow-lg transition-all duration-300">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<CardTitle className="text-2xl font-bold text-gray-700">{t.static_vs_dynamic.static.title}</CardTitle>
|
||||
<Badge variant="success" className="bg-gray-100 text-gray-700 hover:bg-gray-200">{t.static_vs_dynamic.static.subtitle}</Badge>
|
||||
</div>
|
||||
<p className="text-gray-500">{t.static_vs_dynamic.static.description}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-4">
|
||||
{t.static_vs_dynamic.static.features.map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-gray-100 rounded-full flex items-center justify-center mt-0.5">
|
||||
<CheckCircle2 className="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
<span className="text-gray-600">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Dynamic QR Codes */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card className="relative h-full border-2 border-primary-500/20 bg-gradient-to-br from-white to-primary-50/50 shadow-xl shadow-primary-500/10 hover:shadow-2xl hover:shadow-primary-500/20 transition-all duration-300">
|
||||
<div className="absolute top-0 right-0 p-4">
|
||||
<div className="absolute -top-3 -right-3">
|
||||
<span className="relative flex h-4 w-4">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-4 w-4 bg-primary-500"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<CardTitle className="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-purple-600">{t.static_vs_dynamic.dynamic.title}</CardTitle>
|
||||
<Badge variant="info" className="bg-primary-100 text-primary-700">{t.static_vs_dynamic.dynamic.subtitle}</Badge>
|
||||
</div>
|
||||
<p className="text-gray-600 font-medium">{t.static_vs_dynamic.dynamic.description}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-4">
|
||||
{t.static_vs_dynamic.dynamic.features.map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-primary-100 rounded-full flex items-center justify-center mt-0.5">
|
||||
<CheckCircle2 className="w-4 h-4 text-primary-600" />
|
||||
</div>
|
||||
<span className="text-gray-900 font-medium">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
|
||||
interface StaticVsDynamicProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const StaticVsDynamic: React.FC<StaticVsDynamicProps> = ({ t }) => {
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900 sm:text-4xl mb-4">
|
||||
{t.static_vs_dynamic.title}
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
{t.static_vs_dynamic.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-8 max-w-6xl mx-auto">
|
||||
{/* Static QR Codes */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card className="relative h-full border-gray-200 shadow-sm hover:shadow-lg transition-all duration-300">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<CardTitle className="text-2xl font-bold text-gray-700">{t.static_vs_dynamic.static.title}</CardTitle>
|
||||
<Badge variant="success" className="bg-gray-100 text-gray-700 hover:bg-gray-200">{t.static_vs_dynamic.static.subtitle}</Badge>
|
||||
</div>
|
||||
<p className="text-gray-500">{t.static_vs_dynamic.static.description}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-4">
|
||||
{t.static_vs_dynamic.static.features.map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-gray-100 rounded-full flex items-center justify-center mt-0.5">
|
||||
<CheckCircle2 className="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
<span className="text-gray-600">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Dynamic QR Codes */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
<Card className="relative h-full border-2 border-primary-500/20 bg-gradient-to-br from-white to-primary-50/50 shadow-xl shadow-primary-500/10 hover:shadow-2xl hover:shadow-primary-500/20 transition-all duration-300">
|
||||
<div className="absolute top-0 right-0 p-4">
|
||||
<div className="absolute -top-3 -right-3">
|
||||
<span className="relative flex h-4 w-4">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-4 w-4 bg-primary-500"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<CardTitle className="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-purple-600">{t.static_vs_dynamic.dynamic.title}</CardTitle>
|
||||
<Badge variant="info" className="bg-primary-100 text-primary-700">{t.static_vs_dynamic.dynamic.subtitle}</Badge>
|
||||
</div>
|
||||
<p className="text-gray-600 font-medium">{t.static_vs_dynamic.dynamic.description}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ul className="space-y-4">
|
||||
{t.static_vs_dynamic.dynamic.features.map((feature: string, index: number) => (
|
||||
<li key={index} className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-primary-100 rounded-full flex items-center justify-center mt-0.5">
|
||||
<CheckCircle2 className="w-4 h-4 text-primary-600" />
|
||||
</div>
|
||||
<span className="text-gray-900 font-medium">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface StatsStripProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const StatsStrip: React.FC<StatsStripProps> = ({ t }) => {
|
||||
const stats = [
|
||||
{ key: 'users', value: '1,240+', label: t.trust.users },
|
||||
{ key: 'codes', value: '8,500+', label: t.trust.codes },
|
||||
{ key: 'scans', value: '1.2M+', label: t.trust.scans },
|
||||
{ key: 'countries', value: '120+', label: t.trust.countries },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-white border-y border-gray-100">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={stat.key} className="text-center group hover:-translate-y-1 transition-transform duration-300">
|
||||
<div className="text-4xl lg:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-purple-600 mb-2">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="text-gray-500 font-medium uppercase tracking-wider text-sm">
|
||||
{stat.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface StatsStripProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const StatsStrip: React.FC<StatsStripProps> = ({ t }) => {
|
||||
const stats = [
|
||||
{ key: 'users', value: '1,240+', label: t.trust.users },
|
||||
{ key: 'codes', value: '8,500+', label: t.trust.codes },
|
||||
{ key: 'scans', value: '1.2M+', label: t.trust.scans },
|
||||
{ key: 'countries', value: '120+', label: t.trust.countries },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-white border-y border-gray-100">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={stat.key} className="text-center group hover:-translate-y-1 transition-transform duration-300">
|
||||
<div className="text-4xl lg:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-purple-600 mb-2">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="text-gray-500 font-medium uppercase tracking-wider text-sm">
|
||||
{stat.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,70 +1,70 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
interface TemplateCardsProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const TemplateCards: React.FC<TemplateCardsProps> = ({ t }) => {
|
||||
const templates = [
|
||||
{
|
||||
key: 'restaurant',
|
||||
title: t.templates.restaurant,
|
||||
icon: '🍽️',
|
||||
color: 'bg-red-50 border-red-200',
|
||||
iconBg: 'bg-red-100',
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
title: t.templates.business,
|
||||
icon: '💼',
|
||||
color: 'bg-blue-50 border-blue-200',
|
||||
iconBg: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
key: 'vcard',
|
||||
title: t.templates.vcard,
|
||||
icon: '👤',
|
||||
color: 'bg-purple-50 border-purple-200',
|
||||
iconBg: 'bg-purple-100',
|
||||
},
|
||||
{
|
||||
key: 'event',
|
||||
title: t.templates.event,
|
||||
icon: '🎫',
|
||||
color: 'bg-green-50 border-green-200',
|
||||
iconBg: 'bg-green-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t.templates.title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{templates.map((template) => (
|
||||
<Card key={template.key} className={`${template.color} text-center hover:scale-105 transition-transform cursor-pointer`}>
|
||||
<div className={`w-16 h-16 ${template.iconBg} rounded-full flex items-center justify-center mx-auto mb-4`}>
|
||||
<span className="text-2xl">{template.icon}</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
{template.title}
|
||||
</h3>
|
||||
<Button variant="outline" size="sm" className="w-full">
|
||||
{t.templates.use_template}
|
||||
</Button>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
interface TemplateCardsProps {
|
||||
t: any; // i18n translation function
|
||||
}
|
||||
|
||||
export const TemplateCards: React.FC<TemplateCardsProps> = ({ t }) => {
|
||||
const templates = [
|
||||
{
|
||||
key: 'restaurant',
|
||||
title: t.templates.restaurant,
|
||||
icon: '🍽️',
|
||||
color: 'bg-red-50 border-red-200',
|
||||
iconBg: 'bg-red-100',
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
title: t.templates.business,
|
||||
icon: '💼',
|
||||
color: 'bg-blue-50 border-blue-200',
|
||||
iconBg: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
key: 'vcard',
|
||||
title: t.templates.vcard,
|
||||
icon: '👤',
|
||||
color: 'bg-purple-50 border-purple-200',
|
||||
iconBg: 'bg-purple-100',
|
||||
},
|
||||
{
|
||||
key: 'event',
|
||||
title: t.templates.event,
|
||||
icon: '🎫',
|
||||
color: 'bg-green-50 border-green-200',
|
||||
iconBg: 'bg-green-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-gray-900 mb-4">
|
||||
{t.templates.title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{templates.map((template) => (
|
||||
<Card key={template.key} className={`${template.color} text-center hover:scale-105 transition-transform cursor-pointer`}>
|
||||
<div className={`w-16 h-16 ${template.iconBg} rounded-full flex items-center justify-center mx-auto mb-4`}>
|
||||
<span className="text-2xl">{template.icon}</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
{template.title}
|
||||
</h3>
|
||||
<Button variant="outline" size="sm" className="w-full">
|
||||
{t.templates.use_template}
|
||||
</Button>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue