T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/soundstudiopro.com/public_html/crates.php
<?php
/**
 * Crates Browser - Search and discover public crates
 */
session_start();
require_once 'config/database.php';
require_once 'includes/translations.php';

$pdo = getDBConnection();

// Search and filter parameters
$search = isset($_GET['q']) ? trim($_GET['q']) : '';
$artist_filter = isset($_GET['artist']) ? (int)$_GET['artist'] : 0;
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'recent';
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$per_page = 12;
$offset = ($page - 1) * $per_page;

// Build query conditions
$conditions = ["ap.is_public = 1"];
$params = [];

if ($search) {
    $conditions[] = "(ap.name LIKE ? OR ap.description LIKE ? OR u.name LIKE ?)";
    $searchTerm = "%{$search}%";
    $params[] = $searchTerm;
    $params[] = $searchTerm;
    $params[] = $searchTerm;
}

if ($artist_filter) {
    $conditions[] = "ap.user_id = ?";
    $params[] = $artist_filter;
}

$whereClause = implode(' AND ', $conditions);

// Sorting
$orderBy = match($sort) {
    'popular' => 'track_count DESC',
    'duration' => 'total_duration DESC',
    'name' => 'ap.name ASC',
    default => 'ap.updated_at DESC'
};

// Check if is_description_public column exists
$checkDescCol = $pdo->query("SHOW COLUMNS FROM artist_playlists LIKE 'is_description_public'");
$hasDescPublicColumn = $checkDescCol->rowCount() > 0;

// Get total count
$countQuery = "
    SELECT COUNT(DISTINCT ap.id) as total
    FROM artist_playlists ap
    JOIN users u ON ap.user_id = u.id
    LEFT JOIN playlist_tracks pt ON ap.id = pt.playlist_id
    LEFT JOIN music_tracks mt ON pt.track_id = mt.id AND mt.status = 'complete'
    WHERE $whereClause
    GROUP BY ap.id
    HAVING COUNT(DISTINCT pt.track_id) > 0
";
$stmt = $pdo->prepare("SELECT COUNT(*) FROM ($countQuery) as subq");
$stmt->execute($params);
$total_crates = $stmt->fetchColumn();
$total_pages = ceil($total_crates / $per_page);

// Get crates
$query = "
    SELECT 
        ap.id,
        ap.name,
        ap.description,
        ap.created_at,
        ap.updated_at,
        " . ($hasDescPublicColumn ? "ap.is_description_public," : "1 as is_description_public,") . "
        COUNT(DISTINCT pt.track_id) as track_count,
        COALESCE(SUM(mt.duration), 0) as total_duration,
        u.name as artist_name,
        u.id as artist_id,
        up.profile_image
    FROM artist_playlists ap
    JOIN users u ON ap.user_id = u.id
    LEFT JOIN user_profiles up ON u.id = up.user_id
    LEFT JOIN playlist_tracks pt ON ap.id = pt.playlist_id
    LEFT JOIN music_tracks mt ON pt.track_id = mt.id AND mt.status = 'complete'
    WHERE $whereClause
    GROUP BY ap.id, ap.name, ap.description, ap.created_at, ap.updated_at, u.name, u.id, up.profile_image
    HAVING track_count > 0
    ORDER BY $orderBy
    LIMIT $per_page OFFSET $offset
";

$stmt = $pdo->prepare($query);
$stmt->execute($params);
$crates = $stmt->fetchAll(PDO::FETCH_ASSOC);

// Get popular artists for filter
$artistsQuery = "
    SELECT DISTINCT u.id, u.name, COUNT(DISTINCT ap.id) as crate_count
    FROM users u
    JOIN artist_playlists ap ON u.id = ap.user_id AND ap.is_public = 1
    JOIN playlist_tracks pt ON ap.id = pt.playlist_id
    GROUP BY u.id, u.name
    HAVING crate_count > 0
    ORDER BY crate_count DESC
    LIMIT 20
";
$popular_artists = $pdo->query($artistsQuery)->fetchAll(PDO::FETCH_ASSOC);

// Format duration helper
function formatCrateDuration($seconds) {
    $hours = floor($seconds / 3600);
    $minutes = floor(($seconds % 3600) / 60);
    if ($hours > 0) {
        return sprintf('%dh %dm', $hours, $minutes);
    }
    return sprintf('%dm', $minutes);
}

include 'includes/header.php';
?>

<style>
/* Crates Browser Styles */
.crates-page {
    min-height: 100vh;
    background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
    padding: 2rem 0;
}

.crates-container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 0 2rem;
}

/* Header */
.crates-header {
    text-align: center;
    margin-bottom: 3rem;
}

.crates-header h1 {
    font-size: 3rem;
    font-weight: 800;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    margin-bottom: 0.5rem;
}

