/* ============================================================================
App — client-side view switching + global ⌘K command palette
========================================================================== */
// Backdrop patterns baked in for production: hero grid on, newsletter grid off.
const TWEAKS = { newsletterPattern: false, heroPattern: true };
function App() {
const [route, setRoute] = React.useState("home");
const [post, setPost] = React.useState(null);
const tweaks = TWEAKS;
// theme, lifted so both the toggle and the palette action share it
const [theme, setTheme] = React.useState(
() => document.documentElement.getAttribute("data-theme") || "light"
);
React.useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
const toggleTheme = React.useCallback(() => setTheme((t) => (t === "dark" ? "light" : "dark")), []);
// writing tag filter, lifted so palette "Browse #tag" actions can set it
const [writingFilter, setWritingFilter] = React.useState("all");
// command palette
const [searchOpen, setSearchOpen] = React.useState(false);
const go = React.useCallback((id) => {
setRoute(id);
window.scrollTo({ top: 0, left: 0, behavior: "instant" in window ? "instant" : "auto" });
}, []);
const openPost = React.useCallback((p) => {
setPost(p);
setRoute("article");
window.scrollTo({ top: 0, left: 0 });
}, []);
const goWriting = React.useCallback((tag) => {
setWritingFilter(tag || "all");
go("writing");
}, [go]);
// global keyboard: ⌘K / Ctrl+K toggles, "/" opens when not typing
React.useEffect(() => {
const onKey = (e) => {
const k = e.key.toLowerCase();
if ((e.metaKey || e.ctrlKey) && k === "k") {
e.preventDefault();
setSearchOpen((o) => !o);
return;
}
const tag = (e.target && e.target.tagName) || "";
const typing = tag === "INPUT" || tag === "TEXTAREA" || (e.target && e.target.isContentEditable);
if (e.key === "/" && !typing && !searchOpen) {
e.preventDefault();
setSearchOpen(true);
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [searchOpen]);
// map article route back to "writing" for nav highlighting
const navRoute = route === "article" ? "writing" : route;
let view;
switch (route) {
case "writing": view = ; break;
case "article": view = ; break;
case "projects": view = ; break;
case "about": view = ; break;
case "photos": view = ; break;
case "subscribe":view = ; break;
default: view = setSearchOpen(true)} tweaks={tweaks} />;
}
return (
setSearchOpen(true)} />
{view}
setSearchOpen(false)}
go={go}
openPost={openPost}
goWriting={goWriting}
theme={theme}
toggleTheme={toggleTheme}
/>
);
}
ReactDOM.createRoot(document.getElementById("root")).render();