// sections.jsx — content overlays for each section. // The 3D art is rendered by three-scene.js as a global background; these // components only draw the text/buttons/lists that sit on top. const { useState, useEffect, useLayoutEffect, useRef, useCallback, useMemo } = React; // ── Emit gold sparks at a screen point ────────────────────── function emitSparks(x, y, count = 8) { const layer = document.getElementById('sparks'); if (!layer) return; for (let i = 0; i < count; i++) { const s = document.createElement('div'); s.className = 'spark'; s.style.left = x + 'px'; s.style.top = y + 'px'; const angle = Math.random() * Math.PI * 2; const dist = 40 + Math.random() * 80; s.style.setProperty('--dx', Math.cos(angle) * dist + 'px'); s.style.setProperty('--dy', Math.sin(angle) * dist + 'px'); layer.appendChild(s); setTimeout(() => s.remove(), 850); } } window.emitSparks = emitSparks; // ── Glitch / scramble reveal text ─────────────────────────── function GlitchText({ text, className, style, delay = 0, duration = 700, active = true }) { const [out, setOut] = useState(''); useEffect(() => { if (!active) {setOut(text);return;} const chars = '!<>-_\\/[]{}—=+*^?#█▓▒░·'; let start = performance.now() + delay; let raf; const tick = (now) => { const p = Math.max(0, Math.min(1, (now - start) / duration)); if (p <= 0) {setOut('');raf = requestAnimationFrame(tick);return;} let s = ''; for (let i = 0; i < text.length; i++) { const cp = i / text.length; if (p > cp + 0.05) s += text[i];else if (p > cp - 0.05) s += chars[Math.floor(Math.random() * chars.length)];else s += ' '; } setOut(s); if (p < 1) raf = requestAnimationFrame(tick);else setOut(text); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [text, delay, duration, active]); return {out || '\u00A0'}; } // ── Hover/click spark wrapper ─────────────────────────────── function Sparkly({ children }) { const onClick = (e) => { emitSparks(e.clientX, e.clientY, 14); if (window.__pulseFlash) window.__pulseFlash(); if (children.props.onClick) children.props.onClick(e); }; return React.cloneElement(children, { onClick }); } // ── Magnetic button (subtle pull toward cursor) ───────────── function Magnetic({ children, strength = 14 }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; let raf; let tx = 0,ty = 0,cx = 0,cy = 0; const onMove = (e) => { const r = el.getBoundingClientRect(); const cxR = r.left + r.width / 2; const cyR = r.top + r.height / 2; const dist = Math.hypot(e.clientX - cxR, e.clientY - cyR); if (dist < 160) { const k = (1 - dist / 160) * strength; tx = (e.clientX - cxR) / r.width * k; ty = (e.clientY - cyR) / r.height * k; } else { tx = 0;ty = 0; } }; const onLeave = () => {tx = 0;ty = 0;}; const loop = () => { cx += (tx - cx) * 0.18; cy += (ty - cy) * 0.18; el.style.transform = `translate3d(${cx}px, ${cy}px, 0)`; raf = requestAnimationFrame(loop); }; window.addEventListener('pointermove', onMove); window.addEventListener('pointerleave', onLeave); loop(); return () => { cancelAnimationFrame(raf); window.removeEventListener('pointermove', onMove); window.removeEventListener('pointerleave', onLeave); }; }, [strength]); return
{children}
; } // ── Live ticker stats ─────────────────────────────────────── function useTicker(interval = 50) { const [n, setN] = useState(0); useEffect(() => { const t = setInterval(() => setN((x) => x + 1), interval); return () => clearInterval(t); }, [interval]); return n; } // ── Hero ↔ About transition phase ─────────────────────────── // Subscribes to the global `hxtransition` event dispatched by three-scene.js. // Returns `{ phase, variant }` where phase is: // 'active' — transition is running (3D effect + content animating) // 'settling' — 3D done, give about's entry animation a beat to complete // null — idle function useTransitionPhase() { const [data, setData] = useState({ phase: null, variant: null }); useEffect(() => { let settleTimer; const onT = (e) => { const { phase, variant } = e.detail || {}; if (phase === 'start') { clearTimeout(settleTimer); setData({ phase: 'active', variant }); } else if (phase === 'end') { setData((d) => ({ phase: 'settling', variant: d.variant || variant })); clearTimeout(settleTimer); settleTimer = setTimeout(() => setData({ phase: null, variant: null }), 700); } }; window.addEventListener('hxtransition', onT); return () => { window.removeEventListener('hxtransition', onT); clearTimeout(settleTimer); }; }, []); return data; } // ═══════════════════════════════════════════════════════════ // 01 — HERO // ═══════════════════════════════════════════════════════════ function Hero({ active }) { const { phase: tPhase, variant: tVariant } = useTransitionPhase(); const exiting = !!tPhase; // active or settling — keep exited until idle return (
// INICIO hx.node.01

