'use client'; import dynamic from 'next/dynamic'; import { useEffect, useRef, useState } from 'react'; // The building's public door, and the only file a section should import. // // three.js is around 185 kB gzipped and the hero is the LCP — the page opens // from a WhatsApp link on mobile data, so the headline, the CTAs and the Dream // Outcome have to be painted and clickable long before any of this arrives. // `dynamic` holds the scene back until after hydration, and `ssr: false` is not // optional: three.js needs a real canvas, which the static export does not have // at build time. // // The `dynamic` call lives here, behind 'use client', because Next 14 refuses // `ssr: false` inside a Server Component — and `Hero` is one, deliberately: it // is almost all text and has no reason to ship as JavaScript. const EscenaClaridad = dynamic( () => import('@/components/primitives/EscenaClaridad').then((m) => m.EscenaClaridad), { ssr: false, loading: () => null }, ); export function ClaridadHero({ className = '' }: { className?: string }) { const contenedor = useRef(null); const [cerca, setCerca] = useState(false); // On a phone the drawing sits under the two CTAs, a screen and a half down, // and plenty of readers tap through without ever reaching it. Deferring to // hydration is not enough: the chunk waits until the box is nearly in view, // the same deal the six photos of the recorrido got. On a laptop the box is // beside the headline, so this resolves on the first observation and costs // nothing. useEffect(() => { const el = contenedor.current; if (!el) return; if (typeof IntersectionObserver === 'undefined') { setCerca(true); return; } const obs = new IntersectionObserver( ([entrada]) => { if (!entrada.isIntersecting) return; setCerca(true); obs.disconnect(); }, { rootMargin: '300px' }, ); obs.observe(el); return () => obs.disconnect(); }, []); return ( ); }