/* Shared chrome for all Padre Studios pages. * Components on window: Backdrop, Header, Footer, Modal, Eyebrow, PageShell, * SkipLink, useViewport. * * Backdrop variants: * hero — rainy street photo + rain canvas + vignette (Landing only) * quiet — flat dark surface with subtle grain (interior pages) */ /* ---------- useViewport ---------- * * Single source of truth for responsive breakpoints. Returns a stable object * that updates only when the bucket changes — avoids re-renders on every px. */ function useViewport() { const compute = () => { if (typeof window === "undefined") { return { width: 1200, isMobile: false, isTablet: false, isDesktop: true }; } const w = window.innerWidth; return { width: w, isMobile: w < 720, isTablet: w >= 720 && w < 1024, isDesktop: w >= 1024, }; }; const [vp, setVp] = React.useState(compute); React.useEffect(() => { let raf = 0; const onResize = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { const next = compute(); setVp((prev) => prev.isMobile === next.isMobile && prev.isTablet === next.isTablet && prev.isDesktop === next.isDesktop ? prev : next ); }); }; window.addEventListener("resize", onResize); return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", onResize); }; }, []); return vp; } /* ---------- SkipLink (a11y) ---------- */ const skipLinkStyles = { base: { position: "absolute", left: 16, top: -40, background: "var(--fg)", color: "var(--bg)", padding: "10px 14px", borderRadius: 8, fontFamily: "inherit", fontSize: 13, fontWeight: 500, textDecoration: "none", zIndex: 1000, transition: "top 160ms ease", }, }; function SkipLink() { return ( (e.currentTarget.style.top = "16px")} onBlur={(e) => (e.currentTarget.style.top = "-40px")} > Skip to content ); } /* ---------- Backdrop ---------- */ const backdropStyles = { wrap: { position: "absolute", inset: 0, zIndex: 0, overflow: "hidden", background: "var(--bg)", }, img: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", objectPosition: "center bottom", }, vignetteHero: { position: "absolute", inset: 0, background: "linear-gradient(180deg, color-mix(in oklch, var(--bg-deep) 55%, transparent) 0%, color-mix(in oklch, var(--bg-deep) 18%, transparent) 22%, transparent 42%, color-mix(in oklch, var(--bg-deep) 55%, transparent) 72%, color-mix(in oklch, var(--bg-deep) 95%, transparent) 100%), radial-gradient(110% 70% at 65% 38%, transparent 0%, color-mix(in oklch, var(--bg-deep) 35%, transparent) 65%, color-mix(in oklch, var(--bg-deep) 80%, transparent) 100%)", }, quiet: { position: "absolute", inset: 0, background: "radial-gradient(80% 60% at 50% 0%, color-mix(in oklch, var(--fg) 4%, transparent) 0%, transparent 60%), var(--bg-deep)", }, grain: { position: "absolute", inset: 0, opacity: 0.35, mixBlendMode: "overlay", backgroundImage: "repeating-linear-gradient(0deg, color-mix(in oklch, var(--fg) 3%, transparent) 0 1px, transparent 1px 3px)", pointerEvents: "none", }, }; function Backdrop({ variant = "hero", parallaxY = 0 }) { const { isMobile } = useViewport(); if (variant === "hero") { return (
); } return ( ); } /* ---------- Header ---------- */ const NAV_LINKS = [ { label: "Work", href: "Work.html" }, { label: "Studio", href: "Studio.html" }, { label: "Process", href: "Process.html" }, { label: "Journal", href: "Journal.html" }, { label: "Contact", href: "Contact.html" }, ]; const headerStyles = { wrap: { position: "sticky", top: 0, zIndex: 10, padding: "20px 32px 0", }, wrapMobile: { padding: "14px 16px 0" }, bar: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 24, height: 64, padding: "0 22px", borderRadius: 14, background: "color-mix(in oklch, var(--bg-deep) 30%, transparent)", border: "1px solid var(--glass-border)", backdropFilter: "blur(18px) saturate(140%)", WebkitBackdropFilter: "blur(18px) saturate(140%)", boxShadow: "0 1px 0 color-mix(in oklch, var(--fg) 10%, transparent) inset, 0 20px 40px -20px rgba(0,0,0,0.5)", }, barMobile: { height: 56, padding: "0 14px 0 18px", gap: 12 }, brand: { display: "inline-flex", alignItems: "center", color: "var(--fg)", textDecoration: "none", height: "100%", }, brandLogo: { height: 55, width: "auto", display: "block", }, brandLogoMobile: { height: 28, }, nav: { display: "flex", alignItems: "center", gap: 28 }, link: { fontSize: 13, letterSpacing: "0.02em", color: "var(--fg-dim)", textDecoration: "none", cursor: "pointer", transition: "color 160ms ease", position: "relative", padding: "8px 4px", borderRadius: 4, }, linkActive: { color: "var(--fg)" }, linkActiveDot: { position: "absolute", left: "50%", bottom: 0, transform: "translateX(-50%)", width: 4, height: 4, borderRadius: "50%", background: "var(--accent)", }, actions: { display: "flex", alignItems: "center", gap: 10 }, ghost: { fontSize: 13, color: "var(--fg-dim)", background: "transparent", border: "1px solid transparent", padding: "9px 14px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit", letterSpacing: "0.02em", }, cta: { fontSize: 13, color: "var(--bg)", background: "var(--fg)", border: "1px solid var(--fg)", padding: "9px 16px", borderRadius: 999, cursor: "pointer", fontFamily: "inherit", letterSpacing: "0.02em", fontWeight: 500, transition: "background 160ms ease, border-color 160ms ease", textDecoration: "none", display: "inline-flex", alignItems: "center", }, /* mobile menu */ burger: { width: 40, height: 40, border: "1px solid var(--glass-border)", background: "transparent", borderRadius: 999, cursor: "pointer", color: "var(--fg)", display: "inline-flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 4, padding: 0, }, burgerLine: { width: 16, height: 1.5, background: "var(--fg)", borderRadius: 1, transition: "transform 220ms ease, opacity 180ms ease", }, menuScrim: { position: "fixed", inset: 0, zIndex: 50, background: "color-mix(in oklch, var(--bg-deep) 88%, transparent)", backdropFilter: "blur(18px) saturate(140%)", WebkitBackdropFilter: "blur(18px) saturate(140%)", display: "flex", flexDirection: "column", padding: "84px 24px 32px", }, menuLink: { display: "block", padding: "16px 0", fontSize: 28, letterSpacing: "-0.015em", color: "var(--fg)", textDecoration: "none", borderBottom: "1px solid var(--line)", }, menuFoot: { marginTop: "auto", paddingTop: 24, display: "flex", flexDirection: "column", gap: 12, }, menuCta: { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 10, background: "var(--fg)", color: "var(--bg)", padding: "16px 22px", borderRadius: 999, fontSize: 14, fontWeight: 500, textDecoration: "none", }, menuMeta: { fontFamily: "ui-monospace, 'SF Mono', Menlo, Consolas, monospace", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--fg-faint)", textAlign: "center", }, }; function Header({ active }) { const { isMobile } = useViewport(); const [hoverIdx, setHoverIdx] = React.useState(-1); const [menuOpen, setMenuOpen] = React.useState(false); React.useEffect(() => { if (!menuOpen) return; const onKey = (e) => e.key === "Escape" && setMenuOpen(false); window.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [menuOpen]); React.useEffect(() => { if (!isMobile && menuOpen) setMenuOpen(false); }, [isMobile, menuOpen]); return (