"use client"; import { useState, useEffect, useRef } from "react"; import ReactDOM from "react-dom"; import { motion, AnimatePresence } from "framer-motion"; import Link from "next/link"; import Image from "next/image"; import Script from "next/script"; import { ArrowLeft, CheckCircle2, Zap, LayoutDashboard, Cpu, PencilRuler, Factory, MapPin, ChevronDown, Play, FileText, Box, Loader2, Maximize, X, ChevronLeft, ChevronRight } from "lucide-react"; import BreathingField from "@/components/visuals/BreathingField"; import AutoPlayVideo from "@/components/AutoPlayVideo"; import Breadcrumbs from "@/components/seo/Breadcrumbs"; import type { BreadcrumbItem } from "@/components/seo/Breadcrumbs"; // πŸ”₯ EL TRUCO DEFINITIVO PARA TYPESCRIPT Y WEB COMPONENTS πŸ”₯ // Al asignar el string a una variable con 'as any', TypeScript deja de // intentar validar las propiedades intrΓ­nsecas de JSX y compila sin chistar. const ModelViewer = 'model-viewer' as any; // ── LIGHTBOX CORREGIDO ────────────────────────────────────────────────────── function ImageLightbox({ images, initialIndex, onClose }: { images: string[]; initialIndex: number; onClose: () => void; }) { const [currentIndex, setCurrentIndex] = useState(initialIndex); const touchStartX = useRef(null); useEffect(() => { const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, []); const handleNext = (e?: React.MouseEvent | React.TouchEvent) => { e?.stopPropagation(); setCurrentIndex((p) => (p + 1) % images.length); }; const handlePrev = (e?: React.MouseEvent | React.TouchEvent) => { e?.stopPropagation(); setCurrentIndex((p) => (p === 0 ? images.length - 1 : p - 1)); }; useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); if (e.key === 'ArrowRight') setCurrentIndex((p) => (p + 1) % images.length); if (e.key === 'ArrowLeft') setCurrentIndex((p) => (p === 0 ? images.length - 1 : p - 1)); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [images.length, onClose]); const onTouchStart = (e: React.TouchEvent) => { touchStartX.current = e.touches[0].clientX; }; const onTouchEnd = (e: React.TouchEvent) => { if (touchStartX.current === null) return; const delta = e.changedTouches[0].clientX - touchStartX.current; if (Math.abs(delta) > 50) { delta < 0 ? handleNext() : handlePrev(); } touchStartX.current = null; }; const [mounted, setMounted] = useState(false); const portalElRef = useRef(null); useEffect(() => { const el = document.createElement('div'); el.id = 'lightbox-portal-' + Date.now(); el.style.cssText = [ 'position:fixed', 'inset:0', 'z-index:99999', 'pointer-events:auto', 'touch-action:none', 'isolation:isolate', ].join(';'); document.documentElement.appendChild(el); portalElRef.current = el; setMounted(true); return () => { if (portalElRef.current && document.documentElement.contains(portalElRef.current)) { document.documentElement.removeChild(portalElRef.current); portalElRef.current = null; } }; }, []); if (!mounted || !portalElRef.current) return null; return ReactDOM.createPortal(
e.stopPropagation()}> {images.length > 1 ? `${currentIndex + 1} / ${images.length}` : ''}
e.stopPropagation()} style={{ touchAction: 'none' }}> {images.length > 1 && ( <> )}
{images.length > 1 && (
e.stopPropagation()}>
{images.map((_, i) => (
)}
, portalElRef.current ); } // ── INLINE LIGHTWEIGHT 3D VIEWER ──────────────────────────────────────────── function InlineModelViewer({ glbPath, alt }: { glbPath: string; alt?: string }) { const [isActive, setIsActive] = useState(false); const [isLoaded, setIsLoaded] = useState(false); const viewerId = useRef(`imv-${Math.random().toString(36).slice(2)}`); const usdzPath = glbPath.replace(/\.glb$/i, '.usdz'); useEffect(() => { if (!isActive) return; let attempts = 0; const tryRead = () => { const el = document.getElementById(viewerId.current) as any; if (!el) { if (++attempts < 20) setTimeout(tryRead, 300); return; } if ((el as any).modelIsVisible) { setIsLoaded(true); return; } el.addEventListener('load', () => setIsLoaded(true), { once: true }); }; const t = setTimeout(tryRead, 300); return () => clearTimeout(t); }, [isActive]); return (