/* 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 => (
{p.glyph}
// preview placeholder
{p.title}
{p.status}
{p.desc[lang]}
{p.tags.map(tg => {tg})}
))}
{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
{` . * . *
. . *
* .--. .
. /( o)\\ *
* \\\\.\\/. .
. | | .
. __|__|__ *
|________| .
. * .`}
// respuesta típica < 24h
);
}
Object.assign(window, { SkillsPage, ProjectsPage, ContactPage });