// Buyer toast — fixed at top, appears every 8s, stays 4s
const TOAST_BUYERS = [
{ name: 'João Silva', city: 'Braga', kit: 'Campeão', img: 'uploads/homem-1.jpg' },
{ name: 'Sofia Almeida', city: 'Porto', kit: 'Inicial', img: 'uploads/mulher-1.jpg' },
{ name: 'Miguel Costa', city: 'Lisboa', kit: 'Colecionador', img: 'uploads/homem-2.jpg' },
{ name: 'Rita Fernandes', city: 'Coimbra', kit: 'Campeão', img: 'uploads/avatar-rita.png' },
{ name: 'André Pereira', city: 'Faro', kit: 'Inicial', img: 'uploads/homem-3.jpg' },
{ name: 'Carolina Dias', city: 'Aveiro', kit: 'Basic', img: 'uploads/mulher-2.jpg' },
{ name: 'Bruno Oliveira', city: 'Setúbal', kit: 'Campeão', img: 'uploads/avatar-carlos.png' },
{ name: 'Inês Martins', city: 'Funchal', kit: 'Colecionador', img: 'uploads/mulher-3.jpg' },
{ name: 'Tiago Ribeiro', city: 'Évora', kit: 'Campeão', img: 'uploads/avatar-roberto.png' },
];
function BuyerAvatar({ buyer, size = 44 }) {
const [errored, setErrored] = React.useState(false);
const colors = [
[C.green, C.gold], [C.red, C.gold], [C.greenDeep, C.cream],
[C.gold, C.red], [C.green, C.cream],
];
const [c1, c2] = colors[buyer.name.charCodeAt(0) % colors.length];
if (buyer.img && !errored) {
return (
setErrored(true)}
style={{
width: size, height: size, borderRadius: '50%',
objectFit: 'cover', flexShrink: 0,
border: `2px solid ${C.gold}`,
background: '#eee',
}}
/>
);
}
return (
{buyer.name.split(' ').map(n => n[0]).slice(0,2).join('')}
);
}
function BuyerToast() {
const [idx, setIdx] = React.useState(0);
const [visible, setVisible] = React.useState(false);
const [secondsAgo, setSecondsAgo] = React.useState(0);
// First appearance delayed slightly
React.useEffect(() => {
const initial = setTimeout(() => setVisible(true), 1800);
return () => clearTimeout(initial);
}, []);
// Rhythm: visible 4s → hidden 4s → next buyer
React.useEffect(() => {
if (visible) {
setSecondsAgo(Math.floor(2 + Math.random() * 40));
const t = setTimeout(() => setVisible(false), 4000);
return () => clearTimeout(t);
} else {
const t = setTimeout(() => {
setIdx((i) => i + 1);
setVisible(true);
}, 4000);
return () => clearTimeout(t);
}
}, [visible]);
const buyer = TOAST_BUYERS[idx % TOAST_BUYERS.length];
return (
{/* Pulse dot */}
⚽ {buyer.name.split(' ')[0]} de {buyer.city}
comprou o Kit {buyer.kit}
há {secondsAgo}s · pagamento confirmado ✓
);
}
// Landing / Hero screen
function Landing({ onStart }) {
const [tick, setTick] = React.useState(0);
const [buyerIdx, setBuyerIdx] = React.useState(0);
const [buyerVisible, setBuyerVisible] = React.useState(true);
const [secondsAgo, setSecondsAgo] = React.useState(12);
React.useEffect(() => {
const t = setInterval(() => setTick((x) => x + 1), 1000);
return () => clearInterval(t);
}, []);
// Buyer notification rotates every 9–14s with a fade transition
React.useEffect(() => {
let active = true;
const cycle = () => {
if (!active) return;
const wait = 9000 + Math.random() * 5000;
setTimeout(() => {
if (!active) return;
setBuyerVisible(false);
setTimeout(() => {
if (!active) return;
setBuyerIdx((i) => i + 1);
setSecondsAgo(Math.floor(2 + Math.random() * 30));
setBuyerVisible(true);
cycle();
}, 400);
}, wait);
};
cycle();
return () => { active = false; };
}, []);
// Tick the "há Xs" counter up while a buyer is showing
React.useEffect(() => {
const t = setInterval(() => setSecondsAgo((s) => s + 1), 1000);
return () => clearInterval(t);
}, [buyerIdx]);
// Countdown to a fixed future date
const deadline = React.useMemo(() => Date.now() + 1000 * 60 * 60 * 23 + 1000 * 47 * 60 + 1000 * 12, []);
const remaining = Math.max(0, deadline - Date.now());
const hh = String(Math.floor(remaining / 3600000)).padStart(2, '0');
const mm = String(Math.floor((remaining % 3600000) / 60000)).padStart(2, '0');
const ss = String(Math.floor((remaining % 60000) / 1000)).padStart(2, '0');
// Rotating "just bought" notifications
const buyers = [
{ name: 'João', city: 'Braga', kit: 'Campeão', img: 'uploads/homem-1.jpg' },
{ name: 'Sofia', city: 'Porto', kit: 'Inicial', img: 'uploads/mulher-1.jpg' },
{ name: 'Miguel', city: 'Lisboa', kit: 'Colecionador', img: 'uploads/homem-2.jpg' },
{ name: 'Rita', city: 'Coimbra', kit: 'Campeão', img: 'uploads/avatar-rita.png' },
{ name: 'André', city: 'Faro', kit: 'Inicial', img: 'uploads/homem-3.jpg' },
{ name: 'Carolina', city: 'Aveiro', kit: 'Basic', img: 'uploads/mulher-2.jpg' },
];
const buyer = buyers[buyerIdx % buyers.length];
return (
{/* Top notice strip */}
⚡ ENVIO PARA TODO PORTUGAL · STOCK LIMITADO ⚡
{/* Hero */}
{/* Subtle stadium stripes */}
{/* Countdown */}
OFERTA TERMINA EM {hh}:{mm}:{ss}
{/* Headline */}
MUNDIAL · NORTE AMÉRICA · 2026
COLECIONA
O MUNDIAL
INTEIRO
Álbum + saquetas de cromos · 48 seleções · 736 craques.
Faz o quiz e desbloqueia até 35% OFF.
{/* Floating cromo trio */}
🎯 Fazer Quiz · Ganhar Desconto
✓ Stock em PT
✓ Envio 24-48h
✓ MB Way
{/* Live buyer ticker */}
{buyer.name} de {buyer.city} acabou de comprar
Kit {buyer.kit} · há {secondsAgo < 60 ? `${secondsAgo}s` : `${Math.floor(secondsAgo / 60)}min`}
✓
{/* Social proof stats */}
{[
['12 384', 'Colecionadores'],
['4,9★', '1 207 reviews'],
['48h', 'Envio máx.'],
].map(([n, l]) => (
))}
{/* Why */}
A coleção oficial da temporada>} />
{[
{ i: '⚽', t: '736 craques', d: 'Todos os 48 plantéis do Mundial 2026' },
{ i: '✨', t: 'Cromos raros', d: 'Holográficos, foil, lendas e edições limitadas' },
{ i: '📦', t: 'Saquetas seladas', d: 'Diretamente do fornecedor — sem cromos repetidos garantidos por kit' },
{ i: '🇵🇹', t: 'Stock em Portugal', d: 'Envio CTT 24-48h · Pagamento MB Way' },
].map((f, i) => (
{f.i}
{f.t.toUpperCase()}
{f.d}
))}
Quero o meu desconto →
🔒 Pagamento seguro · Garantia 30 dias
);
}
function SoundToggle() {
const [muted, setMuted] = React.useState(false);
return (
);
}
Object.assign(window, { Landing, SoundToggle });