/* Shared components: cards, marquee, navs, etc. */
const { useState, useEffect, useRef, useMemo, useCallback } = React;
// Format price based on currency
const fmtPrice = (p, currency) => {
if (p === 0) return "Free";
if (currency === "USD") return `$${Math.round(p / 83)}`;
return `₹${p.toLocaleString("en-IN")}`;
};
// Badge config
const BADGES = {
trending: { label:"Trending", icon:"fire", color:"var(--primary)" },
selling_fast: { label:"Selling fast", icon:"bolt", color:"#f59e0b" },
almost_sold_out: { label:"Almost sold out", icon:"bolt", color:"#dc2626" },
free: { label:"Free", icon:"sparkle", color:"#10b981" },
new: { label:"New", icon:"sparkle", color:"#06b6d4" },
};
// Marquee for ticker tape brand
const Marquee = ({ items, speed=40 }) => (
{[...items, ...items, ...items].map((item, i) => (
{item}●
))}
);
// Logo wordmark
const Logo = ({ small }) => (
);
// Card variants
const EventCard = ({ event, variant, onClick, currency, dense, friends }) => {
const [saved, setSaved] = useState(false);
const [loaded, setLoaded] = useState(false);
const badge = event.badge ? BADGES[event.badge] : null;
const v = variant || "rich";
// Editorial — magazine-style
if (v === "editorial") {
return (

setLoaded(true)} style={{ width:"100%", height:"100%", objectFit:"cover", opacity:loaded?1:0, transition:"opacity .4s" }} />
{badge && (
{badge.label}
)}
{event.tag}
{event.title}
{event.date}
·
{event.area}
{event.price === 0 ? "FREE" : `from ${fmtPrice(event.price, currency)}`}
{friends && event.friends > 0 && (
{event.friends} friends going
)}
);
}
// Compact — dense list-friendly
if (v === "compact") {
return (

{badge && (() => { const BIcon = I[badge.icon]; return (
); })()}
{event.category}
{event.title}
{event.date} · {event.time}
{event.venue}
{fmtPrice(event.price, currency)}
);
}
// Rich (default) — image-led card
return (
{e.currentTarget.style.transform="translateY(-3px)"; e.currentTarget.style.boxShadow="0 18px 40px rgba(0,0,0,0.3)"}}
onMouseLeave={e=>{e.currentTarget.style.transform=""; e.currentTarget.style.boxShadow=""}}
>

setLoaded(true)} style={{ width:"100%", height:"100%", objectFit:"cover", opacity:loaded?1:0, transition:"opacity .4s" }} />
{badge && (
{badge.label}
)}
{friends && event.friends > 0 && (
{DATA.friends.slice(0, Math.min(3, event.friends)).map((f, i) => (
{f.initial}
))}
{event.friends} going
)}
{event.tag}
{event.title}
{event.date} · {event.time}
{event.venue}
{event.price === 0 ? "FREE" : `from ${fmtPrice(event.price, currency)}`}
Book
);
};
// Bottom navigation (mobile)
const BottomNav = ({ active, onNav }) => {
const items = [
{ id:"home", label:"Home", icon:"home" },
{ id:"search", label:"Discover", icon:"search" },
{ id:"map", label:"Nearby", icon:"map" },
{ id:"tickets", label:"Tickets", icon:"ticket" },
{ id:"profile", label:"You", icon:"user" },
];
return (
{items.map(item => {
const Icon = I[item.icon];
const isActive = active === item.id;
return (
);
})}
);
};
// Top app bar (mobile)
const MobileTopBar = ({ city, onCityClick, onAI }) => (
);
// Desktop nav
const DesktopNav = ({ active, onNav, onAI }) => (
{[
{ id:"home", label:"Discover" },
{ id:"search", label:"Browse" },
{ id:"map", label:"Map" },
{ id:"organiser", label:"For Organisers" },
].map(it => (
))}
);
// Section header
const Section = ({ title, eyebrow, action, onAction, children, dense }) => (
{eyebrow &&
{eyebrow}
}
{title}
{action &&
}
{children}
);
window.fmtPrice = fmtPrice;
window.BADGES = BADGES;
window.Marquee = Marquee;
window.Logo = Logo;
window.EventCard = EventCard;
window.BottomNav = BottomNav;
window.MobileTopBar = MobileTopBar;
window.DesktopNav = DesktopNav;
window.Section = Section;