/* 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 }) => (
TT
TicketTape
); // 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;