hecstrim

Hector. Creador de contenido, constructor de agentes.
IA, automatización y ruido digital con propósito.

scroll ↓ · acerca el cursor
loop activo · ↻
lat 40.4168 · lon −3.7038
render · 60 fps · webgl2
); } // ═══════════════════════════════════════════════════════════ // HectorCard — 3D vertical card body with real Y-axis rotation. // The card has front face (photo), back face (data), and four thin // edge slabs that give the body genuine thickness. Three variants: // 'showcase' → continuous slow 360° Y-spin, museum display feel // 'reveal' → step-rotate: 180° flip, pause, 180° flip, pause // 'magnet' → idle slow spin + cursor X pushes rotation speed // ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════ // HectorCard — 3D vertical card body. Front face always faces the // viewer with a gentle Y-axis sway, biased by cursor position // (pointer adds yaw + slight pitch). Yaw never exceeds ~±30°. // ═══════════════════════════════════════════════════════════ function HectorCard() { const wrapRef = useRef(null); const cardRef = useRef(null); // the rotating body const shadowRef = useRef(null); // ground shadow // Card sizing — kept identical across variants const CARD_W = 'min(340px, 78%)'; const CARD_AR = '5 / 7'; const THICK = 12; // px, half-depth of the card body (full thickness = 24px) // Ground shadow: breathes with the float cycle (used by all variants) useEffect(() => { const shadow = shadowRef.current; if (!shadow) return; let raf; const start = performance.now(); const period = 5200; const tick = () => { const t = (performance.now() - start) / period; const lift = Math.sin(t * Math.PI * 2); const s = 1 + lift * 0.12; const o = 0.42 + lift * 0.18; shadow.style.transform = `translateX(-50%) scale(${s.toFixed(3)}, 1)`; shadow.style.opacity = o.toFixed(3); raf = requestAnimationFrame(tick); }; tick(); return () => cancelAnimationFrame(raf); }, []); // Idle Y-axis sway + cursor parallax. Front face stays visible at all times // (yaw stays well within ±30°). Cursor X biases the yaw; cursor Y biases X-tilt. useEffect(() => { const wrap = wrapRef.current; const card = cardRef.current; if (!wrap || !card) return; let targetYawBias = 0; // deg, from cursor X let targetPitch = 0; // deg, from cursor Y let yawBias = 0,pitch = 0; let raf; const t0 = performance.now(); const onMove = (e) => { const r = wrap.getBoundingClientRect(); const cx = r.left + r.width / 2; const cy = r.top + r.height / 2; // Normalize cursor distance from card center to viewport scale const nx = (e.clientX - cx) / (window.innerWidth * 0.5); const ny = (e.clientY - cy) / (window.innerHeight * 0.5); // Cap effect targetYawBias = Math.max(-1, Math.min(1, nx)) * 18; // ± 18° targetPitch = Math.max(-1, Math.min(1, ny)) * -10; // ± 10° (inverted) }; const onLeave = () => {targetYawBias = 0;targetPitch = 0;}; const tick = (now) => { const elapsed = (now - t0) / 1000; // Idle yaw oscillation: ± 12°, 6 s period const idleYaw = Math.sin(elapsed * (Math.PI * 2) / 6) * 12; // Smooth toward cursor targets yawBias += (targetYawBias - yawBias) * 0.06; pitch += (targetPitch - pitch) * 0.06; const yaw = idleYaw + yawBias; card.style.transform = `rotateY(${yaw.toFixed(2)}deg) rotateX(${pitch.toFixed(2)}deg)`; raf = requestAnimationFrame(tick); }; window.addEventListener('pointermove', onMove); window.addEventListener('pointerleave', onLeave); raf = requestAnimationFrame(tick); return () => { cancelAnimationFrame(raf); window.removeEventListener('pointermove', onMove); window.removeEventListener('pointerleave', onLeave); }; }, []); // Inline keyframes for the vertical float bob const KEYFRAMES = ` @keyframes hxFloatA { 0%, 100% { transform: translateY(-6px); } 50% { transform: translateY(8px); } } `; // Corner radius applied to all six surfaces of the body. const RADIUS = 24; // The card body. Front face holds the photo (framed with margin), back face // holds the persona data, and four edge slabs give the body real thickness. // Edge slabs are inset from the rounded corners so they don't poke past them. const renderBody = () => <> {/* FRONT face — photo framed by margin, rounded corners */}
{/* image area — inset from the card edge with its own rounded corners */}
Hector {/* scanlines */}
{/* header overlay on top of image */}
{/* footer — sits in the frame margin below the image */}
PERSONAJE
· hecstrim
Ingeniero IA · Creador de contenido
{/* BACK face — closes the body, gray surface matching the edge slabs. Reserved for the personal brand logo (to come). */}
{/* EDGE slabs — inset from rounded corners so they don't poke out */} {/* top edge */}
{/* bottom edge */}
{/* left edge */}
{/* right edge */}
; // The wrapper sits inside the section column; everything sits in a perspective stage. return (
{/* ground shadow */}
{/* outer floater: handles the vertical bob */}
{/* inner rotator: the actual 3D body */}
{renderBody()}
); } // ═══════════════════════════════════════════════════════════ // 02 — ABOUT // ═══════════════════════════════════════════════════════════ function About({ active }) { const { phase: tPhase, variant: tVariant } = useTransitionPhase(); // Two-step entry. The transition fires the SAME tick that .hx-transitioning // pins About as a fixed full-viewport overlay — so if About paints at its // settled state for even one frame, the user sees the content flash into the // center BEFORE it snaps to the displaced start state. That's the // "bajan-suben-bajan" glitch. // // Fix: useLayoutEffect runs AFTER React commits but BEFORE the browser // paints. setState inside it triggers a synchronous re-render that is // flushed before paint, so the very first paint already shows About at // hx-enter-start (translateY 60px, opacity 0, snapped via // transition-duration:0s). No center flash, no extra "down" movement. // // Then useEffect (post-paint) schedules a 2-rAF beat to flip to hx-enter so // the CSS transition fires and About slides up to its settled position once, // smoothly. const [enterClass, setEnterClass] = useState(''); useLayoutEffect(() => { if (tPhase === 'active') { setEnterClass('hx-enter-start'); } else if (tPhase === null) { setEnterClass(''); } // 'settling' — leave enterClass at 'hx-enter' so it stays settled }, [tPhase]); useEffect(() => { if (enterClass !== 'hx-enter-start') return; // Hold About at the displaced/invisible state until AFTER the 3D warp // (1.6s) fully resolves, so the dive does the visual heavy lifting // alone and the content arrives as a follow-up beat — not concurrent. const ENTER_HOLD_MS = 1800; const timer = setTimeout(() => setEnterClass('hx-enter'), ENTER_HOLD_MS); return () => clearTimeout(timer); }, [enterClass]); const stats = [ ['focus.areas', 'IA · Automation · Storytelling'], ['based.in', 'Madrid · everywhere online'], ['stack', 'LLMs · Python · Next · Three'], ['status', 'shipping daily · open to collab']]; return (
// PERFIL
{/* Left — title spans into the 3D portrait area */}

