// app.jsx — root + routing + tweaks wiring const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": ["#22d3c4", "#050505", "#f4f4f5"], "markaloudTreatment": "spotlight", "motion": "lively", "density": "regular", "displayFont": "Manrope", "showGrain": true }/*EDITMODE-END*/; const PALETTES = { "Lab teal": ["#22d3c4", "#050505", "#f4f4f5"], "Phosphor": ["#5eead4", "#050608", "#f4f4f5"], "Lab green": ["#7cfc4d", "#050505", "#f4f4f5"], "Emerald": ["#34d399", "#070908", "#f4f4f5"], "Acid lime": ["#d4ff3a", "#060604", "#f4f4f5"], }; const TREATMENT_LABELS = { spotlight: "Spotlight", split: "Split", manifesto: "Manifesto", }; function hexToRgb(hex) { const m = hex.replace("#", ""); const v = parseInt(m.length === 3 ? m.split("").map(c => c + c).join("") : m, 16); return [(v >> 16) & 255, (v >> 8) & 255, v & 255]; } function rgba(hex, a) { const [r, g, b] = hexToRgb(hex); return `rgba(${r}, ${g}, ${b}, ${a})`; } function hexToHue(hex) { const [r, g, b] = hexToRgb(hex).map(v => v / 255); const max = Math.max(r, g, b); const min = Math.min(r, g, b); if (max === min) return 0; const d = max - min; let h; switch (max) { case r: h = ((g - b) / d + (g < b ? 6 : 0)); break; case g: h = ((b - r) / d + 2); break; default: h = ((r - g) / d + 4); } return (h / 6) * 360; } // Brand teal sampled from the source logo PNG. const SOURCE_LOGO_HUE = 175; function applyPalette(palette) { const [accent, bg] = palette; const root = document.documentElement; root.style.setProperty("--accent", accent); root.style.setProperty("--accent-glow", rgba(accent, 0.45)); root.style.setProperty("--accent-soft", rgba(accent, 0.12)); // Pick ink color (text on accent) based on lightness const [r, g, b] = hexToRgb(accent); const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255; root.style.setProperty("--accent-ink", lum > 0.6 ? "#042320" : "#042320"); root.style.setProperty("--bg", bg); // Shift the logo to match the palette: rotate from brand teal hue (~175°) to // the accent hue. White text and black background are unaffected by hue-rotate. let delta = hexToHue(accent) - SOURCE_LOGO_HUE; if (delta > 180) delta -= 360; if (delta < -180) delta += 360; root.style.setProperty("--logo-hue-rotate", `${delta.toFixed(1)}deg`); } function applyMotion(motion) { const v = motion === "off" ? 0 : motion === "subtle" ? 0.5 : 1; document.documentElement.style.setProperty("--motion", v); document.documentElement.setAttribute("data-motion", motion); } function applyDensity(d) { const root = document.documentElement; if (d === "compact") { root.style.setProperty("--pad", "24px"); } else if (d === "comfy") { root.style.setProperty("--pad", "40px"); } else { root.style.setProperty("--pad", "32px"); } } function applyFont(font) { document.documentElement.style.setProperty("--font-display", `"${font}", "Manrope", ui-sans-serif, system-ui, sans-serif`); if (font !== "Manrope") { document.documentElement.style.setProperty("--font-sans", `"${font}", "Manrope", ui-sans-serif, system-ui, sans-serif`); } else { document.documentElement.style.setProperty("--font-sans", `"Manrope", ui-sans-serif, system-ui, system-ui, sans-serif`); } } function useRoute() { const KNOWN = ["home", "markaloud", "work", "about", "contact"]; const parse = () => { // Prefer real pathname routing when the URL has a real path const path = window.location.pathname.replace(/^\/|\/$/g, "").toLowerCase(); if (path && KNOWN.includes(path)) return path; // Fallback: hash routing (works for file://, local previews, etc.) const h = window.location.hash || ""; const p = h.replace(/^#\/?/, "").split("/")[0].toLowerCase(); if (p && KNOWN.includes(p)) return p; return "home"; }; const [route, setRoute] = React.useState(parse); React.useEffect(() => { const onChange = () => { setRoute(parse()); window.scrollTo({ top: 0, behavior: "instant" }); }; window.addEventListener("hashchange", onChange); window.addEventListener("popstate", onChange); return () => { window.removeEventListener("hashchange", onChange); window.removeEventListener("popstate", onChange); }; }, []); const go = (r) => { // Use pushState for real URLs so each route has its own indexable URL const target = r === "home" ? "/" : "/" + r; try { // History API works on http/https; falls back to hash on file:// if (window.location.protocol === "file:") { window.location.hash = r === "home" ? "" : "#/" + r; } else { if (window.location.pathname !== target) { window.history.pushState({}, "", target); } setRoute(r); window.scrollTo({ top: 0, behavior: "instant" }); } } catch { window.location.hash = r === "home" ? "" : "#/" + r; } }; return [route, go]; } function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [route, go] = useRoute(); React.useEffect(() => { applyPalette(t.palette); }, [t.palette]); React.useEffect(() => { applyMotion(t.motion); }, [t.motion]); React.useEffect(() => { applyDensity(t.density); }, [t.density]); React.useEffect(() => { applyFont(t.displayFont); }, [t.displayFont]); // SEO: keep