// components.jsx — shared: logos, icons, attack map, animated counters
function Logo({ height = 28 }) {
return
;
}
function ShieldIcon({ size = 16, color = 'currentColor' }) {
return (
);
}
function CheckIcon({ size = 16, color = '#22c55e' }) {
return (
);
}
function ArrowIcon({ size = 14 }) {
return (
);
}
// Counts up to target whenever component appears in viewport
function CountUp({ to, duration = 1800, format = (n) => Math.round(n).toLocaleString('es') , suffix = '', className = '' }) {
const [val, setVal] = React.useState(0);
const ref = React.useRef(null);
const started = React.useRef(false);
React.useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting && !started.current) {
started.current = true;
const start = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 3);
setVal(eased * to);
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
});
}, { threshold: 0.4 });
obs.observe(el);
return () => obs.disconnect();
}, [to, duration]);
return {format(val)}{suffix};
}
// Background world map shape (SVG silhouette + animated threat blips)
function AttackMap() {
// A handful of "incidents" positioned roughly across continents
const blips = React.useMemo(() => [
{ x: '18%', y: '38%' }, { x: '22%', y: '52%' }, { x: '12%', y: '46%' },
{ x: '32%', y: '34%' }, { x: '46%', y: '32%' }, { x: '54%', y: '38%' },
{ x: '64%', y: '36%' }, { x: '72%', y: '44%' }, { x: '78%', y: '52%' },
{ x: '82%', y: '36%' }, { x: '40%', y: '62%' }, { x: '60%', y: '60%' },
{ x: '28%', y: '64%' }, { x: '88%', y: '60%' }, { x: '14%', y: '60%' },
], []);
return (
{blips.map((b, i) => (
))}
);
}
// Streaming "security events" terminal
function ThreatTerminal({ height = 360 }) {
const seedLines = React.useMemo(() => ([
{ kind: 'block', ip: '185.92.221.14', msg: 'admin brute-force blocked', loc: 'RU' },
{ kind: 'block', ip: '103.45.118.220', msg: 'wp-login.php → 412 attempts', loc: 'CN' },
{ kind: 'warn', ip: '—', msg: 'CVE-2026-2418 detected (Elementor < 3.8.1)' },
{ kind: 'ok', ip: '—', msg: 'backup snapshot saved → s3://wpg/2026-05-14' },
{ kind: 'info', ip: '—', msg: 'plugin scan complete · 14 outdated · 2 critical' },
{ kind: 'block', ip: '45.137.21.88', msg: 'xmlrpc.php flood blocked', loc: 'NL' },
{ kind: 'ok', ip: '—', msg: 'WordPress core 6.9.3 → 6.9.4 ✓' },
{ kind: 'warn', ip: '—', msg: 'AIO Backup last run 13d ago — escalating' },
{ kind: 'block', ip: '94.232.41.7', msg: 'malicious file upload rejected (php in /uploads)', loc: 'UA' },
{ kind: 'info', ip: '—', msg: 'Wordfence rules synced · 1,284 active' },
{ kind: 'block', ip: '198.235.24.12', msg: 'SQLi attempt on ?s= parameter', loc: 'US' },
{ kind: 'ok', ip: '—', msg: 'PHP 8.4.19 OK · WP-CLI 2.12.0 OK' },
{ kind: 'warn', ip: '—', msg: 'theme "Newspaper" outdated · vendor pending' },
{ kind: 'block', ip: '77.83.36.220', msg: 'enumeration probe on /author=1', loc: 'BG' },
]), []);
const [lines, setLines] = React.useState(() => seedLines.slice(0, 9));
React.useEffect(() => {
let i = 9;
const id = setInterval(() => {
setLines(prev => {
const next = [...prev, seedLines[i % seedLines.length]];
i++;
return next.slice(-9);
});
}, 1600);
return () => clearInterval(id);
}, [seedLines]);
const now = () => {
const d = new Date();
return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`;
};
return (
wpguardian · live security log
LIVE
{lines.map((l, i) => (
[{now()}]
{l.kind === 'block' ? 'BLOCK' : l.kind === 'warn' ? 'ALERT' : l.kind === 'ok' ? 'OK' : 'INFO'}
{l.ip !== '—' && {l.ip}}
{l.loc && ·{l.loc}}
{l.msg}
))}
);
}
Object.assign(window, { Logo, ShieldIcon, CheckIcon, ArrowIcon, CountUp, AttackMap, ThreatTerminal });