/* app.jsx — D'SHINE main composition - Mounts all sections - Sparkle cursor trail - Scroll reveal observer - Tweaks panel (light/dark hero, accent gold, sparkles) */ const { useState: appUseState, useEffect: appUseEffect, useRef: appUseRef } = React; /* Sparkle cursor — emits small star particles on mouse move */ function SparkleCursor({ enabled }) { const layerRef = appUseRef(null); const lastRef = appUseRef({ x: 0, y: 0, t: 0 }); appUseEffect(() => { if (!enabled) return; const layer = layerRef.current; if (!layer) return; const onMove = (e) => { const now = performance.now(); const last = lastRef.current; const dx = e.clientX - last.x; const dy = e.clientY - last.y; const dist = Math.hypot(dx, dy); // Throttle: only spawn when moved enough & enough time passed if (dist < 26 || now - last.t < 55) return; lastRef.current = { x: e.clientX, y: e.clientY, t: now }; // Two sparkles per emit with small offset for fullness for (let i = 0; i < 2; i++) { const s = document.createElement("span"); s.className = "spark-cursor"; const offX = (Math.random() - 0.5) * 16; const offY = (Math.random() - 0.5) * 16; const sz = 8 + Math.random() * 10; s.style.left = `${e.clientX + offX}px`; s.style.top = `${e.clientY + offY}px`; s.style.width = `${sz}px`; s.style.height = `${sz}px`; layer.appendChild(s); setTimeout(() => s.remove(), 900); } }; window.addEventListener("mousemove", onMove); return () => window.removeEventListener("mousemove", onMove); }, [enabled]); return
; } /* Scroll reveal — adds .in to .reveal elements when entering viewport */ function useScrollReveal() { appUseEffect(() => { const els = document.querySelectorAll(".reveal:not(.in)"); if (!("IntersectionObserver" in window)) { els.forEach((el) => el.classList.add("in")); return; } const io = new IntersectionObserver( (entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" } ); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, []); } /* Apply accent color globally via CSS variables override */ function useAccentVar(accent) { appUseEffect(() => { if (!accent) return; document.documentElement.style.setProperty("--gold", accent); // Derive a softer/lighter variant by lightening const lighten = (hex, p) => { const n = parseInt(hex.slice(1), 16); const r = Math.min(255, Math.round(((n >> 16) & 0xff) * (1 - p) + 255 * p)); const g = Math.min(255, Math.round(((n >> 8) & 0xff) * (1 - p) + 255 * p)); const b = Math.min(255, Math.round((n & 0xff) * (1 - p) + 255 * p)); return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join(""); }; const darken = (hex, p) => { const n = parseInt(hex.slice(1), 16); const r = Math.round(((n >> 16) & 0xff) * (1 - p)); const g = Math.round(((n >> 8) & 0xff) * (1 - p)); const b = Math.round((n & 0xff) * (1 - p)); return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join(""); }; document.documentElement.style.setProperty("--gold-light", lighten(accent, 0.22)); document.documentElement.style.setProperty("--gold-deep", darken(accent, 0.22)); }, [accent]); } /* ════════════════════════════════════════════════════════════ RESERVA SECTION — wraps Booking ════════════════════════════════════════════════════════════ */ function Reserva() { return (Escoge servicio, fecha y hora. Te confirmamos por WhatsApp en cuestión de minutos. Sin formularios eternos, sin esperas.
¿Prefieres hablar primero? Escríbenos a +57 300 203 1782 .