Este es
HECTOR.

Ingeniero y maker de IA que ayuda a otras personas en su camino con la IA, a través de materiales, formación en inteligencia artificial y tecnología.

{['ingeniero-ia', 'creador', 'investigador', 'open-source'].map((tag) => #{tag} )}
{/* Right — 3D levitating card with portrait */}
// las partículas regresan cuando dejas de perseguirlas
); } // ═══════════════════════════════════════════════════════════ // 03 — DELIVERABLES // ═══════════════════════════════════════════════════════════ const DELIVERABLES = [ { cmd: 'wget', path: '/prompts/agent-system-v3.md', label: 'Prompts de sistema para agentes · v3', size: '12 KB', tag: 'gratis', kind: '.md' }, { cmd: 'git clone', path: 'github.com/hecstrim/llm-toolkit', label: 'Toolkit LLM · helpers en Python, evals, fixtures', size: '2.4 MB', tag: 'gratis', kind: 'repo' }, { cmd: 'curl -O', path: '/packs/voice-clone-recipe.zip', label: 'Receta clon de voz · pipeline completo', size: '48 MB', tag: '€19', kind: '.zip' }, { cmd: 'wget', path: '/prompts/cinematic-shorts.json', label: 'Cortos cinematográficos · 40 prompts de vídeo', size: '36 KB', tag: '€9', kind: '.json' }, { cmd: 'git clone', path: 'github.com/hecstrim/notion-ops', label: 'Notion Ops · plantilla segundo cerebro', size: '1.1 MB', tag: 'gratis', kind: 'repo' }, { cmd: 'curl -O', path: '/skills/diffusion-finetune.pdf', label: 'Fine-tune de difusión · manual de 32 páginas', size: '8.2 MB', tag: '€14', kind: '.pdf' }]; function DelivRow({ item, idx, focused, onFocus }) { const isPaid = item.tag.startsWith('€'); return (
onFocus(idx)} onClick={(e) => {emitSparks(e.clientX, e.clientY, 12);window.__pulseFlash && window.__pulseFlash();}} style={{ display: 'grid', gridTemplateColumns: '36px 110px 1fr auto auto', gap: 16, alignItems: 'center', padding: '16px 22px', cursor: 'pointer', borderBottom: '1px solid var(--line-soft)', background: focused ? 'linear-gradient(90deg, var(--accent-soft), transparent 60%)' : 'transparent', transition: 'background 0.22s, padding 0.22s', paddingLeft: focused ? 32 : 22 }}> {String(idx + 1).padStart(2, '0')} $ {item.cmd} {item.path} {item.size} {item.tag}
); } function Deliverables({ active }) { const [focused, setFocused] = useState(0); const item = DELIVERABLES[focused]; return (
// payloads.registry {DELIVERABLES.length} entries

Descargables .

{DELIVERABLES.filter((d) => d.tag === 'gratis').length} gratis  ·  {DELIVERABLES.filter((d) => d.tag !== 'gratis').length} de pago  ·  actualizado semanalmente
hector@hecstrim:~/descargables
tab ⌥ para explorar
{DELIVERABLES.map((d, i) => )}
{' '} {item.label} · {item.kind} · {item.size}
); } // ═══════════════════════════════════════════════════════════ // 04 — SHOP // ═══════════════════════════════════════════════════════════ const SHOP_DIGITAL = [ { name: 'AGENT.PACK', sub: '120 prompts de sistema para builders de agentes', price: '€29' }, { name: 'CINEMA.JSON', sub: 'Biblioteca de prompts para pipelines de vídeo IA', price: '€19' }, { name: 'NOTION.OS', sub: 'Plantilla segundo cerebro + automatizaciones', price: '€14' }, { name: 'VOICE.KIT', sub: 'Recetas de clon de voz + audio de referencia', price: '€39' }]; const SHOP_PHYSICAL = [ { name: 'TEE · hxnode', sub: 'Algodón pesado negro · estampado de acento', price: '€34' }, { name: 'HOODIE · terminal', sub: 'Corte boxy, cursor bordado', price: '€68' }, { name: 'STICKER PACK', sub: '8 vinilos troquelados · resistentes al agua', price: '€9' }, { name: 'POSTER · A2', sub: 'Risografía, manifiesto en píxeles', price: '€22' }]; function ProductCard({ p }) { const ref = useRef(null); return (
{emitSparks(e.clientX, e.clientY, 12);window.__pulseFlash && window.__pulseFlash();}} style={{ padding: 22, display: 'flex', flexDirection: 'column', gap: 12, cursor: 'pointer', minHeight: 220 }} onMouseMove={(e) => { const r = ref.current.getBoundingClientRect(); const x = ((e.clientX - r.left) / r.width - 0.5) * 8; const y = ((e.clientY - r.top) / r.height - 0.5) * -8; ref.current.style.transform = `perspective(800px) rotateX(${y}deg) rotateY(${x}deg) translateY(-2px)`; }} onMouseLeave={() => {ref.current.style.transform = '';}}>
{p.name.split(' ')[0]}
{p.name}
{p.sub}
{p.price} añadir ↗
); } function Shop({ active }) { const [mode, setMode] = useState('digital'); const items = mode === 'digital' ? SHOP_DIGITAL : SHOP_PHYSICAL; const modeLabel = mode === 'digital' ? 'digital' : 'física'; return (
// 03 tienda.{modeLabel} {items.length} sku

Tienda . {modeLabel}

{[{key:'digital',label:'digital'},{key:'physical',label:'física'}].map((m) => )}
{items.map((p, i) => )}
{items.length} items · {mode === 'digital' ? 'descarga inmediata' : 'envío mundial'} pago seguro · stripe · paypal
); } // ═══════════════════════════════════════════════════════════ // 05 — LINKS // ═══════════════════════════════════════════════════════════ const SOCIALS = [ { name: 'YouTube', handle: '@hecstrim', url: 'youtube.com/@hecstrim', cat: 'vídeo' }, { name: 'Instagram', handle: '@hecstrim', url: 'instagram.com/hecstrim', cat: 'visual' }, { name: 'GitHub', handle: '/hecstrim', url: 'github.com/hecstrim', cat: 'código' }, { name: 'TikTok', handle: '@hecstrim', url: 'tiktok.com/@hecstrim', cat: 'shorts' }, { name: 'X / Twitter', handle: '@hecstrim', url: 'x.com/hecstrim', cat: 'texto' }, { name: 'Spotify', handle: 'hecstrim · lab', url: 'open.spotify.com/user/hx', cat: 'audio' }, { name: 'Substack', handle: 'hecstrim.sub', url: 'hecstrim.substack.com', cat: 'long-form' }, { name: 'Discord', handle: 'hx.node', url: 'discord.gg/hxnode', cat: 'comunidad' }]; function LinkRow({ s, i }) { return ( {emitSparks(e.clientX, e.clientY, 14);window.__pulseFlash && window.__pulseFlash();}} style={{ display: 'grid', gridTemplateColumns: '36px 1fr 1fr auto', alignItems: 'center', gap: 18, padding: '20px 22px', borderBottom: '1px solid var(--line-soft)', textDecoration: 'none', color: 'inherit', transition: 'background 0.22s, padding 0.22s' }} onMouseEnter={(e) => {e.currentTarget.style.background = 'var(--accent-soft)';e.currentTarget.style.paddingLeft = '32px';}} onMouseLeave={(e) => {e.currentTarget.style.background = 'transparent';e.currentTarget.style.paddingLeft = '22px';}}> {String(i + 1).padStart(2, '0')} {s.name} {s.url} {s.cat} ); } function Links({ active }) { return (
// 04 red.contactos {SOCIALS.length} nodos

enlaces .

todas las rutas cifradas · túnel ssh listo
{SOCIALS.map((s, i) => )}
$ echo "saluda → hola@hecstrim.com" // continuar · el loop vuelve al inicio
); } // Pulse keyframes injection (one-off) if (!document.getElementById('__sec-styles')) { const sty = document.createElement('style'); sty.id = '__sec-styles'; sty.textContent = ` @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(0.85); } } `; document.head.appendChild(sty); } Object.assign(window, { Hero, About, Deliverables, Shop, Links, HectorCard });