# EventBookings Plugin Frontend Components

Comprehensive documentation for the frontend JavaScript components, CSS classes, and template structure used in the EventBookings WordPress plugin.

## Table of Contents

1. [Architecture Overview](#architecture-overview)
2. [JavaScript Components](#javascript-components)
3. [CSS Classes and Styling](#css-classes-and-styling)
4. [Template Structure](#template-structure)
5. [Utility Functions](#utility-functions)
6. [Component Integration](#component-integration)
7. [Customization Guide](#customization-guide)
8. [Browser Compatibility](#browser-compatibility)

## Architecture Overview

### Component-Based Design

The frontend architecture follows a modular, component-based design pattern:

```
Frontend Architecture
├── Components/
│   ├── EventCard - Individual event display cards
│   ├── EventDetails - Detailed event information
│   ├── TicketPopup - Ticket selection interface
│   ├── Checkout - Booking form and payment
│   └── ButtonWidget - Embeddable booking buttons
├── Utilities/
│   ├── Helpers - Common utility functions
│   ├── Constants - Application constants
│   ├── Component - Component factory utilities
│   └── TicketValidation - Form validation logic
└── Assets/
    ├── CSS - Styling and themes
    ├── Images - Icons and graphics
    └── Fonts - Custom icon fonts
```

### Build System

- **Bundler**: Webpack for module bundling
- **Transpilation**: Babel for ES6+ compatibility
- **CSS Processing**: PostCSS with autoprefixer
- **Dependencies**: jQuery, Moment.js, Owl Carousel, Select2

### Entry Point

```javascript
// src/index.js - Main entry point
import EventCard from './component/event-card/EventCard';
import EventDetails from './component/event-details/EventDetails';
import TicketPopup from './component/event-details/TicketPopup';
import Checkout from './component/checkout/Checkout';
import ButtonTypeWidget from './component/button-widget/ButtonTypeWidget';

const EventComponents = {
  EventCard,
  EventDetails,
  TicketPopup,
  ButtonTypeWidget,
  Checkout,
};

export default EventComponents;
```

## JavaScript Components

### EventCard Component

**Purpose**: Renders individual event cards in listings and grids.

**File**: `src/component/event-card/EventCard.js`

#### Usage
```javascript
import EventCard from './component/event-card/EventCard';

// Initialize event card
const eventCard = EventCard({
    eventDetails: eventData,
    customized_settings: displaySettings,
    containerSelector: '.event-container'
});
```

#### Parameters
```javascript
{
    eventDetails: {
        uuid: "event-uuid",
        name: "Event Name",
        description: "Event description",
        starts_on: { utc: "2024-01-01 19:00" },
        ends_on: { utc: "2024-01-01 20:00" },
        thumbnail: "path/to/image.jpg",
        cdn_thumbnail: "https://cdn.url/image.webp",
        price: "$25.00",
        currency: "USD",
        currency_symbol: "$",
        has_multiple_price: true,
        ticket_remaining: 50,
        duration: "2h 30m",
        is_past: false,
        is_live: false,
        rating_review: "4.5 (23)",
        location: {
            venue_name: "Conference Center",
            address: "123 Main St"
        }
    },
    customized_settings: {
        display_event_description: true,
        display_countdown: true,
        display_ticket_price_on_event_page: true
    }
}
```

#### Generated Structure
```html
<div class="eb-event-card">
    <div class="eb-event-image-container">
        <img src="event-thumbnail.jpg" alt="Event Name" class="eb-event-image">
        <div class="eb-event-overlay">
            <button class="eb-book-now-btn">Book Now</button>
        </div>
    </div>
    <div class="eb-event-content">
        <h3 class="eb-event-title">Event Name</h3>
        <div class="eb-event-meta">
            <span class="eb-event-date">Jan 15, 2024</span>
            <span class="eb-event-price">$25.00</span>
        </div>
        <p class="eb-event-description">Event description...</p>
        <div class="eb-event-actions">
            <button class="eb-view-details">View Details</button>
        </div>
    </div>
</div>
```

#### Methods
- `render()`: Generate and return event card HTML
- `bindEvents()`: Attach event listeners
- `updateCountdown()`: Update countdown timers
- `handleBookingClick()`: Process booking button clicks

---

### EventDetails Component

**Purpose**: Displays comprehensive event information with booking capabilities.

**File**: `src/component/event-details/EventDetails.js`

#### Usage
```javascript
import EventDetails from './component/event-details/EventDetails';

const eventDetails = EventDetails({
    eventDetails: fullEventData,
    containerSelector: '.event-details-container',
    customized_settings: displaySettings
});
```

#### Features
- **Image Gallery**: Multiple event images with navigation
- **Event Information**: Complete event details and description
- **Ticket Selection**: Interactive ticket type selection
- **Countdown Timers**: Multiple countdown displays
- **Location Map**: Google Maps integration
- **Booking Interface**: Direct booking capability

#### Key Methods
```javascript
// Initialize event details display
eventDetails.init();

// Update event information
eventDetails.updateEventData(newEventData);

// Refresh countdown timers
eventDetails.updateCountdowns();

// Handle ticket selection
eventDetails.onTicketSelect(ticketData);
```

---

### TicketPopup Component

**Purpose**: Modal interface for ticket selection and attendee information.

**File**: `src/component/event-details/TicketPopup.js`

#### Features
- **Modal Interface**: Overlay popup for ticket selection
- **Ticket Types**: Display available ticket categories
- **Quantity Selection**: Interactive quantity controls
- **Price Calculation**: Real-time total calculation
- **Validation**: Form validation before proceeding

#### Usage
```javascript
import TicketPopup from './component/event-details/TicketPopup';

const ticketPopup = TicketPopup({
    eventDetails: eventData,
    ticketTypes: availableTickets,
    onTicketSelect: handleTicketSelection,
    onClose: handlePopupClose
});
```

#### Event Handlers
```javascript
// Ticket selection callback
function handleTicketSelection(selectedTickets) {
    console.log('Selected tickets:', selectedTickets);
    // Process selected tickets
}

// Popup close callback
function handlePopupClose() {
    // Cleanup or state management
}
```

---

### Checkout Component

**Purpose**: Complete booking form with payment processing integration.

**File**: `src/component/checkout/Checkout.js`

#### Features
- **Multi-step Form**: Progressive form completion
- **Attendee Information**: Collect attendee details
- **Payment Integration**: Secure payment processing
- **Form Validation**: Client-side and server-side validation
- **Address Autocomplete**: Google Places integration
- **Coupon Support**: Discount code application

#### Usage
```javascript
import Checkout from './component/checkout/Checkout';

const checkout = Checkout({
    eventDetails: eventData,
    ticketData: selectedTickets,
    orderPayloadCallback: handleOrderSubmission
});
```

#### Form Structure
```html
<div class="eb-event-checkout-wrapper">
    <form class="eb-checkout-form">
        <!-- Order Summary -->
        <div class="eb-order-summary">
            <div class="eb-selected-tickets"></div>
            <div class="eb-order-total"></div>
        </div>

        <!-- Attendee Information -->
        <div class="eb-attendee-section">
            <div class="eb-attendee-form"></div>
        </div>

        <!-- Billing Information -->
        <div class="eb-billing-section">
            <div class="eb-billing-form"></div>
        </div>

        <!-- Payment Section -->
        <div class="eb-payment-section">
            <button type="submit" class="eb-submit-order">Complete Booking</button>
        </div>
    </form>
</div>
```

#### Validation Rules
```javascript
const validationRules = {
    first_name: {
        required: true,
        minlength: 2,
        maxlength: 50
    },
    last_name: {
        required: true,
        minlength: 2,
        maxlength: 50
    },
    email: {
        required: true,
        email: true
    },
    phone: {
        required: true,
        pattern: /^[\+]?[1-9][\d]{0,15}$/
    }
};
```

---

### ButtonWidget Component

**Purpose**: Embeddable booking button for external websites.

**File**: `src/component/button-widget/ButtonTypeWidget.js`

#### Features
- **Customizable Appearance**: Various button styles and colors
- **Direct Booking**: One-click booking initiation
- **Responsive Design**: Works on all device sizes
- **Easy Integration**: Simple embed code

#### Usage
```javascript
import ButtonTypeWidget from './component/button-widget/ButtonTypeWidget';

const bookingButton = ButtonTypeWidget({
    eventId: 'event-uuid',
    buttonText: 'Book Now',
    buttonStyle: 'primary',
    targetUrl: 'https://example.com/event-details'
});
```

## CSS Classes and Styling

### Core CSS Classes

#### Event Card Classes
```css
/* Main event card container */
.eb-event-card {
    background: #ffffff;
    border: 1px solid #e1e1e1;
    border-radius: 8px;
    overflow: hidden;
    transition: all 0.3s ease;
}

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

/* Event image styling */
.eb-event-image-container {
    position: relative;
    overflow: hidden;
    height: 200px;
}

.eb-event-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.3s ease;
}

.eb-event-card:hover .eb-event-image {
    transform: scale(1.05);
}

/* Event content area */
.eb-event-content {
    padding: 16px;
}

.eb-event-title {
    font-size: 18px;
    font-weight: 600;
    margin: 0 0 8px 0;
    color: #333333;
}

.eb-event-meta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: 8px 0;
    font-size: 14px;
    color: #666666;
}

.eb-event-price {
    font-weight: 600;
    color: #e74c3c;
}
```

#### Layout Classes
```css
/* Event grid layout */
.eb-events-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 24px;
    padding: 24px 0;
}

/* Featured events carousel */
.eb-featured-carousel {
    position: relative;
    overflow: hidden;
}

.eb-carousel-item {
    display: flex;
    transition: transform 0.5s ease;
}

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

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

@media (max-width: 480px) {
    .eb-event-image-container {
        height: 160px;
    }

    .eb-event-title {
        font-size: 16px;
    }
}
```

#### Button Styles
```css
/* Primary booking button */
.eb-book-now-btn {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border: none;
    padding: 12px 24px;
    border-radius: 6px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
}

.eb-book-now-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}

.eb-book-now-btn:active {
    transform: translateY(0);
}

/* Secondary button */
.eb-view-details-btn {
    background: transparent;
    color: #667eea;
    border: 2px solid #667eea;
    padding: 10px 20px;
    border-radius: 6px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.3s ease;
}

.eb-view-details-btn:hover {
    background: #667eea;
    color: white;
}
```

#### Form Styles
```css
/* Checkout form styling */
.eb-checkout-form {
    max-width: 600px;
    margin: 0 auto;
    background: white;
    padding: 32px;
    border-radius: 12px;
    box-shadow: 0 8px 32px rgba(0,0,0,0.1);
}

.eb-form-group {
    margin-bottom: 20px;
}

.eb-form-label {
    display: block;
    font-weight: 600;
    margin-bottom: 6px;
    color: #333333;
}

.eb-form-input {
    width: 100%;
    padding: 12px 16px;
    border: 2px solid #e1e1e1;
    border-radius: 6px;
    font-size: 16px;
    transition: border-color 0.3s ease;
}

.eb-form-input:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

.eb-form-error {
    color: #e74c3c;
    font-size: 14px;
    margin-top: 4px;
}
```

#### Countdown Timer Styles
```css
.eb-countdown-container {
    display: flex;
    justify-content: center;
    gap: 16px;
    margin: 16px 0;
}

.eb-countdown-item {
    text-align: center;
    background: #f8f9fa;
    padding: 12px;
    border-radius: 8px;
    min-width: 60px;
}

.eb-countdown-number {
    font-size: 24px;
    font-weight: 700;
    color: #667eea;
    display: block;
}

.eb-countdown-label {
    font-size: 12px;
    color: #666666;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
```

### Theme Customization

#### CSS Custom Properties
```css
:root {
    /* Primary colors */
    --eb-primary-color: #667eea;
    --eb-primary-hover: #5a67d8;
    --eb-primary-light: rgba(102, 126, 234, 0.1);

    /* Secondary colors */
    --eb-secondary-color: #718096;
    --eb-success-color: #48bb78;
    --eb-warning-color: #ed8936;
    --eb-error-color: #e53e3e;

    /* Typography */
    --eb-font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
    --eb-font-size-base: 16px;
    --eb-font-weight-normal: 400;
    --eb-font-weight-medium: 500;
    --eb-font-weight-semibold: 600;
    --eb-font-weight-bold: 700;

    /* Spacing */
    --eb-spacing-xs: 4px;
    --eb-spacing-sm: 8px;
    --eb-spacing-md: 16px;
    --eb-spacing-lg: 24px;
    --eb-spacing-xl: 32px;

    /* Borders */
    --eb-border-radius: 8px;
    --eb-border-color: #e2e8f0;
    --eb-border-width: 1px;

    /* Shadows */
    --eb-shadow-sm: 0 2px 4px rgba(0,0,0,0.05);
    --eb-shadow-md: 0 4px 12px rgba(0,0,0,0.1);
    --eb-shadow-lg: 0 8px 32px rgba(0,0,0,0.15);
}
```

## Template Structure

### Event Listing Template

```html
<div class="eb-events-container">
    <!-- Loading state -->
    <div class="eb-loading-state" id="events-loading">
        <div class="eb-loader">
            <div class="eb-spinner"></div>
            <span>Loading events...</span>
        </div>
    </div>

    <!-- Events grid -->
    <div class="eb-events-grid" id="events-grid">
        <!-- Event cards will be inserted here -->
    </div>

    <!-- Pagination -->
    <div class="eb-pagination" id="events-pagination">
        <button class="eb-pagination-btn" id="prev-page">Previous</button>
        <div class="eb-pagination-info">
            <span id="current-page">1</span> of <span id="total-pages">5</span>
        </div>
        <button class="eb-pagination-btn" id="next-page">Next</button>
    </div>

    <!-- Empty state -->
    <div class="eb-empty-state" id="no-events" style="display: none;">
        <div class="eb-empty-icon">🎫</div>
        <h3>No Events Available</h3>
        <p>Check back soon for upcoming events!</p>
    </div>
</div>
```

### Event Details Template

```html
<div class="eb-event-details-container">
    <!-- Hero section -->
    <div class="eb-event-hero">
        <div class="eb-event-image-gallery">
            <div class="eb-main-image">
                <img src="event-banner.jpg" alt="Event Banner">
            </div>
            <div class="eb-thumbnail-nav">
                <!-- Thumbnail navigation -->
            </div>
        </div>

        <div class="eb-event-info">
            <h1 class="eb-event-title">Event Title</h1>
            <div class="eb-event-meta">
                <div class="eb-event-date">
                    <i class="eb-icon-calendar"></i>
                    <span>January 15, 2024</span>
                </div>
                <div class="eb-event-location">
                    <i class="eb-icon-location"></i>
                    <span>Conference Center</span>
                </div>
                <div class="eb-event-price">
                    <i class="eb-icon-ticket"></i>
                    <span>From $25.00</span>
                </div>
            </div>

            <!-- Countdown timer -->
            <div class="eb-countdown-section">
                <h3>Event Starts In</h3>
                <div class="eb-countdown-display">
                    <!-- Countdown components -->
                </div>
            </div>

            <!-- Action buttons -->
            <div class="eb-event-actions">
                <button class="eb-book-now-btn eb-btn-primary">Book Now</button>
                <button class="eb-share-btn eb-btn-secondary">Share Event</button>
            </div>
        </div>
    </div>

    <!-- Event description -->
    <div class="eb-event-description">
        <h2>About This Event</h2>
        <div class="eb-description-content">
            <!-- Event description content -->
        </div>
    </div>

    <!-- Location map -->
    <div class="eb-event-location-map">
        <h2>Event Location</h2>
        <div class="eb-map-container">
            <div id="event-map"></div>
        </div>
    </div>
</div>
```

## Utility Functions

### Helpers Class

**File**: `src/utils/helpers.js`

```javascript
export class Helpers {
    // Get event display name with fallback
    static getEventDisplayName(eventDetails) {
        return eventDetails?.display_name ?? eventDetails?.name ?? 'Untitled Event';
    }

    // Generate image URLs with CDN support
    static getEventBannerImage(eventDetails, index = 0) {
        const cdnImage = eventDetails?.cdn_banner;
        const regularImage = eventDetails?.banner?.[index]?.image;

        if (cdnImage) return cdnImage;
        if (regularImage) return Config.EB_BASE_URL + '/' + regularImage;
        return this.getEventFallbackImage();
    }

    // Format event dates
    static formatEventDate(dateString, format = 'MMM DD, YYYY h:mm A') {
        return moment(dateString).format(format);
    }

    // Calculate event duration
    static getEventDuration(startDate, endDate) {
        const start = moment(startDate);
        const end = moment(endDate);
        const duration = moment.duration(end.diff(start));

        const hours = duration.hours();
        const minutes = duration.minutes();

        if (hours > 0) {
            return `${hours}h ${minutes > 0 ? minutes + 'm' : ''}`;
        }
        return `${minutes}m`;
    }

    // Format currency with symbol
    static formatPrice(amount, currency = 'USD') {
        const symbol = Constants.SupportedCurrencySigns[currency] || '$';
        return `${symbol}${parseFloat(amount).toFixed(2)}`;
    }

    // Check if event is live or past
    static getEventStatus(eventDetails) {
        const now = moment();
        const startTime = moment(eventDetails.starts_on?.utc);
        const endTime = moment(eventDetails.ends_on?.utc);

        if (now.isBefore(startTime)) return 'upcoming';
        if (now.isBetween(startTime, endTime)) return 'live';
        return 'past';
    }

    // Generate SEO-friendly event URLs
    static getEventSlug(eventName) {
        return eventName
            .toLowerCase()
            .replace(/[^a-z0-9 -]/g, '')
            .replace(/\s+/g, '-')
            .replace(/-+/g, '-')
            .trim('-');
    }
}
```

### Constants Class

**File**: `src/utils/constants.js`

```javascript
export class Constants {
    // Event types
    static EVENT_TYPE = {
        VENUE: 'venue_event',
        ONLINE: 'online_event'
    };

    static EVENT_TYPE_NUMBER = {
        VENUE: 1,
        VIRTUAL: 2,
        UNDECIDED: 3
    };

    // Ticket categories
    static TICKET_CATEGORY = {
        DONATION: 'donation',
        TICKET: 'ticket'
    };

    // Date formats
    static DEFAULT_DATE_FORMAT = "ddd, MMM DD, YYYY h:mm A";
    static SHORT_DATE_FORMAT = "MMM DD, YYYY";
    static TIME_FORMAT = "h:mm A";

    // Currency symbols
    static SupportedCurrencySigns = {
        "AUD": "$", "USD": "$", "GBP": "£", "NZD": "$",
        "CAD": "$", "EUR": "€", "BRL": "R$", "CNY": "¥",
        "DKK": "kr", "EGP": "E£", "HUF": "Ft", "IDR": "Rp",
        "JOD": "د.ا", "MYR": "RM", "NOK": "kr", "PHP": "₱",
        "RON": "lei", "SAR": "ر.س", "SGD": "$", "SEK": "kr",
        "THB": "฿", "TRY": "₺", "ZAR": "R"
    };

    // Widget types
    static WIDGET_TYPE = {
        TICKET_PAGE: 1,
        BUTTON: 3
    };

    // Resource types
    static EVENT_RESOURCE_TEXT = 'text';
    static EVENT_RESOURCE_IMAGE = 'image';
    static EVENT_RESOURCE_VIDEO = 'video';
    static EVENT_RESOURCE_PDF = 'pdf';
}
```

## Component Integration

### WordPress Integration

```javascript
// Integration with WordPress localized data
class WordPressIntegration {
    constructor(localizedData) {
        this.ajaxUrl = localizedData.ajaxurl;
        this.nonce = localizedData.ajax_nonce;
        this.detailsSlug = localizedData.details_slug;
        this.paymentUrl = localizedData.EVENTBOOKINGS_REMOTE_PAYMENT_URL;
        this.seatplanUrl = localizedData.seatplan;
    }

    // Make AJAX requests to WordPress
    async makeAjaxRequest(action, data = {}) {
        const formData = new FormData();
        formData.append('action', action);
        formData.append('ajax_nonce', this.nonce);

        Object.keys(data).forEach(key => {
            formData.append(key, data[key]);
        });

        const response = await fetch(this.ajaxUrl, {
            method: 'POST',
            body: formData
        });

        return response.json();
    }

    // Get events data
    async getEvents(position = 1) {
        return this.makeAjaxRequest('eventbookings_wp_get_events_public', {
            position: position
        });
    }

    // Get featured events
    async getFeaturedEvents(position = 1) {
        return this.makeAjaxRequest('eventbookings_wp_get_events_featured', {
            position: position
        });
    }

    // Get event details
    async getEventDetails(eventUuid) {
        return this.makeAjaxRequest('eventbookings_wp_get_event_details', {
            eventUuid: eventUuid
        });
    }

    // Submit ticket purchase
    async purchaseTickets(orderData) {
        return this.makeAjaxRequest('eventbookings_wp_ticket_purchase', {
            order: JSON.stringify(orderData)
        });
    }
}
```

### Event System

```javascript
// Custom event system for component communication
class EventSystem {
    constructor() {
        this.listeners = {};
    }

    // Register event listener
    on(event, callback, context = null) {
        if (!this.listeners[event]) {
            this.listeners[event] = [];
        }

        this.listeners[event].push({
            callback: callback,
            context: context
        });
    }

    // Remove event listener
    off(event, callback) {
        if (!this.listeners[event]) return;

        this.listeners[event] = this.listeners[event].filter(
            listener => listener.callback !== callback
        );
    }

    // Emit event
    emit(event, data = null) {
        if (!this.listeners[event]) return;

        this.listeners[event].forEach(listener => {
            if (listener.context) {
                listener.callback.call(listener.context, data);
            } else {
                listener.callback(data);
            }
        });
    }

    // One-time event listener
    once(event, callback, context = null) {
        const onceCallback = (data) => {
            callback.call(context, data);
            this.off(event, onceCallback);
        };

        this.on(event, onceCallback);
    }
}

// Global event system instance
const eventBus = new EventSystem();

// Usage examples
eventBus.on('event:selected', (eventData) => {
    console.log('Event selected:', eventData);
});

eventBus.on('ticket:added', (ticketData) => {
    // Update cart display
    updateCartDisplay(ticketData);
});

eventBus.on('booking:completed', (orderData) => {
    // Redirect to confirmation page
    window.location.href = orderData.confirmationUrl;
});
```

## Customization Guide

### Styling Customization

#### Using CSS Custom Properties

```css
/* Override default theme colors */
:root {
    --eb-primary-color: #your-brand-color;
    --eb-primary-hover: #your-hover-color;
    --eb-font-family: 'Your Custom Font', sans-serif;
}
```

#### Custom Event Card Styling

```css
/* Custom event card design */
.eb-event-card {
    border-radius: 16px;
    overflow: hidden;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
}

.eb-event-card .eb-event-title {
    color: white;
}

.eb-event-card .eb-event-meta {
    color: rgba(255, 255, 255, 0.8);
}

/* Custom button styling */
.eb-book-now-btn {
    background: rgba(255, 255, 255, 0.2);
    border: 2px solid white;
    color: white;
    backdrop-filter: blur(10px);
}

.eb-book-now-btn:hover {
    background: white;
    color: #667eea;
}
```

#### Responsive Customization

```css
/* Mobile-first responsive design */
.eb-events-grid {
    display: grid;
    gap: 16px;
    grid-template-columns: 1fr;
}

@media (min-width: 640px) {
    .eb-events-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1024px) {
    .eb-events-grid {
        grid-template-columns: repeat(3, 1fr);
        gap: 24px;
    }
}

@media (min-width: 1280px) {
    .eb-events-grid {
        grid-template-columns: repeat(4, 1fr);
    }
}
```

### JavaScript Customization

#### Custom Event Handlers

```javascript
// Extend EventCard with custom functionality
class CustomEventCard extends EventCard {
    constructor(params) {
        super(params);
        this.addCustomBehaviors();
    }

    addCustomBehaviors() {
        // Add analytics tracking
        this.container.addEventListener('click', (e) => {
            if (e.target.classList.contains('eb-book-now-btn')) {
                // Track booking button click
                gtag('event', 'booking_initiated', {
                    'event_category': 'engagement',
                    'event_label': this.eventDetails.name
                });
            }
        });

        // Add social sharing
        const shareBtn = this.container.querySelector('.eb-share-btn');
        if (shareBtn) {
            shareBtn.addEventListener('click', () => {
                this.shareEvent();
            });
        }
    }

    shareEvent() {
        if (navigator.share) {
            navigator.share({
                title: this.eventDetails.name,
                text: this.eventDetails.description,
                url: window.location.href
            });
        } else {
            // Fallback to clipboard
            navigator.clipboard.writeText(window.location.href);
            alert('Event link copied to clipboard!');
        }
    }
}
```

#### Custom Validation Rules

```javascript
// Extend form validation
const customValidationRules = {
    // Custom phone number validation
    phone: {
        required: true,
        pattern: /^\+?[\d\s\-\(\)]{10,}$/,
        messages: {
            pattern: 'Please enter a valid phone number'
        }
    },

    // Custom age verification
    birth_date: {
        required: true,
        date: true,
        minAge: 18,
        custom: function(value) {
            const birthDate = new Date(value);
            const today = new Date();
            const age = today.getFullYear() - birthDate.getFullYear();

            if (age < 18) {
                return 'You must be 18 or older to register';
            }
            return true;
        }
    }
};
```

### Hook System Integration

```javascript
// WordPress-style hooks for customization
class HookSystem {
    constructor() {
        this.actions = {};
        this.filters = {};
    }

    // Add action hook
    addAction(tag, callback, priority = 10) {
        if (!this.actions[tag]) {
            this.actions[tag] = [];
        }

        this.actions[tag].push({
            callback: callback,
            priority: priority
        });

        // Sort by priority
        this.actions[tag].sort((a, b) => a.priority - b.priority);
    }

    // Execute action
    doAction(tag, ...args) {
        if (this.actions[tag]) {
            this.actions[tag].forEach(action => {
                action.callback(...args);
            });
        }
    }

    // Add filter hook
    addFilter(tag, callback, priority = 10) {
        if (!this.filters[tag]) {
            this.filters[tag] = [];
        }

        this.filters[tag].push({
            callback: callback,
            priority: priority
        });

        this.filters[tag].sort((a, b) => a.priority - b.priority);
    }

    // Apply filter
    applyFilters(tag, value, ...args) {
        if (this.filters[tag]) {
            return this.filters[tag].reduce((filteredValue, filter) => {
                return filter.callback(filteredValue, ...args);
            }, value);
        }
        return value;
    }
}

// Global hooks instance
const hooks = new HookSystem();

// Usage examples
hooks.addAction('event_card_rendered', (eventCard) => {
    console.log('Event card rendered:', eventCard);
});

hooks.addFilter('event_card_html', (html, eventData) => {
    // Add custom badge for premium events
    if (eventData.is_premium) {
        html = html.replace(
            '<div class="eb-event-content">',
            '<div class="eb-event-content"><span class="premium-badge">Premium</span>'
        );
    }
    return html;
});
```

## Browser Compatibility

### Supported Browsers

| Browser | Minimum Version | Notes |
|---------|----------------|-------|
| Chrome | 60+ | Full support |
| Firefox | 55+ | Full support |
| Safari | 12+ | Full support |
| Edge | 79+ | Full support |
| iOS Safari | 12+ | Mobile support |
| Chrome Mobile | 60+ | Mobile support |

### Polyfills and Fallbacks

```javascript
// Feature detection and polyfills
class FeatureDetection {
    static checkFeatures() {
        const features = {
            fetch: typeof fetch !== 'undefined',
            promises: typeof Promise !== 'undefined',
            arrowFunctions: true, // Transpiled by Babel
            flexbox: this.checkCSS('display', 'flex'),
            grid: this.checkCSS('display', 'grid'),
            customProperties: this.checkCSS('--custom', 'property')
        };

        return features;
    }

    static checkCSS(property, value) {
        const element = document.createElement('div');
        element.style[property] = value;
        return element.style[property] === value;
    }

    static loadPolyfills() {
        const features = this.checkFeatures();

        // Load fetch polyfill if needed
        if (!features.fetch) {
            this.loadScript('https://polyfill.io/v3/polyfill.min.js?features=fetch');
        }

        // Load Promise polyfill if needed
        if (!features.promises) {
            this.loadScript('https://polyfill.io/v3/polyfill.min.js?features=Promise');
        }
    }

    static loadScript(src) {
        const script = document.createElement('script');
        script.src = src;
        script.async = true;
        document.head.appendChild(script);
    }
}

// Initialize polyfills
FeatureDetection.loadPolyfills();
```

### Progressive Enhancement

```javascript
// Progressive enhancement strategy
class ProgressiveEnhancement {
    static init() {
        // Start with basic functionality
        this.setupBasicFeatures();

        // Add enhanced features if supported
        if (this.modernFeaturesSupported()) {
            this.setupModernFeatures();
        }

        // Add experimental features if available
        if (this.experimentalFeaturesSupported()) {
            this.setupExperimentalFeatures();
        }
    }

    static setupBasicFeatures() {
        // Basic event display without advanced interactions
        console.log('Setting up basic features');
    }

    static setupModernFeatures() {
        // Enhanced interactions, animations, etc.
        console.log('Setting up modern features');
    }

    static setupExperimentalFeatures() {
        // Cutting-edge features like Web Components, etc.
        console.log('Setting up experimental features');
    }

    static modernFeaturesSupported() {
        return (
            'IntersectionObserver' in window &&
            'requestAnimationFrame' in window &&
            CSS.supports('display', 'grid')
        );
    }

    static experimentalFeaturesSupported() {
        return (
            'customElements' in window &&
            'ResizeObserver' in window
        );
    }
}

// Initialize progressive enhancement
document.addEventListener('DOMContentLoaded', () => {
    ProgressiveEnhancement.init();
});
```

This comprehensive component documentation provides developers with all the information needed to understand, customize, and extend the EventBookings plugin's frontend functionality.