/* ================================================================= Pages + history router Routes: / home /videos category (film) /videos/tides work detail /apps /webtools category /about /contact ================================================================= */ const { useState: pUS, useEffect: pUE, useRef: pUR } = React; /* ---------------- router ---------------- */ function useRoute() { const parse = () => { const raw = location.pathname.replace(/\/$/, ''); const segs = raw.split('/').filter(Boolean); if (!segs.length) return { view: 'home', key: 'home' }; if (segs[0] === 'about') return { view: 'about', key: 'about' }; if (segs[0] === 'contact') return { view: 'contact', key: 'contact' }; const cat = CAT_BY_ROUTE[segs[0]]; if (!cat) return { view: 'home', key: 'home' }; if (segs[1]) return { view: 'work', cat, slug: segs[1], key: `work:${segs[0]}/${segs[1]}` }; return { view: 'category', cat, key: `cat:${segs[0]}` }; }; const [r, setR] = pUS(parse); pUE(() => { const f = () => setR(parse()); window.addEventListener('popstate', f); return () => window.removeEventListener('popstate', f); }, []); return r; } /* label shown on the transition curtain */ function routeLabel(route, lang) { if (!route) return ''; if (route.view === 'home') return 'INDEX'; if (route.view === 'about') return L(window.PORTFOLIO.ui.about, lang).toUpperCase(); if (route.view === 'contact') return L(window.PORTFOLIO.ui.contact, lang).toUpperCase(); const cm = catMeta(route.cat); if (route.view === 'category') return `${cm.no} · ${L(cm.label, lang).toUpperCase()}`; if (route.view === 'work') { const w = workBySlug(route.cat, route.slug); return w ? w.title.toUpperCase() : ''; } return ''; } /* ---------------- footer (global) ---------------- */ function Footer({ data, lang }) { return ( ); } /* ================= HOME ================= */ function HomePage({ data, lang, topDirection }) { const scrollToIndex = () => { const el = document.querySelector('.home-index'); if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 60, behavior: 'smooth' }); }; return (
/ {String( data.works.film.length + data.works.app.length + data.works.web.length ).padStart(2, '0')} {L(data.ui.count, lang)}
{data.categories.map((c, i) => { const items = data.works[c.id]; const sample = items.slice(0, 4).map((w) => w.title).join(' · '); return ( {c.no}
{sample}
{String(items.length).padStart(2, '0')}
); })}
/
); } /* ================= CATEGORY ================= */ function CategoryPage({ data, lang, cat }) { const cm = catMeta(cat); const items = data.works[cat]; return (
{cm.no}

{String(items.length).padStart(2, '0')} {L(data.ui.count, lang)}
{items.map((w, i) => ( ))}
); } /* ================= WORK DETAIL ================= */ function WorkPage({ data, lang, cat, slug }) { const cm = catMeta(cat); const items = data.works[cat]; const idx = items.findIndex((w) => w.slug === slug); const work = items[idx]; pUE(() => { window.scrollTo(0, 0); }, [slug]); if (!work) { return (

404 · NOT FOUND

← {L(cm.label, lang)}
); } const prev = items[(idx - 1 + items.length) % items.length]; const next = items[(idx + 1) % items.length]; const num = String(idx + 1).padStart(2, '0'); const total = String(items.length).padStart(2, '0'); return (
{cm.no} ·
{num} / {total}

{work.title}

{work.year} · ·
{cat === 'film' ? : }
/ {work.links && work.links.length > 0 && (
{work.links.map((lk) => ( {lk.label} ))}
)}
{work.year}
{work.platform &&
Platform
{work.platform}
}
{(work.tools || []).join(' · ')}
); } /* ================= ABOUT ================= */ function AboutPage({ data, lang }) { return (
/

{data.meta.nameEn} {data.meta.nameJp}

{data.facts.map((f, i) => (
))}
); } /* ================= CONTACT ================= */ function ContactPage({ data, lang }) { return (
/ {data.contact.email}
{data.contact.socials.map((s) => ( {s.label} {s.handle} ))}
); } /* ================= dispatcher ================= */ function Page({ route, data, lang, topDirection }) { switch (route.view) { case 'category': return ; case 'work': return ; case 'about': return ; case 'contact': return ; default: return ; } } Object.assign(window, { useRoute, routeLabel, Page, Footer, HomePage, CategoryPage, WorkPage, AboutPage, ContactPage, });