// 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();