// Home page sections
function useReveal() {
React.useEffect(() => {
const io = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('in'); });
}, { threshold: 0.1 });
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
return () => io.disconnect();
}, []);
}
function SocialLinks({ size = 16 }) {
return (
<>
>
);
}
function Hero({ lang, onNav }) {
const c = window.COPY;
return (
{c.hero.tag[lang]}
[ 01 / HOME ]
Alanityc
Projects.
{c.hero.sub[lang]}
02
{lang === 'es' ? 'Publicados' : 'Released'}
03
{lang === 'es' ? 'Plataformas' : 'Platforms'}
02
{lang === 'es' ? 'En el horno' : 'Cooking'}
{c.hero.scroll[lang]}
);
}
function KeyArt({ project, variant = 0 }) {
const ref = React.useRef();
React.useEffect(() => {
const draw = () => window.drawKeyArt(ref.current, project, variant);
draw();
const ro = new ResizeObserver(draw);
if (ref.current) ro.observe(ref.current);
return () => ro.disconnect();
}, [project.id, variant]);
return ;
}
function ProjectCard({ project, lang, onOpen, size = 'default' }) {
const cardRef = React.useRef();
const handleMove = (e) => {
const r = cardRef.current.getBoundingClientRect();
cardRef.current.style.setProperty('--mx', ((e.clientX - r.left) / r.width * 100) + '%');
cardRef.current.style.setProperty('--my', ((e.clientY - r.top) / r.height * 100) + '%');
};
const cls = 'project-card reveal' + (size === 'wide' ? ' wide' : size === 'third' ? ' third' : '');
return (
onOpen(project.id)}>
{project.kind === 'game' ? (lang === 'es' ? 'Videojuego' : 'Game') : (lang === 'es' ? 'App' : 'App')}
{project.title}
{project.tagline[lang]}
{project.platforms.map(p =>
)}
· {project.year}
);
}
function WorkSection({ lang, onOpen }) {
const [filter, setFilter] = React.useState('all');
const c = window.COPY;
const projects = window.PROJECTS;
const shown = projects.filter(p => filter === 'all' ? true : (filter === 'apps' ? p.kind === 'app' : p.kind === 'game'));
// Size variation for visual rhythm
const sizes = ['default', 'default', 'wide', 'default', 'default', 'default', 'wide'];
return (
[ 02 ] {c.sections.work.label[lang]}
{c.sections.work.head1[lang]} {c.sections.work.head2[lang]}.
{['all', 'apps', 'games'].map(k => (
))}
);
}
function InDevSection({ lang }) {
const c = window.COPY;
return (
[ 03 ] {c.sections.indev.label[lang]}
{c.sections.indev.head1[lang]} {c.sections.indev.head2[lang]}.
{lang === 'es' ? 'Teasers de lo que llegará. Menos detalle, más misterio.' : 'Teasers of what\'s coming. Less detail, more mystery.'}
{window.IN_DEV.map((p) => (
{p.eta}
{lang === 'es' ? 'Próximamente' : 'Coming soon'}
{p.title}
{p.kind}
{p.desc[lang]}
{p.stage[lang]}
{p.progress}%
))}
);
}
function DevlogSection({ lang }) {
const c = window.COPY;
return (
{window.DEVLOG.map((d, i) => (
#{String(window.DEVLOG.length - i).padStart(3, '0')}
{d.title[lang]}
{d.excerpt[lang]}
{d.date}
{d.tag}
))}
{c.aiNote[lang]}
);
}
function ContactSection({ lang }) {
const c = window.COPY;
const [form, setForm] = React.useState({ name: '', email: '', message: '' });
const [errors, setErrors] = React.useState({});
const [sent, setSent] = React.useState(false);
const submit = (e) => {
e.preventDefault();
const errs = {};
if (!form.name.trim()) errs.name = lang === 'es' ? 'Requerido' : 'Required';
if (!/\S+@\S+\.\S+/.test(form.email)) errs.email = lang === 'es' ? 'Email inválido' : 'Invalid email';
if (form.message.trim().length < 10) errs.message = lang === 'es' ? 'Mín. 10 caracteres' : 'Min. 10 chars';
setErrors(errs);
if (Object.keys(errs).length === 0) {
setSent(true);
setTimeout(() => { setSent(false); setForm({ name: '', email: '', message: '' }); }, 4000);
}
};
return (
);
}
function Marquee({ lang }) {
const items = [
{ es: 'Apps de utilidad', en: 'Utility apps' },
{ es: 'Videojuegos indie', en: 'Indie games' },
{ es: 'iOS · Android', en: 'iOS · Android' },
{ es: 'Windows · Mac', en: 'Windows · Mac' },
{ es: 'Hecho en Madrid', en: 'Made in Madrid' },
{ es: 'Enviado al mundo', en: 'Shipped worldwide' }
];
const list = [...items, ...items];
return (
{list.map((it, i) => (
{it[lang]} ◆
))}
);
}
function Footer({ lang, onNav }) {
const c = window.COPY;
return (
);
}
Object.assign(window, { Hero, WorkSection, InDevSection, DevlogSection, ContactSection, Marquee, Footer, KeyArt, ProjectCard, SocialLinks, useReveal });