# Widget System

## Overview

The EventBookings widget system provides JavaScript components that can be embedded in Shopify storefronts to display events. The system consists of server-side PHP endpoints that serve JavaScript code and client-side components that render events dynamically.

## Architecture

```
Storefront → Widget Script → API Endpoint → Event Data → Component Render → DOM Update
```

### Components

1. **Widget Controller** (PHP) - Serves JavaScript code and event data
2. **JavaScript Components** - Client-side rendering and interaction
3. **CSS Styling** - Theme-adaptive styling system
4. **API Endpoints** - CORS-enabled data providers

## Server-Side Implementation

### Widget Controller

```php
<?php

namespace App\Http\Controllers;

use App\Models\ShopifyStore;
use App\Models\EventToggle;
use App\Services\TokenManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;

class WidgetController extends Controller
{
    private $tokenManager;

    public function __construct(TokenManager $tokenManager)
    {
        $this->tokenManager = $tokenManager;

        // Apply CORS middleware for cross-origin requests
        $this->middleware('cors');
    }

    /**
     * Serve events list widget JavaScript
     * Route: GET /widgets/events.js
     */
    public function eventsWidget(Request $request)
    {
        $shop = $request->query('shop');

        if (!$shop) {
            return response('// Error: Missing shop parameter', 200, [
                'Content-Type' => 'application/javascript'
            ]);
        }

        try {
            $store = ShopifyStore::where('shop_domain', $shop)->first();

            if (!$store || !$store->storeSettings) {
                return $this->generateErrorWidget('Store not found or not configured');
            }

            $widgetCode = $this->generateEventsWidgetCode($store);

            return response($widgetCode, 200, [
                'Content-Type' => 'application/javascript',
                'Cache-Control' => 'public, max-age=300', // 5 minutes cache
                'Access-Control-Allow-Origin' => '*',
                'Access-Control-Allow-Methods' => 'GET',
                'Access-Control-Allow-Headers' => 'Content-Type'
            ]);

        } catch (\Exception $e) {
            Log::error('Widget generation failed', [
                'shop' => $shop,
                'error' => $e->getMessage()
            ]);

            return $this->generateErrorWidget('Widget temporarily unavailable');
        }
    }

    /**
     * Serve featured events widget JavaScript
     * Route: GET /widgets/featured.js
     */
    public function featuredWidget(Request $request)
    {
        $shop = $request->query('shop');

        if (!$shop) {
            return response('// Error: Missing shop parameter', 200, [
                'Content-Type' => 'application/javascript'
            ]);
        }

        try {
            $store = ShopifyStore::where('shop_domain', $shop)->first();

            if (!$store || !$store->storeSettings) {
                return $this->generateErrorWidget('Store not found or not configured');
            }

            $widgetCode = $this->generateFeaturedWidgetCode($store);

            return response($widgetCode, 200, [
                'Content-Type' => 'application/javascript',
                'Cache-Control' => 'public, max-age=300',
                'Access-Control-Allow-Origin' => '*',
                'Access-Control-Allow-Methods' => 'GET',
                'Access-Control-Allow-Headers' => 'Content-Type'
            ]);

        } catch (\Exception $e) {
            Log::error('Featured widget generation failed', [
                'shop' => $shop,
                'error' => $e->getMessage()
            ]);

            return $this->generateErrorWidget('Widget temporarily unavailable');
        }
    }

    /**
     * API endpoint for event data
     * Route: GET /api/events
     */
    public function eventsApi(Request $request)
    {
        $shop = $request->query('shop');
        $type = $request->query('type', 'all'); // 'all', 'featured'

        if (!$shop) {
            return response()->json(['error' => 'Missing shop parameter'], 400);
        }

        try {
            $store = ShopifyStore::where('shop_domain', $shop)->first();

            if (!$store || !$store->storeSettings) {
                return response()->json(['error' => 'Store not found'], 404);
            }

            $cacheKey = "events_api_{$shop}_{$type}";

            $eventsData = Cache::remember($cacheKey, 300, function() use ($store, $type) {
                return $this->fetchEventsForWidget($store, $type);
            });

            return response()->json($eventsData, 200, [
                'Access-Control-Allow-Origin' => '*',
                'Access-Control-Allow-Methods' => 'GET',
                'Access-Control-Allow-Headers' => 'Content-Type'
            ]);

        } catch (\Exception $e) {
            Log::error('Events API failed', [
                'shop' => $shop,
                'type' => $type,
                'error' => $e->getMessage()
            ]);

            return response()->json(['error' => 'Unable to fetch events'], 500);
        }
    }

    /**
     * Generate events widget JavaScript code
     */
    private function generateEventsWidgetCode(ShopifyStore $store): string
    {
        $apiUrl = config('app.url') . '/api/events?shop=' . urlencode($store->shop_domain);

        return view('widgets.events-widget', [
            'shop' => $store->shop_domain,
            'api_url' => $apiUrl,
            'widget_id' => 'eventbookings-events-widget'
        ])->render();
    }

    /**
     * Generate featured events widget JavaScript code
     */
    private function generateFeaturedWidgetCode(ShopifyStore $store): string
    {
        $apiUrl = config('app.url') . '/api/events?shop=' . urlencode($store->shop_domain) . '&type=featured';

        return view('widgets.featured-widget', [
            'shop' => $store->shop_domain,
            'api_url' => $apiUrl,
            'widget_id' => 'eventbookings-featured-widget'
        ])->render();
    }

    /**
     * Generate error widget JavaScript
     */
    private function generateErrorWidget(string $message): \Illuminate\Http\Response
    {
        $errorCode = "
        console.error('EventBookings Widget Error: {$message}');

        // Try to find widget containers and show error message
        document.addEventListener('DOMContentLoaded', function() {
            var containers = document.querySelectorAll('#eventbookings-events-widget, #eventbookings-featured-widget');
            containers.forEach(function(container) {
                if (container) {
                    container.innerHTML = '<div style=\"padding: 20px; text-align: center; color: #666; border: 1px solid #ddd; border-radius: 8px; background: #f9f9f9;\">Events temporarily unavailable</div>';
                }
            });
        });
        ";

        return response($errorCode, 200, [
            'Content-Type' => 'application/javascript'
        ]);
    }

    /**
     * Fetch events data for widgets
     */
    private function fetchEventsForWidget(ShopifyStore $store, string $type): array
    {
        $storeSettings = $store->storeSettings;

        if (!$storeSettings->is_connected) {
            return [
                'success' => false,
                'message' => 'EventBookings not connected',
                'events' => []
            ];
        }

        try {
            // Fetch events from EventBookings API
            $result = $this->tokenManager->fetchEventsWithRetry($storeSettings);

            if (!$result['success']) {
                return [
                    'success' => false,
                    'message' => $result['message'],
                    'events' => []
                ];
            }

            $events = $result['data']['data'] ?? [];

            // Apply event toggles (visibility and featured status)
            $filteredEvents = $this->applyEventToggles($events, $store->id, $type);

            // Transform events for widget consumption
            $widgetEvents = array_map(function($event) {
                return $this->transformEventForWidget($event);
            }, $filteredEvents);

            return [
                'success' => true,
                'events' => $widgetEvents,
                'count' => count($widgetEvents)
            ];

        } catch (\Exception $e) {
            Log::error('Failed to fetch events for widget', [
                'store_id' => $store->id,
                'type' => $type,
                'error' => $e->getMessage()
            ]);

            return [
                'success' => false,
                'message' => 'Unable to fetch events',
                'events' => []
            ];
        }
    }

    /**
     * Apply event toggles (show/hide, featured)
     */
    private function applyEventToggles(array $events, int $storeId, string $type): array
    {
        $toggles = EventToggle::where('store_id', $storeId)->get()->keyBy('event_uuid');

        return array_filter($events, function($event) use ($toggles, $type) {
            $eventUuid = $event['uuid'] ?? '';
            $toggle = $toggles->get($eventUuid);

            // Check visibility
            $isVisible = $toggle ? $toggle->show_event : true; // Default to visible
            if (!$isVisible) {
                return false;
            }

            // Check featured status if filtering for featured events
            if ($type === 'featured') {
                $isFeatured = $toggle ? $toggle->featured_event : false;
                return $isFeatured;
            }

            return true;
        });
    }

    /**
     * Transform event data for widget consumption
     */
    private function transformEventForWidget(array $event): array
    {
        return [
            'uuid' => $event['uuid'] ?? '',
            'title' => $event['name'] ?? 'Untitled Event',
            'description' => $this->truncateText($event['description'] ?? '', 150),
            'start_date' => $event['starts_on'] ?? '',
            'end_date' => $event['ends_on'] ?? '',
            'location' => $event['location'] ?? 'Location TBD',
            'price' => $event['price'] ?? 0,
            'currency' => $event['currency'] ?? 'USD',
            'image_url' => $event['image_url'] ?? '',
            'booking_url' => "https://eventbookings.com/book/{$event['uuid']}",
            'available_tickets' => max(0, ($event['max_capacity'] ?? 100) - ($event['tickets_sold'] ?? 0)),
            'status' => $event['status'] ?? 'published'
        ];
    }

    /**
     * Truncate text to specified length
     */
    private function truncateText(string $text, int $length): string
    {
        if (strlen($text) <= $length) {
            return $text;
        }

        $truncated = substr($text, 0, $length);
        $lastSpace = strrpos($truncated, ' ');

        if ($lastSpace !== false) {
            $truncated = substr($truncated, 0, $lastSpace);
        }

        return $truncated . '...';
    }
}
```

