const { useEffect, useRef, useState } = React; function SphereCanvas() { const mountRef = useRef(null); const [webglFailed, setWebglFailed] = useState(false); useEffect(() => { const el = mountRef.current; if (!el) return; const testCanvas = document.createElement('canvas'); const gl = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl'); if (!gl) { setWebglFailed(true); return; } const W = el.clientWidth, H = el.clientHeight; let renderer; try { renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'high-performance' }); } catch(e) { setWebglFailed(true); return; } renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(W, H); renderer.setClearColor(0x000000, 0); el.appendChild(renderer.domElement); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, W / H, 0.1, 100); camera.position.set(0, 0, 3.2); const texture = new THREE.TextureLoader().load('assets/sphere.png'); const sphere = new THREE.Mesh( new THREE.SphereGeometry(1, 128, 128), new THREE.MeshStandardMaterial({ map: texture, roughness: 0.65, metalness: 0.15 }) ); scene.add(sphere); scene.add(new THREE.AmbientLight(0xd4ede0, 1.2)); const kl = new THREE.DirectionalLight(0xffffff, 2.2); kl.position.set(3,4,3); scene.add(kl); const rl = new THREE.PointLight(0x2F5D50, 3.5, 12); rl.position.set(-3,-1,1); scene.add(rl); const fl = new THREE.PointLight(0xA8D4C8, 1.8, 10); fl.position.set(2,-2,2); scene.add(fl); let mx=0, my=0, tmx=0, tmy=0; const onMM = e => { tmx=(e.clientX/innerWidth-.5)*.6; tmy=(e.clientY/innerHeight-.5)*.4; }; window.addEventListener('mousemove', onMM); let raf; const animate = () => { raf = requestAnimationFrame(animate); mx+=(tmx-mx)*.04; my+=(tmy-my)*.04; sphere.rotation.y+=0.0028; sphere.rotation.x=my*.6; sphere.rotation.z=mx*.25; renderer.render(scene, camera); }; animate(); const onResize = () => { const w=el.clientWidth,h=el.clientHeight; renderer.setSize(w,h); camera.aspect=w/h; camera.updateProjectionMatrix(); }; window.addEventListener('resize', onResize); return () => { cancelAnimationFrame(raf); window.removeEventListener('mousemove',onMM); window.removeEventListener('resize',onResize); renderer.dispose(); if(el.contains(renderer.domElement)) el.removeChild(renderer.domElement); }; }, []); if (webglFailed) return React.createElement('div', { style: { position:'absolute', right:'-4%', top:'50%', transform:'translateY(-50%)', zIndex:2, width:'52%', maxWidth:620, pointerEvents:'none' } }, React.createElement('img', { src:'assets/sphere.png', alt:'', style:{ width:'100%', mixBlendMode:'screen', animation:'floatSphere 8s ease-in-out infinite' } })); return React.createElement('div', { ref: mountRef, style: { position:'absolute', right:'-4%', top:'50%', transform:'translateY(-50%)', zIndex:2, width:'52%', maxWidth:620, height:620, pointerEvents:'none' } }); } function Hero() { const bgRef = useRef(null); const [isMobile, setIsMobile] = useState(window.innerWidth < 640); const [isTablet, setIsTablet] = useState(window.innerWidth < 1024); useEffect(() => { const onResize = () => { setIsMobile(window.innerWidth < 640); setIsTablet(window.innerWidth < 1024); }; const onScroll = () => { if (bgRef.current) bgRef.current.style.transform = `translateY(${window.scrollY * 0.4}px)`; }; window.addEventListener('resize', onResize); window.addEventListener('scroll', onScroll, { passive: true }); return () => { window.removeEventListener('resize', onResize); window.removeEventListener('scroll', onScroll); }; }, []); const px = isMobile ? '20px' : isTablet ? '32px' : '52px'; const lines = ['I build the systems', 'behind better', 'digital work.']; return ( React.createElement('section', { style: { position:'relative', height: isMobile ? '100svh' : '100vh', overflow:'hidden' } }, /* BG */ React.createElement('div', { ref: bgRef, style: { position:'absolute', inset:0, backgroundImage:"url('assets/bg_wide.png')", backgroundSize:'cover', backgroundPosition:'center top', willChange:'transform' } }), React.createElement('div', { style: { position:'absolute', inset:0, zIndex:1, background:'linear-gradient(to right, rgba(15,32,24,0.74) 0%, rgba(15,32,24,0.44) 52%, rgba(15,32,24,0.12) 100%)' } }), React.createElement('div', { style: { position:'absolute', inset:0, zIndex:1, background:'linear-gradient(to top, rgba(11,25,18,0.88) 0%, transparent 38%)' } }), /* SPHERE — desktop + tablet only */ !isMobile && React.createElement(SphereCanvas), /* CONTENT */ React.createElement('div', { style: { position:'relative', zIndex:3, height:'100%', display:'grid', gridTemplateRows:'1fr auto', padding:`0 ${px}` } }, /* HERO BODY */ React.createElement('div', { style: { display:'flex', flexDirection:'column', justifyContent:'flex-end', paddingBottom: isMobile ? 32 : 0, paddingTop: 90, maxWidth: isMobile ? '100%' : isTablet ? '65%' : '52%' } }, /* EYEBROW */ React.createElement('div', { style: { fontFamily:'var(--mono)', fontSize: isMobile ? 10 : 11, letterSpacing:'0.2em', textTransform:'uppercase', color:'rgba(180,220,200,0.75)', marginBottom:20, display:'flex', alignItems:'center', gap:12 } }, React.createElement('div', { style:{ width:20, height:0.5, background:'rgba(180,220,200,0.5)' } }), 'Staff Software Engineer' ), /* H1 */ React.createElement('h1', { style: { fontFamily:'var(--serif)', fontSize: isMobile ? 'clamp(40px,11vw,56px)' : isTablet ? 'clamp(44px,7vw,64px)' : 'clamp(48px,6.5vw,80px)', fontWeight:400, lineHeight:1.0, letterSpacing:'-0.03em', color:'#F3F8F2' } }, lines.map((line, i) => React.createElement('span', { key:i, style:{ overflow:'hidden', display:'block' } }, React.createElement('span', { style:{ display:'block', transform:'translateY(105%)', animation:`lineUp 1s cubic-bezier(0.16,1,0.3,1) ${0.05+i*0.11}s forwards` } }, line.includes('better') ? [line.split('better')[0], React.createElement('em', { key:'acc', style:{ color:'#A8D4C8', fontStyle:'italic' } }, 'better'), line.split('better')[1]] : line ) ) ) ), /* SUB */ React.createElement('p', { style: { marginTop:22, fontSize: isMobile ? 14 : 15, fontWeight:300, color:'rgba(210,230,220,0.75)', lineHeight:1.85, maxWidth: isMobile ? '100%' : 420 } }, 'I work across frontend, integrations, data flows, and internal tooling to make business systems easier to use and easier to maintain.' ), /* BUTTONS */ React.createElement('div', { style: { marginTop:28, display:'flex', alignItems:'center', gap:12, flexWrap:'wrap' } }, React.createElement('button', { style:{ fontFamily:'var(--mono)', fontSize:11, letterSpacing:'0.12em', textTransform:'uppercase', padding:'12px 24px', background:'rgba(47,93,80,0.55)', color:'#F3F8F2', border:'0.5px solid rgba(168,212,200,0.4)', backdropFilter:'blur(16px)', cursor:'pointer' } }, 'View work'), React.createElement('button', { style:{ fontFamily:'var(--mono)', fontSize:11, letterSpacing:'0.12em', textTransform:'uppercase', padding:'11px 20px', background:'rgba(255,255,255,0.07)', color:'rgba(200,230,215,0.85)', border:'0.5px solid rgba(200,230,215,0.25)', backdropFilter:'blur(12px)', cursor:'pointer' } }, 'What I do') ) ), /* GLASS STAT CARDS */ React.createElement('div', { style: { display:'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3,1fr)', gap: isMobile ? 8 : 10, paddingBottom: isMobile ? 28 : 44, paddingTop: isMobile ? 16 : 0 } }, [ { label:'Current Role', val:['Staff Eng. ','Leviton'] }, { label:'Core Stack', val:['React, OIC, AEM, Node'] }, { label:'Independent Ventures', val:['Alysion ','+', ' Zueta Media'] }, ].map(({ label, val }) => React.createElement('div', { key:label, style:{ background:'rgba(243,248,242,0.08)', backdropFilter:'blur(24px)', WebkitBackdropFilter:'blur(24px)', border:'0.5px solid rgba(255,255,255,0.18)', padding: isMobile ? '14px 16px' : '20px 24px', position:'relative', overflow:'hidden' } }, React.createElement('div', { style:{ position:'absolute', top:0, left:0, right:0, height:1, background:'linear-gradient(90deg,transparent,rgba(255,255,255,0.28),transparent)' } }), React.createElement('div', { style:{ fontFamily:'var(--mono)', fontSize:10, letterSpacing:'0.14em', textTransform:'uppercase', color:'rgba(180,215,195,0.6)', marginBottom:6 } }, label), React.createElement('div', { style:{ fontFamily:'var(--serif)', fontSize: isMobile ? 16 : 19, fontWeight:400, color:'#F3F8F2', lineHeight:1.25 } }, val.map((v,i) => v==='+' || (i===1 && val.length>1) ? React.createElement('em', { key:i, style:{ color:'#A8D4C8', fontStyle:'normal' } }, v) : React.createElement('span', { key:i }, v) ) ) ) ) ) ) ) ); }