// app.jsx — root: snap-scroll, dot nav, top nav, lang toggle, FAB const { SiteCtx, useCurrentPage, I18N } = window.MM_HOOKS; const { useState, useEffect, useRef, useMemo } = React; const PAGE_IDS = ["home", "cases", "projects", "resume", "chat"]; function ProjectDetailModal({ id, onClose }) { const { lang } = React.useContext(SiteCtx); const t = I18N[lang]; const p = window.SITE_DATA.PROJECTS.find(x => x.id === id); const post = window.SITE_POSTS && window.SITE_POSTS[id]; useEffect(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [onClose]); if (!p) return null; const preview = lang === "zh" ? p.preview_zh : p.preview_en; const md = post ? (lang === "zh" ? post.zh : post.en) : null; return (
e.stopPropagation()} style={{ background: "#fff", borderRadius: 18, padding: "36px 44px", maxWidth: 760, width: "100%", marginTop: 60, marginBottom: 60, position: "relative", animation: "rise 320ms cubic-bezier(0.16, 1, 0.3, 1)" }}>
{p.category}

{lang === "zh" ? p.title_zh : p.title_en}

{lang === "zh" ? p.oneLiner_zh : p.oneLiner_en}

{lang === "zh" ? "30 秒预览" : "30-second preview"}
{preview.map((para, i) =>

{para}

)}
{md &&
{md}
}
); } function App() { const [lang, setLangState] = useState(() => { try { return localStorage.getItem("mm-lang") || "en"; } catch (e) { return "en"; } }); const setLang = (l) => { setLangState(l); try { localStorage.setItem("mm-lang", l); } catch (e) {} }; const [openProject, setOpenProject] = useState(null); const [scrolled, setScrolled] = useState(false); const rootRef = useRef(null); const pageIds = useMemo(() => PAGE_IDS, []); const currentPage = useCurrentPage(rootRef, pageIds); useEffect(() => { const root = rootRef.current; if (!root) return; const onScroll = () => setScrolled(root.scrollTop > 40); onScroll(); root.addEventListener("scroll", onScroll, { passive: true }); return () => root.removeEventListener("scroll", onScroll); }, []); const motion = !window.matchMedia || !window.matchMedia("(prefers-reduced-motion: reduce)").matches; const jumpTo = (id) => { const el = document.getElementById(id); if (!el || !rootRef.current) return; rootRef.current.scrollTo({ top: el.offsetTop, behavior: "smooth" }); }; const t = I18N[lang]; const navItems = [ { id: "home", label: t.nav_home }, { id: "cases", label: t.nav_cases }, { id: "projects", label: t.nav_projects }, { id: "resume", label: t.nav_resume }, { id: "chat", label: t.nav_chat }, ]; return (
{navItems.map(n => (
jumpTo(n.id)}> {n.label}
))}
setOpenProject(id)} />
{openProject && setOpenProject(null)} />}
); } ReactDOM.createRoot(document.getElementById("root")).render();