.crates-header p {
    color: #a0aec0;
    font-size: 1.1rem;
}

/* Search & Filters */
.crates-filters {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 16px;
    padding: 1.5rem;
    margin-bottom: 2rem;
    backdrop-filter: blur(10px);
}

.filters-row {
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    align-items: center;
}

.search-box {
    flex: 1;
    min-width: 250px;
    position: relative;
}

.search-box input {
    width: 100%;
    padding: 0.875rem 1rem 0.875rem 3rem;
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
    color: white;
    font-size: 1rem;
    transition: all 0.3s ease;
}

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

.search-box input::placeholder {
    color: #6b7280;
}

.search-box i {
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    color: #6b7280;
}

.filter-select {
    padding: 0.875rem 1rem;
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
    color: white;
    font-size: 0.95rem;
    cursor: pointer;
    min-width: 150px;
}

.filter-select option {
    background: #1a1a2e;
    color: white;
}

.search-submit-btn {
    flex-shrink: 0;
    white-space: nowrap;
    min-width: auto;
}

.search-submit-btn .search-btn-text {
    display: inline;
}

/* Stats Bar */
.stats-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1.5rem;
    padding: 0 0.5rem;
}

.stats-bar .count {
    color: #a0aec0;
    font-size: 0.95rem;
}

.stats-bar .count strong {
    color: #667eea;
}

/* Crates Grid */
.crates-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    gap: 2rem;
}

/* Crate Card - Redesigned */
.crate-card {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 20px;
    overflow: hidden;
    transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    backdrop-filter: blur(10px);
}

.crate-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: linear-gradient(90deg, #667eea, #764ba2, #f093fb);
    opacity: 0;
    transition: opacity 0.3s ease;
}

.crate-card:hover {
    transform: translateY(-8px) scale(1.02);
    border-color: rgba(102, 126, 234, 0.5);
    box-shadow: 0 25px 50px rgba(102, 126, 234, 0.25), 0 0 0 1px rgba(102, 126, 234, 0.1);
}

.crate-card:hover::before {
    opacity: 1;
}

.crate-card-image {
    position: relative;
    height: 200px;
    background: linear-gradient(135deg, rgba(102, 126, 234, 0.3) 0%, rgba(118, 75, 162, 0.3) 50%, rgba(240, 147, 251, 0.3) 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.crate-card-image::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.3) 100%);
}

.crate-icon-large {
    font-size: 4rem;
    color: rgba(255, 255, 255, 0.9);
    z-index: 1;
    position: relative;
    text-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.crate-play-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.4);
    opacity: 0;
    transition: opacity 0.3s ease;
    z-index: 2;
}

.crate-card:hover .crate-play-overlay {
    opacity: 1;
}

.crate-play-btn-large {
    width: 70px;
    height: 70px;
    border-radius: 50%;
    background: linear-gradient(135deg, #667eea, #764ba2);
    border: none;
    color: white;
    font-size: 1.5rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
    box-shadow: 0 8px 24px rgba(102, 126, 234, 0.4);
    position: relative;
}

.crate-play-btn-large:hover {
    transform: scale(1.1);
    box-shadow: 0 12px 32px rgba(102, 126, 234, 0.6);
}

.crate-play-btn-large:active {
    transform: scale(0.95);
}

.crate-play-btn-large.loading {
    pointer-events: none;
}

.crate-play-btn-large.loading i {
    animation: spin 1s linear infinite;
}

@keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

.crate-card-content {
    padding: 1.5rem;
}

.crate-title-section {
    margin-bottom: 1rem;
}

.crate-title {
    font-size: 1.25rem;
    font-weight: 700;
    color: white;
    margin-bottom: 0.5rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: 1.3;
}

.crate-title a {
    color: inherit;
    text-decoration: none;
    transition: color 0.2s;
}

.crate-title a:hover {
    color: #667eea;
}

.crate-artist {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
    color: #a0aec0;
}

.crate-artist-avatar {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid rgba(102, 126, 234, 0.3);
}

.crate-artist a {
    color: #a0aec0;
    text-decoration: none;
    transition: color 0.2s;
}

.crate-artist a:hover {
    color: #667eea;
}

.crate-description {
    color: #a0aec0;
    font-size: 0.9rem;
    line-height: 1.6;
    margin-bottom: 1.25rem;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.7rem;
}

.crate-stats {
    display: flex;
    gap: 1.5rem;
    margin-bottom: 1.25rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.crate-stat {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: #9ca3af;
    font-size: 0.9rem;
}

.crate-stat i {
    color: #667eea;
    font-size: 0.95rem;
}

.crate-actions {
    display: flex;
    gap: 0.75rem;
}

.crate-btn {
    flex: 1;
    padding: 0.875rem 1rem;
    border: none;
    border-radius: 10px;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    transition: all 0.2s ease;
    text-decoration: none;
    position: relative;
    overflow: hidden;
}

.crate-btn::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), transparent);
    opacity: 0;
    transition: opacity 0.2s;
}

