// page-projects.jsx — live demo gallery; click → open in new tab const { SiteCtx: SiteCtx_P, useReveal: useReveal_P, I18N: I18N_P } = window.MM_HOOKS; function ProjectsPage() { const { lang } = React.useContext(SiteCtx_P); const t = I18N_P[lang]; const demos = window.SITE_DATA.DEMOS; const [hRef, hShown] = useReveal_P({ threshold: 0.1 }); return (
{t.projects_eyebrow}

{t.projects_title}

{t.projects_sub}

{demos.map((d, i) => )}
[ {demos.filter(d => !d.placeholder).length} live · {demos.filter(d => d.placeholder).length} wip ]
); } function DemoCard({ d, i }) { const { lang } = React.useContext(SiteCtx_P); const [ref, shown] = useReveal_P({ threshold: 0.1 }); const [hovered, setHovered] = React.useState(false); const [loaded, setLoaded] = React.useState(false); const isLive = !d.placeholder && d.url && (d.url.startsWith("http") || d.url.endsWith(".html")); const initials = (d.title_en || "X").split(/\s+/).slice(0, 2).map(w => w[0]).join("").toUpperCase(); const Tag = isLive ? "a" : "div"; const linkProps = isLive ? { href: d.url, target: "_blank", rel: "noopener noreferrer" } : {}; return ( setHovered(true)} onMouseLeave={() => setHovered(false)} >
{isLive ? d.url.replace(/^https?:\/\//, "").replace(/\/$/, "").slice(0, 40) : "—— wip ——"} {isLive ? "live" : "soon"}
{isLive ? ( <> {!loaded &&
}