## JavaScript Widget Templates

### Events List Widget Template

**File: `resources/views/widgets/events-widget.blade.php`**

```javascript
(function() {
    'use strict';

    // Widget configuration
    var WIDGET_CONFIG = {
        shop: '{{ $shop }}',
        apiUrl: '{{ $api_url }}',
        widgetId: '{{ $widget_id }}',
        version: '1.0.0'
    };

    // EventBookings Widget Namespace
    window.EventBookingsWidget = window.EventBookingsWidget || {};

    /**
     * Events List Widget Class
     */
    function EventsListWidget(container, config) {
        this.container = container;
        this.config = Object.assign({}, WIDGET_CONFIG, config || {});
        this.events = [];
        this.isLoading = false;
        this.error = null;

        this.init();
    }

    EventsListWidget.prototype = {
        init: function() {
            this.render();
            this.loadEvents();
        },

        render: function() {
            this.container.innerHTML = this.getLoadingHTML();
            this.container.className = 'eb-events-widget eb-loading';
        },

        loadEvents: function() {
            var self = this;
            self.isLoading = true;

            fetch(self.config.apiUrl)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error('Network response was not ok: ' + response.statusText);
                    }
                    return response.json();
                })
                .then(function(data) {
                    self.isLoading = false;

                    if (data.success) {
                        self.events = data.events || [];
                        self.renderEvents();
                    } else {
                        self.showError(data.message || 'Failed to load events');
                    }
                })
                .catch(function(error) {
                    self.isLoading = false;
                    self.showError('Unable to load events. Please try again later.');
                    console.error('EventBookings Widget Error:', error);
                });
        },

        renderEvents: function() {
            var self = this;
            self.container.className = 'eb-events-widget eb-loaded';

            if (self.events.length === 0) {
                self.container.innerHTML = self.getEmptyStateHTML();
                return;
            }

            var eventsHTML = self.events.map(function(event) {
                return self.getEventHTML(event);
            }).join('');

            self.container.innerHTML =
                '<div class="eb-events-header">' +
                    '<h3>Upcoming Events</h3>' +
                '</div>' +
                '<div class="eb-events-grid">' + eventsHTML + '</div>';

            // Add click handlers
            self.addEventHandlers();
        },

        getEventHTML: function(event) {
            var startDate = new Date(event.start_date);
            var formattedDate = this.formatDate(startDate);
            var formattedPrice = this.formatPrice(event.price, event.currency);

            return '<div class="eb-event-card" data-event-uuid="' + event.uuid + '">' +
                '<div class="eb-event-image">' +
                    (event.image_url ?
                        '<img src="' + event.image_url + '" alt="' + this.escapeHtml(event.title) + '">' :
                        '<div class="eb-event-placeholder">📅</div>') +
                '</div>' +
                '<div class="eb-event-content">' +
                    '<h4 class="eb-event-title">' + this.escapeHtml(event.title) + '</h4>' +
                    '<p class="eb-event-description">' + this.escapeHtml(event.description) + '</p>' +
                    '<div class="eb-event-meta">' +
                        '<div class="eb-event-date">📅 ' + formattedDate + '</div>' +
                        '<div class="eb-event-location">📍 ' + this.escapeHtml(event.location) + '</div>' +
                        '<div class="eb-event-price">💰 ' + formattedPrice + '</div>' +
                    '</div>' +
                    '<div class="eb-event-actions">' +
                        '<a href="' + event.booking_url + '" target="_blank" class="eb-book-button">' +
                            'Book Now' +
                        '</a>' +
                    '</div>' +
                '</div>' +
            '</div>';
        },

        addEventHandlers: function() {
            var self = this;
            var eventCards = self.container.querySelectorAll('.eb-event-card');

            eventCards.forEach(function(card) {
                card.addEventListener('click', function(e) {
                    if (e.target.classList.contains('eb-book-button')) {
                        return; // Let the link work normally
                    }

                    var eventUuid = card.getAttribute('data-event-uuid');
                    self.onEventClick(eventUuid);
                });
            });
        },

        onEventClick: function(eventUuid) {
            var event = this.events.find(function(e) { return e.uuid === eventUuid; });
            if (event) {
                // Could implement modal view or redirect to event details
                window.open(event.booking_url, '_blank');
            }
        },

        showError: function(message) {
            this.error = message;
            this.container.className = 'eb-events-widget eb-error';
            this.container.innerHTML =
                '<div class="eb-error-state">' +
                    '<div class="eb-error-icon">⚠️</div>' +
                    '<div class="eb-error-message">' + this.escapeHtml(message) + '</div>' +
                    '<button class="eb-retry-button" onclick="this.parentNode.parentNode.parentNode.widget.loadEvents()">Try Again</button>' +
                '</div>';
        },

        getLoadingHTML: function() {
            return '<div class="eb-loading-state">' +
                '<div class="eb-loading-spinner"></div>' +
                '<div class="eb-loading-message">Loading events...</div>' +
            '</div>';
        },

        getEmptyStateHTML: function() {
            return '<div class="eb-empty-state">' +
                '<div class="eb-empty-icon">📅</div>' +
                '<div class="eb-empty-message">No events available at this time</div>' +
            '</div>';
        },

        formatDate: function(date) {
            var options = {
                year: 'numeric',
                month: 'short',
                day: 'numeric',
                hour: '2-digit',
                minute: '2-digit'
            };
            return date.toLocaleDateString('en-US', options);
        },

        formatPrice: function(price, currency) {
            if (price === 0) return 'Free';

            try {
                return new Intl.NumberFormat('en-US', {
                    style: 'currency',
                    currency: currency || 'USD'
                }).format(price);
            } catch (e) {
                return currency + ' ' + price;
            }
        },

        escapeHtml: function(text) {
            var div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
    };

    // Auto-initialize when DOM is ready
    function initializeWidget() {
        var container = document.getElementById(WIDGET_CONFIG.widgetId);
        if (container && !container.widget) {
            container.widget = new EventsListWidget(container, WIDGET_CONFIG);
        }
    }

    // Initialize immediately if DOM is ready, otherwise wait
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeWidget);
    } else {
        initializeWidget();
    }

    // Expose for manual initialization
    window.EventBookingsWidget.EventsList = EventsListWidget;

})();
```

