/* pages-other.jsx — Skills + Projects + Contact */ const { useState: useStateO, useEffect: useEffectO, useRef: useRefO } = React; /* ============================================ SKILLS ============================================ */ const SKILLS_DATA = [ { cat: 'skills.cat.lang', version: 'v2026.5', items: [ { name: 'TypeScript', v: 92, s: 'expert' }, { name: 'JavaScript', v: 95, s: 'expert' }, { name: 'Python', v: 86, s: 'expert' }, { name: 'Bash / Shell', v: 78, s: 'installed' }, { name: 'SQL', v: 80, s: 'installed' }, { name: 'Go', v: 55, s: 'learning' }, { name: 'Rust', v: 40, s: 'learning' }, ], }, { cat: 'skills.cat.front', version: 'v2026.5', items: [ { name: 'React', v: 92, s: 'expert' }, { name: 'Next.js', v: 84, s: 'installed' }, { name: 'HTML5 / CSS3', v: 90, s: 'expert' }, { name: 'Tailwind CSS', v: 86, s: 'installed' }, { name: 'Vite / Webpack', v: 78, s: 'installed' }, { name: 'Svelte', v: 50, s: 'learning' }, ], }, { cat: 'skills.cat.back', version: 'v2026.5', items: [ { name: 'Node.js / Express', v: 88, s: 'expert' }, { name: 'FastAPI / Django', v: 80, s: 'installed' }, { name: 'REST APIs', v: 90, s: 'expert' }, { name: 'GraphQL', v: 72, s: 'installed' }, { name: 'WebSockets', v: 68, s: 'installed' }, ], }, { cat: 'skills.cat.data', version: 'v2026.5', items: [ { name: 'PostgreSQL', v: 84, s: 'installed' }, { name: 'MongoDB / Redis', v: 76, s: 'installed' }, { name: 'Docker', v: 82, s: 'installed' }, { name: 'AWS / GCP', v: 70, s: 'installed' }, { name: 'CI/CD (GH Actions)', v: 78, s: 'installed' }, { name: 'Kubernetes', v: 50, s: 'learning' }, ], }, { cat: 'skills.cat.tools', version: 'v2026.5', items: [ { name: 'Git / GitHub', v: 94, s: 'expert' }, { name: 'Linux / Unix', v: 84, s: 'installed' }, { name: 'VS Code', v: 92, s: 'expert' }, { name: 'vim (basic)', v: 60, s: 'installed' }, { name: 'Figma', v: 72, s: 'installed' }, { name: 'Jest / Vitest', v: 80, s: 'installed' }, ], }, ]; function SkillsPage() { const { t } = window.useI18n(); const barsRef = useRefO(null); // Animate bars after mount (and re-animate on lang change since component remounts). useEffectO(() => { const bars = barsRef.current?.querySelectorAll('.pkg-bar > i'); if (!bars) return; bars.forEach((el, i) => { const target = +el.dataset.target; setTimeout(() => { el.style.width = target + '%'; }, 40 + i * 30); }); }, []); const total = SKILLS_DATA.reduce((n, c) => n + c.items.length, 0); const statusLabel = { expert: 'skills.legend.expert', installed: 'skills.legend.installed', learning: 'skills.legend.learning' }; return (
david @ fontanet : ~/skills $ pkg list-installed --grouped

{t('skills.title')}

{t('skills.intro')}

{total} packages installed | 3 learning | May 2026 last update
{t('skills.legend.expert')} {t('skills.legend.installed')} {t('skills.legend.learning')}
{SKILLS_DATA.map(cat => (
{t(cat.cat)} {cat.items.length} packages · {cat.version}
{cat.items.map(it => (
{it.name} {it.v}% {t(statusLabel[it.s])}
))}
))}
); } /* ============================================ PROJECTS ============================================ */ const PROJECTS_DATA = [ { id: 'p1', glyph: '{ }', title: 'project-alpha', status: 'live', cat: 'web', desc: { es: 'Aplicación web full-stack con foco en performance y DX. Stack moderno y arquitectura modular.', en: 'Full-stack web app focused on performance and DX. Modern stack and modular architecture.' }, tags: ['React', 'Node.js', 'PostgreSQL', 'TypeScript'], repo: 'https://github.com/DavidFB-creator', demo: null }, { id: 'p2', glyph: '', title: 'cli-toolkit', status: 'live', cat: 'cli', desc: { es: 'Herramienta de línea de comandos para automatizar tareas repetitivas del día a día.', en: 'Command-line tool to automate the daily repetitive busywork.' }, tags: ['Python', 'Click', 'Bash'], repo: 'https://github.com/DavidFB-creator', demo: null }, { id: 'p3', glyph: '◆◇', title: 'design-system', status: 'wip', cat: 'web', desc: { es: 'Sistema de diseño y biblioteca de componentes para acelerar el desarrollo de productos internos.', en: 'Design system and component library to speed up internal product work.' }, tags: ['Storybook', 'Tailwind', 'TS'], repo: 'https://github.com/DavidFB-creator', demo: null }, { id: 'p4', glyph: '∑', title: 'data-pipeline', status: 'live', cat: 'data', desc: { es: 'Pipeline ETL para ingesta y normalización de datos de fuentes heterogéneas, con dashboards en tiempo real.', en: 'ETL pipeline for ingesting and normalizing heterogeneous data, plus real-time dashboards.' }, tags: ['Python', 'PostgreSQL', 'Docker', 'Grafana'], repo: 'https://github.com/DavidFB-creator', demo: null }, { id: 'p5', glyph: '◉', title: 'api-gateway', status: 'archive', cat: 'web', desc: { es: 'API gateway con autenticación, rate-limiting y caching para microservicios internos.', en: 'API gateway with auth, rate-limiting and caching for internal microservices.' }, tags: ['Node.js', 'Redis', 'JWT'], repo: 'https://github.com/DavidFB-creator', demo: null }, { id: 'p6', glyph: '~/$', title: 'this-portfolio', status: 'live', cat: 'web', desc: { es: "Justo lo que estás viendo. Multi-page, bilingüe, con un puñado de easter eggs. Escribe 'matrix' abajo.", en: "Exactly what you're looking at. Multi-page, bilingual, with a handful of easter eggs. Type 'matrix' below." }, tags: ['React', 'CSS', 'Vanilla JS'], repo: 'https://github.com/DavidFB-creator', demo: null }, ]; const PROJ_CATS = [ { id: 'all', label: { es: 'todos', en: 'all' } }, { id: 'web', label: { es: 'web', en: 'web' } }, { id: 'cli', label: { es: 'cli', en: 'cli' } }, { id: 'data', label: { es: 'data', en: 'data' } }, ]; function ProjectsPage() { const { t, lang } = window.useI18n(); const [active, setActive] = useStateO('all'); const list = PROJECTS_DATA.filter(p => active === 'all' || p.cat === active); return (
david @ fontanet : ~/projects $ ls --featured

{t('proj.title')}

{t('proj.intro')}

{PROJ_CATS.map(c => { const n = c.id === 'all' ? PROJECTS_DATA.length : PROJECTS_DATA.filter(p => p.cat === c.id).length; return ( ); })}
{list.length === 0 ? (
{t('proj.empty')}
) : list.map(p => ( ))}

{t('proj.note')}

); } /* ============================================ CONTACT ============================================ */ function ContactPage() { const { t } = window.useI18n(); const toasts = window.useToasts(); const [form, setForm] = useStateO({ name: '', email: '', subject: '', message: '' }); const [status, setStatus] = useStateO({ kind: '', text: '' }); const [sending, setSending] = useStateO(false); function onChange(e) { setForm(f => ({ ...f, [e.target.name]: e.target.value })); } function onSubmit(e) { e.preventDefault(); if (!form.name.trim() || !form.email.trim() || !form.message.trim()) { setStatus({ kind: 'err', text: t('contact.error') }); return; } setStatus({ kind: 'ok', text: '...transmitting' }); setSending(true); setTimeout(() => { setStatus({ kind: 'ok', text: '✓ ' + t('contact.sent') }); setSending(false); setForm({ name: '', email: '', subject: '', message: '' }); toasts.push(` ${t('contact.sent')}`, 'success', 6000); }, 900); } return (
david @ fontanet : ~ $ ssh contact@david

{t('contact.title')}

{t('contact.intro')}

ssh · contact@david.dev · port 22
1 client connected
welcome to david.dev{'\n'} authenticated as visitor{'\n'} // envía un mensaje y te responderé en menos de 24h
{status.text}
{t('contact.channels')}
{'{ }'} {t('contact.github')} github.com/DavidFB-creator in {t('contact.linkedin')} linkedin.com/in/david-fontanet-bujaldón @ {t('contact.email_direct')} davidfb@fonta.net

// respuesta típica < 24h

); } Object.assign(window, { SkillsPage, ProjectsPage, ContactPage });