// Globals const { useEffect, useState, useRef } = React; // ===== components/placeholders.jsx ===== // Reusable SVG image placeholders — abstract "studio" imagery // Different palette/composition per id so each tile reads as a distinct piece of work const Placeholder = ({ id = 1, label = "image", className = "" }) => { const variants = [ // 0 – warm dunes , // 1 – architectural lines {Array.from({ length: 24 }).map((_, i) => )} {Array.from({ length: 20 }).map((_, i) => )} , // 2 – portrait silhouette , // 3 – brand mark M , // 4 – chrome curves , // 5 – grid product {Array.from({ length: 16 }).map((_, i) => )} {Array.from({ length: 12 }).map((_, i) => )} , // 6 – cool wash , // 7 – type spec Aa TYPOGRAPHIC STUDY · 01 ]; const idx = (id % variants.length + variants.length) % variants.length; return (
{variants[idx]} {/* Halftone overlay */}
); }; window.Placeholder = Placeholder; // ===== components/loading.jsx ===== const LoadingScreen = ({ onComplete }) => { const [count, setCount] = useState(0); const [wordIdx, setWordIdx] = useState(0); const [exiting, setExiting] = useState(false); const words = ["Design", "Create", "Inspire"]; useEffect(() => { const start = performance.now(); const duration = 2700; let raf; const tick = (t) => { const elapsed = t - start; const pct = Math.min(100, Math.floor(elapsed / duration * 100)); setCount(pct); if (pct < 100) { raf = requestAnimationFrame(tick); } else { setTimeout(() => { setExiting(true); setTimeout(() => onComplete && onComplete(), 500); }, 400); } }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [onComplete]); useEffect(() => { const iv = setInterval(() => setWordIdx((i) => (i + 1) % words.length), 900); return () => clearInterval(iv); }, []); return (
{/* Top-left label */}
Portfolio
'26 — Loading
{/* Center: rotating words */}
{words[wordIdx]}
{/* Bottom-right counter */}
{String(count).padStart(3, "0")}
{/* Bottom-left mini meta */}
Initialising visual
system & assets
{/* Progress bar */}
); }; window.LoadingScreen = LoadingScreen; // ===== components/navbar.jsx ===== const Navbar = ({ active = "Home", onNav }) => { const [scrolled, setScrolled] = React.useState(false); const [menuOpen, setMenuOpen] = React.useState(false); React.useEffect(() => { const fn = () => setScrolled(window.scrollY > 100); fn(); window.addEventListener('scroll', fn, { passive: true }); return () => window.removeEventListener('scroll', fn); }, []); // Lock body scroll while mobile menu is open React.useEffect(() => { document.body.style.overflow = menuOpen ? 'hidden' : ''; return () => { document.body.style.overflow = ''; }; }, [menuOpen]); const links = ["Home", "Áreas de Atuação"]; const handleNav = (label) => { setMenuOpen(false); onNav && onNav(label); }; return ( {/* Mobile menu overlay — outside