// 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]}

onNav('work')}> {c.hero.primary[lang]} onNav('indev')}> {c.hero.ghost[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 => ( ))}
{shown.map((p, i) => )}
); } 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 (
[ 04 ] {c.sections.devlog.label[lang]}

{c.sections.devlog.head1[lang]} {c.sections.devlog.head2[lang]}.

{lang === 'es' ? 'Todas las entradas' : 'All posts'}
{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 (
[ 05 ] {c.sections.contact.label[lang]}
{c.contactTitle.l1[lang]} {c.contactTitle.l2[lang]}
{c.contactTitle.l3[lang]}

{c.contactSub[lang]}

{lang === 'es' ? 'O escríbenos' : 'Or write us'}
hello@alanityc.dev
{sent ? (
  {lang === 'es' ? 'MENSAJE RECIBIDO. TE RESPONDEMOS PRONTO.' : 'MESSAGE RECEIVED. WE\'LL GET BACK SOON.'}
) : (
setForm({ ...form, name: e.target.value })} className={errors.name ? 'err' : ''} /> {errors.name && {errors.name}}
setForm({ ...form, email: e.target.value })} className={errors.email ? 'err' : ''} /> {errors.email && {errors.email}}