### Featured Events Widget Template

**File: `resources/views/widgets/featured-widget.blade.php`**

```javascript
(function() {
    'use strict';

    // Widget configuration
    var WIDGET_CONFIG = {
        shop: '{{ $shop }}',
        apiUrl: '{{ $api_url }}',
        widgetId: '{{ $widget_id }}',
        version: '1.0.0'
    };

    // EventBookings Widget Namespace
    window.EventBookingsWidget = window.EventBookingsWidget || {};

    /**
     * Featured Events Widget Class
     */
    function FeaturedEventsWidget(container, config) {
        this.container = container;
        this.config = Object.assign({}, WIDGET_CONFIG, config || {});
        this.events = [];
        this.currentSlide = 0;
        this.isLoading = false;
        this.slideInterval = null;

        this.init();
    }

    FeaturedEventsWidget.prototype = {
        init: function() {
            this.render();
            this.loadEvents();
        },

        render: function() {
            this.container.innerHTML = this.getLoadingHTML();
            this.container.className = 'eb-featured-widget eb-loading';
        },

        loadEvents: function() {
            var self = this;
            self.isLoading = true;

            fetch(self.config.apiUrl)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then(function(data) {
                    self.isLoading = false;

                    if (data.success) {
                        self.events = data.events || [];
                        self.renderCarousel();
                    } else {
                        self.showError(data.message || 'Failed to load featured events');
                    }
                })
                .catch(function(error) {
                    self.isLoading = false;
                    self.showError('Unable to load featured events. Please try again later.');
                    console.error('EventBookings Featured Widget Error:', error);
                });
        },

        renderCarousel: function() {
            var self = this;
            self.container.className = 'eb-featured-widget eb-loaded';

            if (self.events.length === 0) {
                self.container.innerHTML = self.getEmptyStateHTML();
                return;
            }

            var slidesHTML = self.events.map(function(event, index) {
                return self.getSlideHTML(event, index);
            }).join('');

            var navigationHTML = self.events.length > 1 ? self.getNavigationHTML() : '';

            self.container.innerHTML =
                '<div class="eb-featured-header">' +
                    '<h3>Featured Events</h3>' +
                '</div>' +
                '<div class="eb-carousel-container">' +
                    '<div class="eb-carousel-track" style="transform: translateX(0%)">' +
                        slidesHTML +
                    '</div>' +
                    navigationHTML +
                '</div>';

            // Add event handlers
            self.addCarouselHandlers();

            // Start auto-rotation if multiple events
            if (self.events.length > 1) {
                self.startAutoRotation();
            }
        },

        getSlideHTML: function(event, index) {
            var startDate = new Date(event.start_date);
            var formattedDate = this.formatDate(startDate);
            var formattedPrice = this.formatPrice(event.price, event.currency);

            return '<div class="eb-carousel-slide" data-slide="' + index + '">' +
                '<div class="eb-featured-event">' +
                    '<div class="eb-featured-image">' +
                        (event.image_url ?
                            '<img src="' + event.image_url + '" alt="' + this.escapeHtml(event.title) + '">' :
                            '<div class="eb-featured-placeholder">⭐</div>') +
                    '</div>' +
                    '<div class="eb-featured-content">' +
                        '<div class="eb-featured-badge">Featured Event</div>' +
                        '<h4 class="eb-featured-title">' + this.escapeHtml(event.title) + '</h4>' +
                        '<p class="eb-featured-description">' + this.escapeHtml(event.description) + '</p>' +
                        '<div class="eb-featured-meta">' +
                            '<div class="eb-featured-date">📅 ' + formattedDate + '</div>' +
                            '<div class="eb-featured-location">📍 ' + this.escapeHtml(event.location) + '</div>' +
                            '<div class="eb-featured-price">💰 ' + formattedPrice + '</div>' +
                        '</div>' +
                        '<div class="eb-featured-actions">' +
                            '<a href="' + event.booking_url + '" target="_blank" class="eb-featured-book-button">' +
                                'Book Featured Event' +
                            '</a>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
            '</div>';
        },

        getNavigationHTML: function() {
            var indicators = this.events.map(function(_, index) {
                return '<button class="eb-carousel-indicator' + (index === 0 ? ' active' : '') + '" data-slide="' + index + '"></button>';
            }).join('');

            return '<div class="eb-carousel-navigation">' +
                '<button class="eb-carousel-prev" aria-label="Previous event">‹</button>' +
                '<div class="eb-carousel-indicators">' + indicators + '</div>' +
                '<button class="eb-carousel-next" aria-label="Next event">›</button>' +
            '</div>';
        },

        addCarouselHandlers: function() {
            var self = this;

            // Previous button
            var prevButton = self.container.querySelector('.eb-carousel-prev');
            if (prevButton) {
                prevButton.addEventListener('click', function() {
                    self.previousSlide();
                });
            }

            // Next button
            var nextButton = self.container.querySelector('.eb-carousel-next');
            if (nextButton) {
                nextButton.addEventListener('click', function() {
                    self.nextSlide();
                });
            }

            // Indicators
            var indicators = self.container.querySelectorAll('.eb-carousel-indicator');
            indicators.forEach(function(indicator) {
                indicator.addEventListener('click', function() {
                    var slideIndex = parseInt(this.getAttribute('data-slide'));
                    self.goToSlide(slideIndex);
                });
            });

            // Pause auto-rotation on hover
            self.container.addEventListener('mouseenter', function() {
                self.stopAutoRotation();
            });

            self.container.addEventListener('mouseleave', function() {
                if (self.events.length > 1) {
                    self.startAutoRotation();
                }
            });
        },

        goToSlide: function(index) {
            if (index < 0 || index >= this.events.length) return;

            this.currentSlide = index;
            var track = this.container.querySelector('.eb-carousel-track');
            var translateX = -index * 100;

            track.style.transform = 'translateX(' + translateX + '%)';

            // Update indicators
            var indicators = this.container.querySelectorAll('.eb-carousel-indicator');
            indicators.forEach(function(indicator, i) {
                indicator.classList.toggle('active', i === index);
            });
        },

        nextSlide: function() {
            var nextIndex = (this.currentSlide + 1) % this.events.length;
            this.goToSlide(nextIndex);
        },

        previousSlide: function() {
            var prevIndex = (this.currentSlide - 1 + this.events.length) % this.events.length;
            this.goToSlide(prevIndex);
        },

        startAutoRotation: function() {
            var self = this;
            self.stopAutoRotation();
            self.slideInterval = setInterval(function() {
                self.nextSlide();
            }, 5000); // 5 seconds
        },

        stopAutoRotation: function() {
            if (this.slideInterval) {
                clearInterval(this.slideInterval);
                this.slideInterval = null;
            }
        },

        showError: function(message) {
            this.container.className = 'eb-featured-widget eb-error';
            this.container.innerHTML =
                '<div class="eb-error-state">' +
                    '<div class="eb-error-icon">⚠️</div>' +
                    '<div class="eb-error-message">' + this.escapeHtml(message) + '</div>' +
                    '<button class="eb-retry-button" onclick="this.parentNode.parentNode.widget.loadEvents()">Try Again</button>' +
                '</div>';
        },

        getLoadingHTML: function() {
            return '<div class="eb-loading-state">' +
                '<div class="eb-loading-spinner"></div>' +
                '<div class="eb-loading-message">Loading featured events...</div>' +
            '</div>';
        },

        getEmptyStateHTML: function() {
            return '<div class="eb-empty-state">' +
                '<div class="eb-empty-icon">⭐</div>' +
                '<div class="eb-empty-message">No featured events available</div>' +
            '</div>';
        },

        formatDate: function(date) {
            var options = {
                year: 'numeric',
                month: 'short',
                day: 'numeric',
                hour: '2-digit',
                minute: '2-digit'
            };
            return date.toLocaleDateString('en-US', options);
        },

        formatPrice: function(price, currency) {
            if (price === 0) return 'Free';

            try {
                return new Intl.NumberFormat('en-US', {
                    style: 'currency',
                    currency: currency || 'USD'
                }).format(price);
            } catch (e) {
                return currency + ' ' + price;
            }
        },

        escapeHtml: function(text) {
            var div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
    };

    // Auto-initialize when DOM is ready
    function initializeWidget() {
        var container = document.getElementById(WIDGET_CONFIG.widgetId);
        if (container && !container.widget) {
            container.widget = new FeaturedEventsWidget(container, WIDGET_CONFIG);
        }
    }

    // Initialize immediately if DOM is ready, otherwise wait
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeWidget);
    } else {
        initializeWidget();
    }

    // Expose for manual initialization
    window.EventBookingsWidget.FeaturedEvents = FeaturedEventsWidget;

})();
```

