wilddragon-site/app.jsx

136 lines
5.8 KiB
React
Raw Permalink Normal View History

const TWEAKS_DEFAULTS = /*EDITMODE-BEGIN*/{
"accentPreset": "blue",
"accent": "#3b82f6",
"accentLight": "#60a5fa",
"primary": "#0a0a0a",
"headingFont": "Inter",
"monoFont": "JetBrains Mono",
"imageTreatment": "default",
"density": "comfortable",
"marquee": true,
"cursorGlow": true,
"showPartners": true,
"showStats": true,
"showServices": true,
"showTech": true,
"showOnSet": true,
"ctaLabel": "View Projects"
}/*EDITMODE-END*/;
const FONT_OPTIONS = ["Inter","Manrope","Space Grotesk","DM Sans","Outfit","Plus Jakarta Sans","IBM Plex Sans","Geist","Fraunces"];
const MONO_OPTIONS = ["JetBrains Mono","IBM Plex Mono","Space Mono","Roboto Mono","Fira Code","Geist Mono"];
const ACCENT_PRESETS = {
blue: { accent:"#3b82f6", accentLight:"#60a5fa", label:"Blue" },
ember: { accent:"#e25822", accentLight:"#f3895a", label:"Ember" },
amber: { accent:"#f59e0b", accentLight:"#fbbf24", label:"Amber" },
ruby: { accent:"#dc2626", accentLight:"#f87171", label:"Ruby" },
mono: { accent:"#e5e5e5", accentLight:"#ffffff", label:"Mono" },
};
function loadFont(family) {
const id = "wd-font-" + family.replace(/\s/g,"-");
if (document.getElementById(id)) return;
const l = document.createElement("link");
l.id = id; l.rel = "stylesheet";
l.href = `https://fonts.googleapis.com/css2?family=${family.replace(/\s/g,"+")}:wght@300;400;500;600;700&display=swap`;
document.head.appendChild(l);
}
function App() {
const [tweaks, setTweak] = useTweaks(TWEAKS_DEFAULTS);
const [openSlug, setOpenSlug] = React.useState(() => {
const m = location.hash.match(/#\/projects\/([\w-]+)/);
return m ? m[1] : null;
});
React.useEffect(() => {
if (openSlug) location.hash = `#/projects/${openSlug}`;
else if (location.hash.startsWith("#/projects/")) location.hash = "";
}, [openSlug]);
React.useEffect(() => {
loadFont(tweaks.headingFont);
loadFont(tweaks.monoFont);
const r = document.documentElement.style;
r.setProperty("--color-accent", tweaks.accent);
r.setProperty("--color-accent-light", tweaks.accentLight);
r.setProperty("--color-primary", tweaks.primary);
r.setProperty("--font-inter", `"${tweaks.headingFont}"`);
r.setProperty("--font-mono-stack", `"${tweaks.monoFont}"`);
}, [tweaks]);
// Image treatment via class on body
React.useEffect(() => {
document.body.dataset.imgTreat = tweaks.imageTreatment;
document.body.dataset.density = tweaks.density;
}, [tweaks.imageTreatment, tweaks.density]);
const onAccentPreset = (key) => {
const p = ACCENT_PRESETS[key];
if (!p) return;
setTweak({ accentPreset: key, accent: p.accent, accentLight: p.accentLight });
};
return (
<React.Fragment>
<Curtain/>
{tweaks.cursorGlow && <CursorDot/>}
<Navigation/>
<Hero ctaLabel={tweaks.ctaLabel}/>
<Clients marquee={tweaks.marquee}/>
{tweaks.showPartners && <Partners/>}
<About/>
{tweaks.showStats && <Stats/>}
{tweaks.showServices && <Services/>}
<Projects onOpen={setOpenSlug}/>
{tweaks.showTech && <TechStack/>}
{tweaks.showOnSet && <OnSet/>}
<Contact/>
<Footer/>
{openSlug && <ProjectOverlay slug={openSlug} onClose={()=>setOpenSlug(null)} onOpen={setOpenSlug}/>}
<TweaksPanel title="Tweaks">
<TweakSection title="Accent">
<TweakRadio label="Preset" value={tweaks.accentPreset}
options={Object.entries(ACCENT_PRESETS).map(([k,v]) => ({value:k, label:v.label}))}
onChange={onAccentPreset} />
<TweakColor label="Accent" value={tweaks.accent} onChange={v => setTweak("accent", v)} />
<TweakColor label="Accent (hover)" value={tweaks.accentLight} onChange={v => setTweak("accentLight", v)} />
<TweakColor label="Background" value={tweaks.primary} onChange={v => setTweak("primary", v)} />
</TweakSection>
<TweakSection title="Typography">
<TweakSelect label="Heading / Body" value={tweaks.headingFont} options={FONT_OPTIONS} onChange={v => setTweak("headingFont", v)} />
<TweakSelect label="Mono" value={tweaks.monoFont} options={MONO_OPTIONS} onChange={v => setTweak("monoFont", v)} />
</TweakSection>
<TweakSection title="Layout">
<TweakToggle label="Logo marquee" value={tweaks.marquee} onChange={v => setTweak("marquee", v)} />
<TweakToggle label="Cursor glow" value={tweaks.cursorGlow} onChange={v => setTweak("cursorGlow", v)} />
<TweakRadio label="Density" value={tweaks.density}
options={[{value:"comfortable",label:"Comfy"},{value:"compact",label:"Compact"}]}
onChange={v => setTweak("density", v)} />
</TweakSection>
<TweakSection title="Imagery">
<TweakRadio label="Treatment" value={tweaks.imageTreatment}
options={[{value:"default",label:"Color"},{value:"grayscale",label:"B&W"},{value:"tint",label:"Tint"}]}
onChange={v => setTweak("imageTreatment", v)} />
</TweakSection>
<TweakSection title="Sections">
<TweakToggle label="Partners strip" value={tweaks.showPartners} onChange={v => setTweak("showPartners", v)} />
<TweakToggle label="Stats" value={tweaks.showStats} onChange={v => setTweak("showStats", v)} />
<TweakToggle label="Process" value={tweaks.showServices} onChange={v => setTweak("showServices", v)} />
<TweakToggle label="Tech stack" value={tweaks.showTech} onChange={v => setTweak("showTech", v)} />
<TweakToggle label="On Set" value={tweaks.showOnSet} onChange={v => setTweak("showOnSet", v)} />
</TweakSection>
<TweakSection title="Copy">
<TweakText label="Hero CTA" value={tweaks.ctaLabel} onChange={v => setTweak("ctaLabel", v)} />
</TweakSection>
</TweaksPanel>
</React.Fragment>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);