feat: Complete platform enhancement with multi-tenant architecture

Major additions:
- Territory manager system with application workflow
- Custom pricing and page builder with Craft.js
- Enhanced Stripe Connect onboarding
- CodeReadr QR scanning integration
- Kiosk mode for venue sales
- Super admin dashboard and analytics
- MCP integration for AI-powered operations

Infrastructure improvements:
- Centralized API client and routing system
- Enhanced authentication with organization context
- Comprehensive theme management system
- Advanced event management with custom tabs
- Performance monitoring and accessibility features

Database schema updates:
- Territory management tables
- Custom pages and pricing structures
- Kiosk PIN system
- Enhanced organization profiles
- CodeReadr integration tables

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-12 18:21:40 -06:00
parent a02d64a86c
commit 26a87d0d00
232 changed files with 33175 additions and 5365 deletions

View File

@@ -1,6 +1,13 @@
---
import Layout from '../layouts/Layout.astro';
import PublicHeader from '../components/PublicHeader.astro';
import { verifyAuth } from '../lib/auth';
// Enable server-side rendering for auth checks
export const prerender = false;
// Optional authentication check (calendar is public)
const auth = await verifyAuth(Astro.request);
// Get query parameters for filtering
const url = new URL(Astro.request.url);
@@ -15,21 +22,36 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<Layout title="Event Calendar - Black Canyon Tickets">
<div class="min-h-screen">
<!-- Hero Section with Dynamic Background -->
<section class="relative overflow-hidden bg-gradient-to-br from-indigo-900 via-purple-900 to-slate-900">
<section class="relative overflow-hidden" style="background: var(--bg-gradient);">
<PublicHeader showCalendarNav={true} />
<!-- Theme Toggle -->
<div class="fixed top-20 right-4 z-50">
<button
id="theme-toggle"
class="p-3 rounded-full backdrop-blur-lg transition-all duration-200 hover:scale-110 shadow-lg"
style="background: var(--glass-bg-button); border: 1px solid var(--glass-border);"
aria-label="Toggle theme"
>
<svg class="w-5 h-5" style="color: var(--glass-text-primary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
</div>
<!-- Animated Background Elements -->
<div class="absolute inset-0 opacity-20">
<div class="absolute top-20 left-20 w-64 h-64 bg-gradient-to-br from-blue-400 to-purple-500 rounded-full blur-3xl animate-pulse"></div>
<div class="absolute bottom-20 right-20 w-96 h-96 bg-gradient-to-br from-purple-400 to-pink-500 rounded-full blur-3xl animate-pulse delay-1000"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-gradient-to-br from-cyan-400 to-blue-500 rounded-full blur-3xl animate-pulse delay-500"></div>
<div class="absolute top-20 left-20 w-64 h-64 rounded-full blur-3xl animate-pulse" style="background: var(--bg-orb-1);"></div>
<div class="absolute bottom-20 right-20 w-96 h-96 rounded-full blur-3xl animate-pulse delay-1000" style="background: var(--bg-orb-2);"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-80 h-80 rounded-full blur-3xl animate-pulse delay-500" style="background: var(--bg-orb-3);"></div>
</div>
<!-- Geometric Patterns -->
<div class="absolute inset-0 opacity-10">
<svg class="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
<div class="absolute inset-0" style="opacity: var(--grid-opacity, 0.1);">
<svg class="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice">
<defs>
<pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="white" stroke-width="0.5"/>
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="currentColor" stroke-width="0.5" style="color: var(--grid-pattern);"/>
</pattern>
</defs>
<rect width="100" height="100" fill="url(#grid)" />
@@ -39,32 +61,32 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-32 pb-24 lg:pt-40 lg:pb-32">
<div class="text-center">
<!-- Badge -->
<div class="inline-flex items-center px-4 py-2 rounded-full bg-white/10 backdrop-blur-lg border border-white/20 mb-8">
<span class="text-sm font-medium text-white/90">✨ Discover Extraordinary Events</span>
<div class="inline-flex items-center px-4 py-2 rounded-full backdrop-blur-lg mb-8" style="background: var(--glass-bg); border: 1px solid var(--glass-border);">
<span class="text-sm font-medium" style="color: var(--glass-text-secondary);">✨ Discover Extraordinary Events</span>
</div>
<!-- Main Heading -->
<h1 class="text-5xl lg:text-7xl font-light text-white mb-6 tracking-tight">
<h1 class="text-5xl lg:text-7xl font-light mb-6 tracking-tight" style="color: var(--glass-text-primary);">
Event
<span class="font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
<span class="font-bold" style="color: var(--glass-text-accent);">
Calendar
</span>
</h1>
<!-- Subheading -->
<p class="text-xl lg:text-2xl text-white/80 mb-8 max-w-3xl mx-auto leading-relaxed">
<p class="text-xl lg:text-2xl mb-8 max-w-3xl mx-auto leading-relaxed" style="color: var(--glass-text-secondary);">
Curated experiences in Colorado's most prestigious venues. From intimate galas to grand celebrations.
</p>
<!-- Location Detection -->
<div class="max-w-md mx-auto mb-8">
<div id="location-detector" class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 transition-all duration-300">
<div id="location-detector" class="backdrop-blur-xl rounded-xl p-4 transition-all duration-300" style="background: var(--glass-bg-lg); border: 1px solid var(--glass-border);">
<div id="location-status" class="flex items-center justify-center space-x-2">
<svg class="w-5 h-5 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-5 h-5" style="color: var(--glass-text-secondary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
<button id="enable-location" class="text-white/90 hover:text-white font-medium">
<button id="enable-location" class="font-medium" style="color: var(--glass-text-secondary);">
Enable location for personalized events
</button>
</div>
@@ -74,23 +96,26 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<!-- Advanced Search Bar -->
<div class="max-w-2xl mx-auto">
<div class="relative group">
<div class="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-600 rounded-2xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
<div class="relative bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-2 flex items-center space-x-2">
<div class="flex-1 flex items-center space-x-3 px-4">
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="absolute inset-0 rounded-2xl blur opacity-20 group-hover:opacity-30 transition-opacity" style="background: linear-gradient(to right, rgb(37, 99, 235), rgb(147, 51, 234));"></div>
<div class="relative backdrop-blur-xl rounded-2xl p-2 flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:gap-2" style="background: var(--glass-bg-lg); border: 1px solid var(--glass-border);">
<div class="flex-1 flex items-center space-x-3 px-3 sm:px-4">
<svg class="w-4 h-4 sm:w-5 sm:h-5 flex-shrink-0" style="color: var(--glass-text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
<input
type="text"
id="search-input"
placeholder="Search events, venues, or organizers..."
class="bg-transparent text-white placeholder-white/60 focus:outline-none flex-1 text-lg"
class="bg-transparent focus:outline-none flex-1 text-base sm:text-lg py-2 sm:py-0"
style="color: var(--glass-text-primary);"
placeholder="Search events, venues, or organizers..."
value={search || ''}
/>
</div>
<button
id="search-btn"
class="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-200 shadow-lg hover:shadow-xl"
class="bg-gradient-to-r px-6 sm:px-8 py-3 rounded-xl font-semibold transition-all duration-200 shadow-lg hover:shadow-xl touch-manipulation min-h-[44px] text-sm sm:text-base"
style="background: linear-gradient(to right, rgb(37, 99, 235), rgb(147, 51, 234)); color: var(--glass-text-primary);"
>
Search
</button>
@@ -102,14 +127,14 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
</section>
<!-- What's Hot Section -->
<section id="whats-hot-section" class="py-8 bg-gradient-to-br from-gray-50 to-gray-100 hidden">
<section id="whats-hot-section" class="py-8 hidden" style="background: var(--ui-bg-secondary);">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center space-x-2">
<span class="text-2xl">🔥</span>
<h2 class="text-2xl font-bold text-gray-900">What's Hot Near You</h2>
<h2 class="text-2xl font-bold" style="color: var(--ui-text-primary);">What's Hot Near You</h2>
</div>
<span id="hot-location-text" class="text-sm text-gray-600"></span>
<span id="hot-location-text" class="text-sm" style="color: var(--ui-text-secondary);"></span>
</div>
<div id="hot-events-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- Hot events will be populated here -->
@@ -117,28 +142,30 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
</div>
</section>
<!-- Filter Controls -->
<section class="sticky top-0 z-50 bg-white/95 backdrop-blur-xl border-b border-gray-200/50 shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3 md:py-4">
<div class="flex flex-wrap items-center justify-between gap-2 md:gap-4">
<!-- View Toggle -->
<div class="flex items-center space-x-2">
<span class="text-sm font-medium text-gray-700">View:</span>
<div class="bg-gray-100 rounded-lg p-1 flex border border-gray-200">
<!-- Premium Filter Controls -->
<section class="sticky top-0 z-50 backdrop-blur-xl shadow-2xl" style="background: var(--glass-bg-lg); border-bottom: 1px solid var(--glass-border);">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4">
<!-- View Toggle - Premium Design -->
<div class="flex items-center space-x-4">
<span class="text-sm font-semibold tracking-wide" style="color: var(--glass-text-secondary);">VIEW</span>
<div class="flex rounded-xl p-1 shadow-lg" style="background: var(--glass-bg-button); border: 1px solid var(--glass-border);">
<button
id="calendar-view-btn"
class="px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 bg-white shadow-sm text-gray-900"
class="px-6 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 flex items-center gap-2 hover:scale-105 shadow-sm"
style="background: var(--glass-bg-elevated); color: var(--glass-text-primary);"
>
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
Calendar
</button>
<button
id="list-view-btn"
class="px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 text-gray-600 hover:text-gray-900 hover:bg-gray-50"
class="px-6 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 flex items-center gap-2 hover:scale-105"
style="color: var(--glass-text-secondary);"
>
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
</svg>
List
@@ -146,31 +173,32 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
</div>
</div>
<!-- Advanced Filters -->
<div class="flex flex-wrap items-center space-x-4">
<!-- Premium Filters -->
<div class="flex flex-wrap items-center gap-3">
<!-- Location Display -->
<div id="location-display" class="hidden items-center space-x-2 bg-blue-50 px-3 py-1.5 rounded-lg">
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div id="location-display" class="hidden items-center space-x-2 px-4 py-2 rounded-xl shadow-lg transition-all duration-200" style="background: var(--glass-bg-button); border: 1px solid var(--glass-border);">
<svg class="w-4 h-4" style="color: var(--glass-text-accent);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
<span id="location-text" class="text-sm text-blue-800 font-medium"></span>
<button id="clear-location" class="text-blue-600 hover:text-blue-800 text-xs">×</button>
<span id="location-text" class="text-sm font-medium" style="color: var(--glass-text-accent);"></span>
<button id="clear-location" class="text-sm ml-2 px-2 py-1 rounded-full transition-all duration-200 hover:scale-110" style="color: var(--glass-text-accent); background: var(--glass-bg);">×</button>
</div>
<!-- Distance Filter -->
<div id="distance-filter" class="relative hidden">
<select
id="radius-filter"
class="appearance-none bg-white border border-gray-300 rounded-lg px-4 py-2 pr-8 text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="appearance-none rounded-xl px-4 py-2.5 pr-10 text-sm font-medium backdrop-blur-lg transition-all duration-200 shadow-lg hover:shadow-xl focus:ring-2"
style="background: var(--glass-bg-input); border: 1px solid var(--glass-border); color: var(--glass-text-primary); focus:ring-color: var(--glass-border-focus);"
>
<option value="10">Within 10 miles</option>
<option value="25" selected>Within 25 miles</option>
<option value="50">Within 50 miles</option>
<option value="100">Within 100 miles</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="absolute inset-y-0 right-0 flex items-center px-3 pointer-events-none">
<svg class="w-4 h-4" style="color: var(--glass-text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</div>
@@ -180,7 +208,8 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<div class="relative">
<select
id="category-filter"
class="appearance-none bg-white border border-gray-300 rounded-lg px-4 py-2 pr-8 text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="appearance-none rounded-xl px-4 py-2.5 pr-10 text-sm font-medium backdrop-blur-lg transition-all duration-200 shadow-lg hover:shadow-xl focus:ring-2"
style="background: var(--glass-bg-input); border: 1px solid var(--glass-border); color: var(--glass-text-primary); focus:ring-color: var(--glass-border-focus);"
>
<option value="">All Categories</option>
<option value="music" {category === 'music' ? 'selected' : ''}>Music & Concerts</option>
@@ -190,8 +219,8 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<option value="food" {category === 'food' ? 'selected' : ''}>Food & Wine</option>
<option value="sports" {category === 'sports' ? 'selected' : ''}>Sports & Recreation</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="absolute inset-y-0 right-0 flex items-center px-3 pointer-events-none">
<svg class="w-4 h-4" style="color: var(--glass-text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</div>
@@ -201,7 +230,8 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<div class="relative">
<select
id="date-filter"
class="appearance-none bg-white border border-gray-300 rounded-lg px-4 py-2 pr-8 text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
class="appearance-none rounded-xl px-4 py-2.5 pr-10 text-sm font-medium backdrop-blur-lg transition-all duration-200 shadow-lg hover:shadow-xl focus:ring-2"
style="background: var(--glass-bg-input); border: 1px solid var(--glass-border); color: var(--glass-text-primary); focus:ring-color: var(--glass-border-focus);"
>
<option value="">All Dates</option>
<option value="today">Today</option>
@@ -212,8 +242,8 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<option value="this-month">This Month</option>
<option value="next-month">Next Month</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="absolute inset-y-0 right-0 flex items-center px-3 pointer-events-none">
<svg class="w-4 h-4" style="color: var(--glass-text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</div>
@@ -224,16 +254,18 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<input
type="checkbox"
id="featured-filter"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
class="rounded transition-all duration-200"
style="border-color: var(--glass-border); color: var(--glass-text-accent); focus:ring-color: var(--glass-text-accent);"
{featured ? 'checked' : ''}
/>
<span class="text-sm font-medium text-gray-700">Featured Only</span>
<span class="text-sm font-medium" style="color: var(--glass-text-secondary);">Featured Only</span>
</label>
<!-- Clear Filters -->
<button
id="clear-filters"
class="text-sm font-medium text-gray-500 hover:text-gray-700 transition-colors"
class="text-sm font-medium transition-all duration-200 px-3 py-1.5 rounded-lg hover:scale-105"
style="color: var(--glass-text-tertiary); background: var(--glass-bg-button);"
>
Clear All
</button>
@@ -248,7 +280,7 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<div id="loading-state" class="text-center py-16">
<div class="inline-flex items-center space-x-2">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span class="text-lg font-medium text-gray-600">Loading events...</span>
<span class="text-lg font-medium" style="color: var(--ui-text-secondary);">Loading events...</span>
</div>
</div>
@@ -259,66 +291,69 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<div class="flex items-center space-x-2 md:space-x-4">
<button
id="prev-month"
class="p-1 md:p-2 rounded-lg hover:bg-gray-100 transition-colors"
class="p-2 md:p-3 rounded-lg transition-all duration-200 touch-manipulation min-h-[44px] min-w-[44px] flex items-center justify-center hover:scale-110"
style="background: var(--glass-bg-button); color: var(--glass-text-secondary);"
>
<svg class="w-4 h-4 md:w-5 md:h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
</button>
<h2 id="calendar-month" class="text-lg md:text-2xl font-bold text-gray-900"></h2>
<h2 id="calendar-month" class="text-lg md:text-2xl font-bold" style="color: var(--ui-text-primary);"></h2>
<button
id="next-month"
class="p-1 md:p-2 rounded-lg hover:bg-gray-100 transition-colors"
class="p-2 md:p-3 rounded-lg transition-all duration-200 touch-manipulation min-h-[44px] min-w-[44px] flex items-center justify-center hover:scale-110"
style="background: var(--glass-bg-button); color: var(--glass-text-secondary);"
>
<svg class="w-4 h-4 md:w-5 md:h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
</div>
<button
id="today-btn"
class="px-3 md:px-4 py-1.5 md:py-2 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg transition-all duration-200 font-medium text-sm md:text-base shadow-lg hover:shadow-xl"
class="px-4 md:px-5 py-2 md:py-2.5 rounded-lg transition-all duration-200 font-medium text-sm md:text-base shadow-lg hover:shadow-xl touch-manipulation min-h-[44px] hover:scale-105"
style="background: linear-gradient(to right, rgb(37, 99, 235), rgb(147, 51, 234)); color: var(--glass-text-primary);"
>
Today
</button>
</div>
<!-- Calendar Grid -->
<div class="bg-white rounded-2xl shadow-xl border border-gray-200/50 overflow-hidden">
<div class="rounded-2xl shadow-xl overflow-hidden backdrop-blur-lg" style="background: var(--glass-bg-lg); border: 1px solid var(--glass-border);">
<!-- Day Headers - Responsive -->
<div class="grid grid-cols-7 bg-gradient-to-r from-gray-50 to-gray-100 border-b border-gray-200">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold text-gray-700">
<div class="grid grid-cols-7" style="background: var(--ui-bg-secondary); border-bottom: 1px solid var(--ui-border-secondary);">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold" style="color: var(--ui-text-secondary);">
<span class="hidden md:inline">Sunday</span>
<span class="md:hidden">Sun</span>
</div>
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold text-gray-700">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold" style="color: var(--ui-text-secondary);">
<span class="hidden md:inline">Monday</span>
<span class="md:hidden">Mon</span>
</div>
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold text-gray-700">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold" style="color: var(--ui-text-secondary);">
<span class="hidden md:inline">Tuesday</span>
<span class="md:hidden">Tue</span>
</div>
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold text-gray-700">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold" style="color: var(--ui-text-secondary);">
<span class="hidden md:inline">Wednesday</span>
<span class="md:hidden">Wed</span>
</div>
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold text-gray-700">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold" style="color: var(--ui-text-secondary);">
<span class="hidden md:inline">Thursday</span>
<span class="md:hidden">Thu</span>
</div>
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold text-gray-700">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold" style="color: var(--ui-text-secondary);">
<span class="hidden md:inline">Friday</span>
<span class="md:hidden">Fri</span>
</div>
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold text-gray-700">
<div class="p-2 md:p-4 text-center text-xs md:text-sm font-semibold" style="color: var(--ui-text-secondary);">
<span class="hidden md:inline">Saturday</span>
<span class="md:hidden">Sat</span>
</div>
</div>
<!-- Calendar Days -->
<div id="calendar-grid" class="grid grid-cols-7 gap-px bg-gray-200">
<div id="calendar-grid" class="grid grid-cols-7 gap-px" style="background: var(--ui-border-secondary);">
<!-- Days will be populated by JavaScript -->
</div>
</div>
@@ -334,16 +369,17 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<!-- Empty State -->
<div id="empty-state" class="hidden text-center py-16">
<div class="max-w-md mx-auto">
<div class="w-24 h-24 mx-auto mb-6 bg-gradient-to-br from-gray-100 to-gray-200 rounded-full flex items-center justify-center">
<svg class="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="w-24 h-24 mx-auto mb-6 rounded-full flex items-center justify-center" style="background: linear-gradient(to bottom right, var(--ui-bg-secondary), var(--ui-bg-elevated));">
<svg class="w-12 h-12" style="color: var(--ui-text-muted);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</div>
<h3 class="text-xl font-semibold text-gray-900 mb-2">No Events Found</h3>
<p class="text-gray-600 mb-6">Try adjusting your filters or search terms to find events.</p>
<h3 class="text-xl font-semibold mb-2" style="color: var(--ui-text-primary);">No Events Found</h3>
<p class="mb-6" style="color: var(--ui-text-secondary);">Try adjusting your filters or search terms to find events.</p>
<button
id="clear-filters-empty"
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium"
class="inline-flex items-center px-4 py-2 rounded-lg transition-all duration-200 font-medium hover:scale-105"
style="background: linear-gradient(to right, rgb(37, 99, 235), rgb(147, 51, 234)); color: var(--glass-text-primary);"
>
Clear All Filters
</button>
@@ -353,10 +389,10 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<!-- Event Detail Modal -->
<div id="event-modal" class="fixed inset-0 z-50 hidden">
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" id="modal-backdrop"></div>
<div class="absolute inset-0 backdrop-blur-sm" style="background: rgba(0, 0, 0, 0.6);" id="modal-backdrop"></div>
<div class="relative min-h-screen flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-hidden">
<div id="modal-content" class="p-8">
<div class="rounded-2xl shadow-2xl w-full max-w-sm sm:max-w-lg lg:max-w-2xl max-h-[90vh] overflow-y-auto backdrop-blur-xl" style="background: var(--glass-bg-lg); border: 1px solid var(--glass-border);">
<div id="modal-content" class="p-4 sm:p-6 lg:p-8">
<!-- Modal content will be populated by JavaScript -->
</div>
</div>
@@ -413,26 +449,26 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
}
::-webkit-scrollbar-track {
background: #f1f5f9;
background: var(--ui-bg-secondary);
}
::-webkit-scrollbar-thumb {
background: linear-gradient(45deg, #3b82f6, #8b5cf6);
background: linear-gradient(45deg, rgb(37, 99, 235), rgb(147, 51, 234));
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(45deg, #2563eb, #7c3aed);
background: linear-gradient(45deg, rgb(29, 78, 216), rgb(126, 34, 206));
}
/* Calendar day hover effects */
.calendar-day {
transition: all 0.3s ease;
background: white;
background: var(--ui-bg-elevated);
}
.calendar-day:hover {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
background: linear-gradient(135deg, var(--ui-bg-secondary), var(--ui-bg-elevated));
}
@media (min-width: 768px) {
@@ -453,15 +489,15 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
/* Glassmorphism effects */
.glass {
background: rgba(255, 255, 255, 0.1);
background: var(--glass-bg);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border: 1px solid var(--glass-border);
}
</style>
<script>
// Import geolocation utilities
const MAPBOX_TOKEN = '<%= mapboxToken %>';
const MAPBOX_TOKEN = mapboxToken;
// Calendar state
let currentDate = new Date();
@@ -640,7 +676,7 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<span class="text-green-400 font-medium">Location enabled</span>
${userLocation.city ? `<span class="text-white/60 text-sm ml-2">(${userLocation.city})</span>` : ''}
${userLocation.city ? `<span class="text-sm ml-2" style="color: var(--glass-text-tertiary);">(${userLocation.city})</span>` : ''}
`;
// Show location in filter bar
@@ -681,21 +717,21 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
const categoryColor = getCategoryColor(event.category);
const categoryIcon = getCategoryIcon(event.category);
return `
<div class="bg-white rounded-xl shadow-lg hover:shadow-2xl transition-all duration-300 cursor-pointer transform hover:-translate-y-1" onclick="showEventModal(${JSON.stringify(event).replace(/"/g, '&quot;')})">
<div class="rounded-xl shadow-lg hover:shadow-2xl transition-all duration-300 cursor-pointer transform hover:-translate-y-1 backdrop-blur-lg" style="background: var(--ui-bg-elevated); border: 1px solid var(--ui-border-primary);" onclick="showEventModal(${JSON.stringify(event).replace(/"/g, '&quot;')})">
<div class="relative">
<div class="h-32 bg-gradient-to-br ${categoryColor} rounded-t-xl flex items-center justify-center">
<span class="text-4xl">${categoryIcon}</span>
</div>
${event.popularityScore > 50 ? `
<div class="absolute top-2 right-2 bg-red-500 text-white px-2 py-1 rounded-full text-xs font-bold">
<div class="absolute top-2 right-2 px-2 py-1 rounded-full text-xs font-bold" style="background: var(--error-color); color: var(--glass-text-primary);">
HOT 🔥
</div>
` : ''}
</div>
<div class="p-4">
<h3 class="font-bold text-gray-900 mb-1 line-clamp-1">${event.title}</h3>
<p class="text-sm text-gray-600 mb-2">${event.venue}</p>
<div class="flex items-center justify-between text-xs text-gray-500">
<h3 class="font-bold mb-1 line-clamp-1" style="color: var(--ui-text-primary);">${event.title}</h3>
<p class="text-sm mb-2" style="color: var(--ui-text-secondary);">${event.venue}</p>
<div class="flex items-center justify-between text-xs" style="color: var(--ui-text-tertiary);">
<span>${event.distanceMiles ? `${event.distanceMiles.toFixed(1)} mi` : ''}</span>
<span>${event.ticketsSold || 0} sold</span>
</div>
@@ -708,11 +744,11 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
function clearLocation() {
userLocation = null;
locationStatus.innerHTML = `
<svg class="w-5 h-5 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-5 h-5" style="color: var(--glass-text-secondary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
<button id="enable-location" class="text-white/90 hover:text-white font-medium">
<button id="enable-location" class="font-medium hover:scale-105 transition-all duration-200" style="color: var(--glass-text-secondary);">
Enable location for personalized events
</button>
`;
@@ -960,7 +996,8 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
function createCalendarDay(index, startingDayOfWeek, daysInMonth, daysInPrevMonth, year, month) {
const dayDiv = document.createElement('div');
dayDiv.className = 'calendar-day min-h-[80px] md:min-h-[120px] p-1 md:p-3 border-b border-gray-200 hover:bg-gradient-to-br hover:from-blue-50 hover:to-purple-50 transition-all duration-300 cursor-pointer group';
dayDiv.className = 'calendar-day min-h-[80px] md:min-h-[120px] p-1 md:p-3 transition-all duration-300 cursor-pointer group';
dayDiv.style.borderBottom = '1px solid var(--ui-border-secondary)';
let dayNumber, isCurrentMonth, currentDayDate;
@@ -996,10 +1033,21 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
dayNumberSpan.className = `text-xs md:text-sm font-medium ${
isCurrentMonth
? isToday
? 'bg-gradient-to-r from-blue-600 to-purple-600 text-white px-1 md:px-2 py-0.5 md:py-1 rounded-full'
: 'text-gray-900'
: 'text-gray-400'
? 'px-1 md:px-2 py-0.5 md:py-1 rounded-full'
: ''
: ''
}`;
if (isCurrentMonth) {
if (isToday) {
dayNumberSpan.style.background = 'linear-gradient(to right, rgb(37, 99, 235), rgb(147, 51, 234))';
dayNumberSpan.style.color = 'var(--glass-text-primary)';
} else {
dayNumberSpan.style.color = 'var(--ui-text-primary)';
}
} else {
dayNumberSpan.style.color = 'var(--ui-text-muted)';
}
dayNumberSpan.textContent = dayNumber;
dayDiv.appendChild(dayNumberSpan);
@@ -1018,7 +1066,8 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
visibleEvents.forEach(event => {
const eventDiv = document.createElement('div');
const categoryColor = getCategoryColor(event.category);
eventDiv.className = `text-xs px-1 md:px-2 py-0.5 md:py-1 rounded-md bg-gradient-to-r ${categoryColor} text-white font-medium cursor-pointer transform hover:scale-105 transition-all duration-200 shadow-sm hover:shadow-md truncate`;
eventDiv.className = `text-xs px-1 md:px-2 py-0.5 md:py-1 rounded-md bg-gradient-to-r ${categoryColor} font-medium cursor-pointer transform hover:scale-105 transition-all duration-200 shadow-sm hover:shadow-md truncate`;
eventDiv.style.color = 'var(--glass-text-primary)';
const maxTitleLength = isMobile ? 10 : 20;
eventDiv.textContent = event.title.length > maxTitleLength ? event.title.substring(0, maxTitleLength) + '...' : event.title;
eventDiv.title = event.title; // Full title on hover
@@ -1031,7 +1080,15 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
if (remainingCount > 0) {
const moreDiv = document.createElement('div');
moreDiv.className = 'text-xs text-gray-600 font-medium px-1 md:px-2 py-0.5 md:py-1 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors cursor-pointer';
moreDiv.className = 'text-xs font-medium px-1 md:px-2 py-0.5 md:py-1 rounded-md transition-colors cursor-pointer';
moreDiv.style.color = 'var(--ui-text-secondary)';
moreDiv.style.background = 'var(--ui-bg-secondary)';
moreDiv.addEventListener('mouseenter', () => {
moreDiv.style.background = 'var(--ui-bg-elevated)';
});
moreDiv.addEventListener('mouseleave', () => {
moreDiv.style.background = 'var(--ui-bg-secondary)';
});
moreDiv.textContent = `+${remainingCount}`;
moreDiv.addEventListener('click', (e) => {
e.stopPropagation();
@@ -1101,8 +1158,8 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
}
dateHeader.innerHTML = `
<h3 class="text-lg font-semibold text-gray-900 mb-1">${dateText}</h3>
<div class="w-16 h-1 bg-gradient-to-r from-blue-600 to-purple-600 rounded-full"></div>
<h3 class="text-lg font-semibold mb-1" style="color: var(--ui-text-primary);">${dateText}</h3>
<div class="w-16 h-1 rounded-full" style="background: linear-gradient(to right, rgb(37, 99, 235), rgb(147, 51, 234));"></div>
`;
groupDiv.appendChild(dateHeader);
@@ -1123,7 +1180,9 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
function createEventCard(event) {
const card = document.createElement('div');
card.className = 'event-card bg-white rounded-xl shadow-lg border border-gray-200/50 overflow-hidden hover:shadow-2xl transform hover:-translate-y-2 transition-all duration-500 cursor-pointer group';
card.className = 'event-card rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transform hover:-translate-y-2 transition-all duration-500 cursor-pointer group backdrop-blur-lg';
card.style.background = 'var(--ui-bg-elevated)';
card.style.border = '1px solid var(--ui-border-primary)';
const { date, time, dayOfWeek } = formatDateTime(event.start_time);
const categoryColor = getCategoryColor(event.category);
@@ -1132,17 +1191,17 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
card.innerHTML = `
<div class="relative">
<div class="h-48 bg-gradient-to-br ${categoryColor} relative overflow-hidden">
<div class="absolute inset-0 bg-black/20"></div>
<div class="absolute inset-0" style="background: rgba(0, 0, 0, 0.2);"></div>
<div class="absolute top-4 left-4">
<span class="text-3xl">${categoryIcon}</span>
</div>
<div class="absolute top-4 right-4">
<span class="bg-white/20 backdrop-blur-sm px-2 py-1 rounded-full text-white text-xs font-medium">
<span class="backdrop-blur-sm px-2 py-1 rounded-full text-xs font-medium" style="background: rgba(255, 255, 255, 0.2); color: var(--glass-text-primary);">
${event.category?.charAt(0).toUpperCase() + event.category?.slice(1) || 'Event'}
</span>
</div>
<div class="absolute bottom-4 left-4 right-4">
<h3 class="text-xl font-bold text-white mb-2 line-clamp-2 group-hover:text-yellow-200 transition-colors">
<h3 class="text-xl font-bold mb-2 line-clamp-2 group-hover:text-yellow-200 transition-colors" style="color: var(--glass-text-primary);">
${event.title}
</h3>
</div>
@@ -1150,14 +1209,14 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
</div>
<div class="p-6">
<div class="flex items-center space-x-2 text-sm text-gray-600 mb-3">
<div class="flex items-center space-x-2 text-sm mb-3" style="color: var(--ui-text-secondary);">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<span>${time}</span>
</div>
<div class="flex items-start space-x-2 text-sm text-gray-600 mb-4">
<div class="flex items-start space-x-2 text-sm mb-4" style="color: var(--ui-text-secondary);">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
@@ -1166,7 +1225,7 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
</div>
${event.description ? `
<p class="text-gray-600 text-sm mb-4 line-clamp-3">${event.description}</p>
<p class="text-sm mb-4 line-clamp-3" style="color: var(--ui-text-secondary);">${event.description}</p>
` : ''}
<div class="flex items-center justify-between">
@@ -1178,7 +1237,7 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
` : ''}
</div>
<button class="bg-gradient-to-r ${categoryColor} text-white px-4 py-2 rounded-lg font-medium text-sm hover:shadow-lg transform hover:scale-105 transition-all duration-200">
<button class="bg-gradient-to-r ${categoryColor} px-4 py-2 rounded-lg font-medium text-sm hover:shadow-lg transform hover:scale-105 transition-all duration-200" style="color: var(--glass-text-primary);">
View Details
</button>
</div>
@@ -1203,13 +1262,13 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<div class="flex items-center space-x-3">
<span class="text-3xl">${categoryIcon}</span>
<div>
<h2 class="text-2xl font-bold text-gray-900 mb-1">${event.title}</h2>
<span class="bg-gradient-to-r ${categoryColor} text-white px-3 py-1 rounded-full text-sm font-medium">
<h2 class="text-2xl font-bold mb-1" style="color: var(--ui-text-primary);">${event.title}</h2>
<span class="bg-gradient-to-r ${categoryColor} px-3 py-1 rounded-full text-sm font-medium" style="color: var(--glass-text-primary);">
${event.category?.charAt(0).toUpperCase() + event.category?.slice(1) || 'Event'}
</span>
</div>
</div>
<button id="close-modal" class="text-gray-400 hover:text-gray-600 transition-colors">
<button id="close-modal" class="transition-colors" style="color: var(--ui-text-muted);" onmouseover="this.style.color='var(--ui-text-secondary)'" onmouseout="this.style.color='var(--ui-text-muted)'">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
@@ -1221,34 +1280,34 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
<div class="space-y-4">
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
<svg class="w-5 h-5 text-gray-400 mt-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-5 h-5 mt-1" style="color: var(--ui-text-muted);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</div>
<div>
<h4 class="font-semibold text-gray-900">Date & Time</h4>
<p class="text-gray-600">${dayOfWeek}, ${date}</p>
<p class="text-gray-600">${time}</p>
<h4 class="font-semibold" style="color: var(--ui-text-primary);">Date & Time</h4>
<p style="color: var(--ui-text-secondary);">${dayOfWeek}, ${date}</p>
<p style="color: var(--ui-text-secondary);">${time}</p>
</div>
</div>
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
<svg class="w-5 h-5 text-gray-400 mt-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-5 h-5 mt-1" style="color: var(--ui-text-muted);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
</div>
<div>
<h4 class="font-semibold text-gray-900">Venue</h4>
<p class="text-gray-600">${event.venue || 'Venue information coming soon'}</p>
<h4 class="font-semibold" style="color: var(--ui-text-primary);">Venue</h4>
<p style="color: var(--ui-text-secondary);">${event.venue || 'Venue information coming soon'}</p>
</div>
</div>
</div>
<div class="space-y-4">
${event.featured ? `
<div class="bg-gradient-to-r from-yellow-50 to-orange-50 border border-yellow-200 rounded-lg p-4">
<div class="rounded-lg p-4 backdrop-blur-lg" style="background: linear-gradient(to right, var(--warning-bg), var(--premium-gold-bg)); border: 1px solid var(--warning-border);">
<div class="flex items-center space-x-2">
<span class="text-xl">⭐</span>
<span class="font-semibold text-yellow-800">Featured Event</span>
@@ -1257,30 +1316,34 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
</div>
` : ''}
<div class="bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 rounded-lg p-4">
<h4 class="font-semibold text-gray-900 mb-2">Event Details</h4>
<p class="text-gray-600 text-sm">Click the button below to view full event details and purchase tickets.</p>
<div class="rounded-lg p-4 backdrop-blur-lg" style="background: linear-gradient(to right, var(--glass-bg), var(--glass-bg-lg)); border: 1px solid var(--glass-border);">
<h4 class="font-semibold mb-2" style="color: var(--ui-text-primary);">Event Details</h4>
<p class="text-sm" style="color: var(--ui-text-secondary);">Click the button below to view full event details and purchase tickets.</p>
</div>
</div>
</div>
${event.description ? `
<div>
<h4 class="font-semibold text-gray-900 mb-2">Description</h4>
<p class="text-gray-600 leading-relaxed">${event.description}</p>
<h4 class="font-semibold mb-2" style="color: var(--ui-text-primary);">Description</h4>
<p class="leading-relaxed" style="color: var(--ui-text-secondary);">${event.description}</p>
</div>
` : ''}
<div class="flex space-x-4 pt-6 border-t border-gray-200">
<div class="flex space-x-4 pt-6" style="border-top: 1px solid var(--ui-border-secondary);">
<a
href="/e/${event.slug || event.id}"
class="flex-1 bg-gradient-to-r ${categoryColor} text-white py-3 px-6 rounded-lg font-semibold text-center hover:shadow-lg transform hover:scale-105 transition-all duration-200"
class="flex-1 bg-gradient-to-r ${categoryColor} py-3 px-6 rounded-lg font-semibold text-center hover:shadow-lg transform hover:scale-105 transition-all duration-200"
style="color: var(--glass-text-primary);"
>
View Event & Get Tickets
</a>
<button
id="share-event"
class="px-6 py-3 border border-gray-300 rounded-lg font-semibold text-gray-700 hover:bg-gray-50 transition-colors flex items-center space-x-2"
class="px-6 py-3 rounded-lg font-semibold transition-colors flex items-center space-x-2"
style="border: 1px solid var(--ui-border-primary); color: var(--ui-text-secondary);"
onmouseover="this.style.background='var(--ui-bg-secondary)'"
onmouseout="this.style.background='transparent'"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z"></path>
@@ -1341,7 +1404,10 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
function showToast(message) {
// Simple toast notification
const toast = document.createElement('div');
toast.className = 'fixed top-4 right-4 bg-gray-900 text-white px-6 py-3 rounded-lg shadow-lg z-50 transform translate-x-full transition-transform duration-300';
toast.className = 'fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform translate-x-full transition-transform duration-300 backdrop-blur-lg';
toast.style.background = 'var(--ui-bg-elevated)';
toast.style.color = 'var(--ui-text-primary)';
toast.style.border = '1px solid var(--ui-border-primary)';
toast.textContent = message;
document.body.appendChild(toast);
@@ -1362,15 +1428,23 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
// View toggle functions
function switchToCalendarView() {
currentView = 'calendar';
calendarViewBtn.className = 'px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 bg-white shadow-sm text-gray-900';
listViewBtn.className = 'px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 text-gray-600 hover:text-gray-900 hover:bg-gray-50';
calendarViewBtn.className = 'px-6 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 flex items-center gap-2 hover:scale-105 shadow-sm';
calendarViewBtn.style.background = 'var(--glass-bg-elevated)';
calendarViewBtn.style.color = 'var(--glass-text-primary)';
listViewBtn.className = 'px-6 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 flex items-center gap-2 hover:scale-105';
listViewBtn.style.background = 'transparent';
listViewBtn.style.color = 'var(--glass-text-secondary)';
renderCurrentView();
}
function switchToListView() {
currentView = 'list';
listViewBtn.className = 'px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 bg-white shadow-sm text-gray-900';
calendarViewBtn.className = 'px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 text-gray-600 hover:text-gray-900 hover:bg-gray-50';
listViewBtn.className = 'px-6 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 flex items-center gap-2 hover:scale-105 shadow-sm';
listViewBtn.style.background = 'var(--glass-bg-elevated)';
listViewBtn.style.color = 'var(--glass-text-primary)';
calendarViewBtn.className = 'px-6 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 flex items-center gap-2 hover:scale-105';
calendarViewBtn.style.background = 'transparent';
calendarViewBtn.style.color = 'var(--glass-text-secondary)';
renderCurrentView();
}
@@ -1459,4 +1533,58 @@ const mapboxToken = import.meta.env.PUBLIC_MAPBOX_TOKEN || '';
// Initialize
loadEvents();
// Theme toggle functionality
const themeToggle = document.getElementById('theme-toggle');
const html = document.documentElement;
// Load saved theme or default to system preference
const savedTheme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
html.setAttribute('data-theme', savedTheme);
// Also set Tailwind dark class
if (savedTheme === 'dark') {
html.classList.add('dark');
} else {
html.classList.remove('dark');
}
// Update toggle icon based on theme
function updateToggleIcon() {
const currentTheme = html.getAttribute('data-theme');
const icon = themeToggle.querySelector('svg path');
if (currentTheme === 'light') {
// Sun icon for light mode
icon.setAttribute('d', 'M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z');
} else {
// Moon icon for dark mode
icon.setAttribute('d', 'M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z');
}
}
// Initialize icon
updateToggleIcon();
// Toggle theme
themeToggle.addEventListener('click', () => {
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
// Also toggle Tailwind dark class
if (newTheme === 'dark') {
html.classList.add('dark');
} else {
html.classList.remove('dark');
}
updateToggleIcon();
// Dispatch custom event for theme change
window.dispatchEvent(new CustomEvent('themeChange', { detail: { theme: newTheme } }));
});
</script>