## CSS Styling System

### Widget Base Styles

**File: `public/widgets/widget-styles.css`**

```css
/* EventBookings Widget Base Styles */

/* Reset and base styles */
.eb-events-widget *,
.eb-featured-widget * {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

.eb-events-widget,
.eb-featured-widget {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    line-height: 1.5;
    color: #333;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    overflow: hidden;
    margin: 20px 0;
}

/* Loading states */
.eb-loading-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 40px 20px;
    text-align: center;
}

.eb-loading-spinner {
    width: 32px;
    height: 32px;
    border: 3px solid #f3f3f3;
    border-top: 3px solid #007cba;
    border-radius: 50%;
    animation: eb-spin 1s linear infinite;
    margin-bottom: 16px;
}

@keyframes eb-spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

.eb-loading-message {
    color: #666;
    font-size: 14px;
}

/* Error states */
.eb-error-state {
    padding: 40px 20px;
    text-align: center;
}

.eb-error-icon {
    font-size: 32px;
    margin-bottom: 16px;
}

.eb-error-message {
    color: #d63638;
    margin-bottom: 16px;
    font-size: 14px;
}

.eb-retry-button {
    background: #007cba;
    color: white;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
}

.eb-retry-button:hover {
    background: #005a87;
}

/* Empty states */
.eb-empty-state {
    padding: 40px 20px;
    text-align: center;
}

.eb-empty-icon {
    font-size: 48px;
    margin-bottom: 16px;
    opacity: 0.5;
}

.eb-empty-message {
    color: #666;
    font-size: 16px;
}

/* Events List Widget */
.eb-events-header {
    padding: 20px;
    background: #f8f9fa;
    border-bottom: 1px solid #dee2e6;
}

.eb-events-header h3 {
    font-size: 20px;
    font-weight: 600;
    color: #333;
    margin: 0;
}

.eb-events-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 20px;
    padding: 20px;
}

.eb-event-card {
    background: white;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    overflow: hidden;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
    cursor: pointer;
}

.eb-event-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.eb-event-image {
    height: 200px;
    overflow: hidden;
    background: #f8f9fa;
    display: flex;
    align-items: center;
    justify-content: center;
}

.eb-event-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.eb-event-placeholder {
    font-size: 48px;
    opacity: 0.5;
}

.eb-event-content {
    padding: 20px;
}

.eb-event-title {
    font-size: 18px;
    font-weight: 600;
    margin-bottom: 8px;
    color: #333;
    line-height: 1.3;
}

.eb-event-description {
    color: #666;
    font-size: 14px;
    margin-bottom: 16px;
    line-height: 1.4;
}

.eb-event-meta {
    margin-bottom: 16px;
}

.eb-event-meta > div {
    font-size: 14px;
    color: #666;
    margin-bottom: 4px;
}

.eb-event-actions {
    margin-top: 16px;
}

.eb-book-button {
    display: inline-block;
    background: #007cba;
    color: white;
    text-decoration: none;
    padding: 10px 20px;
    border-radius: 6px;
    font-size: 14px;
    font-weight: 500;
    transition: background-color 0.2s ease;
}

.eb-book-button:hover {
    background: #005a87;
    color: white;
    text-decoration: none;
}

/* Featured Events Widget */
.eb-featured-header {
    padding: 20px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
}

.eb-featured-header h3 {
    font-size: 20px;
    font-weight: 600;
    margin: 0;
}

.eb-carousel-container {
    position: relative;
    overflow: hidden;
}

.eb-carousel-track {
    display: flex;
    transition: transform 0.3s ease;
}

.eb-carousel-slide {
    flex: 0 0 100%;
    width: 100%;
}

.eb-featured-event {
    display: flex;
    min-height: 300px;
}

.eb-featured-image {
    flex: 0 0 40%;
    background: #f8f9fa;
    display: flex;
    align-items: center;
    justify-content: center;
}

.eb-featured-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.eb-featured-placeholder {
    font-size: 64px;
    opacity: 0.5;
}

.eb-featured-content {
    flex: 1;
    padding: 30px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.eb-featured-badge {
    display: inline-block;
    background: #ffd700;
    color: #333;
    padding: 4px 12px;
    border-radius: 16px;
    font-size: 12px;
    font-weight: 600;
    margin-bottom: 12px;
    width: fit-content;
}

.eb-featured-title {
    font-size: 24px;
    font-weight: 700;
    margin-bottom: 12px;
    color: #333;
    line-height: 1.2;
}

.eb-featured-description {
    color: #666;
    font-size: 16px;
    margin-bottom: 20px;
    line-height: 1.5;
}

.eb-featured-meta {
    margin-bottom: 20px;
}

.eb-featured-meta > div {
    font-size: 14px;
    color: #666;
    margin-bottom: 6px;
}

.eb-featured-book-button {
    display: inline-block;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    text-decoration: none;
    padding: 12px 24px;
    border-radius: 8px;
    font-size: 16px;
    font-weight: 600;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
    width: fit-content;
}

.eb-featured-book-button:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
    color: white;
    text-decoration: none;
}

/* Carousel Navigation */
.eb-carousel-navigation {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 16px;
    background: rgba(255, 255, 255, 0.9);
    padding: 8px 16px;
    border-radius: 24px;
    backdrop-filter: blur(10px);
}

.eb-carousel-prev,
.eb-carousel-next {
    background: #333;
    color: white;
    border: none;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 16px;
    transition: background-color 0.2s ease;
}

.eb-carousel-prev:hover,
.eb-carousel-next:hover {
    background: #555;
}

.eb-carousel-indicators {
    display: flex;
    gap: 8px;
}

.eb-carousel-indicator {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: none;
    background: #ccc;
    cursor: pointer;
    transition: background-color 0.2s ease;
}

.eb-carousel-indicator.active {
    background: #333;
}

/* Responsive Design */
@media (max-width: 768px) {
    .eb-events-grid {
        grid-template-columns: 1fr;
    }

    .eb-featured-event {
        flex-direction: column;
    }

    .eb-featured-image {
        flex: 0 0 200px;
    }

    .eb-featured-content {
        padding: 20px;
    }

    .eb-featured-title {
        font-size: 20px;
    }

    .eb-carousel-navigation {
        bottom: 10px;
        padding: 6px 12px;
    }
}

@media (max-width: 480px) {
    .eb-events-widget,
    .eb-featured-widget {
        margin: 10px 0;
        border-radius: 4px;
    }

    .eb-events-grid {
        padding: 10px;
        gap: 10px;
    }

    .eb-event-content {
        padding: 15px;
    }

    .eb-featured-content {
        padding: 15px;
    }

    .eb-featured-title {
        font-size: 18px;
    }
}
```

