feat: add images

This commit is contained in:
Oleg Proskurin 2025-12-05 01:18:05 +07:00
parent 3feaed33c8
commit 84b34f0115
11 changed files with 334 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

View File

@ -3,6 +3,7 @@
import { useState } from 'react';
import { Zap, Globe, FlaskConical, AtSign, Link } from 'lucide-react';
import GlowEffect from './GlowEffect';
import WaitlistPopup from './WaitlistPopup';
export const styles = `
.gradient-text {
@ -45,16 +46,25 @@ const badges = [
export function HeroSection() {
const [email, setEmail] = useState('');
const [showPopup, setShowPopup] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Email submitted:', email);
setShowPopup(true);
};
const handleWaitlistSubmit = async (data: { selected: string[]; other: string }) => {
await fetch('/api/brevo', {
method: 'POST',
body: JSON.stringify({ email, useCases: data.selected, other: data.other }),
});
};
return (
<section className="relative pt-20 pb-20 px-6">
<div className="max-w-4xl mx-auto text-center">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10 text-gray-400 text-xs mb-10">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10 text-gray-400 text-xs mb-6">
<span className="w-1.5 h-1.5 rounded-full bg-gray-500 beta-dot" />
In Active Development
</div>
@ -113,6 +123,11 @@ export function HeroSection() {
))}
</div>
</div>
<WaitlistPopup
isOpen={showPopup}
onClose={() => setShowPopup(false)}
onSubmit={handleWaitlistSubmit}
/>
</section>
);
}

View File

@ -0,0 +1,318 @@
import { useState, useEffect, useCallback } from 'react';
import Image from 'next/image';
import {
X,
Check,
Globe,
ShoppingCart,
Gamepad2,
Smartphone,
Sparkles,
Palette,
PenTool,
Blocks,
Compass,
} from 'lucide-react';
import webDevImg from '../_assets/1.jpg';
import ecommerceImg from '../_assets/2.jpg';
import gameDevImg from '../_assets/3.jpg';
import mobileDevImg from '../_assets/4.jpg';
import vibeCodingImg from '../_assets/5.jpg';
import aiArtImg from '../_assets/6.jpg';
import contentImg from '../_assets/7.jpg';
import nocodeImg from '../_assets/8.jpg';
import justBrowsingImg from '../_assets/9.jpg';
interface WaitlistPopupProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (data: { selected: string[]; other: string }) => void;
}
const USE_CASES = [
{ id: 'web-dev', label: 'Web Dev', icon: Globe, image: webDevImg },
{ id: 'ecommerce', label: 'E-commerce', icon: ShoppingCart, image: ecommerceImg },
{ id: 'game-dev', label: 'Game Dev', icon: Gamepad2, image: gameDevImg },
{ id: 'mobile-dev', label: 'Mobile Dev', icon: Smartphone, image: mobileDevImg },
{ id: 'vibecoding', label: 'Vibecoding', icon: Sparkles, image: vibeCodingImg },
{ id: 'ai-art', label: 'AI Art', icon: Palette, image: aiArtImg },
{ id: 'content', label: 'Content', icon: PenTool, image: contentImg },
{ id: 'nocode', label: 'Nocode', icon: Blocks, image: nocodeImg },
{ id: 'just-browsing', label: 'Just came across', icon: Compass, image: justBrowsingImg, dashed: true },
];
export default function WaitlistPopup({ isOpen, onClose, onSubmit }: WaitlistPopupProps) {
const [selected, setSelected] = useState<string[]>([]);
const [other, setOther] = useState('');
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
onClose();
}
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [isOpen, onClose]);
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen]);
const toggleSelection = useCallback((id: string) => {
setSelected((prev) => (prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]));
}, []);
const handleSubmit = () => {
onSubmit({ selected, other });
setSelected([]);
setOther('');
onClose();
};
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
if (!isOpen) return null;
return (
<>
<style>{`
.waitlist-popup-backdrop {
background: rgba(3, 7, 18, 0.6);
backdrop-filter: blur(4px);
}
.waitlist-popup {
background: linear-gradient(180deg, rgba(15, 15, 30, 0.95) 0%, rgba(10, 10, 20, 0.98) 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.05),
0 25px 50px -12px rgba(0, 0, 0, 0.5),
0 0 100px rgba(99, 102, 241, 0.1);
}
.waitlist-card {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.waitlist-card-image {
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 40%;
mask-image: linear-gradient(to right, transparent 0%, black 50%);
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 50%);
opacity: 0.7;
transition: opacity 0.2s ease;
}
.waitlist-card:hover .waitlist-card-image {
opacity: 0.9;
}
.waitlist-card.selected .waitlist-card-image {
opacity: 1;
}
.waitlist-card:hover {
background: rgba(255, 255, 255, 0.04);
border-color: rgba(255, 255, 255, 0.12);
}
.waitlist-card.selected {
background: rgba(99, 102, 241, 0.12);
border-color: rgba(139, 92, 246, 0.4);
}
.waitlist-card.selected .waitlist-card-icon {
color: #a78bfa;
}
.waitlist-card-dashed {
background: transparent;
border: 1px dashed rgba(255, 255, 255, 0.15);
}
.waitlist-card-dashed:hover {
background: rgba(255, 255, 255, 0.02);
border-color: rgba(255, 255, 255, 0.25);
}
.waitlist-card-dashed.selected {
background: rgba(99, 102, 241, 0.08);
border-color: rgba(139, 92, 246, 0.3);
}
.waitlist-card-icon {
color: rgba(255, 255, 255, 0.5);
transition: color 0.2s ease;
}
.waitlist-card:hover .waitlist-card-icon {
color: rgba(255, 255, 255, 0.7);
}
.waitlist-other-input {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
transition: all 0.2s ease;
}
.waitlist-other-input:focus {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(139, 92, 246, 0.4);
outline: none;
}
.waitlist-submit-btn {
background: transparent;
border: 1px solid rgba(139, 92, 246, 0.6);
transition: all 0.2s ease;
}
.waitlist-submit-btn:hover {
background: rgba(139, 92, 246, 0.1);
border-color: rgba(139, 92, 246, 0.8);
}
.waitlist-close-btn {
color: rgba(255, 255, 255, 0.3);
transition: all 0.2s ease;
}
.waitlist-close-btn:hover {
color: rgba(255, 255, 255, 0.7);
}
.waitlist-divider {
height: 1px;
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.1) 50%, transparent 100%);
}
.waitlist-checkmark-ring {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, rgba(34, 197, 94, 0.2) 0%, rgba(16, 185, 129, 0.1) 100%);
border: 1px solid rgba(34, 197, 94, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
.waitlist-checkmark-inner {
width: 28px;
height: 28px;
border-radius: 50%;
background: linear-gradient(135deg, #22c55e 0%, #10b981 100%);
display: flex;
align-items: center;
justify-content: center;
}
`}</style>
{/* Backdrop */}
<div
className="waitlist-popup-backdrop fixed inset-0 z-50 flex items-center justify-center p-4"
onClick={handleBackdropClick}
>
{/* Popup - wide horizontal layout */}
<div className="waitlist-popup rounded-2xl p-6 sm:p-8 w-full max-w-[900px] max-h-[90vh] overflow-y-auto relative">
{/* Close button */}
<button
className="waitlist-close-btn absolute top-4 right-4 sm:top-5 sm:right-5 p-1"
onClick={onClose}
>
<X className="w-5 h-5" />
</button>
{/* Header row: checkmark + title inline */}
<div className="flex items-center justify-center gap-4 mb-6">
<div className="waitlist-checkmark-ring shrink-0">
<div className="waitlist-checkmark-inner">
<Check className="w-3.5 h-3.5 text-white" strokeWidth={3} />
</div>
</div>
<h2 className="text-white text-xl font-semibold">Sweet! You'll hear from us soon</h2>
</div>
{/* Divider */}
<div className="waitlist-divider mb-5" />
{/* Question */}
<p className="text-white/70 text-center mb-5 text-base font-medium">
Quick one: what brings you here?
</p>
{/* Cards Grid - 3 rows of 3, stretched horizontally */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 mb-4">
{USE_CASES.map(({ id, label, icon: Icon, image, dashed }) => (
<button
key={id}
className={`
waitlist-card rounded-xl py-6 px-5 flex items-center gap-3
${dashed ? 'waitlist-card-dashed' : ''}
${selected.includes(id) ? 'selected' : ''}
`}
onClick={() => toggleSelection(id)}
>
<Image
src={image}
alt={label}
fill
className="waitlist-card-image object-cover"
/>
<Icon className="waitlist-card-icon w-5 h-5 shrink-0 relative z-10" />
<span
className={`text-sm font-medium relative z-10 ${dashed ? 'text-white/50' : 'text-white/80'}`}
>
{label}
</span>
</button>
))}
</div>
{/* Other input */}
<div className="mb-6">
<div className="flex items-center gap-3">
<span className="text-white/40 text-sm whitespace-nowrap">Something cooler:</span>
<input
type="text"
value={other}
onChange={(e) => setOther(e.target.value)}
className="waitlist-other-input flex-1 rounded-lg px-4 py-2.5 text-white text-sm placeholder:text-white/20"
placeholder=""
/>
</div>
</div>
{/* Submit button */}
<button
className="waitlist-submit-btn w-full py-3 rounded-xl text-white font-semibold text-[15px]"
onClick={handleSubmit}
>
Complete
</button>
</div>
</div>
</>
);
}