{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "event-detail",
  "version": "3.1.1",
  "category": "events",
  "meta": {
    "preview": "https://ui.manifest.build/previews/event-detail.png",
    "version": "3.1.1",
    "changelog": {
      "1.0.0": "Initial release with image carousel, organizer info, location, policies, FAQs, and ticket purchase.",
      "2.0.0": "BREAKING: Updated to use simplified Event interface with display-formatted dateTime and string ticketTiers",
      "2.1.0": "Moved Get Tickets CTA from sticky bottom to end of content for fullscreen mode.",
      "2.2.0": "Changed CTA buttons and map tooltip from orange to dark theme colors",
      "3.0.0": "BREAKING: Removed id from EventDetails, Organizer, and tier interfaces. Use array index for key.",
      "3.0.1": "Added aria-labels to image navigation, share, save, and back buttons for accessibility",
      "3.0.2": "Moved demo data to separate file for cleaner component code",
      "3.0.3": "Added comprehensive JSDoc documentation",
      "3.0.4": "Removed Next.js dependency - now uses React-only lazy loading for map components",
      "3.0.5": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
      "3.0.6": "Fixed defensive property access to handle empty objects and null values",
      "3.0.7": "Added types.ts to registry for proper installation",
      "3.0.8": "Added demo/data.ts to registry for proper installation via shadcn CLI",
      "3.0.9": "Removed default content data - component only renders explicitly provided data",
      "3.0.10": "Extracted shared map utilities to shared.tsx and fixed Leaflet CSS deduplication",
      "3.1.0": "Added demo data defaults - component renders demo content when no data prop is provided",
      "3.1.1": "Fixed react-leaflet Invalid hook call errors by using React.lazy instead of useEffect dynamic imports"
    }
  },
  "changelog": {
    "1.0.0": "Initial release with image carousel, organizer info, location, policies, FAQs, and ticket purchase.",
    "2.0.0": "BREAKING: Updated to use simplified Event interface with display-formatted dateTime and string ticketTiers",
    "2.1.0": "Moved Get Tickets CTA from sticky bottom to end of content for fullscreen mode.",
    "2.2.0": "Changed CTA buttons and map tooltip from orange to dark theme colors",
    "3.0.0": "BREAKING: Removed id from EventDetails, Organizer, and tier interfaces. Use array index for key.",
    "3.0.1": "Added aria-labels to image navigation, share, save, and back buttons for accessibility",
    "3.0.2": "Moved demo data to separate file for cleaner component code",
    "3.0.3": "Added comprehensive JSDoc documentation",
    "3.0.4": "Removed Next.js dependency - now uses React-only lazy loading for map components",
    "3.0.5": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
    "3.0.6": "Fixed defensive property access to handle empty objects and null values",
    "3.0.7": "Added types.ts to registry for proper installation",
    "3.0.8": "Added demo/data.ts to registry for proper installation via shadcn CLI",
    "3.0.9": "Removed default content data - component only renders explicitly provided data",
    "3.0.10": "Extracted shared map utilities to shared.tsx and fixed Leaflet CSS deduplication",
    "3.1.0": "Added demo data defaults - component renders demo content when no data prop is provided",
    "3.1.1": "Fixed react-leaflet Invalid hook call errors by using React.lazy instead of useEffect dynamic imports"
  },
  "title": "Event Detail",
  "author": "MNFST, Inc",
  "description": "Full event detail view with organizer info, interactive map, policies, and ticket purchase. Fullwidth mode only.",
  "dependencies": [
    "lucide-react",
    "react-leaflet",
    "leaflet"
  ],
  "devDependencies": [
    "@types/leaflet"
  ],
  "registryDependencies": [
    "button",
    "https://ui.manifest.build/r/manifest-types.json",
    "https://ui.manifest.build/r/event-shared.json"
  ],
  "files": [
    {
      "path": "registry/events/event-detail.tsx",
      "content": "'use client'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\nimport {\n  MapPin,\n  Star,\n  Share2,\n  Heart,\n  ChevronLeft,\n  ChevronRight,\n  CheckCircle,\n  Car,\n  Train,\n  Bike,\n  Footprints,\n  Flag,\n  BadgeCheck,\n  Timer\n} from 'lucide-react'\nimport { Suspense, useState } from 'react'\nimport type { EventDetails } from './types'\nimport {\n  LazyLeafletMap,\n  formatNumber,\n  MapPlaceholder,\n  EventSignalBadge\n} from './shared'\nimport { demoEventDetails } from './demo/events'\n\n// Format date for display\nfunction formatEventDateTime(startDateTime: string, endDateTime?: string): string {\n  const start = new Date(startDateTime)\n  const now = new Date()\n  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n  const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000)\n  const startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate())\n\n  const timeOptions: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: '2-digit', hour12: true }\n  const startTime = start.toLocaleTimeString('en-US', timeOptions)\n\n  let datePrefix: string\n  if (startDay.getTime() === today.getTime()) {\n    datePrefix = 'Today'\n  } else if (startDay.getTime() === tomorrow.getTime()) {\n    datePrefix = 'Tomorrow'\n  } else {\n    datePrefix = start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })\n  }\n\n  if (endDateTime) {\n    const end = new Date(endDateTime)\n    const endTime = end.toLocaleTimeString('en-US', timeOptions)\n    return `${datePrefix} · ${startTime} - ${endTime}`\n  }\n\n  return `${datePrefix} · ${startTime}`\n}\n\n\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * EventDetailProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for the EventDetail component. Displays comprehensive event information\n * including image carousel, location map, organizer details, and ticket actions.\n */\nexport interface EventDetailProps {\n  data?: {\n    /** The full event details object to display. */\n    event?: EventDetails\n  }\n  actions?: {\n    /** Called when \"Get tickets\" button is clicked. */\n    onGetTickets?: (event: EventDetails) => void\n    /** Called when share button is clicked. */\n    onShare?: (event: EventDetails) => void\n    /** Called when save/heart button is clicked. */\n    onSave?: (event: EventDetails) => void\n    /** Called when back navigation button is clicked. */\n    onBack?: () => void\n    /** Called when \"Follow\" organizer button is clicked. */\n    onFollow?: (organizer: EventDetails['organizer']) => void\n    /** Called when \"Contact\" organizer button is clicked. */\n    onContact?: (organizer: EventDetails['organizer']) => void\n  }\n  appearance?: {\n    /**\n     * Whether to show the AI match explanation section.\n     * @default true\n     */\n    showAiMatch?: boolean\n    /**\n     * Whether to show the interactive map.\n     * @default true\n     */\n    showMap?: boolean\n  }\n}\n\nexport function EventDetail({ data, actions, appearance }: EventDetailProps) {\n  const resolved: NonNullable<EventDetailProps['data']> = data ?? { event: demoEventDetails }\n  const event = resolved.event\n  const onGetTickets = actions?.onGetTickets\n  const onShare = actions?.onShare\n  const onSave = actions?.onSave\n  const onBack = actions?.onBack\n  const onFollow = actions?.onFollow\n  const onContact = actions?.onContact\n  const showAiMatch = appearance?.showAiMatch ?? true\n  const showMap = appearance?.showMap ?? true\n\n  const [currentImageIndex, setCurrentImageIndex] = useState(0)\n  const [isSaved, setIsSaved] = useState(false)\n\n  if (!event) {\n    return null\n  }\n\n  const images = event.images?.length ? event.images : (event.image ? [event.image] : [])\n\n  const handlePrevImage = () => {\n    setCurrentImageIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1))\n  }\n\n  const handleNextImage = () => {\n    setCurrentImageIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1))\n  }\n\n  const handleSave = () => {\n    setIsSaved(!isSaved)\n    onSave?.(event)\n  }\n\n  return (\n    <div className=\"mx-auto max-w-lg bg-background\">\n      {/* Image Carousel */}\n      {images.length > 0 && (\n        <div className=\"relative aspect-[4/3] overflow-hidden bg-muted\">\n          <img\n            src={images[currentImageIndex]}\n            alt={event.title || 'Event image'}\n            className=\"h-full w-full object-cover\"\n          />\n\n          {/* Navigation overlay */}\n          <div className=\"absolute inset-0 flex items-center justify-between p-2\">\n            {images.length > 1 && (\n              <>\n                <button\n                  onClick={handlePrevImage}\n                  aria-label=\"Previous image\"\n                  className=\"rounded-full bg-black/50 p-2 text-white hover:bg-black/70\"\n                >\n                  <ChevronLeft className=\"h-5 w-5\" />\n                </button>\n                <button\n                  onClick={handleNextImage}\n                  aria-label=\"Next image\"\n                  className=\"rounded-full bg-black/50 p-2 text-white hover:bg-black/70\"\n                >\n                  <ChevronRight className=\"h-5 w-5\" />\n                </button>\n              </>\n            )}\n          </div>\n\n          {/* Top actions */}\n          <div className=\"absolute top-3 right-3 flex gap-2\">\n            <button\n              onClick={() => onShare?.(event)}\n              aria-label=\"Share event\"\n              className=\"rounded-full bg-white/90 p-2 shadow-sm hover:bg-white\"\n            >\n              <Share2 className=\"h-5 w-5\" />\n            </button>\n            <button\n              onClick={handleSave}\n              aria-label={isSaved ? 'Remove from saved' : 'Save event'}\n              className=\"rounded-full bg-white/90 p-2 shadow-sm hover:bg-white\"\n            >\n              <Heart className={cn('h-5 w-5', isSaved && 'fill-red-500 text-red-500')} />\n            </button>\n          </div>\n\n          {/* Back button */}\n          {onBack && (\n            <button\n              onClick={onBack}\n              aria-label=\"Go back\"\n              className=\"absolute top-3 left-3 rounded-full bg-black/50 p-2 text-white hover:bg-black/70\"\n            >\n              <ChevronLeft className=\"h-5 w-5\" />\n            </button>\n          )}\n\n          {/* Image counter */}\n          {images.length > 1 && (\n            <div className=\"absolute bottom-3 right-3 rounded-full bg-black/60 px-2.5 py-1 text-xs text-white\">\n              {currentImageIndex + 1} / {images.length}\n            </div>\n          )}\n        </div>\n      )}\n\n      <div className=\"space-y-6 p-4\">\n        {/* Signal Badge */}\n        {event.eventSignal && (\n          <div>\n            <EventSignalBadge signal={event.eventSignal} />\n          </div>\n        )}\n\n        {/* Category */}\n        {event.category && (\n          <span className=\"inline-block rounded-full bg-muted px-3 py-1 text-sm font-medium\">\n            {event.category}\n          </span>\n        )}\n\n        {/* Title */}\n        {event.title && (\n          <h1 className=\"text-2xl font-bold leading-tight\">{event.title}</h1>\n        )}\n\n        {/* Organizer + Rating */}\n        {event.organizer && (\n          <div className=\"flex items-center gap-2 text-sm\">\n            {event.organizer.verified && (\n              <BadgeCheck className=\"h-4 w-4 text-blue-500\" />\n            )}\n            {event.organizer.name && (\n              <span className=\"font-medium\">{event.organizer.name}</span>\n            )}\n            {(event.organizer.rating !== undefined || event.organizer.reviewCount !== undefined) && (\n              <>\n                <span className=\"text-muted-foreground\">·</span>\n                <span className=\"flex items-center gap-1\">\n                  <Star className=\"h-4 w-4 fill-current text-yellow-500\" />\n                  {event.organizer.rating !== undefined && event.organizer.rating}\n                  {event.organizer.reviewCount !== undefined && ` (${formatNumber(event.organizer.reviewCount)})`}\n                </span>\n              </>\n            )}\n          </div>\n        )}\n\n        {/* Venue + Location */}\n        {(event.venue_details?.name || event.venue || event.city) && (\n          <div className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n            <MapPin className=\"mt-0.5 h-4 w-4 shrink-0\" />\n            <span>\n              {[event.venue_details?.name || event.venue, event.city].filter(Boolean).join(' · ')}\n              {event.neighborhood && ` (${event.neighborhood})`}\n            </span>\n          </div>\n        )}\n\n        {/* Price + Attendees */}\n        <div className=\"flex items-center gap-4\">\n          {event.priceRange && (\n            <div>\n              <div className=\"text-lg font-semibold\">{event.priceRange}</div>\n            </div>\n          )}\n          {event.attendeesCount !== undefined && (\n            <div className=\"flex items-center gap-2\">\n              {event.friendsGoing && event.friendsGoing.length > 0 && (\n                <div className=\"flex -space-x-2\">\n                  {event.friendsGoing.slice(0, 3).map((friend) => (\n                    friend.avatar && (\n                      <img\n                        key={friend.name || friend.avatar}\n                        src={friend.avatar}\n                        alt={friend.name || 'Friend'}\n                        className=\"h-6 w-6 rounded-full border-2 border-background\"\n                      />\n                    )\n                  ))}\n                </div>\n              )}\n              <span className=\"text-sm text-muted-foreground\">\n                {event.friendsGoing && event.friendsGoing.length > 0 && `+ ${event.friendsGoing.length} friends · `}\n                {event.attendeesCount} going\n              </span>\n            </div>\n          )}\n        </div>\n\n        {/* Vibe Tags */}\n        {event.vibeTags && event.vibeTags.length > 0 && (\n          <div className=\"flex flex-wrap gap-2\">\n            {event.vibeTags.map((tag) => (\n              <span\n                key={tag}\n                className=\"rounded-full border px-3 py-1 text-sm\"\n              >\n                {tag}\n              </span>\n            ))}\n          </div>\n        )}\n\n        {/* CTA Buttons */}\n        <div className=\"flex gap-3\">\n          <Button\n            className=\"flex-1 bg-primary hover:bg-primary/90\"\n            onClick={() => onGetTickets?.(event)}\n          >\n            Get tickets\n          </Button>\n          <Button variant=\"outline\" className=\"flex-1\">\n            Invite friends\n          </Button>\n        </div>\n\n        {/* AI Match */}\n        {showAiMatch && event.aiSummary && (\n          <div className=\"rounded-lg bg-muted/50 p-4\">\n            <h3 className=\"font-semibold\">Why this matches your vibe</h3>\n            <p className=\"mt-1 text-sm text-muted-foreground\">{event.aiSummary}</p>\n          </div>\n        )}\n\n        {/* About */}\n        {event.description && (\n          <div>\n            <h2 className=\"text-lg font-semibold\">About</h2>\n            <p className=\"mt-2 text-sm text-muted-foreground\">{event.description}</p>\n          </div>\n        )}\n\n        {/* Lineup */}\n        {event.lineup && event.lineup.length > 0 && (\n          <div>\n            <h3 className=\"text-sm font-medium text-muted-foreground\">Lineup</h3>\n            <div className=\"mt-2 flex flex-wrap gap-2\">\n              {event.lineup.map((artist) => (\n                <span key={artist} className=\"rounded-full border px-3 py-1 text-sm\">\n                  {artist}\n                </span>\n              ))}\n            </div>\n          </div>\n        )}\n\n        {/* Good to Know */}\n        {event.goodToKnow && (\n          <div>\n            <h2 className=\"text-lg font-semibold\">Good to know</h2>\n            <div className=\"mt-3 grid grid-cols-2 gap-4\">\n              <div className=\"rounded-lg bg-muted/50 p-3\">\n                <h4 className=\"text-sm font-medium\">Highlights</h4>\n                <div className=\"mt-2 space-y-1.5 text-sm text-muted-foreground\">\n                  {event.goodToKnow.duration && (\n                    <div className=\"flex items-center gap-2\">\n                      <Timer className=\"h-4 w-4\" />\n                      {event.goodToKnow.duration}\n                    </div>\n                  )}\n                  {event.locationType !== 'online' && (\n                    <div className=\"flex items-center gap-2\">\n                      <MapPin className=\"h-4 w-4\" />\n                      In person\n                    </div>\n                  )}\n                </div>\n              </div>\n              {event.policies?.refund && (\n                <div className=\"rounded-lg bg-muted/50 p-3\">\n                  <h4 className=\"text-sm font-medium\">Refund Policy</h4>\n                  <p className=\"mt-2 text-sm text-muted-foreground\">\n                    {event.policies.refund}\n                  </p>\n                </div>\n              )}\n            </div>\n          </div>\n        )}\n\n        {/* Location */}\n        {event.venue_details && (\n          <div>\n            <h2 className=\"text-lg font-semibold\">Location</h2>\n            <div className=\"mt-3\">\n              {event.venue_details.name && (\n                <p className=\"font-medium\">{event.venue_details.name}</p>\n              )}\n              {event.venue_details.address && (\n                <p className=\"text-sm text-muted-foreground\">{event.venue_details.address}</p>\n              )}\n              {event.venue_details.city && (\n                <p className=\"text-sm text-muted-foreground\">{event.venue_details.city}</p>\n              )}\n            </div>\n\n            {showMap && event.venue_details.coordinates && (\n              <div className=\"mt-4 aspect-video overflow-hidden rounded-lg bg-muted\">\n                <Suspense fallback={<MapPlaceholder />}>\n                  <LazyLeafletMap\n                    center={[event.venue_details.coordinates.lat, event.venue_details.coordinates.lng]}\n                    zoom={15}\n                    scrollWheelZoom={false}\n                    renderMarkers={({ Marker, L }) => {\n                      const icon = L.divIcon({\n                        className: '',\n                        html: `<div style=\"\n                          position: absolute;\n                          left: 50%;\n                          top: 50%;\n                          transform: translate(-50%, -100%);\n                          display: flex;\n                          flex-direction: column;\n                          align-items: center;\n                        \">\n                          <div style=\"\n                            background-color: #18181b;\n                            color: white;\n                            padding: 6px 10px;\n                            border-radius: 8px;\n                            font-size: 12px;\n                            font-weight: 600;\n                            font-family: system-ui, -apple-system, sans-serif;\n                            white-space: nowrap;\n                            box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n                          \">${event.venue_details!.name}</div>\n                          <div style=\"\n                            width: 0;\n                            height: 0;\n                            border-left: 8px solid transparent;\n                            border-right: 8px solid transparent;\n                            border-top: 8px solid #18181b;\n                            margin-top: -1px;\n                          \"></div>\n                        </div>`,\n                        iconSize: [100, 40],\n                        iconAnchor: [50, 40]\n                      })\n                      return (\n                        <Marker\n                          position={[event.venue_details!.coordinates!.lat, event.venue_details!.coordinates!.lng]}\n                          icon={icon}\n                        />\n                      )\n                    }}\n                  />\n                </Suspense>\n              </div>\n            )}\n\n            <div className=\"mt-4\">\n              <p className=\"text-sm font-medium\">How do you want to get there?</p>\n              <div className=\"mt-2 space-y-2\">\n                {[\n                  { icon: Car, label: 'Driving' },\n                  { icon: Train, label: 'Public transport' },\n                  { icon: Bike, label: 'Biking' },\n                  { icon: Footprints, label: 'Walking' }\n                ].map(({ icon: Icon, label }) => (\n                  <button key={label} className=\"flex w-full items-center gap-3 rounded-lg p-2 hover:bg-muted\">\n                    <Icon className=\"h-5 w-5 text-muted-foreground\" />\n                    <span className=\"text-sm\">{label}</span>\n                  </button>\n                ))}\n              </div>\n            </div>\n          </div>\n        )}\n\n        {/* Organizer */}\n        {event.organizer && (\n          <div>\n            <h2 className=\"text-lg font-semibold\">Organized by</h2>\n            <div className=\"mt-3 rounded-lg border p-4\">\n              <div className=\"flex items-center gap-3\">\n                {event.organizer.image ? (\n                  <img\n                    src={event.organizer.image}\n                    alt={event.organizer.name || 'Organizer'}\n                    className=\"h-12 w-12 rounded-full object-cover\"\n                  />\n                ) : event.organizer.name ? (\n                  <div className=\"flex h-12 w-12 items-center justify-center rounded-full bg-muted text-lg font-medium\">\n                    {event.organizer.name.charAt(0)}\n                  </div>\n                ) : null}\n                <div className=\"flex-1\">\n                  {event.organizer.name && (\n                    <p className=\"font-medium\">{event.organizer.name}</p>\n                  )}\n                  <div className=\"flex gap-4 text-xs text-muted-foreground\">\n                    {event.organizer.followers !== undefined && (\n                      <span>Followers<br /><strong>{formatNumber(event.organizer.followers)}</strong></span>\n                    )}\n                    {event.organizer.eventsCount !== undefined && (\n                      <span>Events<br /><strong>{event.organizer.eventsCount}</strong></span>\n                    )}\n                    {event.organizer.hostingYears !== undefined && (\n                      <span>Hosting<br /><strong>{event.organizer.hostingYears} yrs</strong></span>\n                    )}\n                  </div>\n                </div>\n              </div>\n              <div className=\"mt-3 flex gap-2\">\n                <Button variant=\"outline\" size=\"sm\" className=\"flex-1\" onClick={() => onContact?.(event.organizer)}>\n                  Contact\n                </Button>\n                <Button size=\"sm\" className=\"flex-1 bg-primary hover:bg-primary/90\" onClick={() => onFollow?.(event.organizer)}>\n                  Follow\n                </Button>\n              </div>\n              {(event.organizer.trackRecord || event.organizer.responseRate) && (\n                <div className=\"mt-3 flex flex-wrap gap-2 text-xs\">\n                  {event.organizer.trackRecord === 'great' && (\n                    <span className=\"flex items-center gap-1 text-green-600\">\n                      <CheckCircle className=\"h-3 w-3\" /> Great track record\n                    </span>\n                  )}\n                  {event.organizer.responseRate === 'very responsive' && (\n                    <span className=\"flex items-center gap-1 text-orange-600\">\n                      <CheckCircle className=\"h-3 w-3\" /> Very responsive\n                    </span>\n                  )}\n                </div>\n              )}\n            </div>\n          </div>\n        )}\n\n        {/* Policies & Info */}\n        {event.policies && (\n          <div>\n            <h2 className=\"text-lg font-semibold\">Policies & Info</h2>\n            <div className=\"mt-3 flex flex-wrap gap-2\">\n              {event.goodToKnow?.ageRestriction && (\n                <span className=\"rounded-full border px-3 py-1 text-sm\">{event.goodToKnow.ageRestriction}</span>\n              )}\n              {event.policies.idRequired && (\n                <span className=\"rounded-full border px-3 py-1 text-sm\">ID checks</span>\n              )}\n              {event.policies.securityOnSite && (\n                <span className=\"rounded-full border px-3 py-1 text-sm\">Security on site</span>\n              )}\n            </div>\n          </div>\n        )}\n\n        {/* FAQs */}\n        {event.faq && event.faq.length > 0 && (\n          <div>\n            <h2 className=\"text-lg font-semibold\">FAQs</h2>\n            <div className=\"mt-3 space-y-2\">\n              {event.faq.map((item) => (\n                <div key={item.question} className=\"text-sm\">\n                  <p><strong>{item.question.replace('?', '')}:</strong> {item.answer}</p>\n                </div>\n              ))}\n            </div>\n          </div>\n        )}\n\n        {/* Report */}\n        <button className=\"flex items-center gap-2 text-sm text-blue-600 hover:underline\">\n          <Flag className=\"h-4 w-4\" />\n          Report this event\n        </button>\n\n        {/* Related Tags */}\n        {event.relatedTags && event.relatedTags.length > 0 && (\n          <div>\n            <h2 className=\"text-lg font-semibold\">Related to this event</h2>\n            <div className=\"mt-3 flex flex-wrap gap-2\">\n              {event.relatedTags.map((tag) => (\n                <span key={tag} className=\"rounded-full border px-3 py-1 text-sm hover:bg-muted cursor-pointer\">\n                  {tag}\n                </span>\n              ))}\n            </div>\n          </div>\n        )}\n\n        {/* Bottom CTA */}\n        <div className=\"mt-6 rounded-lg border bg-muted/30 p-4\">\n          <div className=\"flex items-center justify-between gap-4\">\n            <div>\n              {event.priceRange && <p className=\"font-semibold\">{event.priceRange}</p>}\n              {event.startDateTime && <p className=\"text-sm text-muted-foreground\">{formatEventDateTime(event.startDateTime, event.endDateTime)}</p>}\n            </div>\n            <Button\n              className=\"bg-primary hover:bg-primary/90\"\n              onClick={() => onGetTickets?.(event)}\n            >\n              Get tickets\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/event-detail.tsx"
    },
    {
      "path": "registry/events/demo/events.ts",
      "content": "// Demo data for Events category components\n// This file contains sample data used for component previews and documentation\n\nimport type { Event, EventDetails } from '../types'\n\n// Helper to generate dates relative to today\nfunction getDateAt(daysFromNow: number, hour: number): string {\n  const date = new Date()\n  date.setDate(date.getDate() + daysFromNow)\n  date.setHours(hour, 0, 0, 0)\n  return date.toISOString()\n}\n\n// Single event for EventCard default\nexport const demoEvent: Event = {\n  title: 'NEON Vol. 9',\n  category: 'Music',\n  venue: 'Echoplex',\n  neighborhood: 'Echo Park',\n  city: 'Los Angeles',\n  dateTime: 'Tonight 9:00 PM - 3:00 AM',\n  priceRange: '$45 - $150',\n  image: 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=800',\n  vibeTags: ['High energy', 'Late night', 'Dressy'],\n  vibeDescription:\n    'Immersive electronic experience with world-class DJs and stunning visuals.',\n  aiSummary:\n    \"Immersive electronic night with world-class DJs and stunning visuals at LA's top-rated venue.\",\n  lineup: ['DJ Shadow', 'Bonobo', 'Four Tet', 'Caribou'],\n  ticketTiers: [\n    'General Admission $45',\n    'VIP Access $120',\n    'Backstage Pass $150'\n  ],\n  eventSignal: 'going-fast',\n  organizerRating: 4.8,\n  reviewCount: 12453,\n  venueRating: 4.8,\n  ageRestriction: '21+',\n  hasMultipleDates: true\n}\n\n// 15 events for EventList default\nexport const demoEvents: Event[] = [\n  {\n    title: 'NEON Vol. 9',\n    category: 'Music',\n    venue: 'Echoplex',\n    neighborhood: 'Echo Park',\n    city: 'Los Angeles',\n    dateTime: 'Tonight 9:00 PM - 3:00 AM',\n    priceRange: '$45 - $150',\n    image: 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=800',\n    coordinates: { lat: 34.0781, lng: -118.2606 },\n    vibeTags: ['High energy', 'Late night'],\n    eventSignal: 'going-fast',\n    organizerRating: 4.8,\n    reviewCount: 12453,\n    ageRestriction: '21+'\n  },\n  {\n    title: 'The Midnight Show',\n    category: 'Comedy',\n    venue: 'The Comedy Underground',\n    neighborhood: 'Santa Monica',\n    city: 'Los Angeles',\n    dateTime: 'Tonight 10:00 PM - 12:00 AM',\n    priceRange: '$15 - $35',\n    image: 'https://images.unsplash.com/photo-1585699324551-f6c309eedeca?w=800',\n    coordinates: { lat: 34.0195, lng: -118.4912 },\n    vibeTags: ['Social', 'Late night'],\n    eventSignal: 'popular',\n    organizerRating: 4.7,\n    reviewCount: 3241,\n    discount: 'TONIGHT ONLY - 40% OFF'\n  },\n  {\n    title: 'Salsa Sundays @ Echo Park',\n    category: 'Classes',\n    venue: 'Echo Park Lake',\n    neighborhood: 'Echo Park',\n    city: 'Los Angeles',\n    dateTime: 'Saturday 6:00 PM - 10:00 PM',\n    priceRange: 'Free',\n    image: 'https://images.unsplash.com/photo-1504609813442-a8924e83f76e?w=800',\n    coordinates: { lat: 34.0731, lng: -118.2608 },\n    vibeTags: ['High energy', 'Social'],\n    eventSignal: 'just-added',\n    organizerRating: 4.9,\n    reviewCount: 8764\n  },\n  {\n    title: 'Dawn Flow: Griffith Park',\n    category: 'Classes',\n    venue: 'Griffith Park',\n    neighborhood: 'Los Feliz',\n    city: 'Los Angeles',\n    dateTime: 'Tomorrow 6:00 AM - 8:00 AM',\n    priceRange: 'Free',\n    image: 'https://images.unsplash.com/photo-1545205597-3d9d02c29597?w=800',\n    coordinates: { lat: 34.1365, lng: -118.2943 },\n    vibeTags: ['Chill', 'Wellness', 'Outdoor'],\n    organizerRating: 4.9,\n    reviewCount: 8764,\n    discount: 'FREE - First 50 Only'\n  },\n  {\n    title: 'Lakers vs Celtics',\n    category: 'Sports',\n    venue: 'Crypto.com Arena',\n    neighborhood: 'Downtown',\n    city: 'Los Angeles',\n    dateTime: 'Friday 7:30 PM - 10:30 PM',\n    priceRange: '$125 - $850',\n    image: 'https://images.unsplash.com/photo-1546519638-68e109498ffc?w=800',\n    coordinates: { lat: 34.0430, lng: -118.2673 },\n    vibeTags: ['High energy', 'Social', 'Premium'],\n    eventSignal: 'sales-end-soon',\n    organizerRating: 4.5,\n    reviewCount: 2341\n  },\n  {\n    title: 'Smorgasburg LA: Sunday Market',\n    category: 'Food & Drink',\n    venue: 'ROW DTLA',\n    neighborhood: 'Arts District',\n    city: 'Los Angeles',\n    dateTime: 'Sunday 10:00 AM - 4:00 PM',\n    priceRange: 'Free',\n    image: 'https://images.unsplash.com/photo-1555939594-58d7cb561ad1?w=800',\n    coordinates: { lat: 34.0341, lng: -118.2324 },\n    vibeTags: ['Family-friendly', 'Outdoor', 'Social'],\n    organizerRating: 4.8,\n    reviewCount: 5632\n  },\n  {\n    title: 'LACMA After Hours',\n    category: 'Arts',\n    venue: 'LACMA',\n    neighborhood: 'Miracle Mile',\n    city: 'Los Angeles',\n    dateTime: 'Friday 7:00 PM - 11:00 PM',\n    priceRange: '$35 - $75',\n    image: 'https://images.unsplash.com/photo-1531243269054-5ebf6f34081e?w=800',\n    coordinates: { lat: 34.0639, lng: -118.3592 },\n    vibeTags: ['Chill', 'Date night', 'Sophisticated'],\n    organizerRating: 4.7,\n    reviewCount: 1234,\n    ageRestriction: '21+',\n    discount: 'MEMBER PRICE'\n  },\n  {\n    title: 'Blue Note Under Stars',\n    category: 'Music',\n    venue: 'Hollywood Bowl',\n    neighborhood: 'Hollywood Hills',\n    city: 'Los Angeles',\n    dateTime: 'Saturday 8:00 PM - 11:00 PM',\n    priceRange: '$45 - $200',\n    image: 'https://images.unsplash.com/photo-1514320291840-2e0a9bf2a9ae?w=800',\n    coordinates: { lat: 34.1122, lng: -118.3391 },\n    vibeTags: ['Chill', 'Date night', 'Outdoor'],\n    lineup: ['Kamasi Washington', 'Thundercat', 'Terrace Martin'],\n    organizerRating: 4.8,\n    reviewCount: 12453\n  },\n  {\n    title: 'Meraki: Seth Troxler',\n    category: 'Nightlife',\n    venue: 'Sound Nightclub',\n    neighborhood: 'Hollywood',\n    city: 'Los Angeles',\n    dateTime: 'Saturday 10:00 PM - 4:00 AM',\n    priceRange: '$35 - $65',\n    image: 'https://images.unsplash.com/photo-1571266028243-e4733b0f0bb0?w=800',\n    coordinates: { lat: 34.0928, lng: -118.3287 },\n    vibeTags: ['High energy', 'Late night', 'Underground'],\n    lineup: ['Amelie Lens', 'I Hate Models', 'FJAAK'],\n    organizerRating: 4.6,\n    reviewCount: 1876,\n    ageRestriction: '21+'\n  },\n  {\n    title: 'Whitney Cummings + Friends',\n    category: 'Comedy',\n    venue: 'The Laugh Factory',\n    neighborhood: 'Hollywood',\n    city: 'Los Angeles',\n    dateTime: 'In 2 days 8:00 PM - 11:00 PM',\n    priceRange: '$25 - $55',\n    image: 'https://images.unsplash.com/photo-1527224538127-2104bb71c51b?w=800',\n    coordinates: { lat: 34.0901, lng: -118.3615 },\n    vibeTags: ['Chill', 'Social', 'Date night'],\n    organizerRating: 4.7,\n    reviewCount: 3241,\n    ageRestriction: '18+'\n  },\n  {\n    title: 'Venice Beach Drum Circle',\n    category: 'Music',\n    venue: 'Venice Beach Boardwalk',\n    neighborhood: 'Venice',\n    city: 'Los Angeles',\n    dateTime: 'Sunday 4:00 PM - 8:00 PM',\n    priceRange: 'Free',\n    image: 'https://images.unsplash.com/photo-1506157786151-b8491531f063?w=800',\n    coordinates: { lat: 33.9850, lng: -118.4695 },\n    vibeTags: ['Outdoor', 'Social', 'Chill'],\n    eventSignal: 'popular',\n    organizerRating: 4.6,\n    reviewCount: 2145\n  },\n  {\n    title: 'Rooftop Cinema: Blade Runner',\n    category: 'Film',\n    venue: 'Rooftop Cinema Club',\n    neighborhood: 'DTLA',\n    city: 'Los Angeles',\n    dateTime: 'Friday 8:30 PM - 11:00 PM',\n    priceRange: '$25 - $45',\n    image: 'https://images.unsplash.com/photo-1489599849927-2ee91cede3ba?w=800',\n    coordinates: { lat: 34.0407, lng: -118.2468 },\n    vibeTags: ['Date night', 'Views', 'Chill'],\n    organizerRating: 4.8,\n    reviewCount: 892\n  },\n  {\n    title: 'Dodgers vs Giants',\n    category: 'Sports',\n    venue: 'Dodger Stadium',\n    neighborhood: 'Elysian Park',\n    city: 'Los Angeles',\n    dateTime: 'Saturday 1:10 PM - 4:30 PM',\n    priceRange: '$35 - $350',\n    image: 'https://images.unsplash.com/photo-1566577739112-5180d4bf9390?w=800',\n    coordinates: { lat: 34.0739, lng: -118.2400 },\n    vibeTags: ['Family-friendly', 'Social', 'High energy'],\n    eventSignal: 'few-tickets-left',\n    organizerRating: 4.7,\n    reviewCount: 15678\n  },\n  {\n    title: 'Natural Wine Fair',\n    category: 'Food & Drink',\n    venue: 'Grand Central Market',\n    neighborhood: 'Downtown',\n    city: 'Los Angeles',\n    dateTime: 'Sunday 12:00 PM - 6:00 PM',\n    priceRange: '$45 - $85',\n    image: 'https://images.unsplash.com/photo-1510812431401-41d2bd2722f3?w=800',\n    coordinates: { lat: 34.0508, lng: -118.2490 },\n    vibeTags: ['Tasting', 'Social', 'Sophisticated'],\n    eventSignal: 'just-added',\n    organizerRating: 4.5,\n    reviewCount: 567,\n    ageRestriction: '21+'\n  },\n  {\n    title: 'Meditation in the Gardens',\n    category: 'Wellness',\n    venue: 'The Getty Center',\n    neighborhood: 'Brentwood',\n    city: 'Los Angeles',\n    dateTime: 'Sunday 7:00 AM - 9:00 AM',\n    priceRange: 'Free',\n    image: 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=800',\n    coordinates: { lat: 34.0780, lng: -118.4741 },\n    vibeTags: ['Wellness', 'Outdoor', 'Chill'],\n    organizerRating: 4.9,\n    reviewCount: 1234\n  }\n]\n\n// Detailed event for EventDetail default\nexport const demoEventDetails: EventDetails = {\n  title: 'Sunglasses at Night: Underground Techno',\n  category: 'Nightlife',\n  venue: 'The White Rabbit',\n  neighborhood: 'The Woodlands',\n  city: 'Houston, TX',\n  startDateTime: getDateAt(2, 22),\n  endDateTime: getDateAt(3, 4),\n  priceRange: '$15 - $30',\n  images: [\n    'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=800',\n    'https://images.unsplash.com/photo-1514525253161-7a46d19cd819?w=800',\n    'https://images.unsplash.com/photo-1533174072545-7a4b6ad7a6c3?w=800'\n  ],\n  vibeTags: ['High energy', 'Late night', 'Underground'],\n  eventSignal: 'going-fast',\n  aiSummary: 'Raw, unfiltered techno in an authentic warehouse setting.',\n  description:\n    'Experience the raw energy of underground techno. Industrial beats, immersive visuals, and a crowd that lives for the music.',\n  lineup: ['Amelie Lens', 'I Hate Models', 'FJAAK'],\n  attendeesCount: 537,\n  friendsGoing: [\n    { name: 'Alex', avatar: 'https://i.pravatar.cc/40?u=alex' },\n    { name: 'Sam', avatar: 'https://i.pravatar.cc/40?u=sam' }\n  ],\n  organizer: {\n    name: 'Midnight Lovers',\n    image: 'https://i.pravatar.cc/80?u=midnight',\n    rating: 4.5,\n    reviewCount: 1067,\n    verified: true,\n    followers: 1200,\n    eventsCount: 154,\n    hostingYears: 8,\n    trackRecord: 'great',\n    responseRate: 'very responsive'\n  },\n  venue_details: {\n    name: 'The White Rabbit',\n    address: '8827 Nasher Ave',\n    city: 'Houston TX',\n    coordinates: { lat: 29.7604, lng: -95.3698 }\n  },\n  tiers: [\n    { name: 'General Admission', price: 15, available: 50 },\n    {\n      name: 'VIP Access',\n      price: 30,\n      available: 20,\n      benefits: ['Skip the line', 'Exclusive lounge']\n    }\n  ],\n  goodToKnow: {\n    duration: '2 hours',\n    doorsOpen: '7:00 PM',\n    showtime: '7:30 PM',\n    ageRestriction: '21+',\n    dressCode: 'Casual',\n    parking: 'Limited, leave early to avoid long queues'\n  },\n  policies: {\n    refund: 'No refunds. Tickets are transferable.',\n    entry: 'Open 2 hours before event',\n    idRequired: true,\n    securityOnSite: true\n  },\n  faq: [\n    {\n      question: 'What is the refund policy?',\n      answer: 'No refunds. Tickets are transferable.'\n    },\n    {\n      question: 'When do doors open?',\n      answer: 'Open 2 hours before event.'\n    },\n    {\n      question: 'Is there parking?',\n      answer: 'Limited, leave early to avoid long queues.'\n    }\n  ],\n  relatedTags: ['Houston Events', 'Texas Nightlife', 'Techno Parties']\n}\n\n// Ticket tiers for TicketTierSelect\nexport const demoTicketTiers = [\n  {\n    id: '1',\n    name: 'General Admission',\n    price: 45,\n    fee: 5,\n    available: 100,\n    maxPerOrder: 10,\n  },\n  {\n    id: '2',\n    name: 'VIP',\n    price: 150,\n    fee: 15,\n    available: 20,\n    maxPerOrder: 4,\n    description: 'Includes backstage access',\n  },\n]\n\n// Event confirmation data\nexport const demoEventConfirmation = {\n  orderNumber: 'EVT-12345',\n  eventTitle: 'Summer Music Festival',\n  ticketCount: 2,\n  recipientEmail: 'customer@example.com',\n  eventDate: 'Jan 20, 2024',\n  eventLocation: 'Central Park, New York',\n  organizer: {\n    name: 'Live Nation',\n  },\n}\n",
      "type": "registry:lib",
      "target": "components/ui/demo/events.ts"
    }
  ],
  "categories": [
    "events"
  ],
  "type": "registry:block"
}