## Widget Integration Guide

### Theme Integration Methods

#### 1. App Embed Blocks (Recommended)

Create Shopify app embed blocks for easy theme integration:

**File: `blocks/events-list.liquid`**
```liquid
{{ 'widget-styles.css' | asset_url | stylesheet_tag }}

<div id="eventbookings-events-widget-{{ block.id }}"></div>

<script>
  document.addEventListener('DOMContentLoaded', function() {
    var script = document.createElement('script');
    script.src = '{{ shop.permanent_domain | append: '/apps/eventbookings/widgets/events.js?shop=' | append: shop.permanent_domain }}';
    script.onload = function() {
      var container = document.getElementById('eventbookings-events-widget-{{ block.id }}');
      if (container && window.EventBookingsWidget) {
        new window.EventBookingsWidget.EventsList(container);
      }
    };
    document.head.appendChild(script);
  });
</script>

{% schema %}
{
  "name": "EventBookings Events",
  "target": "section",
  "settings": [
    {
      "type": "header",
      "content": "Event Display Settings"
    },
    {
      "type": "select",
      "id": "layout",
      "label": "Layout",
      "options": [
        { "value": "grid", "label": "Grid" },
        { "value": "list", "label": "List" }
      ],
      "default": "grid"
    },
    {
      "type": "range",
      "id": "events_per_row",
      "label": "Events per row",
      "min": 1,
      "max": 4,
      "step": 1,
      "default": 3
    }
  ]
}
{% endschema %}
```

#### 2. Direct HTML Integration

```html
<!-- Place this where you want the events to appear -->
<div id="eventbookings-events-widget"></div>

<!-- Load the widget script -->
<script src="https://your-app-domain.com/widgets/events.js?shop=your-shop.myshopify.com"></script>
```

### Custom Styling

Merchants can customize widget appearance using CSS:

```css
/* Override default widget styles */
.eb-events-widget {
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

.eb-event-card {
    border: 2px solid #your-brand-color;
}

.eb-book-button {
    background: #your-brand-color;
}

.eb-book-button:hover {
    background: #your-brand-color-dark;
}
```

This comprehensive widget system provides flexible, responsive, and theme-compatible components for displaying EventBookings events in Shopify storefronts while maintaining good performance and user experience.