// app.jsx — root, routing, tweaks const { useState: useStateA, useEffect: useEffectA } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": "prism", "heroAnim": "dispersion", "typeset": "editorial", "density": "regular" }/*EDITMODE-END*/; // palettes — each sets the prism accent colors const PALETTES = { prism: { cyan: "#00d4ff", blue: "#4a6cff", violet: "#8a4dff", magenta: "#ff2bd8", pink: "#ff5fa6", coral: "#ff7a4d", amber: "#ffb347", yellow: "#ffd84d" }, cool: { cyan: "#5ee9ff", blue: "#3a7bff", violet: "#7c3aed", magenta: "#3a7bff", pink: "#5ee9ff", coral: "#3a7bff", amber: "#7c3aed", yellow: "#5ee9ff" }, sunset: { cyan: "#ff8a3d", blue: "#ff4561", violet: "#ff2d6b", magenta: "#ff5757", pink: "#ffa66e", coral: "#ff6a4d", amber: "#ffd178", yellow: "#ffe17a" }, mono: { cyan: "#e8e6f0", blue: "#c0bdd0", violet: "#a09db0", magenta: "#e8e6f0", pink: "#e8e6f0", coral: "#a09db0", amber: "#e8e6f0", yellow: "#e8e6f0" }, }; const ROUTES = ["home","work","about","pricing","contact","order","confirmation","faq","blog"]; function routeFromPath(pathname) { const p = pathname.replace(/^\/+/, "").replace(/\/+$/, ""); if (p === "") return "home"; return ROUTES.includes(p) ? p : null; } function routeFromHash(hash) { const h = (hash || "").replace("#/", ""); return ROUTES.includes(h) ? h : null; } const SITE_URL = "https://visimatic.net"; const SEO_META = { home: { title: "Visimatic - Custom Logo Animation Studio", description: "Bespoke logo animations hand-keyed in After Effects. No templates. A one-person motion studio turning static logos into animated brand assets in 2-5 days.", }, work: { title: "Work - Logo Animation Portfolio | Visimatic", description: "Browse animated logo projects built from scratch in After Effects - minimal, kinetic, and cinematic styles for brands that want motion done right.", }, about: { title: "About - Meet the Animator | Visimatic", description: "Visimatic is a solo motion designer specializing in custom logo animation. Every frame hand-keyed in After Effects - no templates, no presets.", }, pricing: { title: "Pricing - Logo Animation Packages | Visimatic", description: "Logo animation packages from $150 to $300. Transparent pricing, fast delivery, and add-ons like sound effects, perfect loops, and rush turnaround.", }, contact: { title: "Contact - Start Your Logo Animation | Visimatic", description: "Get in touch to discuss your logo animation project. Fast replies, custom quotes, and a straightforward process from brief to delivery.", }, order: { title: "Order Your Logo Animation | Visimatic", description: "Start your custom logo animation order - pick a style, choose a package, and check out securely in minutes.", }, confirmation: { title: "Order Confirmed | Visimatic", description: "Your logo animation order is confirmed. Upload your logo file to begin production.", }, faq: { title: "FAQ - Logo Animation Questions Answered | Visimatic", description: "Answers to common questions about custom logo animation: pricing, delivery time, file formats, revisions, and the After Effects process.", }, blog: { title: "Blog - Logo Animation Insights | Visimatic", description: "Notes on logo animation: file formats explained, what separates premium animated logos from cheap ones, and tips for YouTube intro animations.", }, }; function applySeoMeta(route) { const meta = SEO_META[route] || SEO_META.home; document.title = meta.title; let metaDesc = document.querySelector('meta[name="description"]'); if (!metaDesc) { metaDesc = document.createElement("meta"); metaDesc.setAttribute("name", "description"); document.head.appendChild(metaDesc); } metaDesc.setAttribute("content", meta.description); let canonical = document.querySelector('link[rel="canonical"]'); if (!canonical) { canonical = document.createElement("link"); canonical.setAttribute("rel", "canonical"); document.head.appendChild(canonical); } canonical.setAttribute("href", `${SITE_URL}${route === "home" ? "/" : "/" + route}`); } function App() { const [route, setRoute] = useStateA(() => { const fromPath = routeFromPath(window.location.pathname); const fromHash = routeFromHash(window.location.hash); // honor an old-style shared hash link even when pathname is just "/" if (fromHash && (window.location.pathname === "/" || window.location.pathname === "")) return fromHash; if (fromPath) return fromPath; if (fromHash) return fromHash; return "home"; }); const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); // sync URL path for refresh-stable, indexable nav + per-route SEO metadata useEffectA(() => { const targetPath = route === "home" ? "/" : `/${route}`; if (window.location.pathname !== targetPath) { window.history.pushState(null, "", targetPath + window.location.search); } window.scrollTo({ top: 0, behavior: "instant" }); applySeoMeta(route); }, [route]); // browser back/forward — pushState alone doesn't trigger React re-renders useEffectA(() => { const onPopState = () => { const fromPath = routeFromPath(window.location.pathname); setRoute(fromPath || "home"); }; window.addEventListener("popstate", onPopState); return () => window.removeEventListener("popstate", onPopState); }, []); // apply tweaks → CSS vars useEffectA(() => { const p = PALETTES[t.palette] || PALETTES.prism; const r = document.documentElement; r.style.setProperty("--p-cyan", p.cyan); r.style.setProperty("--p-blue", p.blue); r.style.setProperty("--p-violet", p.violet); r.style.setProperty("--p-magenta", p.magenta); r.style.setProperty("--p-pink", p.pink); r.style.setProperty("--p-coral", p.coral); r.style.setProperty("--p-amber", p.amber); r.style.setProperty("--p-yellow", p.yellow); r.setAttribute("data-density", t.density); r.setAttribute("data-typeset", t.typeset); }, [t.palette, t.density, t.typeset]); return ( <>