function WebsiteHero() { const label = { fontFamily: 'Helvetica, Arial, sans-serif', fontWeight: 400, fontSize: 12, letterSpacing: '.12em', textTransform: 'uppercase', color: '#5A5A57', margin: 0, lineHeight: 1.8, }; const [hov, setHov] = React.useState(-1); const { isMobile, isPhone } = useViewport(); const roles = ['Art Direction', 'Creative Marketing Direction', 'Creative AI', 'Branding Strategy']; /* ── mouse-glow effect ── */ const glowRef = React.useRef(null); const mousePos = React.useRef({ x: 0.5, y: 0.5 }); const targetPos = React.useRef({ x: 0.5, y: 0.5 }); React.useEffect(() => { const section = sectionRef.current; const glow = glowRef.current; if (!section || !glow || isMobile) return; const lerp = (a, b, t) => a + (b - a) * t; const onMove = (e) => { const r = section.getBoundingClientRect(); targetPos.current = { x: (e.clientX - r.left) / r.width, y: (e.clientY - r.top) / r.height, }; }; section.addEventListener('mousemove', onMove); let raf; const tick = () => { const m = mousePos.current; const t = targetPos.current; m.x = lerp(m.x, t.x, 0.045); m.y = lerp(m.y, t.y, 0.045); const px = (m.x * 100).toFixed(2); const py = (m.y * 100).toFixed(2); glow.style.background = [ `radial-gradient(ellipse 65% 55% at ${px}% ${py}%, rgba(210,162,96,0.09) 0%, rgba(180,130,70,0.04) 40%, transparent 72%)`, `radial-gradient(circle 22% at ${px}% ${py}%, rgba(240,210,150,0.05) 0%, transparent 60%)`, ].join(', '); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => { section.removeEventListener('mousemove', onMove); cancelAnimationFrame(raf); }; }, [isMobile]); const NAME = 'LUCAS QUEIROZ'; const items = NAME.split(''); const lineRef = React.useRef(null); const innersRef = React.useRef([]); const sectionRef = React.useRef(null); const [nameSize, setNameSize] = React.useState(null); /* ── fit name to 100% of available width, single line ── */ React.useEffect(() => { const el = lineRef.current; if (!el) return; function fit() { const parent = el.parentElement; if (!parent) return; const avail = parent.clientWidth; if (!avail) return; // measure imperatively at a known size, then commit via state so a // later re-render (e.g. role hover) can't reset fontSize to the clamp el.style.fontSize = '120px'; let w = el.scrollWidth; if (!w) return; let size = 120 * avail / w; el.style.fontSize = size + 'px'; w = el.scrollWidth; size = size * avail / w; setNameSize(size + 'px'); } fit(); const ro = new ResizeObserver(fit); ro.observe(el.parentElement); if (document.fonts && document.fonts.ready) document.fonts.ready.then(fit); [120, 400, 900, 1600].forEach((t) => setTimeout(fit, t)); window.addEventListener('resize', fit); return () => { ro.disconnect(); window.removeEventListener('resize', fit); }; }, []); /* ── entrance rise + scroll-driven disappear (letters sliding up & out) ── */ React.useEffect(() => { const inners = innersRef.current.filter(Boolean); if (!inners.length) return; const EASE = 'cubic-bezier(.19,1,.22,1)'; inners.forEach((s, i) => { s.style.transform = 'translateY(112%)'; s.style.transition = 'transform 1s ' + EASE; s.style.transitionDelay = (0.15 + i * 0.03) + 's'; }); requestAnimationFrame(() => requestAnimationFrame(() => { inners.forEach((s) => { s.style.transform = 'translateY(0)'; }); })); const section = sectionRef.current; let entranceDone = false, raf = 0; const dt = setTimeout(() => { entranceDone = true; inners.forEach((s) => { s.style.transition = 'transform .12s linear, opacity .12s linear'; s.style.transitionDelay = '0s'; }); }, 1500); const N = inners.length; const STAG = 0.55; // rect-driven (works regardless of scroll container): as the hero // scrolls up, letters slide up & fade out, staggered left-to-right function loop() { if (entranceDone && section) { const r = section.getBoundingClientRect(); const vh = window.innerHeight || 1; const progress = Math.min(Math.max(-r.top / (vh * 0.72), 0), 1); for (let i = 0; i < N; i++) { const frac = N > 1 ? i / (N - 1) : 0; let cp = (progress - frac * STAG) / (1 - STAG); cp = Math.min(Math.max(cp, 0), 1); inners[i].style.transform = 'translateY(' + (-cp * 1.15).toFixed(3) + 'em)'; inners[i].style.opacity = (1 - cp).toFixed(3); } } raf = requestAnimationFrame(loop); } raf = requestAnimationFrame(loop); return () => { clearTimeout(dt); cancelAnimationFrame(raf); }; }, []); const maskStyle = { display: 'inline-block', overflow: 'hidden', lineHeight: 0.92, paddingBottom: '0.16em', marginBottom: '-0.16em', verticalAlign: 'bottom', }; const innerStyle = { display: 'inline-block', willChange: 'transform, opacity' }; let k = 0; const nameNodes = items.map((ch, idx) => { if (ch === ' ') return ; const myK = k++; return ( { innersRef.current[myK] = el; }} style={innerStyle}>{ch} ); }); return ( {/* Mouse-glow layer — behind everything via z-index:-1 */} {/* Decorative slow-rotating rings */} {/* ── TOP ROW ── */} {/* ── CENTER : NAME + ROLES ── */} Portfolio — 2026 {/* Full-width single-line name */} {nameNodes} {/* Roles — generous gap below the name */} {roles.map((r, i) => ( {i > 0 && !isPhone && } {r} ))} {/* ── BOTTOM : line + contact ── */} {/* Scroll cue — centered, visible on the first screen */} {!isMobile && ( { const el = document.getElementById('portfolio'); if (el) window.scrollTo({ top: el.offsetTop, behavior: 'smooth' }); }} style={{ position: 'absolute', left: '50%', bottom: 0, transform: 'translateX(-50%)', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 9, animation: 'scrollBob 2s ease-in-out infinite', cursor: 'pointer', }} > Scroll )} lucasqueiroz@gmail.com +55 11 97551 4667 Marketing Consulting ); } Object.assign(window, { WebsiteHero });
Portfolio — 2026
Marketing Consulting