function NewsletterForm() {
const [email, setEmail] = React.useState('');
const [done, setDone] = React.useState(false);
const [err, setErr] = React.useState('');
function submit(e) {
e.preventDefault();
if (!email.includes('@')) { setErr('Valid email required'); return; }
window.UmbraDB?.saveSubscriber?.(email, 'homepage');
setDone(true);
}
if (done) return (
You're in the shadow now.
We'll be in touch — sparingly.
);
return (
);
}
function Nav({ cartCount, onLogoClick, onNavigate, page, onCartClick, onJoinClick }) {
const links = [
{ id: 'home', label: 'HOME' },
{ id: 'shop', label: 'SHOP' },
{ id: 'about', label: 'ABOUT' },
{ id: 'ritual', label: 'RITUAL' },
];
return (
{/* Logo + wordmark */}
e.currentTarget.style.display = 'none'}
/>
UMBRA
{/* Nav links */}
{links.map(l => {
const active = page === l.id || (l.id === 'home' && page === 'detail');
return (
onNavigate(l.id)} className="link-draw" style={{
background: 'none', border: 'none', padding: 0,
fontFamily: "'Montserrat', sans-serif", fontSize: 12,
letterSpacing: '0.24em', cursor: 'crosshair',
color: active ? '#ffffff' : 'rgba(255,255,255,0.35)',
paddingBottom: 2,
transition: 'color 0.2s',
}}
onMouseEnter={e => { if (!active) e.currentTarget.style.color = 'rgba(255,255,255,0.85)'; }}
onMouseLeave={e => { if (!active) e.currentTarget.style.color = 'rgba(255,255,255,0.35)'; }}
>{l.label}
);
})}
{/* Cart + Join */}
{ e.currentTarget.style.background = '#ffffff'; e.currentTarget.style.color = '#000000'; e.currentTarget.style.borderColor = '#ffffff'; }}
onMouseLeave={e => { e.currentTarget.style.background = 'none'; e.currentTarget.style.color = 'rgba(255,255,255,0.6)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.3)'; }}
>JOIN
0 ? '#ffffff' : 'rgba(255,255,255,0.32)',
transition: 'color 0.25s', padding: '4px 0',
}}
onMouseEnter={e => e.currentTarget.style.color = '#ffffff'}
onMouseLeave={e => { if (cartCount === 0) e.currentTarget.style.color = 'rgba(255,255,255,0.32)'; }}
>
CART{cartCount > 0 && (
{cartCount}
)}
);
}
function ProductRow({ coffee, onSelect, density }) {
const [hovered, setHovered] = React.useState(false);
const compact = density === 'compact';
return (
onSelect(coffee)}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{
borderTop: '1px solid rgba(255,255,255,0.1)',
padding: compact ? '26px 48px' : '38px 48px',
cursor: 'crosshair',
background: hovered ? '#ffffff' : 'transparent',
transition: 'background 0.45s cubic-bezier(0.25,0.46,0.45,0.94)',
display: 'grid',
gridTemplateColumns: '56px 1fr auto',
alignItems: 'center', gap: '32px',
}}
>
{coffee.index}
{coffee.name}
{coffee.origin} · {coffee.process}
{coffee.altitude.toLocaleString()}m
altitude
→
);
}
function HomePage({ coffees, onSelect, tweaks }) {
const [heroVisible, setHeroVisible] = React.useState(false);
const [manifestoVisible, setManifestoVisible] = React.useState(false);
const [mouse, setMouse] = React.useState({ x: 0, y: 0 });
const manifestoRef = React.useRef(null);
React.useEffect(() => {
const t = setTimeout(() => setHeroVisible(true), 80);
const obs = new IntersectionObserver(
([e]) => { if (e.isIntersecting) setManifestoVisible(true); },
{ threshold: 0.25 }
);
if (manifestoRef.current) obs.observe(manifestoRef.current);
return () => { clearTimeout(t); obs.disconnect(); };
}, []);
function onMouseMove(e) {
setMouse({
x: (e.clientX / window.innerWidth - 0.5) * 24,
y: (e.clientY / window.innerHeight - 0.5) * 12,
});
}
const trans = 'opacity 1.1s ease, transform 1.1s cubic-bezier(0.16, 1, 0.3, 1)';
return (
{/* ── HERO ── */}
{/* Background roastery image — grayscale + parallax */}
{/* Dark vignette overlay — deepens toward edges */}
{/* Bottom fade into black */}
{/* Main UMBRA */}
UMBRA
{/* Subtitle */}
Specialty Coffee Roasters · Makassar, Indonesia · Est. 2021
{/* Thin divider line — fades in */}
{/* Scroll cue */}
{/* ── MANIFESTO ── */}
Manifesto
{[
'"Umbra: from Latin, the darkest shadow.',
'The shadow cast by a sun-grown cherry.',
'The shadow inside the cup.',
'We source where altitude writes the flavour —',
'and roast to the edge of darkness."',
].map((line, i) => (
{line}
))}
{/* ── PRODUCT GRID ── */}
Current Harvest — {new Date().getFullYear()}
4 Origins
{coffees.map((c, i) => (
))}
{/* ── NEWSLETTER ── */}
The Dispatch
Stay inthe shadow.
New arrivals, roast dates, and the occasional manifesto. Never frequent — always worth opening.
{/* ── FOOTER ── */}
);
}
Object.assign(window, { Nav, ProductRow, HomePage });