/* global React, ReactDOM */ const { useState, useEffect, useRef } = React; // ====== ICONS ====== const Icon = { Python: () => ( ), Django: () => ( ), JS: () => ( JS ), Flutter: () => ( ), Android: () => ( ), Algo: () => ( ), Grad: () => ( ), Clock: () => ( ), Moon: () => ( ), Arrow: () => ( ), Menu: () => ( ), Close: () => ( ), Pin: () => ( ), }; window.Icon = Icon; // ====== REVEAL ====== function Reveal({ children, stagger = false, className = '', as: As = 'div', ...rest }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver( (entries) => entries.forEach(e => { if (e.isIntersecting) { el.classList.add('in'); io.disconnect(); } }), { threshold: 0.15 } ); io.observe(el); return () => io.disconnect(); }, []); return {children}; } window.Reveal = Reveal; // ====== PARTICLES ====== function Particles({ count = 24 }) { const [items] = useState(() => { const syms = ['', '{}', '[]', '0x', '#', '//', '=>', '&&', '||', '::', '()', '<>']; return Array.from({ length: count }, (_, i) => ({ id: i, sym: syms[Math.floor(Math.random() * syms.length)], left: Math.random() * 100, delay: Math.random() * 20, dur: 15 + Math.random() * 20, size: 10 + Math.random() * 10, color: ['', 'cyan', 'magenta'][Math.floor(Math.random() * 3)], top: 100 + Math.random() * 50, })); }); return (
{items.map(p => ( {p.sym} ))}
); } window.Particles = Particles; // ====== TYPING ====== function Typing({ text, speed = 45 }) { const [out, setOut] = useState(''); useEffect(() => { let i = 0; setOut(''); const t = setInterval(() => { i++; setOut(text.slice(0, i)); if (i >= text.length) clearInterval(t); }, speed); return () => clearInterval(t); }, [text, speed]); return {out}; } window.Typing = Typing; // ====== MATRIX RAIN ====== function MatrixRain() { const CHARSET = '01{}[]#0xABCDEFabcdefKODAIFROADS+*=!?&'; const COLS = 18; const LINES = 30; // hidden easter egg phrase — drops rarely, in a random column const SECRET = 'ACHEI O BRINDE'; const randCh = () => CHARSET[Math.floor(Math.random() * CHARSET.length)]; const randomColumnText = () => { let s = ''; for (let j = 0; j < LINES; j++) s += randCh() + '\n'; return s; }; const [cols, setCols] = useState(() => Array.from({ length: COLS }, (_, i) => ({ id: i, left: (i / COLS) * 100, delay: Math.random() * 5, dur: 6 + Math.random() * 6, text: randomColumnText(), opacity: 0.2 + Math.random() * 0.5, })) ); // Easter egg cycle — every 60-120s embed SECRET into a random column, // then restore the column to random chars after one animation cycle. useEffect(() => { let cancelled = false; let timer; const inject = () => { if (cancelled) return; const target = Math.floor(Math.random() * COLS); // letters of SECRET as a vertical sequence, padded by random chars const cleanLetters = SECRET.split('').map(ch => (ch === ' ' ? '·' : ch)); const padTop = 5 + Math.floor(Math.random() * (LINES - cleanLetters.length - 6)); let liveDur = 0; setCols(prev => prev.map((c, idx) => { if (idx !== target) return c; const lines = []; for (let j = 0; j < LINES; j++) { const k = j - padTop; lines.push(k >= 0 && k < cleanLetters.length ? cleanLetters[k] : randCh()); } liveDur = c.dur; return { ...c, text: lines.join('\n') }; })); // After one full fall, scramble the column back to noise setTimeout(() => { if (cancelled) return; setCols(prev => prev.map((c, idx) => idx === target ? { ...c, text: randomColumnText() } : c )); }, (liveDur + 1) * 1000); // schedule next injection (60-120s) timer = setTimeout(inject, 60000 + Math.random() * 60000); }; // first injection: 45-90s after mount (so it isn't visible immediately) timer = setTimeout(inject, 45000 + Math.random() * 45000); return () => { cancelled = true; clearTimeout(timer); }; }, []); return (
{cols.map(c => (
{c.text}
))}
); } window.MatrixRain = MatrixRain; Object.assign(window, { Icon, Reveal, Particles, Typing, MatrixRain });