// ============ ANGELO OS ============ const { useState: useStateOS, useEffect: useEffectOS, useRef: useRefOS, useCallback: useCallbackOS } = React; const FOLDER_ORDER = ["Proyectos","Educación","Experiencia","Café Tecnológico","Investigación","Contacto"]; function Clock() { const [now, setNow] = useStateOS(new Date()); useEffectOS(() => { const t = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(t); }, []); const fmt = now.toLocaleString('es-AR', { weekday: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); return {fmt}; } function OSWindow({ kind, name, onClose, onFocus, z, initialPos }) { // kind: "folder" | "terminal" | "readme" const winRef = useRefOS(null); const [pos, setPos] = useStateOS(initialPos || { x: 60, y: 40 }); const dragRef = useRefOS({ dragging: false }); const startDrag = (e) => { onFocus?.(); const r = winRef.current.getBoundingClientRect(); const parent = winRef.current.parentElement.getBoundingClientRect(); dragRef.current = { dragging: true, offX: e.clientX - r.left, offY: e.clientY - r.top, parent }; const move = (ev) => { const d = dragRef.current; if (!d.dragging) return; const newX = Math.max(0, Math.min(d.parent.width - 200, ev.clientX - d.parent.left - d.offX)); const newY = Math.max(0, Math.min(d.parent.height - 60, ev.clientY - d.parent.top - d.offY)); setPos({ x: newX, y: newY }); }; const up = () => { dragRef.current.dragging = false; window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); }; window.addEventListener('mousemove', move); window.addEventListener('mouseup', up); }; return (
{name}
{kind === 'folder' && } {kind === 'readme' && } {kind === 'terminal' && }
); } function FolderBody({ folder }) { const files = window.OS_FS[folder] || []; const [sel, setSel] = useStateOS(0); const cur = files[sel]; return ( <>
~/{folder.toLowerCase().replace(/ /g, '-')}/ {files.length} items
{files.map((f, i) => (
setSel(i)}> 📄{f.name}
))}
{cur ? cur.body : — vacío —}
); } function ReadmeBody() { const file = window.OS_DESKTOP_FILES[0]; return ( <>
~/README.txt
{file.body}
); } const TERM_HELP = `comandos disponibles: help lista de comandos whoami quién soy ls lista carpetas cat leer archivo (ej: cat about.txt) contact abrir info de contacto clear limpiar terminal konami ✨ chess ir a sección AURA`; function TerminalBody() { const [lines, setLines] = useStateOS([ { t: 'out', v: 'angelo.os terminal · v1.0' }, { t: 'dim', v: 'tipeá "help" para ver comandos disponibles' } ]); const [input, setInput] = useStateOS(''); const inputRef = useRefOS(null); useEffectOS(() => { inputRef.current?.focus(); }, []); const run = (cmd) => { const c = cmd.trim().toLowerCase(); const echo = { t: 'echo', v: angelo@uai:~$ {cmd} }; let out = []; if (!c) out = []; else if (c === 'help') out = [{ t: 'out', v: TERM_HELP }]; else if (c === 'whoami') out = [{ t: 'out', v: 'angelo@uai:~$ ingeniero en formación · constructor de cosas' }]; else if (c === 'ls') out = [{ t: 'out', v: FOLDER_ORDER.join(' ') + ' README.txt CV.pdf' }]; else if (c.startsWith('cat ')) { const f = c.slice(4).trim(); if (f === 'about.txt' || f === 'readme.txt') out = [{ t: 'out', v: window.OS_DESKTOP_FILES[0].body }]; else out = [{ t: 'err', v: `cat: ${f}: no encontrado. probá: cat about.txt` }]; } else if (c === 'contact') out = [{ t: 'out', v: window.OS_FS['Contacto'][0].body }]; else if (c === 'clear') { setLines([]); setInput(''); return; } else if (c === 'konami') { out = [{ t: 'accent', v: '✨ konami activado · 5 segundos en verde matrix' }]; document.documentElement.style.setProperty('--cyan', '#22c55e'); document.documentElement.style.setProperty('--violet', '#16a34a'); setTimeout(() => { document.documentElement.style.removeProperty('--cyan'); document.documentElement.style.removeProperty('--violet'); }, 5000); } else if (c === 'chess' || c === 'aura') { out = [{ t: 'accent', v: '→ scrolling a AURA...' }]; setTimeout(() => document.getElementById('aura')?.scrollIntoView({ behavior: 'smooth' }), 300); } else out = [{ t: 'err', v: `comando no encontrado: ${cmd} — tipeá "help"` }]; setLines(L => [...L, echo, ...out]); setInput(''); }; const onKey = (e) => { if (e.key === 'Enter') run(input); }; return ( <>
{lines.map((l, i) => (
{l.v}
))}
angelo@uai:~$ setInput(e.target.value)} onKeyDown={onKey} placeholder="" autoFocus aria-label="Terminal input" />
); } function AngeloOS() { const { t } = window.useLang(); const ref = window.useReveal(); const [wins, setWins] = useStateOS([]); // { id, kind, name } const [topZ, setTopZ] = useStateOS(10); const openWin = useCallbackOS((kind, name) => { setWins(W => { const exists = W.find(w => w.kind === kind && w.name === name); if (exists) { // bring to front setTopZ(z => z + 1); return W.map(w => w === exists ? { ...w, z: topZ + 1 } : w); } setTopZ(z => z + 1); const offset = W.length * 28; return [...W, { id: Date.now() + Math.random(), kind, name, z: topZ + 1, pos: { x: 80 + offset, y: 60 + offset } }]; }); }, [topZ]); const closeWin = (id) => setWins(W => W.filter(w => w.id !== id)); const focusWin = (id) => { setTopZ(z => z + 1); setWins(W => W.map(w => w.id === id ? { ...w, z: topZ + 1 } : w)); }; const icons = [ ...FOLDER_ORDER.map(f => ({ glyph: '📂', label: f, action: () => openWin('folder', f) })), { glyph: '📄', label: 'README.txt', action: () => openWin('readme', 'README.txt') }, { glyph: '📕', label: 'CV.pdf', action: () => { const a = document.createElement('a'); a.href = 'cv.pdf'; a.download = 'CV-Angelo-Perrotta.pdf'; a.target = '_blank'; a.rel = 'noopener noreferrer'; document.body.appendChild(a); a.click(); a.remove(); } }, { glyph: '⌨️', label: 'Terminal', action: () => openWin('terminal', 'angelo@uai ~ /bin/bash') } ]; return (
{t.os.kicker}

{t.os.h2}

{t.os.sub}

🍎 Angelo OS ArchivoEditarVerVentanaAyuda 📶🔋 92%
{/* Desktop view */}
{icons.map((ic, i) => ( ))}
{wins.map(w => ( closeWin(w.id)} onFocus={() => focusWin(w.id)} /> ))}
{/* Mobile fallback */}
{FOLDER_ORDER.map(f => (
📂 {f} {(window.OS_FS[f] || []).map((file, i) => (
📄 {file.name}
{file.body}
))}
))}
📄 README.txt
{window.OS_DESKTOP_FILES[0].body}
📕 CV.pdf
Descargá el CV completo · descargar PDF
); } window.AngeloOS = AngeloOS;