135 lines
5.8 KiB
JavaScript
135 lines
5.8 KiB
JavaScript
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/>);
|