// ============================================================ // OSCILADOR — primitives & shared components // Exposes globals: Logo, WaveSVG, Marquee, Reveal, Photo, // Icon, SectionHead, Eyebrow // ============================================================ // ──────────────── Wave SVG (used in logo, dividers, hero bg) function WaveSVG({ stroke = "currentColor", strokeWidth = 1.4, width = 800, height = 40 }) { // sinusoidal — single period repeats across width const periods = 4; const amp = height * 0.35; const mid = height / 2; const step = width / (periods * 2); let d = `M 0 ${mid}`; for (let i = 0; i < periods * 2; i++) { const x1 = step * i + step / 2; const y1 = i % 2 === 0 ? mid - amp : mid + amp; const x2 = step * (i + 1); d += ` Q ${x1} ${y1}, ${x2} ${mid}`; } return ( ); } // ──────────────── Logo (icon + wordmark) function LogoIcon({ size = 36 }) { return (
Oscilador Academy
); } function Logo({ size = 36, compact = false }) { return ( {!compact && ( Oscilador Academy )} ); } // ──────────────── Eyebrow label function Eyebrow({ children, num }) { return (
{num && {num}} {children}
); } // ──────────────── Section header with eyebrow + title + optional sub function SectionHead({ eyebrow, num, title, sub, align = "left", titleClass = "h-3" }) { return (
{(eyebrow || num) && {eyebrow}}

{title}

{sub &&

{sub}

}
); } // ──────────────── Marquee (technical band of data) function Marquee({ items }) { // duplicate for seamless loop const all = [...items, ...items]; return ( ); } // ──────────────── Reveal-on-scroll function Reveal({ children, delay = 0, as: As = "div", className = "", style = {}, ...rest }) { const ref = React.useRef(null); React.useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { el.classList.add("in"); io.unobserve(el); } }); }, { threshold: 0.15, rootMargin: "0px 0px -8% 0px" }); io.observe(el); return () => io.disconnect(); }, []); return ( {children} ); } // ──────────────── Photo placeholder (rider-style annotated empty state) function Photo({ id = "IMG-001", caption = "Placeholder", src, className = "", style = {}, aspect, children }) { const hasImg = Boolean(src); const s = { ...style, background: "linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%)", border: "1px solid rgba(255,255,255,0.12)", borderRadius: "var(--r-4)", }; if (aspect) s.aspectRatio = aspect; return (
{hasImg ? ( {caption} ) : (
)}
{caption}
{children}
); } // ──────────────── Arrow glyph function Arrow({ size = 14 }) { return ( ); } // ──────────────── Cal.com booking URLs — one per event type, easy to replace const CAL_LINKS = { practicaDj1h: "https://cal.com/oscilador-ktd9dy/practica-dj-1-hora", practicaDj2h: "https://cal.com/oscilador-ktd9dy/practica-dj-2-horas", practicaDj2p: "https://cal.com/oscilador-ktd9dy/practica-dj-2-personas-1-hora", claseSesionGuiada: "https://cal.com/oscilador-ktd9dy/clase-sesion-guiada", grabacionSet: "https://cal.com/oscilador-ktd9dy/grabacion-de-set", membresiaPlanMensual: "https://wa.me/59167165251?text=Hola%2C%20quiero%20consultar%20sobre%20membres%C3%ADas", }; const CAL_PRACTICA_URL = CAL_LINKS.practicaDj1h; // alias — backward compat // ──────────────── WhatsApp glyph (minimal) function WhatsAppIcon({ size = 18 }) { return ( ); } // ──────────────── Section divider (thin line between sections) function SectionDivider({ target = "#top" }) { return (
); } Object.assign(window, { CAL_LINKS, CAL_PRACTICA_URL, WaveSVG, LogoIcon, Logo, Eyebrow, SectionHead, SectionDivider, Marquee, Reveal, Photo, Arrow, WhatsAppIcon, });