// Components.jsx — shared primitives for 1snob umbrella site
const { useState, useEffect, useMemo } = React;
// ---------- Copy dictionary (EN / FR) ----------
const COPY = {
nav: {
home: { en: 'Index', fr: 'Index' },
work: { en: 'Work', fr: 'Travail' },
about: { en: 'About', fr: 'À propos' },
contact: { en: 'Contact', fr: 'Contact' },
},
footer: {
cta: { en: 'Hire a snob.', fr: 'Engagez un snob.' },
note: { en: 'Based in Montréal. Working worldwide.', fr: 'Basé à Montréal. Partout, autrement.' },
email: { en: 'hi@1snob.com', fr: 'hi@1snob.com' },
rights: { en: '© 2025 1snob. All rights reserved.', fr: '© 2025 1snob. Tous droits réservés.' },
est: { en: 'Est. 2024 — Montréal', fr: 'Depuis 2024 — Montréal' },
},
};
// ---------- Logo ----------
const Logo = ({ size = 32, color = 'var(--ink-1)', onClick }) => (
SNOB
);
// ---------- Rainbow bar ----------
const RainbowBar = ({ thick = false }) => (
);
// ---------- Lang toggle ----------
const LangToggle = ({ lang, setLang }) => (
);
// ---------- Header ----------
const Header = ({ route, go, lang, setLang }) => {
const L = (k) => COPY.nav[k][lang];
const item = (id) => (
{ e.preventDefault(); go(id); }}
>{L(id)}
);
return (
);
};
// ---------- Footer ----------
const Footer = ({ lang, go }) => {
const F = COPY.footer;
return (
);
};
// ---------- Button ----------
const Btn = ({ children, href, onClick, ghost, arrow = true }) => {
const Comp = href ? 'a' : 'button';
return (
{children}{arrow && →}
);
};
// ---------- Section wrapper ----------
const Section = ({ eyebrow, children, dark, noBorder, style }) => (
{eyebrow && (
{eyebrow}
)}
{children}
);
// ---------- useHashRoute ----------
const useHashRoute = () => {
const parse = () => (location.hash.replace(/^#\/?/, '') || 'home').split('/')[0] || 'home';
const [route, setRoute] = useState(parse());
useEffect(() => {
const on = () => setRoute(parse());
window.addEventListener('hashchange', on);
return () => window.removeEventListener('hashchange', on);
}, []);
const go = (id) => {
location.hash = '#/' + (id === 'home' ? '' : id);
window.scrollTo({ top: 0, behavior: 'instant' });
};
return [route, go];
};
// ---------- Export to window ----------
Object.assign(window, { COPY, Logo, RainbowBar, LangToggle, Header, Footer, Btn, Section, useHashRoute });