// Componentes principales: Topbar, ProductCard, Hero, Tabbar, etc.
const { useState, useEffect, useRef } = React;
// ====================== TOPBAR ======================
function Topbar({ cartCount, wishCount, activeTab, onNavigate, onOpenSearch, onOpenCart, onOpenWish, transparent }) {
const navItems = [
{ id: "home", label: "Inicio" },
{ id: "shop", label: "Tienda" },
{ id: "wish", label: "Deseos" },
{ id: "account", label: "Cuenta" },
];
return (
);
}
// ====================== TABBAR ======================
function Tabbar({ active, onChange }) {
const tabs = [
{ id: "home", label: "Inicio", icon: I.Home },
{ id: "shop", label: "Tienda", icon: I.Compass },
{ id: "wish", label: "Deseos", icon: I.Heart },
{ id: "account", label: "Cuenta", icon: I.User },
];
return (
);
}
// ====================== PRODUCT CARD ======================
function ProductCard({ product, currency, rates, onOpen, onAdd, onFav, isFav }) {
const cardRef = useRef(null);
const handleAdd = (e) => {
e.stopPropagation();
if (product.stock === 0) return;
onAdd(product, cardRef.current);
};
const out = product.stock === 0;
const low = product.stock > 0 && product.stock < 10;
return (
onOpen(product)} ref={cardRef}>
{product.isNew &&
Nuevo}
{out &&
Sin stock}
{!product.isNew && !out && product.compareAt &&
Oferta}
{product.name}
{product.tagline}
{formatPrice(product.price, currency, rates)}
{product.compareAt && {formatPrice(product.compareAt, currency, rates)}}
);
}
// ====================== HERO ======================
function Hero({ variant, onCta, brand }) {
const heroRef = useRef(null);
// parallax
useEffect(() => {
const scrollEl = heroRef.current?.closest(".body-scroll");
if (!scrollEl) return;
const onScroll = () => {
const bg = heroRef.current?.querySelector(".hero-bg");
if (bg) bg.style.transform = `translateY(${scrollEl.scrollTop * 0.3}px) scale(1.05)`;
};
scrollEl.addEventListener("scroll", onScroll, { passive: true });
return () => scrollEl.removeEventListener("scroll", onScroll);
}, []);
if (variant === "editorial") {
return (
Belleza elegida con cuidado
El ritual
vuelve a casa.
Seleccion cuidada de belleza para rutinas simples, calidas y confiables.
);
}
if (variant === "mono") {
return (
mima edit
Pequeños rituales,
grandes cambios.
Una boutique digital con productos seleccionados a mano.
);
}
// default: warm gradient hero
return (
Belleza elegida con cuidado
Aromas que
te abrazan.
Perfumeria y cuidado personal con productos seleccionados de Natura, Avon y marcas elegidas.
);
}
// ====================== CATEGORÍAS RAIL ======================
function CategoryRail({ cats, active, onChange }) {
return (
{cats.map(c => (
))}
);
}
// ====================== BANNER CARRUSEL ======================
function BannerRail() {
const banners = [
{ cls: "b1", eyebrow: "Hasta 30% off", title: "Liquidación de invierno" },
{ cls: "b2", eyebrow: "Edición limitada", title: "Sérum Vit. C, ahora con niacinamida" },
{ cls: "b3", eyebrow: "Envío gratis", title: "En compras desde $ 35.000" },
];
return (
{banners.map((b, i) => (
{b.eyebrow}
{b.title}
))}
);
}
// ====================== LOYALTY CARD ======================
function LoyaltyCard({ points, target = 1000 }) {
const pct = Math.min(100, Math.round((points / target) * 100));
return (
mima edit - beneficios
{points}/ {target} puntos
Te faltan {target - points} puntos para tu próxima recompensa.
);
}
// ====================== TOAST ======================
function Toast({ message, onClose }) {
useEffect(() => {
const t = setTimeout(onClose, 2200);
return () => clearTimeout(t);
}, [message, onClose]);
if (!message) return null;
return (
{message}
);
}
Object.assign(window, { Topbar, Tabbar, ProductCard, Hero, CategoryRail, BannerRail, LoyaltyCard, Toast });