// ============================================================
// 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 (
);
}
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 (
{all.map((it, i) => (
{it}
))}
);
}
// ──────────────── 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}
{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,
});