![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/public_html/ |
<?php
// Enable error reporting for debugging (remove in production)
error_reporting(E_ALL);
ini_set('display_errors', 0); // Don't display errors, but log them
ini_set('log_errors', 1);
// Ensure UTF-8 encoding for proper handling of emojis and Unicode characters
if (function_exists('mb_internal_encoding')) {
mb_internal_encoding('UTF-8');
}
if (function_exists('mb_http_output')) {
mb_http_output('UTF-8');
}
require_once 'includes/event_modal_bootstrap.php';
$is_ajax_event_modal = isset($_GET['ajax']) && $_GET['ajax'] === '1';
if ($is_ajax_event_modal) {
// Start output buffer - ensure UTF-8 encoding
if (function_exists('ob_start') && function_exists('mb_output_handler')) {
ob_start('mb_output_handler');
} else {
ob_start();
}
}
$user_id = $_SESSION['user_id'] ?? null;
// Get event details
$stmt = $pdo->prepare("
SELECT
e.*,
u.name as creator_name,
COALESCE(up.profile_image, u.profile_image) as creator_image,
COUNT(DISTINCT ea.user_id) as attendee_count,
COUNT(DISTINCT el.user_id) as like_count,
COUNT(DISTINCT ec.id) as comment_count
FROM events e
JOIN users u ON e.creator_id = u.id
LEFT JOIN event_attendees ea ON e.id = ea.event_id AND ea.status = 'attending'
LEFT JOIN user_profiles up ON u.id = up.user_id
LEFT JOIN event_likes el ON e.id = el.event_id
LEFT JOIN event_comments ec ON e.id = ec.event_id
WHERE e.id = ? AND e.status = 'published'
GROUP BY e.id
");
try {
$stmt->execute([$event_id]);
$event = $stmt->fetch(PDO::FETCH_ASSOC);
// Check if fetch returned false (no results)
if ($event === false) {
$event = null;
}
// Ensure description is properly encoded (in case it was corrupted)
if ($event && isset($event['description'])) {
// Force UTF-8 encoding if not already
if (!mb_check_encoding($event['description'], 'UTF-8')) {
$event['description'] = mb_convert_encoding($event['description'], 'UTF-8', 'auto');
}
}
} catch (PDOException $e) {
// Clear any output buffers
if (ob_get_level()) {
ob_clean();
}
header('Content-Type: application/json; charset=utf-8');
http_response_code(500);
$error_msg = 'Database query failed: ' . $e->getMessage();
echo json_encode(['success' => false, 'message' => $error_msg]);
error_log("Event modal query error: " . $e->getMessage());
error_log("Query params: user_id=" . ($user_id ?? 'null') . ", event_id=" . $event_id);
exit;
} catch (Exception $e) {
// Clear any output buffers
if (ob_get_level()) {
ob_clean();
}
header('Content-Type: application/json; charset=utf-8');
http_response_code(500);
$error_msg = 'Unexpected error: ' . $e->getMessage();
echo json_encode(['success' => false, 'message' => $error_msg]);
error_log("Event modal general error: " . $e->getMessage());
exit;
}
if (!$event) {
// Clear any output buffers
if (ob_get_level()) {
ob_clean();
}
header('Content-Type: application/json; charset=utf-8');
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Event not found']);
exit;
}
// Check if event is a private password party
// Only check if columns exist and are explicitly set to private
$is_private_party = false;
$has_password = false;
// Check if columns exist in the result (they might not exist if migration hasn't run)
if (isset($event['is_private_party'])) {
$is_private_party = ((int)$event['is_private_party'] === 1 || $event['is_private_party'] === true);
}
if (isset($event['party_password']) && !empty(trim($event['party_password']))) {
$has_password = true;
}
// Only apply password protection if BOTH conditions are met: is private AND has password
if ($is_private_party && $has_password) {
$is_creator = $user_id && $user_id == $event['creator_id'];
// Creator can always access
if (!$is_creator) {
// Ensure session is active
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
// Check if user has valid session access
$session_key = 'party_access_' . $event_id;
$time_key = 'party_access_time_' . $event_id;
$has_access = false;
if (isset($_SESSION[$session_key]) && $_SESSION[$session_key] === true) {
if (isset($_SESSION[$time_key])) {
$access_age = time() - (int)$_SESSION[$time_key];
if ($access_age < 3600) { // 1 hour access
$has_access = true;
} else {
// Access expired, clear session
unset($_SESSION[$session_key]);
unset($_SESSION[$time_key]);
}
}
}
if (!$has_access) {
// Return JSON response for AJAX or redirect for direct access
if ($is_ajax_event_modal) {
// Clear any output buffers
if (ob_get_level()) {
ob_clean();
}
header('Content-Type: application/json; charset=utf-8');
http_response_code(403);
echo json_encode([
'success' => false,
'message' => 'This is a private password party. Please enter the password.',
'requires_password' => true,
'redirect_url' => '/party_gate.php?id=' . $event_id
]);
exit;
} else {
header('Location: /party_gate.php?id=' . $event_id);
exit;
}
}
}
}
// Ensure all required fields exist with defaults
$event['is_free'] = isset($event['is_free']) ? (bool)$event['is_free'] : true;
$event['ticket_price'] = isset($event['ticket_price']) && $event['ticket_price'] !== null ? (float)$event['ticket_price'] : 0.00;
$event['max_attendees'] = isset($event['max_attendees']) && $event['max_attendees'] !== null ? (int)$event['max_attendees'] : null;
$event['event_type'] = isset($event['event_type']) ? $event['event_type'] : 'other';
$event['title'] = isset($event['title']) ? $event['title'] : 'Untitled Event';
$event['description'] = isset($event['description']) ? $event['description'] : '';
$event['creator_name'] = isset($event['creator_name']) ? $event['creator_name'] : 'Unknown';
$event['creator_id'] = isset($event['creator_id']) ? (int)$event['creator_id'] : 0;
$event['id'] = isset($event['id']) ? (int)$event['id'] : 0;
$event['start_date'] = isset($event['start_date']) ? $event['start_date'] : date('Y-m-d H:i:s');
$event['end_date'] = isset($event['end_date']) ? $event['end_date'] : date('Y-m-d H:i:s');
$event['location'] = isset($event['location']) ? $event['location'] : '';
$event['venue_name'] = isset($event['venue_name']) ? $event['venue_name'] : '';
$event['banner_image'] = isset($event['banner_image']) ? $event['banner_image'] : '';
$event['cover_image'] = isset($event['cover_image']) ? $event['cover_image'] : '';
$event['attendee_count'] = isset($event['attendee_count']) ? (int)$event['attendee_count'] : 0;
$event['like_count'] = isset($event['like_count']) ? (int)$event['like_count'] : 0;
$event['comment_count'] = isset($event['comment_count']) ? (int)$event['comment_count'] : 0;
// Get attendees with more info
try {
$stmt = $pdo->prepare("
SELECT
ea.*,
u.name,
u.profile_image,
u.bio,
DATE_FORMAT(ea.rsvp_date, '%M %d, %Y') as rsvp_date_formatted
FROM event_attendees ea
JOIN users u ON ea.user_id = u.id
WHERE ea.event_id = ? AND ea.status = 'attending'
ORDER BY ea.rsvp_date DESC
LIMIT 15
");
$stmt->execute([$event_id]);
$attendees = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("Event modal attendees query error: " . $e->getMessage());
$attendees = [];
}
// Check if user has a ticket (with error handling in case table doesn't exist)
$user_has_ticket = false;
$user_ticket = null;
$tickets_sold = 0;
try {
// Check if event_tickets table exists
$table_check = $pdo->query("SHOW TABLES LIKE 'event_tickets'")->fetch(PDO::FETCH_ASSOC);
if ($table_check && $user_id) {
$stmt = $pdo->prepare("
SELECT * FROM event_tickets
WHERE event_id = ? AND user_id = ? AND status IN ('pending', 'confirmed', 'used')
ORDER BY purchase_date DESC
LIMIT 1
");
$stmt->execute([$event_id, $user_id]);
$user_ticket = $stmt->fetch(PDO::FETCH_ASSOC);
$user_has_ticket = (bool)$user_ticket;
}
// Get ticket sales count
if ($table_check) {
$stmt = $pdo->prepare("
SELECT COUNT(*) as tickets_sold
FROM event_tickets
WHERE event_id = ? AND status IN ('pending', 'confirmed', 'used')
");
$stmt->execute([$event_id]);
$ticket_stats = $stmt->fetch(PDO::FETCH_ASSOC);
$tickets_sold = isset($ticket_stats['tickets_sold']) ? (int)$ticket_stats['tickets_sold'] : 0;
}
} catch (PDOException $e) {
// Table doesn't exist or query failed - use defaults
error_log("Event tickets query error: " . $e->getMessage());
$user_has_ticket = false;
$user_ticket = null;
$tickets_sold = 0;
} catch (Exception $e) {
// Any other error
error_log("Event tickets general error: " . $e->getMessage());
$user_has_ticket = false;
$user_ticket = null;
$tickets_sold = 0;
}
// Get comments
try {
$stmt = $pdo->prepare("
SELECT
ec.*,
u.name,
COALESCE(up.profile_image, u.profile_image) AS profile_image
FROM event_comments ec
JOIN users u ON ec.user_id = u.id
LEFT JOIN user_profiles up ON u.id = up.user_id
WHERE ec.event_id = ?
ORDER BY ec.created_at DESC
LIMIT 20
");
$stmt->execute([$event_id]);
$comments = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("Event modal comments query error: " . $e->getMessage());
$comments = [];
}
// Helper functions
function formatEventDate($start_date, $end_date) {
$start = new DateTime($start_date);
$end = new DateTime($end_date);
if ($start->format('Y-m-d') === $end->format('Y-m-d')) {
return $start->format('l, F j, Y') . ' at ' . $start->format('g:i A');
} else {
return $start->format('l, F j, Y g:i A') . ' - ' . $end->format('l, F j, Y g:i A');
}
}
function getEventTypeIcon($event_type) {
$icons = [
'live_music' => 'fas fa-music',
'workshop' => 'fas fa-graduation-cap',
'networking' => 'fas fa-users',
'release_party' => 'fas fa-champagne-glasses',
'battle' => 'fas fa-trophy',
'open_mic' => 'fas fa-microphone',
'studio_session' => 'fas fa-record-vinyl',
'other' => 'fas fa-calendar'
];
return $icons[$event_type] ?? 'fas fa-calendar';
}
function getEventTypeColor($event_type) {
$colors = [
'live_music' => '#667eea',
'workshop' => '#48bb78',
'networking' => '#ed8936',
'release_party' => '#9f7aea',
'battle' => '#f56565',
'open_mic' => '#38b2ac',
'studio_session' => '#ed64a6',
'other' => '#a0aec0'
];
return $colors[$event_type] ?? '#a0aec0';
}
function getEventTypeLabel($event_type) {
$translationKey = 'events.types.' . $event_type;
$label = t($translationKey);
if ($label === $translationKey) {
return ucwords(str_replace('_', ' ', $event_type));
}
return $label;
}
?>
<div class="event-modal-overlay" id="eventModalOverlay">
<div class="event-modal-container">
<button class="event-modal-close" onclick="closeEventModal()">
<i class="fas fa-times"></i>
</button>
<!-- Particle Canvas -->
<canvas id="eventModalParticles" class="event-modal-particles"></canvas>
<!-- Floating Attendees - Positioned outside scrollable content -->
<?php if (!empty($attendees) && count($attendees) > 0): ?>
<div class="event-floating-attendees" data-attendee-count="<?= count($attendees) ?>">
<?php
$totalAttendees = count($attendees);
foreach ($attendees as $index => $attendee):
// Smart positioning: distribute in a circular/spiral pattern for better visual appeal
$angle = ($index / $totalAttendees) * 2 * M_PI;
$radius = 35; // Base radius percentage
$leftPercent = 50 + ($radius * cos($angle)); // Center at 50%
$topPercent = 50 + ($radius * sin($angle)); // Center at 50%
// Add some randomness for natural look
$leftPercent += (($index % 3) - 1) * 5;
$topPercent += (($index % 4) - 1.5) * 4;
// Clamp values
$leftPercent = max(5, min(95, $leftPercent));
$topPercent = max(5, min(95, $topPercent));
?>
<div class="floating-attendee-wrapper"
data-index="<?= $index ?>"
data-user-id="<?= $attendee['user_id'] ?>"
style="
left: <?= $leftPercent ?>%;
top: <?= $topPercent ?>%;
animation-delay: <?= $index * 0.12 ?>s;
">
<a href="/artist_profile.php?id=<?= $attendee['user_id'] ?>"
class="floating-attendee-badge"
onclick="handleAttendeeClick(event, <?= $attendee['user_id'] ?>)">
<div class="floating-badge-glow"></div>
<div class="floating-badge-content">
<div class="floating-badge-avatar">
<?php if (!empty($attendee['profile_image'])): ?>
<img src="<?= htmlspecialchars($attendee['profile_image']) ?>"
alt="<?= htmlspecialchars($attendee['name']) ?>"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<div class="floating-badge-fallback" style="display: none;">
<?= strtoupper(substr($attendee['name'], 0, 1)) ?>
</div>
<?php else: ?>
<div class="floating-badge-fallback">
<?= strtoupper(substr($attendee['name'], 0, 1)) ?>
</div>
<?php endif; ?>
<div class="floating-status-indicator"></div>
</div>
<div class="floating-badge-name"><?= htmlspecialchars($attendee['name']) ?></div>
<?php if (!empty($attendee['rsvp_date_formatted'])): ?>
<div class="floating-badge-date">
<i class="fas fa-calendar-check"></i>
<?= $attendee['rsvp_date_formatted'] ?>
</div>
<?php endif; ?>
</div>
</a>
<!-- Tooltip on hover -->
<div class="floating-attendee-tooltip">
<div class="tooltip-header">
<strong><?= htmlspecialchars($attendee['name']) ?></strong>
</div>
<?php if (!empty($attendee['bio'])): ?>
<div class="tooltip-bio"><?= htmlspecialchars(substr($attendee['bio'], 0, 100)) ?><?= strlen($attendee['bio']) > 100 ? '...' : '' ?></div>
<?php endif; ?>
<div class="tooltip-action"><?= t('events.modal.tooltip.view_profile') ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Modal Content -->
<div class="event-modal-content">
<!-- Hero Section -->
<div class="event-modal-hero" style="background: linear-gradient(135deg, <?= getEventTypeColor($event['event_type']) ?>22, <?= getEventTypeColor($event['event_type']) ?>11);">
<!-- Event Banner Image -->
<?php if (!empty($event['banner_image']) || !empty($event['cover_image'])): ?>
<div class="event-modal-banner">
<img src="<?= htmlspecialchars($event['banner_image'] ?: $event['cover_image']) ?>"
alt="<?= htmlspecialchars($event['title']) ?>"
onerror="this.style.display='none'; this.parentElement.style.display='none';">
</div>
<?php endif; ?>
<div class="event-modal-hero-content">
<div class="event-modal-badge" style="background: <?= getEventTypeColor($event['event_type']) ?>">
<i class="<?= getEventTypeIcon($event['event_type']) ?>"></i>
<?= htmlspecialchars(getEventTypeLabel($event['event_type'])) ?>
</div>
<h1 class="event-modal-title"><?= htmlspecialchars($event['title'], ENT_QUOTES, 'UTF-8') ?></h1>
<div class="event-modal-creator">
<div class="event-creator-avatar">
<?php if (!empty($event['creator_image'])): ?>
<img src="<?= htmlspecialchars($event['creator_image']) ?>"
alt="<?= htmlspecialchars($event['creator_name']) ?>"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<span class="avatar-initial" style="display:none;">
<?= strtoupper(substr($event['creator_name'], 0, 1)) ?>
</span>
<?php else: ?>
<span class="avatar-initial">
<?= strtoupper(substr($event['creator_name'], 0, 1)) ?>
</span>
<?php endif; ?>
</div>
<span><?= t('events.modal.created_by') ?> <a href="/artist_profile.php?id=<?= $event['creator_id'] ?>"><?= htmlspecialchars($event['creator_name']) ?></a></span>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div class="event-modal-grid">
<!-- Left Column -->
<div class="event-modal-main">
<!-- Countdown -->
<div class="event-modal-card countdown-card" id="modalCountdownCard" data-start-timestamp="<?= $event['start_date'] ? (int)strtotime($event['start_date']) : '' ?>" data-start-date="<?= htmlspecialchars($event['start_date']) ?>">
<h3><i class="fas fa-clock"></i> <?= t('events.modal.countdown_title') ?></h3>
<div class="countdown-display">
<div class="countdown-unit">
<span class="countdown-value" id="modalDays">00</span>
<span class="countdown-label"><?= t('events.modal.countdown.days') ?></span>
</div>
<span class="countdown-colon">:</span>
<div class="countdown-unit">
<span class="countdown-value" id="modalHours">00</span>
<span class="countdown-label"><?= t('events.modal.countdown.hours') ?></span>
</div>
<span class="countdown-colon">:</span>
<div class="countdown-unit">
<span class="countdown-value" id="modalMinutes">00</span>
<span class="countdown-label"><?= t('events.modal.countdown.minutes') ?></span>
</div>
<span class="countdown-colon">:</span>
<div class="countdown-unit">
<span class="countdown-value" id="modalSeconds">00</span>
<span class="countdown-label"><?= t('events.modal.countdown.seconds') ?></span>
</div>
</div>
<div class="countdown-start-label">
<div class="countdown-start-icon">
<i class="fas fa-calendar-day"></i>
</div>
<div class="countdown-start-text">
<span class="countdown-start-prefix"><?= t('events.modal.countdown.starts_on') ?></span>
<span class="countdown-start-date"><?= date('M j, Y g:i A', strtotime($event['start_date'])) ?></span>
</div>
</div>
</div>
<!-- Description -->
<div class="event-modal-card">
<h3><i class="fas fa-info-circle"></i> <?= t('events.modal.about_title') ?></h3>
<div class="event-about-text">
<div class="event-about-text-content">
<?php
// Get description from database
$description = $event['description'] ?? '';
// Ensure it's UTF-8 encoded
if ($description && !mb_check_encoding($description, 'UTF-8')) {
$description = mb_convert_encoding($description, 'UTF-8', 'auto');
}
// Strip any existing HTML tags for security, but preserve all Unicode characters
$description = strip_tags($description);
// Escape HTML special characters but preserve UTF-8 (emojis pass through)
$description = htmlspecialchars($description, ENT_QUOTES, 'UTF-8');
// Convert newlines to <br> tags
$description = nl2br($description, false);
// Output - emojis should display correctly
echo $description;
?>
</div>
</div>
</div>
<!-- Details -->
<div class="event-modal-card info-panel-card">
<h3><i class="fas fa-calendar-alt"></i> <?= t('events.modal.details_title') ?></h3>
<?php
$directionPieces = array_filter([
$event['venue_name'] ?? '',
$event['address'] ?? '',
$event['location'] ?? ''
]);
$directionUrl = !empty($directionPieces)
? 'https://www.google.com/maps/dir/?api=1&destination=' . urlencode(implode(', ', $directionPieces))
: null;
?>
<div class="event-details-grid">
<div class="detail-item">
<i class="fas fa-calendar"></i>
<div>
<strong><?= t('events.modal.details.date_time') ?></strong>
<p><?= formatEventDate($event['start_date'], $event['end_date']) ?></p>
</div>
</div>
<?php if ($event['location']): ?>
<div class="detail-item">
<i class="fas fa-map-marker-alt"></i>
<div>
<strong><?= t('events.modal.details.location') ?></strong>
<p><?= htmlspecialchars($event['location']) ?></p>
<?php if ($directionUrl): ?>
<a href="<?= $directionUrl ?>" class="direction-btn" target="_blank" rel="noopener">
<i class="fas fa-location-arrow"></i> <?= t('events.modal.actions.get_directions') ?>
</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($event['venue_name']): ?>
<div class="detail-item">
<i class="fas fa-building"></i>
<div>
<strong><?= t('events.modal.details.venue') ?></strong>
<p><?= htmlspecialchars($event['venue_name']) ?></p>
</div>
</div>
<?php endif; ?>
<?php if ($event['address']): ?>
<div class="detail-item">
<i class="fas fa-map"></i>
<div>
<strong><?= t('events.modal.details.address') ?></strong>
<p><?= nl2br(htmlspecialchars($event['address'])) ?></p>
<?php if ($directionUrl): ?>
<a href="<?= $directionUrl ?>" class="direction-btn" target="_blank" rel="noopener">
<i class="fas fa-route"></i> <?= t('events.modal.actions.directions') ?>
</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<div class="detail-item">
<i class="fas fa-ticket-alt"></i>
<div>
<strong><?= t('events.modal.details.price') ?></strong>
<p><?= $event['is_free'] ? t('events.modal.price_free') : '$' . number_format($event['ticket_price'], 2) ?></p>
</div>
</div>
</div>
</div>
<!-- Comments -->
<div class="event-modal-card comments-panel-card">
<?php $commentCount = count($comments); ?>
<h3><i class="fas fa-comments"></i> <?= t('events.modal.comments_title', ['count' => $commentCount]) ?></h3>
<?php if ($user_id): ?>
<div class="comment-input-group">
<textarea id="modalCommentText" placeholder="<?= htmlspecialchars(t('events.modal.comment_placeholder')) ?>"></textarea>
<div class="comment-attachment-row">
<label class="comment-attachment-label">
<i class="fas fa-paperclip"></i>
<span><?= t('events.modal.comment_attach') ?></span>
<input type="file" id="modalCommentImage" accept="image/*" onchange="handleCommentImagePreview(event)">
</label>
<button type="button" class="comment-clear-image" onclick="clearCommentImageSelection()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="comment-image-preview" id="modalCommentImagePreview" style="display:none;">
<img src="" alt="Selected attachment">
</div>
<button onclick="addEventComment(<?= $event['id'] ?>)" class="comment-submit-btn">
<i class="fas fa-paper-plane"></i>
</button>
</div>
<?php endif; ?>
<div class="comments-feed">
<?php if (empty($comments)): ?>
<div class="no-comments-msg">
<i class="fas fa-comment-slash"></i>
<p><?= t('events.modal.no_comments') ?></p>
</div>
<?php else: ?>
<?php foreach ($comments as $comment): ?>
<div class="comment-bubble">
<a class="comment-avatar-link" href="/artist_profile.php?id=<?= (int)$comment['user_id'] ?>">
<div class="comment-avatar-small">
<?php if (!empty($comment['profile_image'])): ?>
<img src="<?= htmlspecialchars($comment['profile_image']) ?>" alt="<?= htmlspecialchars($comment['name']) ?>" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<span class="comment-avatar-initial" style="display:none;">
<?= strtoupper(substr($comment['name'], 0, 1)) ?>
</span>
<?php else: ?>
<span class="comment-avatar-initial">
<?= strtoupper(substr($comment['name'], 0, 1)) ?>
</span>
<?php endif; ?>
</div>
</a>
<div class="comment-body">
<div class="comment-header-small">
<strong>
<a href="/artist_profile.php?id=<?= (int)$comment['user_id'] ?>" class="comment-author-link">
<?= htmlspecialchars($comment['name']) ?>
</a>
</strong>
<span><?= date('M j, g:i A', strtotime($comment['created_at'])) ?></span>
</div>
<?php $commentText = trim($comment['comment']); ?>
<?php if ($commentText !== ''): ?>
<p class="comment-text"><?= nl2br(htmlspecialchars($comment['comment'], ENT_QUOTES, 'UTF-8')) ?></p>
<?php elseif (!empty($comment['comment_image'])): ?>
<p class="comment-text comment-text-muted"><?= t('events.modal.comment_photo_only') ?></p>
<?php endif; ?>
<?php if (!empty($comment['comment_image'])): ?>
<div class="comment-media">
<img src="<?= htmlspecialchars($comment['comment_image']) ?>" alt="Comment attachment" loading="lazy" onclick="openCommentLightbox('<?= htmlspecialchars($comment['comment_image'], ENT_QUOTES, 'UTF-8') ?>')">
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
<!-- Right Sidebar -->
<div class="event-modal-sidebar">
<div class="event-modal-card ticket-purchase-card">
<h3><i class="fas fa-ticket-alt"></i> <?= t('events.modal.ticket.title') ?></h3>
<?php if ($user_has_ticket && $user_ticket): ?>
<div class="ticket-display">
<div class="ticket-qr-container">
<div class="ticket-qr-code">
<img src="/generate_ticket_qr.php?code=<?= htmlspecialchars($user_ticket['ticket_code']) ?>"
alt="<?= htmlspecialchars(t('events.modal.ticket.qr_alt')) ?>"
id="ticketQRCode">
</div>
<div class="ticket-code-display">
<div class="ticket-code-label"><?= t('events.modal.ticket.code_label') ?></div>
<div class="ticket-code-value"><?= htmlspecialchars($user_ticket['ticket_code']) ?></div>
<button class="ticket-copy-btn" onclick="copyTicketCode('<?= htmlspecialchars($user_ticket['ticket_code']) ?>')">
<i class="fas fa-copy"></i> <?= t('events.modal.ticket.copy_code') ?>
</button>
</div>
</div>
<div class="ticket-status">
<div class="status-badge status-confirmed">
<i class="fas fa-check-circle"></i>
<?= t('events.modal.ticket.confirmed') ?>
</div>
<p class="ticket-info"><?= t('events.modal.ticket.instructions') ?></p>
</div>
</div>
<?php elseif ($user_id): ?>
<div class="ticket-purchase-interface">
<div class="ticket-price-display">
<?php if ($event['is_free']): ?>
<div class="price-free">
<i class="fas fa-gift"></i>
<span class="price-amount"><?= t('events.modal.free_badge') ?></span>
</div>
<?php else: ?>
<div class="price-paid">
<span class="price-currency">$</span>
<span class="price-amount" id="ticket-unit-price"><?= number_format($event['ticket_price'], 2) ?></span>
<span class="price-per-ticket"><?= t('events.modal.ticket.per_ticket') ?></span>
</div>
<?php endif; ?>
</div>
<?php if ($event['max_attendees'] && $event['max_attendees'] > 0): ?>
<div class="ticket-availability">
<div class="availability-bar">
<?php
$percentage = $event['max_attendees'] > 0
? min(100, ($tickets_sold / $event['max_attendees']) * 100)
: 0;
?>
<div class="availability-fill" style="width: <?= $percentage ?>%"></div>
</div>
<div class="availability-text">
<?= t('events.modal.ticket.capacity', ['sold' => $tickets_sold, 'total' => $event['max_attendees']]) ?>
</div>
<?php if ($tickets_sold >= $event['max_attendees']): ?>
<div class="sold-out-badge">
<i class="fas fa-times-circle"></i> <?= t('events.modal.sold_out') ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!$event['max_attendees'] || $tickets_sold < $event['max_attendees']): ?>
<?php
$buttonSingleLabel = $event['is_free']
? t('events.modal.ticket.get_free')
: t('events.modal.ticket.add_to_cart');
$buttonPluralLabel = $event['is_free']
? t('events.modal.ticket.get_free_plural')
: t('events.modal.ticket.cta_plural');
?>
<?php
$max_available = $event['max_attendees'] ? ($event['max_attendees'] - $tickets_sold) : 999;
$max_per_purchase = min($max_available, 10);
?>
<div class="ticket-quantity-section">
<label class="quantity-label"><?= t('events.modal.ticket.quantity') ?></label>
<div class="ticket-quantity-controls">
<button class="qty-btn qty-decrease" onclick="updateTicketQuantity(-1, <?= $max_per_purchase ?>, event)" type="button" disabled>
<i class="fas fa-minus"></i>
</button>
<span class="quantity-display" id="ticket-quantity">1</span>
<button class="qty-btn qty-increase" onclick="updateTicketQuantity(1, <?= $max_per_purchase ?>, event)" type="button" <?= $max_per_purchase <= 1 ? 'disabled' : '' ?>>
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<?php if (!$event['is_free']): ?>
<div class="ticket-total-price">
<span><?= t('events.modal.ticket.total') ?></span>
<span class="total-amount" id="ticket-total-price">$<?= number_format($event['ticket_price'], 2) ?></span>
</div>
<?php endif; ?>
<button class="ticket-purchase-btn"
data-event-id="<?= $event['id'] ?>"
data-is-free="<?= $event['is_free'] ? 'true' : 'false' ?>"
data-price="<?= $event['ticket_price'] ?>"
onclick="addTicketsToCart(<?= $event['id'] ?>, <?= $event['is_free'] ? 'true' : 'false' ?>, <?= $event['ticket_price'] ?>, event)">
<i class="fas fa-shopping-cart"></i>
<span
id="purchase-btn-text"
data-single-label="<?= htmlspecialchars($buttonSingleLabel, ENT_QUOTES, 'UTF-8') ?>"
data-plural-label="<?= htmlspecialchars($buttonPluralLabel, ENT_QUOTES, 'UTF-8') ?>"
>
<?= htmlspecialchars($buttonSingleLabel) ?>
</span>
</button>
<?php else: ?>
<div class="sold-out-notice">
<i class="fas fa-ban"></i>
<span><?= t('events.modal.ticket.sold_out_notice') ?></span>
</div>
<?php endif; ?>
</div>
<?php else: ?>
<div class="login-prompt-box">
<i class="fas fa-sign-in-alt"></i>
<p><?= t('events.modal.ticket.login_prompt') ?></p>
<a href="/auth/login.php" class="action-btn-primary"><?= t('user.login') ?></a>
</div>
<?php endif; ?>
</div>
<!-- Stats -->
<div class="event-modal-card">
<h3><?= t('events.modal.stats.title') ?></h3>
<div class="stats-display">
<div class="stat-box">
<i class="fas fa-ticket-alt"></i>
<div>
<div class="stat-number"><?= $tickets_sold ?></div>
<div class="stat-text"><?= t('events.modal.stats.tickets') ?></div>
</div>
</div>
<div class="stat-box">
<i class="fas fa-users"></i>
<div>
<div class="stat-number"><?= $event['attendee_count'] ?></div>
<div class="stat-text"><?= t('events.modal.stats.attending') ?></div>
</div>
</div>
<div class="stat-box">
<i class="fas fa-heart"></i>
<div>
<div class="stat-number"><?= $event['like_count'] ?></div>
<div class="stat-text"><?= t('events.modal.stats.likes') ?></div>
</div>
</div>
<div class="stat-box">
<i class="fas fa-comment"></i>
<div>
<div class="stat-number"><?= $event['comment_count'] ?></div>
<div class="stat-text"><?= t('events.modal.stats.comments') ?></div>
</div>
</div>
</div>
</div>
<div class="event-modal-card quick-actions-card">
<h3><i class="fas fa-bolt"></i> <?= t('events.modal.quick.title') ?></h3>
<p class="card-subtitle"><?= t('events.modal.quick.subtitle') ?></p>
<div class="quick-actions-panel">
<button onclick="addEventToCalendar(<?= (int)$event['id'] ?>)"
data-event-title="<?= htmlspecialchars($event['title'], ENT_QUOTES, 'UTF-8') ?>"
data-event-start="<?= htmlspecialchars($event['start_date']) ?>"
data-event-end="<?= htmlspecialchars($event['end_date']) ?>"
data-event-description="<?= htmlspecialchars(strip_tags($event['description']), ENT_QUOTES, 'UTF-8') ?>"
data-event-location="<?= htmlspecialchars($event['location'] ?: $event['venue_name'] ?: '', ENT_QUOTES) ?>">
<i class="fas fa-calendar-plus"></i> <?= t('events.modal.quick.add_calendar') ?>
</button>
<button onclick="shareEventModal(<?= (int)$event['id'] ?>)"><i class="fas fa-share-alt"></i> <?= t('events.modal.quick.share') ?></button>
<button onclick="likeEventModal(<?= (int)$event['id'] ?>)"><i class="fas fa-heart"></i> <?= t('events.modal.quick.like') ?></button>
<button onclick="inviteFriends(<?= (int)$event['id'] ?>)"><i class="fas fa-user-plus"></i> <?= t('events.modal.quick.invite') ?></button>
<button onclick="document.getElementById('modalCommentText')?.focus()"><i class="fas fa-question"></i> <?= t('events.modal.quick.ask') ?></button>
</div>
</div>
<!-- Attendees List -->
<?php if (!empty($attendees)): ?>
<div class="event-modal-card attendees-card">
<h3><i class="fas fa-users"></i> <?= t('events.modal.attendees_title', ['count' => count($attendees)]) ?></h3>
<div class="attendees-list-compact">
<?php foreach ($attendees as $attendee): ?>
<a href="/artist_profile.php?id=<?= $attendee['user_id'] ?>" class="attendee-item-compact">
<div class="attendee-avatar-compact">
<?php if (!empty($attendee['profile_image'])): ?>
<img src="<?= htmlspecialchars($attendee['profile_image']) ?>" alt="<?= htmlspecialchars($attendee['name']) ?>" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<div class="attendee-avatar-fallback-compact" style="display: none;">
<?= strtoupper(substr($attendee['name'], 0, 1)) ?>
</div>
<?php else: ?>
<div class="attendee-avatar-fallback-compact">
<?= strtoupper(substr($attendee['name'], 0, 1)) ?>
</div>
<?php endif; ?>
</div>
<span class="attendee-name-compact"><?= htmlspecialchars($attendee['name']) ?></span>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<div class="comment-lightbox" id="commentLightbox" onclick="closeCommentLightbox(event)">
<button class="comment-lightbox-close" aria-label="Close">×</button>
<img src="" alt="">
</div>
<style>
/* Event Modal Styles */
.event-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.95);
backdrop-filter: blur(20px);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
overflow-y: auto;
animation: modalFadeIn 0.3s ease;
-webkit-overflow-scrolling: touch;
}
@keyframes modalFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.event-modal-container {
position: relative;
width: 100%;
max-width: 1400px;
max-height: 95vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
border-radius: 24px;
overflow: visible !important;
box-shadow: 0 25px 100px rgba(0, 0, 0, 0.8);
animation: modalSlideIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
border: 2px solid rgba(79, 172, 254, 0.3);
display: flex;
flex-direction: column;
}
@keyframes modalSlideIn {
from {
transform: scale(0.9) translateY(50px);
opacity: 0;
}
to {
transform: scale(1) translateY(0);
opacity: 1;
}
}
@keyframes modalFadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.event-modal-close {
position: absolute;
top: 1.5rem;
right: 1.5rem;
width: 50px;
height: 50px;
background: rgba(239, 68, 68, 0.9);
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
color: white;
font-size: 1.5rem;
cursor: pointer;
z-index: 10001;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.4);
}
.event-modal-close:hover {
background: rgba(220, 38, 38, 1);
transform: scale(1.1) rotate(90deg);
box-shadow: 0 6px 25px rgba(239, 68, 68, 0.6);
}
.event-modal-particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
opacity: 0.4;
}
.event-modal-content {
position: relative;
z-index: 2;
overflow-y: auto;
overflow-x: visible;
max-height: 95vh;
-webkit-overflow-scrolling: touch;
flex: 1;
display: flex;
flex-direction: column;
}
/* Floating attendees are now positioned relative to modal container, not content */
.event-modal-hero {
padding: 4rem 3rem;
text-align: center;
position: relative;
border-bottom: 2px solid rgba(79, 172, 254, 0.3);
}
.event-modal-banner {
width: min(900px, 82%);
margin: 0 auto;
height: 220px;
position: relative;
overflow: hidden;
border-radius: 24px;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.45);
border: 2px solid rgba(79, 172, 254, 0.35);
background: linear-gradient(135deg, rgba(79, 172, 254, 0.1), rgba(118, 75, 162, 0.1));
}
.event-modal-banner::before,
.event-modal-banner::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
}
.event-modal-banner::before {
background: radial-gradient(circle at 20% 20%, rgba(79, 172, 254, 0.25), transparent 60%),
radial-gradient(circle at 80% 30%, rgba(118, 75, 162, 0.25), transparent 60%);
opacity: 0.5;
}
.event-modal-banner::after {
top: 0;
left: 0;
right: 0;
height: 3px;
margin: 0 auto;
width: 65%;
border-radius: 999px;
background: linear-gradient(90deg, #4facfe, #667eea, #764ba2, #f093fb);
background-size: 200% 100%;
animation: shimmer 3s ease-in-out infinite;
}
.event-modal-banner img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: inherit;
}
.event-modal-hero-content {
padding: 2.5rem 3rem 3rem;
margin-top: -90px;
}
.event-modal-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 25px;
color: white;
font-weight: 700;
font-size: 0.9rem;
margin-bottom: 2rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.event-modal-title {
font-size: 3.5rem;
font-weight: 800;
margin-bottom: 1.5rem;
background: linear-gradient(135deg, #4facfe, #667eea, #764ba2, #f093fb);
background-size: 300% 300%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradientShift 3s ease-in-out infinite;
color: #ffffff;
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.event-modal-creator {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
color: #ffffff;
font-size: 1.1rem;
}
.event-creator-avatar {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #4facfe, #667eea, #764ba2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 1.5rem;
box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4);
overflow: hidden;
}
.event-creator-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
display: block;
}
.event-creator-avatar .avatar-initial {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.event-modal-creator a {
color: #4facfe;
text-decoration: none;
font-weight: 600;
}
.event-modal-creator a:hover {
color: #ffffff;
text-decoration: underline;
}
.card-subtitle {
display: block;
font-size: 0.9rem;
color: #a0aec0;
margin-bottom: 1rem;
}
.quick-actions-panel {
margin-top: 0.75rem;
display: grid;
grid-template-columns: 1fr;
gap: 0.6rem;
}
.quick-actions-panel button {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 0.75rem;
color: #fff;
display: flex;
align-items: center;
gap: 0.45rem;
}
/* Floating Attendees - Positioned relative to modal container */
.event-floating-attendees {
position: absolute !important;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
overflow: visible !important;
z-index: 50 !important;
pointer-events: none;
width: 100%;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
padding: 0;
}
.floating-attendee-wrapper {
position: absolute;
pointer-events: auto;
z-index: 51;
transform: translate(-50%, -50%);
transition: transform 0.3s ease;
will-change: transform;
cursor: grab;
}
.floating-attendee-wrapper:active {
cursor: grabbing;
}
.floating-attendee-badge {
position: relative;
width: 120px;
min-height: 160px;
background: rgba(8, 19, 47, 0.75);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
padding: 0.75rem 0.9rem;
text-align: center;
text-decoration: none;
color: #ffffff !important;
backdrop-filter: blur(10px);
box-shadow: 0 12px 30px rgba(5, 10, 30, 0.8);
animation: floatBadge 5s ease-in-out infinite;
transition: transform 0.4s ease, box-shadow 0.3s ease;
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: space-between;
pointer-events: auto;
z-index: 52 !important;
opacity: 1 !important;
overflow: hidden;
}
.floating-badge-glow {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(79, 172, 254, 0.4) 0%, transparent 70%);
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
z-index: 0;
}
.floating-attendee-badge:hover .floating-badge-glow {
opacity: 1;
animation: pulseGlow 2s ease-in-out infinite;
}
@keyframes pulseGlow {
0%, 100% { transform: scale(1); opacity: 0.4; }
50% { transform: scale(1.1); opacity: 0.6; }
}
.floating-badge-content {
position: relative;
z-index: 1;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
@keyframes floatBadge {
0%, 100% {
transform: translateY(0px) translateX(0px) rotate(0deg) scale(1);
}
25% {
transform: translateY(-15px) translateX(5px) rotate(1deg) scale(1.02);
}
50% {
transform: translateY(-25px) translateX(-3px) rotate(-1deg) scale(1.05);
}
75% {
transform: translateY(-15px) translateX(-5px) rotate(1deg) scale(1.02);
}
}
.floating-attendee-wrapper:hover {
z-index: 100 !important;
transform: translate(-50%, -50%) scale(1.1);
}
.floating-attendee-badge:hover {
transform: translateY(-10px) scale(1.15) rotate(3deg) !important;
box-shadow:
0 25px 70px rgba(0, 0, 0, 0.8),
0 0 80px rgba(79, 172, 254, 0.8),
0 0 100px rgba(118, 75, 162, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.5) !important;
border-color: rgba(79, 172, 254, 1);
border-width: 4px;
}
.floating-badge-avatar {
position: relative;
width: 70px;
height: 70px;
border-radius: 12px;
overflow: hidden;
border: 2px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
transition: all 0.4s ease;
}
.floating-attendee-badge:hover .floating-badge-avatar {
transform: scale(1.15) rotate(5deg);
border-color: rgba(255, 255, 255, 0.9);
box-shadow:
0 15px 40px rgba(79, 172, 254, 0.8),
0 0 30px rgba(118, 75, 162, 0.6);
}
.floating-badge-avatar img,
.floating-badge-fallback {
width: 100%;
height: 100%;
object-fit: cover;
background: #0e172f;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 2rem;
transition: transform 0.4s ease;
}
.floating-attendee-badge:hover .floating-badge-avatar img {
transform: scale(1.1);
}
.floating-status-indicator {
position: absolute;
bottom: 2px;
right: 2px;
width: 18px;
height: 18px;
background: #48bb78;
border: 3px solid rgba(255, 255, 255, 0.9);
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
animation: statusPulse 2s ease-in-out infinite;
}
@keyframes statusPulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.8; }
}
.floating-badge-name {
font-size: 0.85rem;
font-weight: 700;
color: #ffffff !important;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100px;
line-height: 1.2;
}
.floating-attendee-badge:hover .floating-badge-name {
transform: translateY(-2px);
text-shadow: 0 4px 15px rgba(79, 172, 254, 0.8);
}
.floating-badge-date {
display: none;
}
.floating-badge-date i {
font-size: 0.65rem;
color: rgba(255, 255, 255, 0.7);
}
/* Tooltip */
.floating-attendee-tooltip {
position: absolute;
bottom: calc(100% + 15px);
left: 50%;
transform: translateX(-50%);
background: linear-gradient(135deg, rgba(26, 26, 46, 0.95) 0%, rgba(22, 33, 62, 0.95) 100%);
border: 2px solid rgba(79, 172, 254, 0.6);
border-radius: 12px;
padding: 0.75rem 1rem;
min-width: 180px;
max-width: 220px;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 1000;
backdrop-filter: blur(20px);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8), 0 0 30px rgba(79, 172, 254, 0.4);
transform: translateX(-50%) translateY(10px);
}
.floating-attendee-wrapper:hover .floating-attendee-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0);
}
.tooltip-header {
font-size: 0.9rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 0.5rem;
text-align: center;
}
.tooltip-bio {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.8);
line-height: 1.4;
margin-bottom: 0.5rem;
text-align: center;
}
.tooltip-action {
font-size: 0.7rem;
color: #4facfe;
text-align: center;
font-weight: 600;
border-top: 1px solid rgba(79, 172, 254, 0.3);
padding-top: 0.5rem;
margin-top: 0.5rem;
}
.floating-attendee-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 8px solid transparent;
border-top-color: rgba(79, 172, 254, 0.6);
}
/* Ripple effect for clicks */
.badge-ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
transform: scale(0);
animation: rippleEffect 0.6s ease-out;
pointer-events: none;
z-index: 1000;
}
@keyframes rippleEffect {
to {
transform: scale(4);
opacity: 0;
}
}
/* Modal Grid */
.event-modal-grid {
display: grid;
grid-template-columns: 1fr 350px;
gap: 2rem;
padding: 2rem 3rem;
}
.event-modal-main {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.event-modal-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.event-modal-card {
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
padding: 2rem;
backdrop-filter: blur(15px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.event-modal-card h3 {
font-size: 1.3rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.event-modal-card h3 i {
color: #4facfe;
}
.event-modal-card p {
color: #ffffff;
line-height: 1.8;
font-size: 1rem;
}
.event-about-text {
position: relative;
margin: 0;
padding: 1.35rem 1.6rem;
border-radius: 18px;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.16), rgba(14, 165, 233, 0.08));
border: 1px solid rgba(168, 182, 255, 0.35);
box-shadow: 0 18px 35px rgba(5, 10, 30, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.08);
overflow: hidden;
}
.event-about-text::before {
content: '';
position: absolute;
top: 1rem;
left: 1rem;
width: 4px;
height: calc(100% - 2rem);
border-radius: 999px;
background: linear-gradient(180deg, #a5b4fc, #60a5fa, #38bdf8);
box-shadow: 0 0 20px rgba(96, 165, 250, 0.8);
opacity: 0.9;
}
.event-about-text::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at top right, rgba(255, 255, 255, 0.12), transparent 55%);
pointer-events: none;
}
.event-about-text-content {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 800;
font-size: 1.08rem;
line-height: 1.9;
background: linear-gradient(120deg, #f093fb, #4facfe);
background-clip: border-box;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 15px rgba(79, 172, 254, 0.35);
}
.event-about-text-content br {
content: '';
}
.event-modal-card.info-panel-card,
.event-modal-card.comments-panel-card {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.12), rgba(14, 165, 233, 0.08));
border: 1px solid rgba(168, 182, 255, 0.35);
box-shadow: 0 18px 35px rgba(5, 10, 30, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.event-modal-card.info-panel-card .event-details-grid .detail-item strong,
.event-modal-card.info-panel-card .event-details-grid .detail-item p {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 800;
background: linear-gradient(120deg, #f093fb, #4facfe);
background-clip: border-box;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.event-modal-card.info-panel-card .event-details-grid .detail-item p {
font-size: 1.02rem;
line-height: 1.8;
}
.event-modal-card.comments-panel-card .comment-input-group,
.event-modal-card.comments-panel-card .comments-feed {
background: rgba(13, 16, 32, 0.4);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 16px;
padding: 1.25rem;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.event-modal-card.comments-panel-card .comment-input-group {
margin-bottom: 1.5rem;
}
.event-modal-card.comments-panel-card .comment-text p,
.event-modal-card.comments-panel-card .comment-text .image-only-caption {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 800;
background: linear-gradient(120deg, #f093fb, #4facfe);
background-clip: border-box;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 12px rgba(79, 172, 254, 0.35);
}
/* Countdown */
.countdown-display {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 2rem 0;
}
.countdown-start-label {
margin-top: 1.5rem;
display: flex;
align-items: center;
gap: 1.2rem;
padding: 1.2rem 1.5rem;
background: rgba(79, 172, 254, 0.12);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 18px;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.45);
}
/* Exciting Live State Styles */
.countdown-live-state {
position: relative;
padding: 1.5rem 2.5rem !important;
background: linear-gradient(135deg, rgba(240, 147, 251, 0.2), rgba(79, 172, 254, 0.2)) !important;
border: 2px solid rgba(240, 147, 251, 0.4) !important;
border-radius: 24px !important;
box-shadow:
0 0 30px rgba(240, 147, 251, 0.5),
0 0 60px rgba(79, 172, 254, 0.3),
0 20px 40px rgba(15, 23, 42, 0.6) !important;
animation: livePulse 2s ease-in-out infinite;
overflow: visible;
}
.countdown-live-state::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(135deg, #f093fb, #4facfe, #f093fb);
border-radius: 24px;
z-index: -1;
opacity: 0.6;
animation: borderGlow 3s linear infinite;
filter: blur(8px);
}
@keyframes livePulse {
0%, 100% {
transform: scale(1);
box-shadow:
0 0 30px rgba(240, 147, 251, 0.5),
0 0 60px rgba(79, 172, 254, 0.3),
0 20px 40px rgba(15, 23, 42, 0.6);
}
50% {
transform: scale(1.02);
box-shadow:
0 0 40px rgba(240, 147, 251, 0.7),
0 0 80px rgba(79, 172, 254, 0.5),
0 25px 50px rgba(15, 23, 42, 0.7);
}
}
@keyframes borderGlow {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.live-indicator {
position: relative;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.live-indicator i {
font-size: 1.2rem;
color: #ff3366;
z-index: 2;
position: relative;
animation: liveBlink 1.5s ease-in-out infinite;
text-shadow: 0 0 10px rgba(255, 51, 102, 0.8);
}
.live-pulse {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: rgba(255, 51, 102, 0.4);
animation: pulseRing 2s ease-out infinite;
}
@keyframes pulseRing {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(2);
opacity: 0;
}
}
@keyframes liveBlink {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(0.95);
}
}
.live-text {
font-size: 1.8rem;
font-weight: 900;
background: linear-gradient(135deg, #f093fb 0%, #4facfe 50%, #f093fb 100%);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-transform: uppercase;
letter-spacing: 0.1em;
animation: gradientShift 3s linear infinite;
text-shadow: 0 0 30px rgba(240, 147, 251, 0.5);
position: relative;
}
.live-text::after {
content: '🎉';
margin-left: 0.5rem;
display: inline-block;
animation: bounce 1s ease-in-out infinite;
}
@keyframes gradientShift {
0% {
background-position: 0% center;
}
100% {
background-position: 200% center;
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(0) rotate(0deg);
}
25% {
transform: translateY(-5px) rotate(-10deg);
}
75% {
transform: translateY(-5px) rotate(10deg);
}
}
.countdown-display.countdown-live {
animation: fadeInLive 0.5s ease-out;
}
@keyframes fadeInLive {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.countdown-start-icon {
width: 60px;
height: 60px;
border-radius: 16px;
background: rgba(102, 126, 234, 0.2);
border: 1px solid rgba(102, 126, 234, 0.4);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
color: #ffffff;
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
.countdown-start-text {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.countdown-start-prefix {
font-size: 0.85rem;
letter-spacing: 0.3em;
text-transform: uppercase;
color: #cbd5e0;
}
.countdown-start-date {
font-size: 2rem;
font-weight: 800;
line-height: 1.2;
background: linear-gradient(120deg, #f093fb, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.countdown-unit {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.countdown-value {
font-size: 3rem;
font-weight: 800;
color: #ffffff;
text-shadow: 0 4px 20px rgba(79, 172, 254, 0.8);
min-width: 70px;
text-align: center;
}
.countdown-label {
font-size: 0.85rem;
color: #cbd5e0;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 1px;
}
.countdown-colon {
font-size: 2.5rem;
color: #4facfe;
font-weight: 700;
}
/* Details Grid */
.event-details-grid {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.detail-item {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1.5rem;
background: rgba(79, 172, 254, 0.15);
border: 1px solid rgba(79, 172, 254, 0.3);
border-radius: 15px;
}
.detail-item i {
font-size: 1.5rem;
color: #4facfe;
margin-top: 0.2rem;
}
.detail-item strong {
display: block;
color: #ffffff;
font-size: 1rem;
margin-bottom: 0.5rem;
font-weight: 700;
}
.detail-item p {
color: #ffffff;
margin: 0;
font-size: 0.95rem;
}
.direction-btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.45rem 0.9rem;
background: rgba(79, 172, 254, 0.15);
color: #4facfe;
border-radius: 999px;
font-size: 0.85rem;
font-weight: 600;
text-decoration: none;
margin-top: 0.5rem;
transition: all 0.2s ease;
}
.direction-btn:hover {
background: rgba(79, 172, 254, 0.25);
transform: translateY(-1px);
}
/* Comments */
.comment-input-group {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 2rem;
}
.comment-input-group textarea {
width: 100%;
padding: 1rem;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(79, 172, 254, 0.3);
border-radius: 12px;
color: #ffffff;
font-size: 1rem;
resize: vertical;
min-height: 80px;
}
.comment-input-group textarea:focus {
outline: none;
border-color: #4facfe;
box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.2);
}
.comment-submit-btn {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #4facfe, #667eea);
border: none;
border-radius: 12px;
color: white;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.3s ease;
align-self: flex-start;
}
.comment-submit-btn:hover {
transform: scale(1.1);
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.4);
}
.comment-attachment-row {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.comment-attachment-label {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.45rem 0.9rem;
border-radius: 999px;
border: 1px dashed rgba(255, 255, 255, 0.4);
cursor: pointer;
font-size: 0.85rem;
color: #fff;
}
.comment-attachment-label input {
display: none;
}
.comment-clear-image {
border: none;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
width: 32px;
height: 32px;
color: #fff;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.comment-image-preview {
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 0.5rem;
max-width: 200px;
background: rgba(255, 255, 255, 0.05);
}
.comment-image-preview img {
width: 100%;
border-radius: 8px;
object-fit: cover;
}
.comments-feed {
display: flex;
flex-direction: column;
gap: 1rem;
}
.comment-bubble {
display: flex;
gap: 1rem;
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 15px;
}
.comment-avatar-small {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #4facfe, #667eea, #764ba2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
flex-shrink: 0;
}
.comment-avatar-small img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.comment-avatar-link {
display: inline-flex;
border-radius: 50%;
}
.comment-avatar-initial {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.comment-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.comment-header-small {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.comment-header-small strong {
color: #4facfe;
font-weight: 700;
}
.comment-author-link {
color: inherit;
text-decoration: none;
}
.comment-author-link:hover {
text-decoration: underline;
}
.comment-header-small span {
color: #a0aec0;
font-size: 0.85rem;
}
.comment-text {
color: #ffffff;
margin: 0;
line-height: 1.6;
}
.comment-text-muted {
color: #a0aec0;
font-style: italic;
}
.comment-media {
margin-top: 0.25rem;
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.15);
max-width: 240px;
box-shadow: 0 12px 25px rgba(15, 23, 42, 0.4);
}
.comment-media img {
width: 100%;
display: block;
max-height: 220px;
object-fit: cover;
cursor: pointer;
}
.comment-lightbox {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 20000;
padding: 2rem;
}
.comment-lightbox img {
max-width: 90vw;
max-height: 90vh;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
.comment-lightbox-close {
position: absolute;
top: 2rem;
right: 2rem;
background: rgba(0, 0, 0, 0.6);
color: #fff;
border: none;
border-radius: 50%;
width: 48px;
height: 48px;
font-size: 1.5rem;
cursor: pointer;
}
.no-comments-msg {
text-align: center;
padding: 3rem 2rem;
color: #a0aec0;
}
.no-comments-msg i {
font-size: 3rem;
margin-bottom: 1rem;
color: #4facfe;
}
/* Actions */
.action-buttons-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
}
.action-btn-primary {
grid-column: 1 / -1;
padding: 1rem 1.5rem;
background: linear-gradient(135deg, #4facfe, #667eea);
border: none;
border-radius: 12px;
color: white;
font-weight: 700;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.action-btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.4);
}
.action-btn-secondary {
padding: 1rem;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
color: white;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.action-btn-secondary:hover {
background: rgba(79, 172, 254, 0.2);
border-color: rgba(79, 172, 254, 0.4);
}
.action-btn-icon {
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
color: #a0aec0;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn-icon:hover {
color: white;
border-color: rgba(79, 172, 254, 0.5);
background: rgba(79, 172, 254, 0.1);
}
.login-prompt-box {
text-align: center;
padding: 2rem;
}
.login-prompt-box i {
font-size: 3rem;
color: #4facfe;
margin-bottom: 1rem;
}
.login-prompt-box p {
color: #ffffff;
margin-bottom: 1.5rem;
}
/* Stats */
.stats-display {
display: flex;
flex-direction: column;
gap: 1rem;
}
.stat-box {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: rgba(79, 172, 254, 0.1);
border: 1px solid rgba(79, 172, 254, 0.2);
border-radius: 12px;
}
.stat-box i {
font-size: 1.5rem;
color: #4facfe;
}
.stat-number {
font-size: 1.8rem;
font-weight: 800;
color: #ffffff;
}
.stat-text {
font-size: 0.9rem;
color: #cbd5e0;
}
/* Attendees List */
.attendees-list-compact {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-height: 300px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.attendee-item-compact {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
text-decoration: none;
color: inherit;
transition: all 0.3s ease;
}
.attendee-item-compact:hover {
background: rgba(79, 172, 254, 0.15);
border-color: rgba(79, 172, 254, 0.3);
transform: translateX(5px);
}
.attendee-avatar-compact {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
border: 2px solid rgba(79, 172, 254, 0.4);
}
.attendee-avatar-compact img,
.attendee-avatar-fallback-compact {
width: 100%;
height: 100%;
object-fit: cover;
background: linear-gradient(135deg, #4facfe, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 1rem;
}
.attendee-name-compact {
color: #ffffff;
font-weight: 600;
font-size: 0.9rem;
flex: 1;
}
/* Responsive */
@media (max-width: 1024px) {
.event-modal-grid {
grid-template-columns: 1fr;
}
.event-modal-container {
max-width: 95%;
}
}
@media (max-width: 768px) {
.event-modal-overlay {
padding: 0 !important;
align-items: flex-start !important;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.event-modal-container {
max-width: 100% !important;
max-height: 100vh !important;
height: 100vh !important;
border-radius: 0 !important;
border: none !important;
display: flex;
flex-direction: column;
}
.event-modal-content {
max-height: 100vh !important;
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.event-modal-close {
top: 1rem;
right: 1rem;
width: 44px;
height: 44px;
font-size: 1.2rem;
background: rgba(239, 68, 68, 1);
z-index: 10002;
}
.event-modal-hero {
min-height: 260px;
padding: 0 1rem 2rem;
}
.event-modal-banner {
width: 92%;
height: 160px;
border-radius: 16px;
}
.event-modal-hero-content {
padding: 1.5rem 1.5rem 2rem;
padding-top: 70px;
margin-top: -50px;
}
.event-modal-title {
font-size: 2rem !important;
line-height: 1.2;
margin-bottom: 1rem;
}
.event-modal-badge {
font-size: 0.8rem;
padding: 0.6rem 1.2rem;
margin-bottom: 1rem;
}
.event-modal-creator {
font-size: 0.95rem;
}
.event-creator-avatar {
width: 40px;
height: 40px;
font-size: 1.2rem;
}
.event-modal-grid {
grid-template-columns: 1fr !important;
padding: 1rem 1.5rem;
gap: 1rem;
}
.event-modal-main {
margin-top: 2.5rem;
}
.event-modal-card {
padding: 1.5rem;
}
.event-modal-card h3 {
font-size: 1.1rem;
margin-bottom: 1rem;
}
.countdown-display {
gap: 0.5rem;
padding: 1.5rem 0;
}
.countdown-start-label {
justify-content: center;
text-align: center;
}
.countdown-value {
font-size: 2rem;
min-width: 50px;
}
.countdown-label {
font-size: 0.75rem;
}
.countdown-colon {
font-size: 1.8rem;
}
.event-floating-attendees {
height: 400px;
top: 200px;
padding: 0 1rem;
}
.floating-attendee-badge {
width: 110px;
min-height: 150px;
padding: 1rem 0.75rem;
}
.floating-badge-avatar {
width: 65px;
height: 65px;
}
.floating-badge-name {
font-size: 0.85rem;
max-width: 100px;
}
.floating-badge-date {
font-size: 0.65rem;
}
.floating-attendee-tooltip {
min-width: 150px;
max-width: 180px;
padding: 0.6rem 0.8rem;
font-size: 0.7rem;
}
.floating-attendee-badge {
width: 100px;
height: 130px;
padding: 0.75rem;
}
.floating-badge-avatar {
width: 60px;
height: 60px;
}
.floating-badge-name {
font-size: 0.75rem;
}
.action-buttons-grid {
gap: 0.5rem;
}
.action-btn-primary,
.action-btn-secondary,
.action-btn-icon {
padding: 0.875rem;
font-size: 0.9rem;
}
.comment-input-group textarea {
min-height: 60px;
font-size: 0.95rem;
}
.detail-item {
padding: 1rem;
gap: 0.75rem;
}
.detail-item i {
font-size: 1.2rem;
}
.detail-item strong {
font-size: 0.9rem;
}
.detail-item p {
font-size: 0.85rem;
}
.direction-btn {
width: 100%;
justify-content: center;
}
.stat-box {
padding: 0.875rem;
}
.stat-number {
font-size: 1.5rem;
}
.stat-text {
font-size: 0.85rem;
}
.attendees-list-compact {
max-height: 200px;
}
.attendee-item-compact {
padding: 0.625rem;
gap: 0.625rem;
}
.attendee-avatar-compact {
width: 35px;
height: 35px;
}
.attendee-name-compact {
font-size: 0.85rem;
}
}
/* Ticket Purchase Styles */
.ticket-purchase-card {
background: linear-gradient(135deg, rgba(79, 172, 254, 0.15) 0%, rgba(102, 126, 234, 0.1) 100%);
border: 2px solid rgba(79, 172, 254, 0.3);
}
.ticket-purchase-interface {
text-align: center;
}
.ticket-price-display {
margin: 2rem 0;
padding: 2rem;
background: rgba(0, 0, 0, 0.3);
border-radius: 16px;
border: 2px solid rgba(79, 172, 254, 0.3);
}
.price-free {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
font-size: 2.5rem;
font-weight: 800;
color: #48bb78;
}
.price-paid {
display: flex;
align-items: baseline;
justify-content: center;
gap: 0.5rem;
}
.price-currency {
font-size: 2rem;
font-weight: 700;
color: #4facfe;
}
.price-amount {
font-size: 3.5rem;
font-weight: 800;
background: linear-gradient(135deg, #4facfe, #667eea, #764ba2);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.ticket-availability {
margin: 1.5rem 0;
padding: 1rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 12px;
}
.availability-bar {
width: 100%;
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.availability-fill {
height: 100%;
background: linear-gradient(90deg, #4facfe, #48bb78);
transition: width 0.5s ease;
}
.availability-text {
font-size: 0.95rem;
font-weight: 800;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.65rem;
background: linear-gradient(120deg, #a5b4fc, #f0abfc, #4facfe);
background-clip: border-box;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 12px rgba(79, 172, 254, 0.35);
}
.sold-out-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(239, 68, 68, 0.2);
border: 1px solid rgba(239, 68, 68, 0.5);
border-radius: 20px;
color: #f56565;
font-weight: 600;
font-size: 0.85rem;
}
.ticket-purchase-btn {
width: 100%;
padding: 1.25rem 2rem;
background: linear-gradient(135deg, #4facfe, #667eea, #764ba2);
border: none;
border-radius: 16px;
color: white;
font-size: 1.1rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
margin: 1.5rem 0;
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.4);
}
.ticket-purchase-btn:hover {
transform: translateY(-2px);
box-shadow: 0 12px 35px rgba(79, 172, 254, 0.6);
}
.ticket-purchase-btn:active {
transform: translateY(0);
}
.ticket-quantity-section {
margin: 1.5rem 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.quantity-label {
color: #a0aec0;
font-size: 0.95rem;
font-weight: 600;
}
.ticket-quantity-controls {
display: flex;
align-items: center;
gap: 1rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(102, 126, 234, 0.2);
border-radius: 12px;
padding: 0.5rem;
}
.ticket-quantity-controls .qty-btn {
background: rgba(102, 126, 234, 0.2);
border: 1px solid rgba(102, 126, 234, 0.3);
color: #667eea;
width: 40px;
height: 40px;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-size: 1.2rem;
}
.ticket-quantity-controls .qty-btn:hover {
background: rgba(102, 126, 234, 0.3);
border-color: #667eea;
transform: scale(1.1);
}
.ticket-quantity-controls .qty-btn:active {
transform: scale(0.95);
}
.ticket-quantity-controls .qty-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.ticket-quantity-controls .quantity-display {
color: white;
font-weight: 700;
font-size: 1.5rem;
min-width: 40px;
text-align: center;
}
.ticket-total-price {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 12px;
margin: 1rem 0;
border: 1px solid rgba(102, 126, 234, 0.2);
}
.ticket-total-price span:first-child {
color: #a0aec0;
font-size: 1.1rem;
font-weight: 600;
}
.ticket-total-price .total-amount {
color: white;
font-size: 1.8rem;
font-weight: 800;
background: linear-gradient(135deg, #667eea, #764ba2);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.price-per-ticket {
font-size: 0.9rem;
color: #a0aec0;
margin-left: 0.5rem;
}
.ticket-features {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.feature-item {
display: flex;
align-items: center;
gap: 0.75rem;
color: #a0aec0;
font-size: 0.9rem;
}
.feature-item i {
color: #4facfe;
width: 20px;
}
/* Ticket Display (After Purchase) */
.ticket-display {
text-align: center;
}
.ticket-qr-container {
background: white;
padding: 2rem;
border-radius: 20px;
margin: 1.5rem 0;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}
.ticket-qr-code {
margin-bottom: 1.5rem;
}
.ticket-qr-code img {
width: 250px;
height: 250px;
border: 4px solid #4facfe;
border-radius: 12px;
padding: 1rem;
background: white;
}
.ticket-code-display {
margin-top: 1.5rem;
}
.ticket-code-label {
font-size: 0.85rem;
color: #a0aec0;
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.ticket-code-value {
font-size: 1.5rem;
font-weight: 800;
font-family: 'Courier New', monospace;
color: #4facfe;
margin: 1rem 0;
padding: 1rem;
background: rgba(79, 172, 254, 0.1);
border-radius: 12px;
border: 2px solid rgba(79, 172, 254, 0.3);
letter-spacing: 2px;
}
.ticket-copy-btn {
padding: 0.75rem 1.5rem;
background: rgba(79, 172, 254, 0.2);
border: 1px solid rgba(79, 172, 254, 0.4);
border-radius: 12px;
color: #4facfe;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
.ticket-copy-btn:hover {
background: rgba(79, 172, 254, 0.3);
transform: translateY(-2px);
}
.ticket-status {
margin-top: 1.5rem;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 25px;
font-weight: 700;
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
.status-confirmed {
background: rgba(72, 187, 120, 0.2);
border: 2px solid rgba(72, 187, 120, 0.5);
color: #48bb78;
}
.ticket-info {
color: #a0aec0;
font-size: 0.9rem;
margin: 0;
}
</style>
<script data-event-modal-script="1">
const eventModalI18n = {
liveMessage: <?= json_encode(t('events.modal.countdown.live')) ?>,
shareCopied: <?= json_encode(t('events.modal.share.copied')) ?>,
inviteText: <?= json_encode(t('events.modal.share.invite_text')) ?>,
commentRequired: <?= json_encode(t('events.modal.comment_required')) ?>,
errorPrefix: <?= json_encode(t('events.modal.error_prefix')) ?>,
errorGeneric: <?= json_encode(t('events.modal.error_generic')) ?>,
cartFailed: <?= json_encode(t('events.modal.cart_failed')) ?>,
errorConsole: <?= json_encode(t('events.modal.error_console')) ?>,
paymentFailed: <?= json_encode(t('events.modal.payment_failed')) ?>,
ticketCodeCopied: <?= json_encode(t('events.modal.ticket_code_copied')) ?>,
ticketUnavailable: <?= json_encode(t('events.modal.ticket_unavailable')) ?>,
ticketAdding: <?= json_encode(t('events.modal.ticket.adding_status')) ?>,
ticketAdded: <?= json_encode(t('events.modal.ticket.added_status')) ?>,
ticketAddedSingle: <?= json_encode(t('events.modal.ticket.added_single')) ?>,
ticketAddedPlural: <?= json_encode(t('events.modal.ticket.added_plural')) ?>,
ticketCopySuccess: <?= json_encode(t('events.modal.ticket.copied_label')) ?>,
paymentTitle: <?= json_encode(t('events.modal.payment.title')) ?>,
paymentTicketSingle: <?= json_encode(t('events.modal.payment.ticket_single')) ?>,
paymentTicketPlural: <?= json_encode(t('events.modal.payment.ticket_plural')) ?>,
paymentTotalLabel: <?= json_encode(t('events.modal.payment.total')) ?>,
paymentPayLabel: <?= json_encode(t('events.modal.payment.pay_button')) ?>,
paymentCancelLabel: <?= json_encode(t('events.modal.payment.cancel')) ?>,
paymentProcessing: <?= json_encode(t('events.modal.payment.processing')) ?>
};
// Particle System for Modal
(function() {
const canvas = document.getElementById('eventModalParticles');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let particles = [];
let animationId;
function resizeCanvas() {
const container = canvas.closest('.event-modal-container');
if (container) {
canvas.width = container.offsetWidth;
canvas.height = container.offsetHeight;
}
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
class Particle {
constructor() {
this.reset();
this.y = Math.random() * canvas.height;
}
reset() {
this.x = Math.random() * canvas.width;
this.y = -10;
this.size = Math.random() * 2 + 1;
this.speedY = Math.random() * 1.5 + 0.5;
this.speedX = (Math.random() - 0.5) * 0.3;
this.opacity = Math.random() * 0.4 + 0.2;
this.color = this.getRandomColor();
}
getRandomColor() {
const colors = [
{ r: 79, g: 172, b: 254 },
{ r: 102, g: 126, b: 234 },
{ r: 118, g: 75, b: 162 },
{ r: 240, g: 147, b: 251 }
];
return colors[Math.floor(Math.random() * colors.length)];
}
update() {
this.y += this.speedY;
this.x += this.speedX;
this.opacity -= 0.001;
if (this.y > canvas.height + 10 || this.opacity <= 0) {
this.reset();
}
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity;
ctx.fillStyle = `rgb(${this.color.r}, ${this.color.g}, ${this.color.b})`;
ctx.shadowBlur = 10;
ctx.shadowColor = `rgba(${this.color.r}, ${this.color.g}, ${this.color.b}, 0.8)`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
function initParticles() {
const particleCount = Math.min(50, Math.floor((canvas.width * canvas.height) / 20000));
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
particle.update();
particle.draw();
});
animationId = requestAnimationFrame(animate);
}
initParticles();
animate();
})();
// Date parsing helpers (handles Safari/Firefox formats)
function parseEventDateString(dateString) {
if (!dateString) return null;
if (dateString instanceof Date) {
return isNaN(dateString.getTime()) ? null : dateString;
}
let normalized = typeof dateString === 'string' ? dateString.trim() : '';
if (!normalized) return null;
// Convert "YYYY-MM-DD HH:MM:SS" to "YYYY-MM-DDTHH:MM:SS"
if (normalized.indexOf('T') === -1 && normalized.indexOf(' ') !== -1) {
normalized = normalized.replace(' ', 'T');
}
let parsed = new Date(normalized);
// Fallback manual parsing for Safari (which needs slashes)
if (isNaN(parsed.getTime())) {
const parts = normalized.split(/[- :T]/).map(part => parseInt(part, 10));
if (parts.length >= 3) {
const [year, month, day, hour = 0, minute = 0, second = 0] = parts;
parsed = new Date(year, (month || 1) - 1, day || 1, hour || 0, minute || 0, second || 0);
}
}
return isNaN(parsed.getTime()) ? null : parsed;
}
function formatEventDateString(dateString) {
const parsed = parseEventDateString(dateString);
return parsed ? parsed.toLocaleString() : (dateString || 'N/A');
}
// Countdown Timer
function initModalCountdown() {
const card = document.getElementById('modalCountdownCard');
const daysEl = document.getElementById('modalDays');
const hoursEl = document.getElementById('modalHours');
const minutesEl = document.getElementById('modalMinutes');
const secondsEl = document.getElementById('modalSeconds');
if (!card || !daysEl || !hoursEl || !minutesEl || !secondsEl) {
console.warn('Countdown elements missing, skipping modal countdown init.');
return;
}
const timestampAttr = card.getAttribute('data-start-timestamp');
let startTimestampSeconds = timestampAttr ? parseInt(timestampAttr, 10) : NaN;
if (!timestampAttr || isNaN(startTimestampSeconds)) {
const parsedDate = parseEventDateString(<?= json_encode($event['start_date']) ?>);
if (parsedDate) {
startTimestampSeconds = Math.floor(parsedDate.getTime() / 1000);
}
}
if (!startTimestampSeconds || Number.isNaN(startTimestampSeconds)) {
console.warn('Unable to determine event start timestamp for modal countdown:', <?= json_encode($event['start_date']) ?>);
return;
}
const liveMessage = '<div class="countdown-start-label countdown-live-state"><div class="live-indicator"><span class="live-pulse"></span><i class="fas fa-circle"></i></div><span class="live-text">' + eventModalI18n.liveMessage + '</span></div>';
let countdownInterval = null;
function setLiveState() {
const countdownDisplay = card.querySelector('.countdown-display');
if (countdownDisplay) {
countdownDisplay.innerHTML = liveMessage;
countdownDisplay.classList.add('countdown-live');
}
if (countdownInterval) {
clearInterval(countdownInterval);
}
}
function updateCountdown() {
const nowSeconds = Math.floor(Date.now() / 1000);
const remaining = Math.max(0, startTimestampSeconds - nowSeconds);
if (remaining === 0) {
setLiveState();
return;
}
const days = Math.floor(remaining / 86400);
const hours = Math.floor((remaining % 86400) / 3600);
const minutes = Math.floor((remaining % 3600) / 60);
const seconds = Math.floor(remaining % 60);
daysEl.textContent = String(days).padStart(2, '0');
hoursEl.textContent = String(hours).padStart(2, '0');
minutesEl.textContent = String(minutes).padStart(2, '0');
secondsEl.textContent = String(seconds).padStart(2, '0');
}
updateCountdown();
countdownInterval = setInterval(updateCountdown, 1000);
}
// Make initModalCountdown available globally
window.initModalCountdown = initModalCountdown;
// Initialize countdown when modal loads
setTimeout(function() {
if (document.getElementById('modalDays')) {
initModalCountdown();
}
}, 200);
// Event functions
function likeEventModal(eventId) {
if (!<?= $user_id ? 'true' : 'false' ?>) {
window.location.href = '/auth/login.php';
return;
}
fetch('/api_events.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'like', event_id: eventId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(eventModalI18n.errorPrefix + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert(eventModalI18n.errorGeneric);
});
}
function shareEventModal(eventId) {
const url = window.location.origin + '/event_details.php?id=' + eventId;
const title = <?= json_encode($event['title']) ?>;
if (navigator.share) {
navigator.share({ title: title, url: url });
} else {
navigator.clipboard.writeText(url).then(() => {
alert(eventModalI18n.shareCopied);
});
}
}
function addEventToCalendar(eventId) {
// Find the button to get event data from data attributes
const button = document.querySelector(`button[onclick*="addEventToCalendar(${eventId})"]`);
let eventData = null;
if (button) {
// Get event data from button data attributes
eventData = {
title: button.getAttribute('data-event-title') || 'Event',
start: button.getAttribute('data-event-start'),
end: button.getAttribute('data-event-end'),
description: button.getAttribute('data-event-description') || '',
location: button.getAttribute('data-event-location') || ''
};
} else {
// Fallback: try to extract from modal content
const modal = document.getElementById('eventModalOverlay');
if (modal) {
const titleEl = modal.querySelector('.event-title, h2, h3');
const dateEl = modal.querySelector('.event-date, [data-start-date]');
const locationEl = modal.querySelector('.event-location, [data-location]');
const descEl = modal.querySelector('.event-description, .event-details');
eventData = {
title: titleEl ? titleEl.textContent.trim() : 'Event',
start: dateEl ? (dateEl.getAttribute('data-start-date') || dateEl.textContent) : '',
end: dateEl ? (dateEl.getAttribute('data-end-date') || '') : '',
description: descEl ? descEl.textContent.trim().substring(0, 500) : '',
location: locationEl ? locationEl.textContent.trim() : ''
};
}
}
if (!eventData || !eventData.start) {
console.error('Unable to get event data for calendar:', eventId);
alert('Unable to load event details for calendar. Please try refreshing the page.');
return;
}
createCalendarLink(eventData);
}
function createCalendarLink(eventData) {
try {
// Format dates for Google Calendar (YYYYMMDDTHHmmssZ)
const startDate = new Date(eventData.start);
const endDate = new Date(eventData.end);
// Check if dates are valid
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
throw new Error('Invalid date format');
}
// Format dates as YYYYMMDDTHHmmssZ
const formatDate = (date) => {
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getUTCDate()).padStart(2, '0');
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
return `${year}${month}${day}T${hours}${minutes}${seconds}Z`;
};
const start = formatDate(startDate);
const end = formatDate(endDate);
// Encode parameters
const text = encodeURIComponent(eventData.title);
const details = encodeURIComponent(eventData.description);
const location = encodeURIComponent(eventData.location);
// Create Google Calendar URL
const url = `https://www.google.com/calendar/render?action=TEMPLATE&text=${text}&dates=${start}/${end}&details=${details}&location=${location}`;
// Open in new tab
window.open(url, '_blank');
} catch (error) {
console.error('Error creating calendar link:', error);
alert('Unable to create calendar link. Please check the event dates.');
}
}
function inviteFriends(eventId) {
if (navigator.share) {
navigator.share({
title: <?= json_encode($event['title']) ?>,
url: window.location.origin + '/event_details.php?id=' + eventId,
text: eventModalI18n.inviteText
}).catch(() => {});
return;
}
shareEventModal(eventId);
}
function addEventComment(eventId) {
const textarea = document.getElementById('modalCommentText');
const imageInput = document.getElementById('modalCommentImage');
const commentText = textarea ? textarea.value.trim() : '';
const imageFile = imageInput && imageInput.files && imageInput.files[0] ? imageInput.files[0] : null;
if (!commentText && !imageFile) {
alert(eventModalI18n.commentRequired);
return;
}
const formData = new FormData();
formData.append('action', 'comment');
formData.append('event_id', eventId);
formData.append('comment', commentText);
if (imageFile) {
formData.append('comment_image', imageFile);
}
fetch('/api_events.php', {
method: 'POST',
body: formData
})
.then(response => {
// Ensure response is treated as UTF-8
return response.text().then(text => {
try {
return JSON.parse(text);
} catch (e) {
console.error('JSON parse error:', e);
throw new Error('Invalid JSON response');
}
});
})
.then(data => {
if (data.success) {
location.reload();
} else {
alert(eventModalI18n.errorPrefix + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert(eventModalI18n.errorGeneric);
})
.finally(() => {
if (textarea) textarea.value = '';
if (imageInput) {
imageInput.value = '';
}
resetCommentImagePreview();
});
}
function handleCommentImagePreview(event) {
const input = event?.target || document.getElementById('modalCommentImage');
const preview = document.getElementById('modalCommentImagePreview');
if (!input || !preview) return;
const file = input.files && input.files[0];
if (!file) {
resetCommentImagePreview();
return;
}
const reader = new FileReader();
reader.onload = () => {
const img = preview.querySelector('img');
if (img) img.src = reader.result;
preview.style.display = 'block';
};
reader.readAsDataURL(file);
}
function clearCommentImageSelection() {
const input = document.getElementById('modalCommentImage');
if (input) {
input.value = '';
}
resetCommentImagePreview();
}
function resetCommentImagePreview() {
const preview = document.getElementById('modalCommentImagePreview');
if (!preview) return;
preview.style.display = 'none';
const img = preview.querySelector('img');
if (img) img.src = '';
}
function openCommentLightbox(src) {
const lightbox = document.getElementById('commentLightbox');
if (!lightbox) return;
const img = lightbox.querySelector('img');
img.src = src;
lightbox.style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeCommentLightbox(event) {
const lightbox = document.getElementById('commentLightbox');
if (!lightbox) return;
if (event && event.target && event.target !== lightbox && !event.target.classList.contains('comment-lightbox-close')) {
return;
}
lightbox.style.display = 'none';
const img = lightbox.querySelector('img');
if (img) img.src = '';
document.body.style.overflow = '';
}
// Close modal function
function closeEventModal() {
const overlay = document.getElementById('eventModalOverlay');
if (overlay) {
overlay.style.animation = 'modalFadeOut 0.3s ease';
setTimeout(() => {
overlay.remove();
document.body.style.overflow = '';
}, 300);
}
}
// Make closeEventModal available globally
window.closeEventModal = closeEventModal;
// Setup modal event handlers (runs after modal is inserted)
setTimeout(function() {
const overlay = document.getElementById('eventModalOverlay');
const container = document.querySelector('.event-modal-container');
if (overlay) {
// Close on overlay click
overlay.addEventListener('click', function(e) {
if (e.target === overlay) {
closeEventModal();
}
});
// Prevent container clicks from closing
if (container) {
container.addEventListener('click', function(e) {
e.stopPropagation();
});
}
}
// Handle paste events for the comment textarea to preserve Unicode characters (emojis/icons)
const commentTextarea = document.getElementById('modalCommentText');
if (commentTextarea) {
commentTextarea.addEventListener('paste', function(e) {
e.preventDefault();
// Get pasted data as plain text
const pastedData = (e.clipboardData || window.clipboardData).getData('text/plain');
// Ensure proper UTF-8 handling
if (pastedData) {
// Get current cursor position
const start = this.selectionStart;
const end = this.selectionEnd;
const currentValue = this.value;
// Insert pasted text at cursor position
const newValue = currentValue.substring(0, start) + pastedData + currentValue.substring(end);
this.value = newValue;
// Restore cursor position after the pasted text
const newCursorPos = start + pastedData.length;
this.setSelectionRange(newCursorPos, newCursorPos);
// Trigger input event to ensure any listeners are notified
this.dispatchEvent(new Event('input', { bubbles: true }));
}
});
}
}, 100);
// Close on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
const overlay = document.getElementById('eventModalOverlay');
if (overlay) {
closeEventModal();
}
}
});
// Handle attendee badge clicks with AJAX navigation support
function handleAttendeeClick(event, userId) {
event.preventDefault();
event.stopPropagation();
const url = `/artist_profile.php?id=${userId}`;
// Use AJAX navigation if available, otherwise use regular navigation
if (window.ajaxNavigation && typeof window.ajaxNavigation.navigateToPage === 'function') {
// Close modal first, then navigate
closeEventModal();
setTimeout(() => {
window.ajaxNavigation.navigateToPage(url);
}, 300);
} else {
// Regular navigation
window.location.href = url;
}
}
// Make function available globally
window.handleAttendeeClick = handleAttendeeClick;
// Ticket Purchase Functions
function updateTicketQuantity(change, maxQuantity, event) {
// Prevent event propagation if event is provided
if (event) {
event.preventDefault();
event.stopPropagation();
}
const quantityDisplay = document.getElementById('ticket-quantity');
if (!quantityDisplay) {
console.error('Quantity display element not found');
return;
}
let currentQuantity = parseInt(quantityDisplay.textContent) || 1;
let newQuantity = currentQuantity + change;
// Clamp between 1 and maxQuantity
newQuantity = Math.max(1, Math.min(newQuantity, maxQuantity));
if (newQuantity !== currentQuantity) {
quantityDisplay.textContent = newQuantity;
// Update total price if not free
const unitPriceEl = document.getElementById('ticket-unit-price');
const totalPriceEl = document.getElementById('ticket-total-price');
const purchaseBtnText = document.getElementById('purchase-btn-text');
if (unitPriceEl && totalPriceEl) {
const unitPrice = parseFloat(unitPriceEl.textContent.replace(/,/g, '')) || 0;
const totalPrice = unitPrice * newQuantity;
totalPriceEl.textContent = '$' + totalPrice.toFixed(2);
}
// Update button text
if (purchaseBtnText) {
const singleLabel = purchaseBtnText.dataset.singleLabel || purchaseBtnText.textContent;
const pluralLabel = purchaseBtnText.dataset.pluralLabel || singleLabel;
purchaseBtnText.textContent = newQuantity === 1
? singleLabel
: pluralLabel.replace(':count', newQuantity);
}
// Update button states
const decreaseBtn = document.querySelector('.ticket-quantity-controls .qty-decrease');
const increaseBtn = document.querySelector('.ticket-quantity-controls .qty-increase');
if (decreaseBtn) {
decreaseBtn.disabled = (newQuantity <= 1);
}
if (increaseBtn) {
increaseBtn.disabled = (newQuantity >= maxQuantity);
}
}
}
function addTicketsToCart(eventId, isFree, price, evt) {
if (!<?= $user_id ? 'true' : 'false' ?>) {
if (window.ajaxNavigation) {
window.ajaxNavigation.navigateToPage('/auth/login.php');
} else {
window.location.href = '/auth/login.php';
}
return;
}
// Get event from parameter or global event
const event = evt || window.event;
const btn = event ? event.target.closest('.ticket-purchase-btn') : document.querySelector('.ticket-purchase-btn');
if (!btn) {
console.error('Ticket purchase button not found');
return;
}
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ' + eventModalI18n.ticketAdding;
btn.disabled = true;
// Get quantity
const quantityDisplay = document.getElementById('ticket-quantity');
const quantity = quantityDisplay ? parseInt(quantityDisplay.textContent) || 1 : 1;
console.log('🎫 Adding tickets to cart:', { eventId, quantity });
fetch('/api/add_ticket_to_cart.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
event_id: eventId,
quantity: quantity
})
})
.then(response => {
console.log('📡 API Response status:', response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('📦 API Response data:', data);
if (data.success) {
// Show success message
btn.innerHTML = '<i class="fas fa-check"></i> ' + eventModalI18n.ticketAdded;
btn.style.background = 'linear-gradient(135deg, #48bb78, #38a169)';
// Update cart count badge
const totalCount = data.cart_count || quantity;
console.log('🛒 Updating cart count to:', totalCount);
updateCartCountBadge(totalCount);
// Refresh cart modal if it's open
if (typeof refreshCartModal === 'function') {
console.log('🔄 Refreshing cart modal...');
refreshCartModal();
}
// Show notification if available
const addedMessage = quantity > 1
? eventModalI18n.ticketAddedPlural.replace(':count', quantity)
: eventModalI18n.ticketAddedSingle;
if (typeof showNotification === 'function') {
showNotification(addedMessage, 'success');
} else {
console.log(`✅ ${addedMessage} Total cart items: ${totalCount}`);
}
// Reset button after 2 seconds
setTimeout(() => {
btn.innerHTML = originalText;
btn.style.background = '';
btn.disabled = false;
}, 2000);
} else {
console.error('❌ API returned error:', data.error);
alert(eventModalI18n.errorPrefix + (data.error || eventModalI18n.cartFailed));
btn.innerHTML = originalText;
btn.disabled = false;
}
})
.catch(error => {
console.error('❌ Add to cart error:', error);
alert(eventModalI18n.errorConsole);
btn.innerHTML = originalText;
btn.disabled = false;
});
}
// Keep old function for backward compatibility (free tickets might still use direct purchase)
function purchaseEventTicket(eventId, isFree, price) {
// For free tickets, we can still process directly
if (isFree) {
addTicketsToCart(eventId, isFree, price);
return;
}
// For paid tickets, redirect to add to cart
addTicketsToCart(eventId, isFree, price);
}
function processTicketStripePayment(paymentData) {
const stripe = Stripe('pk_live_51Rn8TtD0zXLMB4gHMCZ5OMunyo0YtN6hBR30BoXFEiQxPG9I6U2tko6Axxwl0yJS21DCCykhC9PxAMdZoEfwJI0p00KlrZUR3w');
// Create payment element
const elements = stripe.elements();
const paymentElement = elements.create('payment');
// Show payment form
const purchaseCard = document.querySelector('.ticket-purchase-card');
const originalContent = purchaseCard.innerHTML;
const quantity = paymentData.quantity || 1;
const unitPrice = paymentData.unit_price || paymentData.amount;
const ticketText = quantity === 1
? eventModalI18n.paymentTicketSingle
: eventModalI18n.paymentTicketPlural.replace(':count', quantity);
const payLabel = eventModalI18n.paymentPayLabel.replace(':amount', `$${paymentData.amount.toFixed(2)}`);
purchaseCard.innerHTML = `
<h3><i class="fas fa-credit-card"></i> ${eventModalI18n.paymentTitle}</h3>
<div class="ticket-payment-form">
<div class="payment-summary">
<div class="payment-item">
<span>${ticketText}</span>
<span>$${paymentData.amount.toFixed(2)}</span>
</div>
${quantity > 1 ? `
<div class="payment-item" style="font-size: 0.9rem; color: #a0aec0; padding-top: 0.5rem;">
<span>($${unitPrice.toFixed(2)} × ${quantity})</span>
<span></span>
</div>
` : ''}
<div class="payment-total">
<span>${eventModalI18n.paymentTotalLabel}</span>
<span>$${paymentData.amount.toFixed(2)}</span>
</div>
</div>
<div id="ticket-payment-element" style="margin: 2rem 0;"></div>
<div class="payment-actions">
<button class="ticket-purchase-btn" onclick="confirmTicketPayment()">
<i class="fas fa-lock"></i>
${payLabel}
</button>
<button class="ticket-cancel-btn" onclick="cancelTicketPayment()" style="margin-top: 1rem; background: rgba(239, 68, 68, 0.2); border: 1px solid rgba(239, 68, 68, 0.4); color: #f56565;">
${eventModalI18n.paymentCancelLabel}
</button>
</div>
</div>
`;
paymentElement.mount('#ticket-payment-element');
window.currentTicketPayment = {
stripe: stripe,
clientSecret: paymentData.payment_intent,
eventId: paymentData.event.id,
originalContent: originalContent
};
}
function confirmTicketPayment() {
const payment = window.currentTicketPayment;
if (!payment) return;
const btn = event.target.closest('.ticket-purchase-btn');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ' + eventModalI18n.paymentProcessing;
btn.disabled = true;
payment.stripe.confirmPayment({
clientSecret: payment.clientSecret,
confirmParams: {
return_url: window.location.href
}
}).then(result => {
if (result.error) {
alert(eventModalI18n.paymentFailed + result.error.message);
btn.innerHTML = originalText;
btn.disabled = false;
} else {
// Payment succeeded - reload modal to show ticket
closeEventModal();
setTimeout(() => {
if (typeof showEventDetails === 'function') {
showEventDetails(payment.eventId);
} else {
window.location.reload();
}
}, 1000);
}
});
}
function cancelTicketPayment() {
const payment = window.currentTicketPayment;
if (payment && payment.originalContent) {
const purchaseCard = document.querySelector('.ticket-purchase-card');
purchaseCard.innerHTML = payment.originalContent;
window.currentTicketPayment = null;
}
}
function showTicketSuccess(ticket) {
// Reload modal to show ticket
const eventId = <?= (int)$event['id'] ?>;
closeEventModal();
setTimeout(() => {
if (typeof showEventDetails === 'function') {
showEventDetails(eventId);
} else {
window.location.reload();
}
}, 500);
}
function copyTicketCode(code) {
navigator.clipboard.writeText(code).then(() => {
const btn = event.target.closest('.ticket-copy-btn');
const originalText = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> ' + eventModalI18n.ticketCopySuccess;
setTimeout(() => {
btn.innerHTML = originalText;
}, 2000);
}).catch(() => {
// Fallback
const textarea = document.createElement('textarea');
textarea.value = code;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert(eventModalI18n.ticketCodeCopied);
});
}
// Function to update cart count badge
function updateCartCountBadge(count) {
const cartCounts = document.querySelectorAll('.cart-count');
cartCounts.forEach(badge => {
if (count > 0) {
badge.textContent = count;
badge.style.display = 'flex';
} else {
badge.style.display = 'none';
}
});
}
// Make functions globally available
window.updateTicketQuantity = updateTicketQuantity;
window.addTicketsToCart = addTicketsToCart;
window.updateCartCountBadge = updateCartCountBadge;
window.purchaseEventTicket = purchaseEventTicket;
window.copyTicketCode = copyTicketCode;
window.handleCommentImagePreview = handleCommentImagePreview;
window.clearCommentImageSelection = clearCommentImageSelection;
window.resetCommentImagePreview = resetCommentImagePreview;
window.openCommentLightbox = openCommentLightbox;
window.closeCommentLightbox = closeCommentLightbox;
function setupModalHandlers(overlay, eventId) {
const closeBtn = overlay.querySelector('.event-modal-close');
if (closeBtn) {
closeBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
closeEventModal();
};
}
overlay.onclick = (e) => {
if (e.target === overlay) {
closeEventModal();
}
};
const container = overlay.querySelector('.event-modal-container');
if (container) {
container.onclick = (e) => e.stopPropagation();
}
const likeBtn = overlay.querySelector('[onclick*="likeEventModal"]');
if (likeBtn) {
likeBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const match = likeBtn.getAttribute('onclick').match(/likeEventModal\((\d+)\)/);
if (match) {
likeEventModal(parseInt(match[1], 10));
}
};
}
const commentBtn = overlay.querySelector('[onclick*="addEventComment"]');
if (commentBtn) {
commentBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const match = commentBtn.getAttribute('onclick').match(/addEventComment\((\d+)\)/);
if (match) {
addEventComment(parseInt(match[1], 10));
}
};
}
const shareBtn = overlay.querySelector('[onclick*="shareEventModal"]');
if (shareBtn) {
shareBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const match = shareBtn.getAttribute('onclick').match(/shareEventModal\((\d+)\)/);
if (match) {
shareEventModal(parseInt(match[1], 10));
}
};
}
const decreaseBtn = overlay.querySelector('.ticket-quantity-controls .qty-decrease');
const increaseBtn = overlay.querySelector('.ticket-quantity-controls .qty-increase');
if (decreaseBtn) {
const maxQty = increaseBtn ? (() => {
const onclickAttr = increaseBtn.getAttribute('onclick');
const match = onclickAttr ? onclickAttr.match(/updateTicketQuantity\([^,]+,\s*(\d+)/) : null;
return match ? parseInt(match[1], 10) : 10;
})() : 10;
decreaseBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (typeof window.updateTicketQuantity === 'function') {
window.updateTicketQuantity(-1, maxQty, e);
}
};
}
if (increaseBtn) {
const onclickAttr = increaseBtn.getAttribute('onclick');
const match = onclickAttr ? onclickAttr.match(/updateTicketQuantity\([^,]+,\s*(\d+)/) : null;
const maxQty = match ? parseInt(match[1], 10) : 10;
increaseBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (typeof window.updateTicketQuantity === 'function') {
window.updateTicketQuantity(1, maxQty, e);
}
};
}
const ticketPurchaseBtn = overlay.querySelector('.ticket-purchase-btn');
if (ticketPurchaseBtn) {
let eventIdAttr = ticketPurchaseBtn.dataset.eventId;
let isFree = ticketPurchaseBtn.dataset.isFree === 'true';
let price = parseFloat(ticketPurchaseBtn.dataset.price || '0');
if (!eventIdAttr) {
const onclickAttr = ticketPurchaseBtn.getAttribute('onclick');
if (onclickAttr) {
const match = onclickAttr.match(/addTicketsToCart\((\d+),\s*(true|false),\s*([\d.]+)/);
if (match) {
eventIdAttr = parseInt(match[1], 10);
isFree = match[2] === 'true';
price = parseFloat(match[3]);
}
}
}
if (eventIdAttr) {
ticketPurchaseBtn.removeAttribute('onclick');
ticketPurchaseBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (typeof window.addTicketsToCart === 'function') {
window.addTicketsToCart(parseInt(eventIdAttr, 10), isFree, price, e);
} else {
alert(eventModalI18n.ticketUnavailable);
}
};
}
}
}
window.setupModalHandlers = setupModalHandlers;
// Enhanced floating attendees interactions
function initFloatingAttendees() {
try {
const badges = document.querySelectorAll('.floating-attendee-badge');
if (badges.length === 0) {
return; // No badges to initialize
}
badges.forEach((badge, index) => {
const wrapper = badge.closest('.floating-attendee-wrapper');
if (!wrapper) return;
if (wrapper.dataset.initialized === 'true') return;
wrapper.dataset.initialized = 'true';
let posX = wrapper.offsetLeft;
let posY = wrapper.offsetTop;
let isDragging = false;
let startX, startY;
const morphBackground = () => {
const colors = ['#4facfe', '#43e97b', '#fa709a', '#fee140'];
wrapper.style.filter = `drop-shadow(0 10px 25px rgba(0,0,0,0.45))`;
badge.style.borderColor = colors[index % colors.length];
};
const bringToFront = () => {
document.querySelectorAll('.floating-attendee-wrapper').forEach(el => el.style.zIndex = 51);
wrapper.style.zIndex = 200;
};
const pointerDown = (e) => {
isDragging = true;
bringToFront();
morphBackground();
startX = e.clientX - posX;
startY = e.clientY - posY;
wrapper.classList.add('floating-dragging');
e.preventDefault();
};
const pointerMove = (e) => {
if (!isDragging) return;
posX = e.clientX - startX;
posY = e.clientY - startY;
wrapper.style.left = posX + 'px';
wrapper.style.top = posY + 'px';
};
const pointerUp = () => {
if (!isDragging) return;
isDragging = false;
wrapper.classList.remove('floating-dragging');
};
badge.addEventListener('pointerdown', pointerDown);
window.addEventListener('pointermove', pointerMove);
window.addEventListener('pointerup', pointerUp);
});
} catch (error) {
console.error('Error initializing floating attendees:', error);
}
}
// Make function available globally
window.initFloatingAttendees = initFloatingAttendees;
setTimeout(() => {
if (document.querySelector('.floating-attendee-badge')) {
initFloatingAttendees();
}
}, 200);
</script>
<?php
if (!empty($is_ajax_event_modal)) {
$modalHtml = ob_get_clean();
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['html' => $modalHtml], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
?>