.crate-btn:hover::before {
    opacity: 1;
}

.crate-btn.play-btn {
    background: linear-gradient(135deg, #667eea, #764ba2);
    color: white;
    flex: 1.5;
    font-weight: 700;
}

.crate-btn.play-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}

.crate-btn.play-btn:active {
    transform: translateY(0);
}

.crate-btn.play-btn.loading {
    pointer-events: none;
    opacity: 0.7;
}

.crate-btn.secondary {
    background: rgba(255, 255, 255, 0.08);
    color: white;
    border: 1px solid rgba(255, 255, 255, 0.15);
}

.crate-btn.secondary:hover {
    background: rgba(255, 255, 255, 0.12);
    transform: translateY(-1px);
}

/* Pagination */
.pagination {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin-top: 3rem;
}

.pagination a, .pagination span {
    padding: 0.75rem 1rem;
    border-radius: 8px;
    color: white;
    text-decoration: none;
    font-size: 0.95rem;
    transition: all 0.2s;
}

.pagination a {
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid rgba(255, 255, 255, 0.1);
}

.pagination a:hover {
    background: rgba(102, 126, 234, 0.2);
    border-color: rgba(102, 126, 234, 0.4);
}

.pagination .current {
    background: linear-gradient(135deg, #667eea, #764ba2);
}

.pagination .disabled {
    opacity: 0.5;
    pointer-events: none;
}

/* Empty State */
.empty-state {
    text-align: center;
    padding: 4rem 2rem;
    background: rgba(255, 255, 255, 0.02);
    border-radius: 16px;
    border: 1px dashed rgba(255, 255, 255, 0.1);
}

.empty-state i {
    font-size: 4rem;
    color: #667eea;
    margin-bottom: 1.5rem;
    opacity: 0.5;
}

.empty-state h3 {
    color: white;
    font-size: 1.5rem;
    margin-bottom: 0.5rem;
}

.empty-state p {
    color: #a0aec0;
    font-size: 1rem;
}

/* Responsive */
@media (max-width: 768px) {
    .crates-container {
        padding: 0 1rem;
    }
    
    .crates-header {
        margin-bottom: 2rem;
    }
    
    .crates-header h1 {
        font-size: 2rem;
    }
    
    .crates-header p {
        font-size: 1rem;
    }
    
    .crates-filters {
        padding: 1rem;
        margin-bottom: 1.5rem;
    }
    
    .filters-row {
        flex-direction: column;
        gap: 0.75rem;
    }
    
    .search-box {
        width: 100%;
        min-width: unset;
    }
    
    .filter-select {
        width: 100%;
        min-width: unset;
    }
    
    .search-submit-btn {
        width: 100%;
        justify-content: center;
        padding: 0.875rem 1rem;
    }
    
    .search-submit-btn .search-btn-text {
        display: inline;
    }
    
    .crates-grid {
        grid-template-columns: 1fr;
        gap: 1.5rem;
    }
    
    .stats-bar {
        flex-direction: column;
        gap: 0.5rem;
        text-align: center;
        padding: 0;
    }
    
    .crate-card-image {
        height: 180px;
    }
    
    .crate-icon-large {
        font-size: 3rem;
    }
    
    .crate-play-btn-large {
        width: 60px;
        height: 60px;
        font-size: 1.25rem;
    }
    
    .crate-card-content {
        padding: 1.25rem;
    }
    
    .crate-actions {
        flex-wrap: wrap;
        gap: 0.5rem;
    }
    
    .crate-btn.play-btn {
        flex: 1 1 100%;
        margin-bottom: 0.5rem;
    }
    
    .crate-btn.secondary {
        flex: 1;
        min-width: 48px;
    }
    
    .pagination {
        flex-wrap: wrap;
        gap: 0.25rem;
        margin-top: 2rem;
    }
    
    .pagination a, .pagination span {
        padding: 0.5rem 0.75rem;
        font-size: 0.85rem;
    }
}

/* Extra small devices */
@media (max-width: 480px) {
    .crates-header h1 {
        font-size: 1.75rem;
    }
    
    .crate-card-image {
        height: 160px;
    }
    
    .crate-icon-large {
        font-size: 2.5rem;
    }
    
    .crate-play-btn-large {
        width: 50px;
        height: 50px;
        font-size: 1rem;
    }
    
    .crate-title {
        font-size: 1.1rem;
    }
    
    .crate-stats {
        flex-direction: column;
        gap: 0.75rem;
        align-items: flex-start;
    }
}
</style>

<div class="crates-page">
    <div class="crates-container">
        <!-- Header -->
        <div class="crates-header">
            <h1><i class="fas fa-box-open"></i> <?= t('crates.browse_title') ?></h1>
            <p><?= t('crates.browse_subtitle') ?></p>
        </div>
        
        <!-- Filters -->
        <form class="crates-filters" method="GET" action="" id="cratesSearchForm">
            <div class="filters-row">
                <div class="search-box">
                    <i class="fas fa-search"></i>
                    <input type="text" name="q" value="<?= htmlspecialchars($search) ?>" placeholder="<?= t('crates.search_placeholder') ?>" id="cratesSearchInput" autocomplete="off">
                </div>
                
                <select name="artist" class="filter-select" onchange="this.form.submit()">
                    <option value=""><?= t('crates.all_artists') ?></option>
                    <?php foreach ($popular_artists as $artist): ?>
                        <option value="<?= $artist['id'] ?>" <?= $artist_filter == $artist['id'] ? 'selected' : '' ?>>
                            <?= htmlspecialchars($artist['name']) ?> (<?= $artist['crate_count'] ?>)
                        </option>
                    <?php endforeach; ?>
                </select>
                
                <select name="sort" class="filter-select" onchange="this.form.submit()">
                    <option value="recent" <?= $sort == 'recent' ? 'selected' : '' ?>><?= t('crates.sort_recent') ?></option>
                    <option value="popular" <?= $sort == 'popular' ? 'selected' : '' ?>><?= t('crates.sort_popular') ?></option>
                    <option value="duration" <?= $sort == 'duration' ? 'selected' : '' ?>><?= t('crates.sort_duration') ?></option>
                    <option value="name" <?= $sort == 'name' ? 'selected' : '' ?>><?= t('crates.sort_name') ?></option>
                </select>
                
                <button type="submit" class="crate-btn primary search-submit-btn">
                    <i class="fas fa-search"></i> <span class="search-btn-text"><?= t('crates.search_btn') ?></span>
                </button>
            </div>
        </form>
        
        <!-- Stats -->
        <div class="stats-bar">
            <div class="count">
                <?php if ($search || $artist_filter): ?>
                    <strong><?= $total_crates ?></strong> <?= t('crates.results_found') ?>
                <?php else: ?>
                    <strong><?= $total_crates ?></strong> <?= t('crates.public_crates') ?>
                <?php endif; ?>
            </div>
        </div>
        
        <!-- Crates Grid -->
        <?php if (empty($crates)): ?>
            <div class="empty-state">
                <i class="fas fa-box-open"></i>
                <h3><?= t('crates.no_crates_found') ?></h3>
                <p><?= t('crates.try_different_search') ?></p>
            </div>
        <?php else: ?>
            <div class="crates-grid">
                <?php foreach ($crates as $crate): 
                    $duration_formatted = formatCrateDuration($crate['total_duration']);
                    $show_description = $crate['is_description_public'] && !empty($crate['description']);
                    $artist_avatar = !empty($crate['profile_image']) ? htmlspecialchars($crate['profile_image']) : '';
                ?>
                    <div class="crate-card" data-crate-id="<?= $crate['id'] ?>">
                        <div class="crate-card-image">
                            <i class="fas fa-box-open crate-icon-large"></i>
                            <div class="crate-play-overlay">
                                <button class="crate-play-btn-large" onclick="playCrateFromCard(<?= $crate['id'] ?>, this)" title="<?= t('library.crates.play_all') ?>">
                                    <i class="fas fa-play"></i>
                                </button>
                            </div>
                        </div>
                        <div class="crate-card-content">
                            <div class="crate-title-section">
                                <div class="crate-title">
                                    <a href="javascript:void(0)" onclick="openCrateModal(<?= $crate['id'] ?>)"><?= htmlspecialchars($crate['name']) ?></a>
                                </div>
                                <div class="crate-artist">
                                    <?php if ($artist_avatar): ?>
                                        <img src="<?= $artist_avatar ?>" alt="<?= htmlspecialchars($crate['artist_name']) ?>" class="crate-artist-avatar" onerror="this.style.display='none'">
                                    <?php endif; ?>
                                    <span><?= t('crates.by') ?> <a href="/artist/<?= $crate['artist_id'] ?>" target="_blank"><?= htmlspecialchars($crate['artist_name']) ?></a></span>
                                </div>
                            </div>
                            <div class="crate-description">
                                <?= $show_description ? htmlspecialchars($crate['description']) : '<em style="opacity: 0.5;">' . t('crates.no_description') . '</em>' ?>
                            </div>
                            <div class="crate-stats">
                                <div class="crate-stat">
                                    <i class="fas fa-music"></i>
                                    <span><?= $crate['track_count'] ?> <?= t('crates.tracks') ?></span>
                                </div>
                                <div class="crate-stat">
                                    <i class="fas fa-clock"></i>
                                    <span><?= $duration_formatted ?></span>
                                </div>
                            </div>
                            <div class="crate-actions">
                                <button class="crate-btn play-btn" onclick="playCrateFromCard(<?= $crate['id'] ?>, this)">
                                    <i class="fas fa-play"></i>
                                    <span><?= t('library.crates.play_all') ?></span>
                                </button>
                                <button class="crate-btn secondary" onclick="openCrateModal(<?= $crate['id'] ?>)" title="<?= t('crates.view') ?>">
                                    <i class="fas fa-eye"></i>
                                </button>
                                <button class="crate-btn secondary" onclick="shareCrate(<?= $crate['id'] ?>, '<?= htmlspecialchars($crate['name'], ENT_QUOTES) ?>')" title="<?= t('library.crates.share') ?>">
                                    <i class="fas fa-share-alt"></i>
                                </button>
                            </div>
                        </div>
                    </div>
                <?php endforeach; ?>
            </div>
            
            <!-- Pagination -->
            <?php if ($total_pages > 1): ?>
                <div class="pagination">
                    <?php if ($page > 1): ?>
                        <a href="?<?= http_build_query(array_merge($_GET, ['page' => $page - 1])) ?>">
                            <i class="fas fa-chevron-left"></i>
                        </a>
                    <?php endif; ?>
                    
                    <?php 
                    $start = max(1, $page - 2);
                    $end = min($total_pages, $page + 2);
                    
                    if ($start > 1): ?>
                        <a href="?<?= http_build_query(array_merge($_GET, ['page' => 1])) ?>">1</a>
                        <?php if ($start > 2): ?><span>...</span><?php endif; ?>
                    <?php endif;
                    
                    for ($i = $start; $i <= $end; $i++): ?>
                        <?php if ($i == $page): ?>
                            <span class="current"><?= $i ?></span>
                        <?php else: ?>
                            <a href="?<?= http_build_query(array_merge($_GET, ['page' => $i])) ?>"><?= $i ?></a>
                        <?php endif; ?>
                    <?php endfor;
                    
                    if ($end < $total_pages): ?>
                        <?php if ($end < $total_pages - 1): ?><span>...</span><?php endif; ?>
                        <a href="?<?= http_build_query(array_merge($_GET, ['page' => $total_pages])) ?>"><?= $total_pages ?></a>
                    <?php endif; ?>
                    
                    <?php if ($page < $total_pages): ?>
                        <a href="?<?= http_build_query(array_merge($_GET, ['page' => $page + 1])) ?>">
                            <i class="fas fa-chevron-right"></i>
                        </a>
                    <?php endif; ?>
                </div>
            <?php endif; ?>
        <?php endif; ?>
    </div>
</div>

<script>
function shareCrate(crateId, crateName) {
    const url = window.location.origin + '/crate/' + crateId;
    
    if (navigator.share) {
        navigator.share({
            title: crateName,
            url: url
        });
    } else if (navigator.clipboard) {
        navigator.clipboard.writeText(url).then(() => {
            if (typeof showNotification === 'function') {
                showNotification('<?= t('crates.link_copied') ?>', 'success');
            } else {
                alert('<?= t('crates.link_copied') ?>');
            }
        });
    }
}

// Play crate from card
async function playCrateFromCard(crateId, button) {
    // Find all buttons for this crate (overlay and action button)
    const card = button.closest('.crate-card');
    const allButtons = card.querySelectorAll('.crate-play-btn-large, .crate-btn.play-btn');
    
    // Set loading state
    allButtons.forEach(btn => {
        btn.classList.add('loading');
        const icon = btn.querySelector('i');
        if (icon) {
            icon.className = 'fas fa-spinner fa-spin';
        }
        btn.disabled = true;
    });
    
    try {
        // Fetch crate tracks
        const response = await fetch(`/api/get_crate_tracks.php?crate_id=${crateId}&public=1`);
        const data = await response.json();
        
        if (!data.success || !data.tracks || data.tracks.length === 0) {
            throw new Error(data.error || 'No tracks found in this crate');
        }
        
        // Format tracks for playlist (we'll fetch signed URLs when playing)
        const formattedTracks = data.tracks.map(track => ({
            id: track.id,
            title: track.title || 'Untitled',
            artist_name: track.artist_name || data.crate.artist_name || 'Unknown Artist',
            duration: track.duration || 300,
            user_id: track.user_id || null,
            artist_id: track.user_id || null // Support both for compatibility
        }));
        
        // Load playlist into global player
        if (window.enhancedGlobalPlayer && typeof window.enhancedGlobalPlayer.loadPagePlaylist === 'function') {
            window.enhancedGlobalPlayer.loadPagePlaylist(formattedTracks, 'crate', 0);
        } else {
            // Fallback
            window._communityPlaylist = formattedTracks;
            window._communityPlaylistType = 'crate';
            window._communityTrackIndex = 0;
        }
        
        // Play first track
        if (formattedTracks.length > 0) {
            const firstTrack = formattedTracks[0];
            
            // Fetch signed audio token for first track
            const tokenResponse = await fetch(`/api/get_audio_token.php?track_id=${firstTrack.id}&duration=${firstTrack.duration}`);
            const tokenData = await tokenResponse.json();
            
            if (tokenData.success && tokenData.url) {
                // Play using global player
                if (window.enhancedGlobalPlayer && typeof window.enhancedGlobalPlayer.playTrack === 'function') {
                    window.enhancedGlobalPlayer.playTrack(
                        tokenData.url,
                        firstTrack.title,
                        firstTrack.artist_name,
                        firstTrack.id,
                        firstTrack.user_id || firstTrack.artist_id
                    );
                } else if (typeof window.playTrack === 'function') {
                    window.playTrack(
                        tokenData.url,
                        firstTrack.title,
                        firstTrack.artist_name,
                        firstTrack.id,
                        firstTrack.user_id || firstTrack.artist_id
                    );
                }
                
                // Show notification
                if (typeof window.showNotification === 'function') {
                    window.showNotification(`🎵 Playing: ${data.crate.name} (${formattedTracks.length} tracks)`, 'success');
                }
            } else {
                throw new Error('Failed to get audio token');
            }
        }
        
    } catch (error) {
        console.error('Error playing crate:', error);
        if (typeof window.showNotification === 'function') {
            window.showNotification('Error loading crate: ' + error.message, 'error');
        } else {
            alert('Error loading crate: ' + error.message);
        }
    } finally {
        // Reset button states
        allButtons.forEach(btn => {
            btn.classList.remove('loading');
            const icon = btn.querySelector('i');
            if (icon) {
                icon.className = 'fas fa-play';
            }
            btn.disabled = false;
        });
    }
}

// Open crate modal
async function openCrateModal(crateId) {
    const modal = document.getElementById('crateViewModal');
    const modalContent = document.getElementById('crateModalContent');
    const modalTitle = document.getElementById('crateModalTitle');
    
    if (!modal || !modalContent) return;
    
    // Show loading state
    modalContent.innerHTML = `
        <div style="text-align: center; padding: 3rem;">
            <i class="fas fa-spinner fa-spin" style="font-size: 2rem; color: #667eea; margin-bottom: 1rem;"></i>
            <p style="color: #a0aec0;"><?= t('crates.loading') ?></p>
        </div>
    `;
    
    modal.style.display = 'flex';
    document.body.style.overflow = 'hidden';
    
    try {
        // Fetch crate details
        const response = await fetch(`/api/get_crate_tracks.php?crate_id=${crateId}&public=1`);
        const data = await response.json();
        
        if (!data.success || !data.crate) {
            throw new Error(data.error || 'Failed to load crate');
        }
        
        const crate = data.crate;
        const tracks = data.tracks || [];
        
        // Format duration
        const totalSeconds = tracks.reduce((sum, track) => sum + (parseInt(track.duration) || 0), 0);
        const hours = Math.floor(totalSeconds / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const durationFormatted = hours > 0 
            ? `${hours}h ${minutes}m`
            : `${minutes}m`;
        
        // Build modal content
        const artistAvatar = crate.profile_image ? `<img src="${escapeHtml(crate.profile_image)}" alt="${escapeHtml(crate.artist_name)}" class="crate-modal-artist-avatar" onerror="this.style.display='none'">` : '';
        
        let tracksHtml = '';
        if (tracks.length > 0) {
            tracksHtml = tracks.map((track, index) => `
                <div class="crate-modal-track" data-track-id="${track.id}">
                    <div class="crate-modal-track-number">${index + 1}</div>
                    <div class="crate-modal-track-info">
                        <div class="crate-modal-track-title">${escapeHtml(track.title || 'Untitled')}</div>
                        <div class="crate-modal-track-artist">${escapeHtml(track.artist_name || crate.artist_name || 'Unknown')}</div>
                    </div>
                    <div class="crate-modal-track-duration">${formatTrackDuration(track.duration || 0)}</div>
                </div>
            `).join('');
        } else {
            tracksHtml = '<div style="text-align: center; padding: 2rem; color: #a0aec0;"><?= t("crates.no_tracks") ?></div>';
        }
        
        modalTitle.textContent = escapeHtml(crate.name);
        modalContent.innerHTML = `
            <div class="crate-modal-header">
                <div class="crate-modal-artwork">
                    <i class="fas fa-box-open"></i>
                </div>
                <div class="crate-modal-info">
                    <div class="crate-modal-type"><?= t('crates.crate') ?></div>
                    <h2 class="crate-modal-name">${escapeHtml(crate.name)}</h2>
                    <div class="crate-modal-artist">
                        ${artistAvatar}
                        <span><?= t('crates.by') ?> <a href="/artist/${crate.artist_id}" target="_blank">${escapeHtml(crate.artist_name)}</a></span>
                    </div>
                    <div class="crate-modal-stats">
                        <div class="crate-modal-stat">
                            <i class="fas fa-music"></i>
                            <span>${tracks.length} <?= t('crates.tracks') ?></span>
                        </div>
                        <div class="crate-modal-stat">
                            <i class="fas fa-clock"></i>
                            <span>${durationFormatted}</span>
                        </div>
                    </div>
                    ${crate.description && crate.is_description_public ? `<div class="crate-modal-description">${escapeHtml(crate.description)}</div>` : ''}
                    <div class="crate-modal-actions">
                        <button class="crate-modal-btn primary" onclick="playCrateFromModal(${crateId})">
                            <i class="fas fa-play"></i>
                            <span><?= t('library.crates.play_all') ?></span>
                        </button>
                        <a href="/crate/${crateId}" class="crate-modal-btn secondary" target="_blank">
                            <i class="fas fa-external-link-alt"></i>
                            <span><?= t('crates.view_full') ?></span>
                        </a>
                        <button class="crate-modal-btn secondary" onclick="shareCrate(${crateId}, '${escapeHtml(crate.name).replace(/'/g, "\\'")}')">
                            <i class="fas fa-share-alt"></i>
                        </button>
                    </div>
                </div>
            </div>
            <div class="crate-modal-tracks">
                <h3 class="crate-modal-tracks-title"><?= t('crates.track_list') ?></h3>
                <div class="crate-modal-tracks-list">
                    ${tracksHtml}
                </div>
            </div>
        `;
        
    } catch (error) {
        console.error('Error loading crate:', error);
        modalContent.innerHTML = `
            <div style="text-align: center; padding: 3rem;">
                <i class="fas fa-exclamation-triangle" style="font-size: 2rem; color: #e53e3e; margin-bottom: 1rem;"></i>
                <p style="color: #a0aec0;">${escapeHtml(error.message || '<?= t("crates.load_error") ?>')}</p>
            </div>
        `;
    }
}

// Play crate from modal
async function playCrateFromModal(crateId) {
    await playCrateFromCard(crateId, document.querySelector(`[data-crate-id="${crateId}"] .crate-btn.play-btn`));
}

// Close modal
function closeCrateModal() {
    const modal = document.getElementById('crateViewModal');
    if (modal) {
        modal.style.display = 'none';
        document.body.style.overflow = '';
    }
}

// Escape HTML helper
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// Format track duration
function formatTrackDuration(seconds) {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins}:${secs.toString().padStart(2, '0')}`;
}

// Close modal on overlay click
document.addEventListener('click', function(e) {
    const modal = document.getElementById('crateViewModal');
    if (modal && e.target === modal) {
        closeCrateModal();
    }
});

// Close modal on escape key
document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') {
        closeCrateModal();
    }
});

// Improve search form UX
document.addEventListener('DOMContentLoaded', function() {
    const searchForm = document.getElementById('cratesSearchForm');
    const searchInput = document.getElementById('cratesSearchInput');
    const searchSubmitBtn = document.querySelector('.search-submit-btn');
    
    if (searchForm && searchInput) {
        // Allow Enter key to submit
        searchInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                e.preventDefault();
                searchForm.submit();
            }
        });
        
        // Show loading state on submit
        searchForm.addEventListener('submit', function() {
            if (searchSubmitBtn) {
                searchSubmitBtn.disabled = true;
                const originalHTML = searchSubmitBtn.innerHTML;
                searchSubmitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
                
                // Re-enable after a delay in case of error
                setTimeout(() => {
                    searchSubmitBtn.disabled = false;
                    searchSubmitBtn.innerHTML = originalHTML;
                }, 3000);
            }
        });
    }
});
</script>

<!-- Crate View Modal -->
<div id="crateViewModal" class="crate-view-modal" style="display: none;">
    <div class="crate-view-modal-content">
        <button class="crate-view-modal-close" onclick="closeCrateModal()">
            <i class="fas fa-times"></i>
        </button>
        <h2 id="crateModalTitle" class="crate-view-modal-title"></h2>
        <div id="crateModalContent" class="crate-view-modal-body"></div>
    </div>
</div>

<style>
/* Crate View Modal Styles */
.crate-view-modal {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.85);
    backdrop-filter: blur(10px);
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    overflow-y: auto;
    animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.crate-view-modal-content {
    background: linear-gradient(135deg, rgba(26, 26, 46, 0.95) 0%, rgba(22, 33, 62, 0.95) 100%);
    border: 1px solid rgba(102, 126, 234, 0.3);
    border-radius: 24px;
    max-width: 900px;
    width: 100%;
    max-height: 90vh;
    overflow-y: auto;
    position: relative;
    animation: slideUp 0.3s ease;
    box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
}

@keyframes slideUp {
    from { transform: translateY(20px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
}

.crate-view-modal-close {
    position: absolute;
    top: 1.5rem;
    right: 1.5rem;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.2);
    color: white;
    font-size: 1.2rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
    z-index: 10;
}

.crate-view-modal-close:hover {
    background: rgba(255, 255, 255, 0.2);
    transform: rotate(90deg);
}

.crate-view-modal-title {
    font-size: 1.5rem;
    font-weight: 700;
    color: white;
    padding: 2rem 2rem 1rem;
    margin: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.crate-view-modal-body {
    padding: 2rem;
}

.crate-modal-header {
    display: flex;
    gap: 2rem;
    margin-bottom: 2rem;
    padding-bottom: 2rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.crate-modal-artwork {
    width: 150px;
    height: 150px;
    background: linear-gradient(135deg, rgba(102, 126, 234, 0.4) 0%, rgba(118, 75, 162, 0.4) 100%);
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.crate-modal-artwork i {
    font-size: 4rem;
    color: white;
}

.crate-modal-info {
    flex: 1;
}

.crate-modal-type {
    font-size: 0.85rem;
    color: #667eea;
    text-transform: uppercase;
    letter-spacing: 1px;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.crate-modal-name {
    font-size: 2rem;
    font-weight: 700;
    color: white;
    margin: 0 0 1rem 0;
}

.crate-modal-artist {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: #a0aec0;
    font-size: 1rem;
    margin-bottom: 1rem;
}

.crate-modal-artist-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid rgba(102, 126, 234, 0.3);
}

.crate-modal-artist a {
    color: #667eea;
    text-decoration: none;
}

.crate-modal-artist a:hover {
    text-decoration: underline;
}

.crate-modal-stats {
    display: flex;
    gap: 2rem;
    margin-bottom: 1rem;
}

.crate-modal-stat {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: #a0aec0;
    font-size: 0.95rem;
}

.crate-modal-stat i {
    color: #667eea;
}

.crate-modal-description {
    color: #a0aec0;
    font-size: 0.95rem;
    line-height: 1.6;
    margin-bottom: 1.5rem;
}

.crate-modal-actions {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.crate-modal-btn {
    padding: 0.75rem 1.5rem;
    border-radius: 10px;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    transition: all 0.2s ease;
    text-decoration: none;
    border: none;
}

.crate-modal-btn.primary {
    background: linear-gradient(135deg, #667eea, #764ba2);
    color: white;
}

.crate-modal-btn.primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}

.crate-modal-btn.secondary {
    background: rgba(255, 255, 255, 0.08);
    color: white;
    border: 1px solid rgba(255, 255, 255, 0.15);
}

.crate-modal-btn.secondary:hover {
    background: rgba(255, 255, 255, 0.12);
}

.crate-modal-tracks {
    margin-top: 2rem;
}

.crate-modal-tracks-title {
    font-size: 1.25rem;
    font-weight: 600;
    color: white;
    margin-bottom: 1rem;
}

.crate-modal-tracks-list {
    max-height: 400px;
    overflow-y: auto;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.03);
    padding: 0.5rem;
}

.crate-modal-track {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem;
    border-radius: 8px;
    transition: background 0.2s ease;
    cursor: pointer;
}

.crate-modal-track:hover {
    background: rgba(255, 255, 255, 0.05);
}

.crate-modal-track-number {
    width: 30px;
    text-align: center;
    color: #6b7280;
    font-size: 0.9rem;
    font-weight: 600;
}

.crate-modal-track-info {
    flex: 1;
    min-width: 0;
}

.crate-modal-track-title {
    color: white;
    font-size: 0.95rem;
    font-weight: 500;
    margin-bottom: 0.25rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.crate-modal-track-artist {
    color: #a0aec0;
    font-size: 0.85rem;
}

.crate-modal-track-duration {
    color: #6b7280;
    font-size: 0.9rem;
    font-weight: 500;
}

@media (max-width: 768px) {
    .crate-view-modal {
        padding: 1rem;
    }
    
    .crate-view-modal-content {
        max-height: 95vh;
    }
    
    .crate-modal-header {
        flex-direction: column;
    }
    
    .crate-modal-artwork {
        width: 120px;
        height: 120px;
        margin: 0 auto;
    }
    
    .crate-modal-name {
        font-size: 1.5rem;
        text-align: center;
    }
    
    .crate-modal-actions {
        flex-direction: column;
    }
    
    .crate-modal-btn {
        width: 100%;
        justify-content: center;
    }
}
</style>

<?php include 'includes/footer.php'; ?>


CasperSecurity Mini