// ============ 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 (
{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;