![]() 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
session_start();
require_once 'config/database.php';
// Include audio token system for signed URLs
require_once 'utils/audio_token.php';
// Include header (simplified approach like dashboard.php)
include 'includes/header.php';
// Global player is included via footer.php
$pdo = getDBConnection();
// Function to get sort order based on filter
function getSortOrder($sort_filter) {
switch ($sort_filter) {
case 'trending':
// OPTIMIZED: Use JOIN aliases instead of subqueries
return "
CASE
WHEN mt.created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR) THEN 1
WHEN mt.created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN 2
ELSE 3
END,
COALESCE(like_stats.like_count, 0) DESC,
mt.created_at DESC
";
case 'latest':
return "mt.created_at DESC";
case 'popular':
// OPTIMIZED: Use JOIN aliases instead of subqueries
return "COALESCE(like_stats.like_count, 0) DESC, mt.created_at DESC";
case 'most-liked':
// OPTIMIZED: Use JOIN aliases instead of subqueries
return "COALESCE(like_stats.like_count, 0) DESC, mt.created_at DESC";
case 'most-commented':
// OPTIMIZED: Use JOIN aliases instead of subqueries
return "COALESCE(comment_stats.comment_count, 0) DESC, mt.created_at DESC";
case 'most-shared':
// OPTIMIZED: Use JOIN aliases instead of subqueries
return "COALESCE(share_stats.share_count, 0) DESC, mt.created_at DESC";
case 'most-played':
// OPTIMIZED: Use JOIN aliases instead of subqueries
return "COALESCE(play_stats.play_count, 0) DESC, mt.created_at DESC";
case 'most-viewed':
// OPTIMIZED: Use JOIN aliases instead of subqueries
return "COALESCE(view_stats.view_count, 0) DESC, mt.created_at DESC";
default:
return "mt.created_at DESC";
}
}
// Get user name from database if logged in
$user_name = 'Guest';
$user_id = null;
if (isset($_SESSION['user_id'])) {
$stmt = $pdo->prepare("SELECT name FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
$user_name = $user['name'] ?? 'User';
$user_id = $_SESSION['user_id'];
}
// Get sorting and filtering parameters
$sort_filter = $_GET['sort'] ?? 'trending';
$time_filter = $_GET['time'] ?? 'all';
$page = max(1, intval($_GET['page'] ?? 1));
$per_page = 30; // Increased from 20 to 30 tracks per page
$offset = ($page - 1) * $per_page;
// Build time filter condition
$time_condition = '';
switch ($time_filter) {
case 'today':
$time_condition = 'AND mt.created_at >= CURDATE()';
break;
case 'week':
$time_condition = 'AND mt.created_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)';
break;
case 'month':
$time_condition = 'AND mt.created_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)';
break;
default:
$time_condition = '';
}
// Get recent community tracks with social data
try {
$stmt = $pdo->prepare("
SELECT
mt.id,
mt.title,
mt.prompt,
mt.audio_url,
mt.duration,
mt.created_at,
mt.user_id,
u.name as artist_name,
u.id as artist_id,
u.profile_image,
COALESCE(like_stats.like_count, 0) as like_count,
COALESCE(comment_stats.comment_count, 0) as comment_count,
COALESCE(share_stats.share_count, 0) as share_count,
COALESCE(play_stats.play_count, 0) as play_count,
COALESCE(view_stats.view_count, 0) as view_count,
COALESCE((SELECT COUNT(*) FROM user_follows WHERE follower_id = ? AND following_id = mt.user_id), 0) as is_following,
CASE WHEN user_like_stats.track_id IS NOT NULL THEN 1 ELSE 0 END as user_liked,
CASE
WHEN mt.created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR) THEN '🔥 Hot'
WHEN mt.created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN '⭐ New'
WHEN COALESCE(like_stats.like_count, 0) > 10 THEN '💎 Popular'
ELSE ''
END as badge
FROM music_tracks mt
JOIN users u ON mt.user_id = u.id
LEFT JOIN (SELECT track_id, COUNT(*) as like_count FROM track_likes GROUP BY track_id) like_stats ON mt.id = like_stats.track_id
LEFT JOIN (SELECT track_id, COUNT(*) as comment_count FROM track_comments GROUP BY track_id) comment_stats ON mt.id = comment_stats.track_id
LEFT JOIN (SELECT track_id, COUNT(*) as share_count FROM track_shares GROUP BY track_id) share_stats ON mt.id = share_stats.track_id
LEFT JOIN (SELECT track_id, COUNT(*) as play_count FROM track_plays GROUP BY track_id) play_stats ON mt.id = play_stats.track_id
LEFT JOIN (SELECT track_id, COUNT(*) as view_count FROM track_views GROUP BY track_id) view_stats ON mt.id = view_stats.track_id
LEFT JOIN (SELECT track_id FROM track_likes WHERE user_id = ?) user_like_stats ON mt.id = user_like_stats.track_id
WHERE mt.status = 'complete'
AND mt.audio_url IS NOT NULL
AND mt.audio_url != ''
AND mt.audio_url LIKE '%apiboxfiles.erweima.ai%' -- Only CDN tracks
AND (mt.user_id IS NOT NULL AND mt.user_id != 0) -- Exclude orphaned tracks from community display
$time_condition
ORDER BY " . getSortOrder($sort_filter) . "
LIMIT ? OFFSET ?
");
// Parameters: is_following user_id, user_liked user_id, per_page, offset
$stmt->execute([$user_id ?? 0, $user_id ?? 0, $per_page, $offset]);
$recent_tracks = $stmt->fetchAll();
} catch (Exception $e) {
// Fallback to basic query if social query fails
$stmt = $pdo->prepare("
SELECT
mt.id,
mt.title,
mt.prompt,
mt.audio_url,
mt.duration,
mt.created_at,
mt.user_id,
u.name as artist_name,
u.id as artist_id,
u.profile_image,
0 as like_count,
0 as comment_count,
0 as share_count,
0 as play_count,
0 as view_count,
0 as is_following,
0 as user_liked,
'' as badge
FROM music_tracks mt
JOIN users u ON mt.user_id = u.id
WHERE mt.status = 'complete'
AND mt.audio_url IS NOT NULL
AND mt.audio_url != ''
AND mt.audio_url LIKE '%apiboxfiles.erweima.ai%' -- Only CDN tracks
$time_condition
ORDER BY " . getSortOrder($sort_filter) . "
LIMIT ? OFFSET ?
");
$stmt->execute([$per_page, $offset]);
$recent_tracks = $stmt->fetchAll();
// Get total count for pagination
$count_stmt = $pdo->prepare("
SELECT COUNT(*) as total
FROM music_tracks mt
JOIN users u ON mt.user_id = u.id
WHERE mt.status = 'complete'
AND mt.audio_url IS NOT NULL
AND mt.audio_url != ''
AND mt.audio_url LIKE '%apiboxfiles.erweima.ai%'
$time_condition
");
$count_stmt->execute();
$total_tracks = $count_stmt->fetch()['total'];
$total_pages = ceil($total_tracks / $per_page);
}
// OPTIMIZED: Using single query with subqueries (not N+1, but more efficient than separate queries)
// Get community stats
try {
$stmt = $pdo->prepare("
SELECT
COUNT(DISTINCT mt.id) as total_tracks,
COUNT(DISTINCT mt.user_id) as total_artists,
COALESCE(SUM(mt.duration), 0) as total_duration,
(SELECT COUNT(*) FROM track_likes) as total_likes,
(SELECT COUNT(*) FROM track_comments) as total_comments,
(SELECT COUNT(*) FROM user_follows) as total_follows,
(SELECT COUNT(DISTINCT follower_id) FROM user_follows) as total_followers,
(SELECT COUNT(DISTINCT following_id) FROM user_follows) as total_following
FROM music_tracks mt
WHERE mt.status = 'complete'
");
$stmt->execute();
$community_stats = $stmt->fetch();
} catch (Exception $e) {
// Fallback stats if query fails
$community_stats = [
'total_tracks' => count($recent_tracks),
'total_artists' => count(array_unique(array_column($recent_tracks, 'artist_name'))),
'total_duration' => 0,
'total_likes' => 0,
'total_comments' => 0,
'total_follows' => 0,
'total_followers' => 0,
'total_following' => 0
];
}
// Set page variables for header
$page_title = 'Music Feed - SoundStudioPro';
$page_description = 'Explore the latest AI-generated music from our community. Discover amazing tracks from talented creators.';
$current_page = 'community';
// Header already included above
?>
<div class="main-content">
<style>
/* Ensure no horizontal overflow and space for global player */
body {
overflow-x: hidden;
width: 100%;
padding-bottom: 120px; /* Extra space for enhanced global player */
}
/* Ensure mobile menu button is visible and working */
.mobile-menu-toggle {
z-index: 10002 !important;
position: relative !important;
}
.mobile-menu {
z-index: 10003 !important;
}
/* Text overflow fixes */
.track-card * {
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
}
.track-prompt {
text-overflow: unset;
white-space: normal;
overflow: visible;
}
/* Main Content */
.main-content {
margin-top: 0;
padding: 0;
min-height: calc(100vh - 100px);
overflow-x: hidden;
width: 100%;
}
/* Hero Section */
.hero {
padding: 8rem 0 6rem;
text-align: center;
color: white;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0a0a0a 100%);
position: relative;
overflow: hidden;
margin-bottom: 4rem;
margin-top: 0; /* Account for fixed header */
}
.hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(102,126,234,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
opacity: 0.3;
}
.hero-content {
max-width: 90rem;
margin: 0 auto;
position: relative;
z-index: 2;
}
.hero-badge {
display: inline-block;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(118, 75, 162, 0.2));
color: #667eea;
padding: 1.2rem 2.4rem;
border-radius: 50px;
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 3rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(102, 126, 234, 0.3);
}
.hero-title {
font-size: 5.6rem;
font-weight: 900;
line-height: 1.1;
margin-bottom: 2.4rem;
background: linear-gradient(135deg, #ffffff, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 2rem;
font-weight: 400;
margin-bottom: 4rem;
opacity: 0.9;
max-width: 70rem;
margin-left: auto;
margin-right: auto;
color: #a0aec0;
}
/* Community Content */
.community-content {
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
padding: 6rem 0;
border-radius: 40px 40px 0 0;
margin-top: -2rem;
position: relative;
z-index: 10;
overflow-x: hidden;
width: 100%;
}
.community-container {
max-width: 120rem;
margin: 0 auto;
}
/* Community Stats */
.community-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 3rem;
margin-bottom: 6rem;
}
.stat-card {
background: rgba(255, 255, 255, 0.05);
padding: 3rem;
border-radius: 20px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
text-align: center;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
border-color: rgba(102, 126, 234, 0.3);
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.1);
}
.stat-number {
font-size: 4.8rem;
font-weight: 900;
color: #667eea;
margin-bottom: 1rem;
}
.stat-label {
color: #a0aec0;
font-size: 1.8rem;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.stat-label i {
font-size: 1.6rem;
opacity: 0.8;
color: #667eea;
}
/* Tracks Section */
.tracks-section {
margin-bottom: 6rem;
}
.section-header {
text-align: center;
margin-bottom: 4rem;
}
.section-title {
font-size: 4.8rem;
font-weight: 700;
color: white;
margin-bottom: 1.5rem;
}
.section-subtitle {
font-size: 2rem;
color: #a0aec0;
margin-bottom: 4rem;
}
.section-title-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 2rem;
}
.filter-controls {
display: flex;
gap: 1rem;
align-items: center;
}
.filter-controls select {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 1rem 1.5rem;
color: white;
font-size: 1.4rem;
cursor: pointer;
transition: all 0.3s ease;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 1rem center;
background-repeat: no-repeat;
background-size: 1.5rem;
padding-right: 3rem;
}
.filter-controls select:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
}
.filter-controls select:focus {
outline: none;
border-color: rgba(102, 126, 234, 0.5);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.filter-controls option {
background: #1a1a1a;
color: white;
}
.tracks-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 3rem;
}
.track-card {
background: rgba(255, 255, 255, 0.05);
padding: 3rem;
border-radius: 20px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.track-card:hover {
transform: translateY(-5px);
border-color: rgba(102, 126, 234, 0.3);
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.1);
}
/* Touch-friendly improvements */
@media (hover: none) and (pointer: coarse) {
.track-card:hover {
transform: none;
box-shadow: none;
}
.track-card:active {
transform: scale(0.98);
background: rgba(255, 255, 255, 0.08);
}
.btn:active,
.social-btn:active {
transform: scale(0.95);
}
.filter-controls select:active {
background: rgba(255, 255, 255, 0.2);
}
.artist-avatar:hover {
transform: none;
}
.artist-avatar:active {
transform: scale(0.95);
}
}
.track-card.has-badge {
border-color: rgba(255, 193, 7, 0.3);
box-shadow: 0 10px 30px rgba(255, 193, 7, 0.1);
}
.track-badge {
position: absolute;
top: 1rem;
right: 1rem;
background: linear-gradient(135deg, #ff6b6b, #ffa500);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 1.2rem;
font-weight: 700;
z-index: 10;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.track-stats {
display: flex;
gap: 1rem;
align-items: center;
}
.stat-item {
display: flex;
align-items: center;
gap: 0.3rem;
font-size: 1.2rem;
color: #a0aec0;
background: rgba(255, 255, 255, 0.05);
padding: 0.3rem 0.8rem;
border-radius: 12px;
transition: all 0.3s ease;
}
.stat-item:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.track-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 2rem;
}
.track-info {
display: flex;
align-items: center;
gap: 1.5rem;
flex: 1;
}
.artist-profile {
flex-shrink: 0;
}
.artist-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
border: 3px solid rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.artist-avatar:hover {
transform: scale(1.05);
border-color: rgba(102, 126, 234, 0.6);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.clickable-avatar {
cursor: pointer;
position: relative;
}
.clickable-avatar::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
background: rgba(102, 126, 234, 0.1);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.clickable-avatar:hover::after {
opacity: 1;
}
.default-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.4rem;
font-weight: 700;
border: 3px solid rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.default-avatar:hover {
transform: scale(1.05);
border-color: rgba(102, 126, 234, 0.6);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.default-avatar.clickable-avatar {
cursor: pointer;
}
/* Comments Modal */
.comments-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
.comments-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.comments-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
width: 100%;
max-width: 600px;
max-height: 80vh;
display: flex;
flex-direction: column;
}
.comments-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 2rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.comments-header h3 {
color: white;
font-size: 2rem;
margin: 0;
}
.close-btn {
background: none;
border: none;
color: white;
font-size: 2.4rem;
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
transition: all 0.3s ease;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
.comments-list {
flex: 1;
overflow-y: auto;
padding: 2rem;
max-height: 400px;
}
.comment-item {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
}
.comment-avatar {
flex-shrink: 0;
}
.default-avatar-small {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.6rem;
font-weight: 700;
}
.comment-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.comment-content {
flex: 1;
}
.comment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.comment-author {
color: white;
font-weight: 600;
font-size: 1.4rem;
}
.comment-date {
color: #a0aec0;
font-size: 1.2rem;
}
.comment-text {
color: #e2e8f0;
font-size: 1.4rem;
line-height: 1.5;
}
.comment-form {
padding: 2rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.comment-form textarea {
width: 100%;
min-height: 80px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 1rem;
color: white;
font-size: 1.4rem;
resize: vertical;
margin-bottom: 1rem;
}
.comment-form textarea::placeholder {
color: #a0aec0;
}
.no-comments, .loading, .error {
text-align: center;
color: #a0aec0;
font-size: 1.4rem;
padding: 2rem;
}
.loading-tracks {
text-align: center;
color: #a0aec0;
font-size: 1.6rem;
padding: 4rem;
grid-column: 1 / -1;
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
animation: pulse 2s infinite;
}
.track-details-info {
flex: 1;
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
overflow: visible;
}
.track-title {
font-size: 2rem;
font-weight: 700;
color: white;
margin-bottom: 0.5rem;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
line-height: 1.3;
max-width: 100%;
}
.track-artist {
font-size: 1.4rem;
color: #667eea;
font-weight: 500;
}
.track-prompt {
font-size: 1.4rem;
color: #a0aec0;
line-height: 1.6;
margin-bottom: 2rem;
background: rgba(255, 255, 255, 0.05);
padding: 1.5rem;
border-radius: 12px;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
max-width: 100%;
white-space: normal;
overflow: visible;
}
.track-details {
display: flex;
gap: 2rem;
margin-bottom: 2rem;
font-size: 1.2rem;
color: #a0aec0;
}
.track-details span {
display: flex;
align-items: center;
gap: 0.5rem;
}
.track-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.btn {
padding: 1rem 2rem;
border: none;
border-radius: 12px;
font-size: 1.4rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.8rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
}
/* Enhanced play button states for global player integration */
.play-track-btn {
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.play-track-btn.playing {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
animation: playingPulse 2s ease-in-out infinite;
}
.play-track-btn.playing i {
animation: playingIcon 1s ease-in-out infinite alternate;
}
@keyframes playingPulse {
0%, 100% { box-shadow: 0 0 20px rgba(102, 126, 234, 0.5); }
50% { box-shadow: 0 0 30px rgba(102, 126, 234, 0.8); }
}
@keyframes playingIcon {
0% { transform: scale(1); }
100% { transform: scale(1.1); }
}
/* Social Actions */
.social-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
flex-wrap: wrap;
}
.social-btn {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
color: #a0aec0;
padding: 1rem 1.5rem;
border-radius: 16px;
font-size: 1.4rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
min-height: 44px;
min-width: 120px;
justify-content: center;
position: relative;
overflow: hidden;
}
.social-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
transition: left 0.5s ease;
}
.social-btn:hover::before {
left: 100%;
}
.social-btn:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.25);
color: white;
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.social-btn:active {
transform: translateY(-1px);
}
.social-btn.liked {
background: linear-gradient(135deg, rgba(245, 101, 101, 0.2), rgba(245, 101, 101, 0.1));
border-color: rgba(245, 101, 101, 0.5);
color: #f56565;
box-shadow: 0 4px 15px rgba(245, 101, 101, 0.3);
}
.social-btn.liked:hover {
background: linear-gradient(135deg, rgba(245, 101, 101, 0.3), rgba(245, 101, 101, 0.2));
box-shadow: 0 8px 25px rgba(245, 101, 101, 0.4);
}
.social-btn.following {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(102, 126, 234, 0.1));
border-color: rgba(102, 126, 234, 0.5);
color: #667eea;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.social-btn.following:hover {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.3), rgba(102, 126, 234, 0.2));
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
}
.social-count {
font-size: 1.3rem;
font-weight: 600;
}
.social-btn i {
font-size: 1.5rem;
transition: transform 0.3s ease;
}
.social-btn:hover i {
transform: scale(1.1);
}
.social-btn.liked i {
animation: heartBeat 0.6s ease;
}
@keyframes heartBeat {
0% { transform: scale(1); }
25% { transform: scale(1.3); }
50% { transform: scale(1.1); }
75% { transform: scale(1.3); }
100% { transform: scale(1); }
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem;
grid-column: 1 / -1;
}
.empty-icon {
font-size: 6rem;
margin-bottom: 2rem;
}
.empty-title {
font-size: 2.4rem;
color: white;
margin-bottom: 1rem;
}
.empty-description {
font-size: 1.6rem;
color: #a0aec0;
margin-bottom: 3rem;
}
/* Comments Modal */
.comments-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.comments-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
}
.comments-container {
position: relative;
background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.comments-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 2rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
}
.comments-header h3 {
color: white;
font-size: 2rem;
font-weight: 600;
margin: 0;
}
.close-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #a0aec0;
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 2rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
background: rgba(245, 101, 101, 0.2);
color: #f56565;
transform: scale(1.1);
}
.comments-list {
max-height: 400px;
overflow-y: auto;
padding: 2rem;
}
.comment-item {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.comment-item:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.2);
}
.comment-avatar {
flex-shrink: 0;
}
.default-avatar-small {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.6rem;
font-weight: 700;
color: white;
}
.comment-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.comment-content {
flex: 1;
}
.comment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.comment-author {
color: white;
font-weight: 600;
font-size: 1.4rem;
}
.comment-date {
color: #a0aec0;
font-size: 1.2rem;
}
.comment-text {
color: #e2e8f0;
font-size: 1.4rem;
line-height: 1.5;
}
.comment-form {
padding: 2rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.02);
}
.comment-form textarea {
width: 100%;
min-height: 80px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 1rem;
color: white;
font-size: 1.4rem;
resize: vertical;
margin-bottom: 1rem;
transition: all 0.3s ease;
}
.comment-form textarea:focus {
outline: none;
border-color: rgba(102, 126, 234, 0.5);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
background: rgba(255, 255, 255, 0.12);
}
.comment-form textarea::placeholder {
color: #a0aec0;
}
.comment-form .btn {
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
color: white;
padding: 1rem 2rem;
border-radius: 12px;
font-size: 1.4rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.comment-form .btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
}
.no-comments {
text-align: center;
color: #a0aec0;
font-size: 1.6rem;
padding: 3rem;
}
.loading {
text-align: center;
color: #a0aec0;
font-size: 1.6rem;
padding: 3rem;
}
.error {
text-align: center;
color: #f56565;
font-size: 1.6rem;
padding: 3rem;
}
/* Notification System */
.notification {
position: fixed;
top: 20px;
right: 20px;
z-index: 10001;
max-width: 400px;
background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
transform: translateX(100%);
transition: transform 0.3s ease;
overflow: hidden;
}
.notification.show {
transform: translateX(0);
}
.notification-content {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.5rem;
}
.notification-icon {
font-size: 2rem;
flex-shrink: 0;
}
.notification-message {
color: white;
font-size: 1.4rem;
font-weight: 500;
flex: 1;
}
.notification-close {
background: none;
border: none;
color: #a0aec0;
font-size: 1.6rem;
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
transition: all 0.3s ease;
flex-shrink: 0;
}
.notification-close:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}
.notification-success {
border-left: 4px solid #22c55e;
}
.notification-success .notification-icon {
color: #22c55e;
}
.notification-error {
border-left: 4px solid #f56565;
}
.notification-error .notification-icon {
color: #f56565;
}
.notification-warning {
border-left: 4px solid #f59e0b;
}
.notification-warning .notification-icon {
color: #f59e0b;
}
.notification-info {
border-left: 4px solid #3b82f6;
}
.notification-info .notification-icon {
color: #3b82f6;
}
/* Responsive */
@media (max-width: 768px) {
.hero-title {
font-size: 3.2rem;
line-height: 1.2;
}
.hero-subtitle {
font-size: 1.4rem;
line-height: 1.5;
}
.hero-badge {
padding: 1rem 2rem;
font-size: 1.2rem;
}
.section-title {
font-size: 2.8rem;
line-height: 1.2;
}
.section-title-row {
flex-direction: column;
gap: 1.5rem;
align-items: flex-start;
}
.filter-controls {
flex-wrap: wrap;
gap: 0.8rem;
width: 100%;
}
.filter-controls select {
padding: 1rem 1.5rem;
font-size: 1.4rem;
min-height: 48px;
flex: 1;
min-width: 150px;
}
.section-subtitle {
font-size: 1.3rem;
line-height: 1.5;
}
.community-stats {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
margin: 2rem 0;
}
.stat-card {
padding: 1.5rem;
text-align: center;
}
.stat-number {
font-size: 2.4rem;
}
.stat-label {
font-size: 1.1rem;
}
.stat-label i {
font-size: 1.3rem;
}
.tracks-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
padding: 0 1rem;
width: 100%;
max-width: 100%;
}
.track-card {
padding: 1.5rem;
margin: 0;
width: 100%;
max-width: 100%;
box-sizing: border-box;
overflow: visible;
word-wrap: break-word;
overflow-wrap: break-word;
}
.track-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.track-info {
flex-direction: row;
align-items: center;
gap: 1rem;
width: 100%;
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
}
.track-stats {
align-self: flex-start;
gap: 1rem;
}
.track-title {
font-size: 1.8rem;
line-height: 1.3;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
white-space: normal;
}
.track-artist {
font-size: 1.3rem;
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
.track-prompt {
font-size: 1.3rem;
line-height: 1.5;
margin: 1rem 0;
max-width: 100%;
white-space: normal;
overflow: visible;
word-wrap: break-word;
overflow-wrap: break-word;
}
.track-details {
flex-direction: column;
gap: 0.5rem;
margin: 1rem 0;
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
}
.track-details span {
font-size: 1.2rem;
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
.track-actions {
flex-direction: column;
gap: 1rem;
margin-top: 1.5rem;
}
.track-actions .btn {
width: 100%;
justify-content: center;
padding: 1.2rem 2rem;
font-size: 1.4rem;
min-height: 48px;
}
.social-actions {
flex-wrap: wrap;
gap: 0.8rem;
justify-content: center;
margin-top: 1rem;
}
.social-btn {
padding: 1rem 1.5rem;
font-size: 1.3rem;
min-height: 44px;
flex: 1;
min-width: 120px;
}
.social-count {
font-size: 1.2rem;
}
.container {
padding: 0 1rem;
}
.community-content {
padding: 2rem 0;
}
.artist-avatar {
width: 5rem;
height: 5rem;
font-size: 2rem;
}
.default-avatar {
width: 5rem;
height: 5rem;
font-size: 2rem;
}
}
@media (max-width: 480px) {
.hero-title {
font-size: 2.8rem;
}
.hero-subtitle {
font-size: 1.2rem;
}
.hero-badge {
padding: 0.8rem 1.5rem;
font-size: 1.1rem;
}
.section-title {
font-size: 2.4rem;
}
.section-subtitle {
font-size: 1.2rem;
}
.community-stats {
grid-template-columns: 1fr;
gap: 1rem;
}
.stat-card {
padding: 1.2rem;
}
.stat-number {
font-size: 2rem;
}
.stat-label {
font-size: 1rem;
}
.track-card {
padding: 1.2rem;
}
.track-title {
font-size: 1.6rem;
line-height: 1.3;
max-width: 100%;
white-space: normal;
}
.track-artist {
font-size: 1.1rem;
max-width: 100%;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
.track-prompt {
font-size: 1.2rem;
line-height: 1.5;
max-width: 100%;
white-space: normal;
overflow: visible;
}
.track-actions .btn {
padding: 1rem 1.5rem;
font-size: 1.3rem;
min-height: 44px;
}
.track-info {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.track-stats {
align-self: flex-start;
gap: 0.8rem;
}
.artist-avatar {
width: 4rem;
height: 4rem;
font-size: 1.8rem;
}
.default-avatar {
width: 4rem;
height: 4rem;
font-size: 1.8rem;
}
.social-btn {
padding: 0.8rem 1rem;
font-size: 1.1rem;
min-height: 40px;
min-width: 100px;
}
.filter-controls {
flex-direction: column;
align-items: stretch;
}
.filter-controls select {
width: 100%;
min-width: auto;
}
.notification {
top: 10px;
right: 10px;
left: 10px;
max-width: none;
font-size: 1.2rem;
padding: 1rem;
}
.container {
padding: 0 0.8rem;
}
.community-content {
padding: 2rem 0;
}
.tracks-grid {
padding: 0 0.5rem;
width: 100%;
max-width: 100%;
}
.track-card {
width: 100%;
max-width: 100%;
box-sizing: border-box;
overflow: visible;
word-wrap: break-word;
overflow-wrap: break-word;
}
}
</style>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-content">
<div class="hero-badge">🎵 Music Feed</div>
<h1 class="hero-title">Music Feed</h1>
<p class="hero-subtitle">Explore the latest tracks from our creative community. Get inspired by AI-generated music from talented artists around the world.</p>
</div>
</div>
</section>
<!-- Community Content -->
<section class="community-content">
<div class="container">
<div class="community-container">
<!-- Community Stats -->
<div class="community-stats">
<div class="stat-card">
<div class="stat-number"><?= $community_stats['total_tracks'] ?? count($recent_tracks) ?></div>
<div class="stat-label">Total Tracks</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $community_stats['total_artists'] ?? count(array_unique(array_column($recent_tracks, 'artist_name'))) ?></div>
<div class="stat-label">Active Artists</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $community_stats['total_likes'] ?? 0 ?></div>
<div class="stat-label">Total Likes</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $community_stats['total_followers'] ?? 0 ?></div>
<div class="stat-label"><i class="fas fa-users"></i> Followers</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $community_stats['total_following'] ?? 0 ?></div>
<div class="stat-label"><i class="fas fa-user-plus"></i> Following</div>
</div>
</div>
<!-- Recent Tracks -->
<div class="tracks-section">
<div class="section-header">
<div class="section-title-row">
<div>
<h2 class="section-title">Latest Music Feed</h2>
<p class="section-subtitle">Fresh AI-generated music from our talented community</p>
</div>
<div class="filter-controls">
<select id="sort-filter" onchange="filterTracks()">
<option value="trending" <?= $sort_filter === 'trending' ? 'selected' : '' ?>>🔥 Trending</option>
<option value="latest" <?= $sort_filter === 'latest' ? 'selected' : '' ?>>🕒 Latest</option>
<option value="popular" <?= $sort_filter === 'popular' ? 'selected' : '' ?>>💎 Most Popular</option>
<option value="most-liked" <?= $sort_filter === 'most-liked' ? 'selected' : '' ?>>❤️ Most Liked</option>
<option value="most-commented" <?= $sort_filter === 'most-commented' ? 'selected' : '' ?>>💬 Most Commented</option>
<option value="most-shared" <?= $sort_filter === 'most-shared' ? 'selected' : '' ?>>📤 Most Shared</option>
<option value="most-played" <?= $sort_filter === 'most-played' ? 'selected' : '' ?>>▶️ Most Played</option>
<option value="most-viewed" <?= $sort_filter === 'most-viewed' ? 'selected' : '' ?>>👁️ Most Viewed</option>
</select>
<select id="time-filter" onchange="filterTracks()">
<option value="all" <?= $time_filter === 'all' ? 'selected' : '' ?>>All Time</option>
<option value="today" <?= $time_filter === 'today' ? 'selected' : '' ?>>Today</option>
<option value="week" <?= $time_filter === 'week' ? 'selected' : '' ?>>This Week</option>
<option value="month" <?= $time_filter === 'month' ? 'selected' : '' ?>>This Month</option>
</select>
</div>
</div>
</div>
<div class="tracks-grid">
<?php if (empty($recent_tracks)): ?>
<div class="empty-state">
<div class="empty-icon">🎵</div>
<h3 class="empty-title">No tracks yet</h3>
<p class="empty-description">Be the first to create amazing AI music for the community!</p>
<a href="/#create" class="btn btn-primary">
<i class="fas fa-plus"></i> Create Your First Track
</a>
</div>
<?php else: ?>
<?php foreach ($recent_tracks as $track): ?>
<div class="track-card <?= !empty($track['badge']) ? 'has-badge' : '' ?>" data-track-id="<?= $track['id'] ?>">
<?php if (!empty($track['badge'])): ?>
<div class="track-badge"><?= $track['badge'] ?></div>
<?php endif; ?>
<div class="track-header">
<div class="track-info">
<div class="artist-profile">
<?php if (!empty($track['profile_image'])): ?>
<img src="<?= htmlspecialchars($track['profile_image']) ?>"
alt="<?= htmlspecialchars($track['artist_name']) ?>"
class="artist-avatar clickable-avatar"
onclick="loadArtistProfile(<?= $track['user_id'] ?>)"
title="View <?= htmlspecialchars($track['artist_name']) ?>'s profile"
onerror="this.parentElement.innerHTML='<div class=\'default-avatar clickable-avatar\' onclick=\'loadArtistProfile(<?= $track['user_id'] ?>)\' title=\'View <?= htmlspecialchars($track['artist_name']) ?>\\'s profile\'><?= substr(htmlspecialchars($track['artist_name']), 0, 1) ?></div>'">
<?php else: ?>
<div class="default-avatar clickable-avatar" onclick="loadArtistProfile(<?= $track['user_id'] ?>)" title="View <?= htmlspecialchars($track['artist_name']) ?>'s profile"><?= substr(htmlspecialchars($track['artist_name']), 0, 1) ?></div>
<?php endif; ?>
</div>
<div class="track-details-info">
<?php
$displayTitle = $track['title'];
if (empty($displayTitle)) {
// Generate title from prompt if available
if (!empty($track['prompt'])) {
$displayTitle = substr($track['prompt'], 0, 50);
if (strlen($track['prompt']) > 50) {
$displayTitle .= '...';
}
} else {
$displayTitle = 'Untitled Track';
}
}
?>
<div class="track-title"><?= htmlspecialchars($displayTitle) ?></div>
<div class="track-artist">by <?= htmlspecialchars(ucwords(strtolower($track['artist_name']))) ?></div>
</div>
</div>
<div class="track-stats">
<span class="stat-item" title="Views">
<i class="fas fa-eye"></i> <?= number_format($track['view_count'] ?? 0) ?>
</span>
<span class="stat-item" title="<?= t('artist_profile.plays') ?>">
<i class="fas fa-play"></i> <?= number_format($track['play_count'] ?? 0) ?>
</span>
</div>
</div>
<div class="track-prompt"><?= htmlspecialchars(substr($track['prompt'], 0, 100)) ?>...</div>
<div class="track-details">
<span><i class="fas fa-clock"></i> <?= floor($track['duration'] / 60) ?>m <?= $track['duration'] % 60 ?>s</span>
<span><i class="fas fa-calendar"></i> <?= date('M j, Y', strtotime($track['created_at'])) ?></span>
<span><i class="fas fa-share"></i> <?= number_format($track['share_count'] ?? 0) ?> shares</span>
</div>
<div class="track-actions">
<button class="btn btn-primary play-track-btn"
data-audio-url="<?= htmlspecialchars(getSignedAudioUrl($track['id'])) ?>"
data-title="<?= htmlspecialchars($displayTitle) ?>"
data-artist="<?= htmlspecialchars($track['artist_name']) ?>"
data-track-id="<?= $track['id'] ?>">
<i class="fas fa-play"></i> Play
</button>
<button class="btn btn-secondary" onclick="loadArtistProfile(<?= $track['user_id'] ?>)">
<i class="fas fa-user"></i> View Artist
</button>
</div>
<!-- Social Actions -->
<div class="social-actions">
<button class="social-btn <?= $track['user_liked'] ? 'liked' : '' ?>" onclick="toggleLike(<?= $track['id'] ?>, this)">
<i class="fas fa-heart"></i>
<span class="social-count"><?= $track['like_count'] ?></span>
</button>
<button class="social-btn" onclick="showComments(<?= $track['id'] ?>)">
<i class="fas fa-comment"></i>
<span class="social-count"><?= $track['comment_count'] ?></span>
</button>
<button class="social-btn" onclick="shareTrack(<?= $track['id'] ?>, '<?= htmlspecialchars($displayTitle) ?>', '<?= htmlspecialchars($track['artist_name']) ?>')">
<i class="fas fa-share-alt"></i>
<span class="social-count"><?= $track['share_count'] ?? 0 ?></span>
</button>
<?php if ($user_id && $user_id != $track['user_id']): ?>
<button class="social-btn <?= $track['is_following'] ? 'following' : '' ?>" onclick="toggleFollow(<?= $track['user_id'] ?>, this)">
<i class="fas fa-user-plus"></i>
<span><?= $track['is_following'] ? 'Following' : 'Follow' ?></span>
</button>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
<!-- Pagination Controls -->
<?php if ($total_pages > 1): ?>
<div class="pagination-container" style="margin-top: 3rem; text-align: center;">
<div class="pagination-info" style="margin-bottom: 1rem; color: #a0aec0;">
Showing <?= $offset + 1 ?>-<?= min($offset + $per_page, $total_tracks) ?> of <?= number_format($total_tracks) ?> tracks
</div>
<div class="pagination-controls" style="display: flex; justify-content: center; gap: 0.5rem; flex-wrap: wrap;">
<?php if ($page > 1): ?>
<a href="?sort=<?= $sort_filter ?>&time=<?= $time_filter ?>&page=1" class="btn btn-secondary">
<i class="fas fa-angle-double-left"></i> First
</a>
<a href="?sort=<?= $sort_filter ?>&time=<?= $time_filter ?>&page=<?= $page - 1 ?>" class="btn btn-secondary">
<i class="fas fa-angle-left"></i> Previous
</a>
<?php endif; ?>
<?php
$start_page = max(1, $page - 2);
$end_page = min($total_pages, $page + 2);
for ($i = $start_page; $i <= $end_page; $i++):
?>
<a href="?sort=<?= $sort_filter ?>&time=<?= $time_filter ?>&page=<?= $i ?>"
class="btn <?= $i == $page ? 'btn-primary' : 'btn-secondary' ?>">
<?= $i ?>
</a>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<a href="?sort=<?= $sort_filter ?>&time=<?= $time_filter ?>&page=<?= $page + 1 ?>" class="btn btn-secondary">
Next <i class="fas fa-angle-right"></i>
</a>
<a href="?sort=<?= $sort_filter ?>&time=<?= $time_filter ?>&page=<?= $total_pages ?>" class="btn btn-secondary">
Last <i class="fas fa-angle-double-right"></i>
</a>
<?php endif; ?>
</div>
<?php if ($page < $total_pages): ?>
<div class="load-more-section" style="margin-top: 2rem;">
<button onclick="loadMoreTracks()" class="btn btn-primary" style="padding: 1rem 2rem; font-size: 1.1rem;">
<i class="fas fa-plus"></i> Load More Tracks
</button>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</section>
<script>
// BASIC JAVASCRIPT TEST
console.log('🎵 JavaScript is working on community.php!');
console.log('🎵 Current page URL:', window.location.href);
// Community page functionality using global player
// Ensure mobile menu works on this page
document.addEventListener('DOMContentLoaded', function() {
console.log('🎵 Community page DOM loaded - checking mobile menu');
// Re-initialize mobile menu if needed
if (typeof toggleMobileMenu === 'function') {
console.log('🎵 Mobile menu function exists');
} else {
console.error('🎵 Mobile menu function not found - redefining');
// Redefine mobile menu functions if they're missing
window.toggleMobileMenu = function() {
const mobileMenu = document.getElementById('mobileMenu');
const mobileMenuToggle = document.getElementById('mobileMenuToggle');
console.log('🎵 Toggle mobile menu called from community page');
if (mobileMenu) {
mobileMenu.classList.toggle('active');
const isActive = mobileMenu.classList.contains('active');
if (mobileMenuToggle) {
const icon = mobileMenuToggle.querySelector('i');
if (icon) {
icon.className = isActive ? 'fas fa-times' : 'fas fa-bars';
}
}
document.body.style.overflow = isActive ? 'hidden' : '';
}
};
window.closeMobileMenu = function() {
const mobileMenu = document.getElementById('mobileMenu');
const mobileMenuToggle = document.getElementById('mobileMenuToggle');
if (mobileMenu) {
mobileMenu.classList.remove('active');
if (mobileMenuToggle) {
const icon = mobileMenuToggle.querySelector('i');
if (icon) {
icon.className = 'fas fa-bars';
}
}
document.body.style.overflow = '';
}
};
}
});
// Check if enhanced global player is loaded
window.addEventListener('load', function() {
console.log('🎵 Community page loaded');
console.log('🎵 Enhanced global player status:', typeof window.enhancedGlobalPlayer !== 'undefined');
if (typeof window.enhancedGlobalPlayer !== 'undefined' && typeof window.enhancedGlobalPlayer.playTrack === 'function') {
console.log('🎵 Enhanced global player loaded successfully');
} else {
console.error('🎵 Enhanced global player not available');
}
});
// Social Functions
function toggleLike(trackId, button) {
console.log('🎵 User ID from PHP:', <?= $user_id ? $user_id : 'null' ?>);
console.log('🎵 Is logged in:', <?= $user_id ? 'true' : 'false' ?>);
if (!<?= $user_id ? 'true' : 'false' ?>) {
showNotification('Please log in to like tracks', 'warning');
return;
}
console.log('🎵 Toggling like for track:', trackId);
// Add loading state
button.style.pointerEvents = 'none';
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
fetch('/api_social.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'like', track_id: trackId })
})
.then(response => {
console.log('🎵 Like API response status:', response.status);
return response.json();
})
.then(data => {
console.log('🎵 Like API response data:', data);
if (data.success) {
button.classList.toggle('liked');
const countSpan = button.querySelector('.social-count');
if (countSpan) {
const currentCount = parseInt(countSpan.textContent);
countSpan.textContent = button.classList.contains('liked') ? currentCount + 1 : currentCount - 1;
}
// Show success notification
const action = button.classList.contains('liked') ? 'liked' : 'unliked';
showNotification(`Track ${action}!`, 'success');
} else {
showNotification(data.message || 'Failed to like track', 'error');
}
})
.catch(error => {
console.error('🎵 Like error:', error);
showNotification('Failed to like track. Please try again.', 'error');
})
.finally(() => {
// Restore button
button.style.pointerEvents = 'auto';
button.innerHTML = originalText;
});
}
function toggleFollow(userId, button) {
if (!<?= $user_id ? 'true' : 'false' ?>) {
showNotification('Please log in to follow users', 'warning');
return;
}
// Add loading state
button.style.pointerEvents = 'none';
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
fetch('/api_social.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'follow', user_id: userId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
button.classList.toggle('following');
const span = button.querySelector('span');
const action = button.classList.contains('following') ? 'Following' : 'Follow';
span.textContent = action;
// Show success notification
const message = button.classList.contains('following') ? 'User followed!' : 'User unfollowed!';
showNotification(message, 'success');
} else {
showNotification(data.message || 'Failed to follow user', 'error');
}
})
.catch(error => {
console.error('Follow error:', error);
showNotification('Failed to follow user. Please try again.', 'error');
})
.finally(() => {
// Restore button
button.style.pointerEvents = 'auto';
button.innerHTML = originalText;
});
}
function showComments(trackId) {
// Create and show comments modal
const modal = document.createElement('div');
modal.className = 'comments-modal';
modal.innerHTML = `
<div class="comments-overlay">
<div class="comments-container">
<div class="comments-header">
<h3>Comments</h3>
<button class="close-btn" onclick="closeComments()">×</button>
</div>
<div class="comments-list" id="comments-list-${trackId}">
<div class="loading">Loading comments...</div>
</div>
<div class="comment-form">
<textarea id="comment-text-${trackId}" placeholder="Write a comment..." maxlength="500"></textarea>
<button onclick="addComment(${trackId})" class="btn btn-primary">Post Comment</button>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// Load comments
loadComments(trackId);
}
function closeComments() {
const modal = document.querySelector('.comments-modal');
if (modal) {
modal.remove();
}
}
function loadComments(trackId) {
console.log('🎵 Loading comments for track:', trackId);
fetch(`/api_social.php?action=get_comments&track_id=${trackId}`)
.then(response => {
console.log('🎵 Load comments API response status:', response.status);
return response.json();
})
.then(data => {
console.log('🎵 Load comments API response data:', data);
const commentsList = document.getElementById(`comments-list-${trackId}`);
if (data.success && data.comments.length > 0) {
console.log('🎵 Found comments:', data.comments.length);
commentsList.innerHTML = data.comments.map(comment => `
<div class="comment-item">
<div class="comment-avatar">
${comment.profile_image ?
`<img src="${comment.profile_image}" alt="${comment.user_name}" onerror="this.parentElement.innerHTML='<div class=\'default-avatar-small\'>${comment.user_name.charAt(0)}</div>'">` :
`<div class="default-avatar-small">${comment.user_name.charAt(0)}</div>`
}
</div>
<div class="comment-content">
<div class="comment-header">
<span class="comment-author">${comment.user_name}</span>
<span class="comment-date">${formatDate(comment.created_at)}</span>
</div>
<div class="comment-text">${comment.comment}</div>
</div>
</div>
`).join('');
} else {
console.log('🎵 No comments found');
commentsList.innerHTML = '<div class="no-comments">No comments yet. Be the first to comment!</div>';
}
})
.catch(error => {
console.error('🎵 Load comments error:', error);
document.getElementById(`comments-list-${trackId}`).innerHTML = '<div class="error">Failed to load comments</div>';
});
}
function addComment(trackId) {
const commentText = document.getElementById(`comment-text-${trackId}`).value.trim();
if (!commentText) {
showNotification('Please enter a comment', 'warning');
return;
}
if (!<?= $user_id ? 'true' : 'false' ?>) {
showNotification('Please log in to comment', 'warning');
return;
}
console.log('🎵 Adding comment:', { trackId, commentText });
const submitBtn = document.querySelector(`[onclick="addComment(${trackId})"]`);
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Posting...';
submitBtn.disabled = true;
fetch('/api_social.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'comment', track_id: trackId, comment: commentText })
})
.then(response => {
console.log('🎵 Comment API response status:', response.status);
return response.json();
})
.then(data => {
console.log('🎵 Comment API response data:', data);
if (data.success) {
document.getElementById(`comment-text-${trackId}`).value = '';
loadComments(trackId); // Reload comments
// Update comment count
const commentBtn = document.querySelector(`[onclick="showComments(${trackId})"]`);
if (commentBtn) {
const countSpan = commentBtn.querySelector('.social-count');
if (countSpan) {
const currentCount = parseInt(countSpan.textContent);
countSpan.textContent = currentCount + 1;
}
}
showNotification('Comment posted successfully!', 'success');
} else {
showNotification(data.message || 'Failed to post comment', 'error');
}
})
.catch(error => {
console.error('🎵 Comment error:', error);
showNotification('Failed to post comment. Please try again.', 'error');
})
.finally(() => {
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
}
function formatDate(dateString) {
const date = new Date(dateString);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
}
function filterTracks() {
const sortFilter = document.getElementById('sort-filter').value;
const timeFilter = document.getElementById('time-filter').value;
console.log('🎵 Filtering tracks:', { sortFilter, timeFilter });
// Update URL without page reload
const url = new URL(window.location);
url.searchParams.set('sort', sortFilter);
url.searchParams.set('time', timeFilter);
window.history.pushState({}, '', url);
// Show loading state
const tracksGrid = document.querySelector('.tracks-grid');
tracksGrid.innerHTML = '<div class="loading-tracks">Loading tracks...</div>';
// Fetch filtered tracks
fetch(`/community.php?ajax=1&sort=${sortFilter}&time=${timeFilter}`)
.then(response => {
console.log('🎵 Filter response status:', response.status);
return response.text();
})
.then(html => {
console.log('🎵 Filter response received, length:', html.length);
// Extract just the tracks-grid content
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const newTracksGrid = doc.querySelector('.tracks-grid');
if (newTracksGrid) {
tracksGrid.innerHTML = newTracksGrid.innerHTML;
// Note: AJAX content replacement breaks event listeners
// Would need to re-attach here but keeping it simple for now
console.log('🎵 Tracks filtered successfully');
} else {
console.error('🎵 No tracks-grid found in response');
tracksGrid.innerHTML = '<div class="error">Failed to load tracks</div>';
}
})
.catch(error => {
console.error('🎵 Filter error:', error);
tracksGrid.innerHTML = '<div class="error">Failed to load tracks</div>';
});
}
// Debug and setup play button event listeners
document.addEventListener('DOMContentLoaded', function() {
console.log('🎵 =================================================');
console.log('🎵 Community DOMContentLoaded FIRED');
console.log('🎵 =================================================');
// First, let's see what's in the page
console.log('🎵 Total buttons on page:', document.querySelectorAll('button').length);
console.log('🎵 Play buttons found:', document.querySelectorAll('.play-track-btn').length);
console.log('🎵 All elements with play-track-btn class:', document.querySelectorAll('.play-track-btn'));
const playButtons = document.querySelectorAll('.play-track-btn');
if (playButtons.length === 0) {
console.error('🎵 CRITICAL: NO PLAY BUTTONS FOUND!');
console.log('🎵 Page HTML length:', document.body.innerHTML.length);
console.log('🎵 Looking for any buttons:', document.querySelectorAll('button'));
return;
}
playButtons.forEach((button, index) => {
console.log(`🎵 Setting up button ${index + 1}:`, button);
console.log(`🎵 Button data attributes:`, {
audioUrl: button.getAttribute('data-audio-url'),
title: button.getAttribute('data-title'),
artist: button.getAttribute('data-artist'),
trackId: button.getAttribute('data-track-id')
});
button.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const audioUrl = this.getAttribute('data-audio-url');
const title = this.getAttribute('data-title');
const artist = this.getAttribute('data-artist');
const trackId = this.getAttribute('data-track-id');
console.log('🎵 Play button clicked:', { audioUrl, title, artist, trackId });
// Validate audio URL
if (!audioUrl || audioUrl === 'NULL' || audioUrl === 'null') {
alert('This track is not available for playback.');
return;
}
// Clear all other playing states
document.querySelectorAll('.play-track-btn').forEach(btn => {
btn.classList.remove('playing');
btn.innerHTML = '<i class="fas fa-play"></i> Play';
});
// Set this button as playing
this.classList.add('playing');
this.innerHTML = '<i class="fas fa-pause"></i> Playing';
// Use the enhanced global player
if (typeof window.enhancedGlobalPlayer !== 'undefined' && typeof window.enhancedGlobalPlayer.playTrack === 'function') {
window.enhancedGlobalPlayer.playTrack(audioUrl, title, artist);
console.log('🎵 Community: Track sent to enhanced global player -', title);
// Store reference for global player callbacks
window.currentCommunityPlayButton = this;
window.currentCommunityTrackId = trackId;
} else if (typeof window.playTrack === 'function') {
window.playTrack(audioUrl, title, artist);
console.log('🎵 Community: Track sent via universal playTrack -', title);
} else {
console.error('🎵 Enhanced global player not available');
alert('Player not available. Please refresh the page.');
// Reset button state
this.classList.remove('playing');
this.innerHTML = '<i class="fas fa-play"></i> Play';
}
});
});
});
// Test function to verify enhanced global player functionality
window.testGlobalPlayer = function() {
console.log('🎵 Testing enhanced global player functionality...');
if (typeof window.enhancedGlobalPlayer !== 'undefined' && typeof window.enhancedGlobalPlayer.playTrack === 'function') {
console.log('🎵 Enhanced global player is ready');
// Test with a sample track
const testUrl = 'https://apiboxfiles.erweima.ai/MTk4YTg3OGYtM2Y4NS00YWJhLWIxMjMtMjk1OWFjOTUwMDFk.mp3';
const testTitle = 'Test Track';
const testArtist = 'Test Artist';
window.enhancedGlobalPlayer.playTrack(testUrl, testTitle, testArtist);
console.log('🎵 Test track sent to enhanced global player');
alert('Enhanced global player is ready and test track is playing!');
} else {
console.error('🎵 Enhanced global player not found');
alert('Enhanced global player not found');
}
};
// Function to load more tracks via AJAX
window.loadMoreTracks = function() {
const currentPage = <?= $page ?>;
const nextPage = currentPage + 1;
const sortFilter = '<?= $sort_filter ?>';
const timeFilter = '<?= $time_filter ?>';
console.log('🎵 Loading more tracks for page:', nextPage);
// Show loading state
const loadMoreBtn = document.querySelector('.load-more-section button');
if (loadMoreBtn) {
loadMoreBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...';
loadMoreBtn.disabled = true;
}
// Fetch next page of tracks
fetch(`/community.php?ajax=1&sort=${sortFilter}&time=${timeFilter}&page=${nextPage}`)
.then(response => response.text())
.then(html => {
// Extract just the tracks from the response
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const newTracks = doc.querySelectorAll('.track-card');
if (newTracks.length > 0) {
// Append new tracks to the grid
const tracksGrid = document.querySelector('.tracks-grid');
newTracks.forEach(track => {
tracksGrid.appendChild(track.cloneNode(true));
});
// Note: AJAX load more breaks event listeners
// Would need to re-attach here but keeping it simple for now
// Update pagination info
const paginationInfo = document.querySelector('.pagination-info');
if (paginationInfo) {
const currentCount = document.querySelectorAll('.track-card').length;
const totalTracks = <?= $total_tracks ?>;
paginationInfo.textContent = `Showing 1-${currentCount} of ${totalTracks} tracks`;
}
// Update URL without page reload
const url = new URL(window.location);
url.searchParams.set('page', nextPage);
window.history.pushState({}, '', url);
console.log('🎵 Loaded', newTracks.length, 'more tracks');
// Hide load more button if no more pages
if (nextPage >= <?= $total_pages ?>) {
const loadMoreSection = document.querySelector('.load-more-section');
if (loadMoreSection) {
loadMoreSection.style.display = 'none';
}
}
} else {
console.log('🎵 No more tracks to load');
const loadMoreSection = document.querySelector('.load-more-section');
if (loadMoreSection) {
loadMoreSection.innerHTML = '<p style="color: #a0aec0;">No more tracks to load</p>';
}
}
})
.catch(error => {
console.error('🎵 Error loading more tracks:', error);
if (loadMoreBtn) {
loadMoreBtn.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Error - Try Again';
loadMoreBtn.disabled = false;
}
});
};
// Function to check the status of all play buttons
window.checkPlayButtons = function() {
console.log('🎵 Checking all play buttons...');
const buttons = document.querySelectorAll('.play-track-btn');
console.log('🎵 Found', buttons.length, 'play buttons');
buttons.forEach((button, index) => {
const audioUrl = button.getAttribute('data-audio-url');
const title = button.getAttribute('data-title');
const artist = button.getAttribute('data-artist');
const trackId = button.getAttribute('data-track-id');
console.log(`🎵 Button ${index + 1}:`, {
audioUrl: audioUrl ? 'Valid' : 'Missing',
title: title || 'Missing',
artist: artist || 'Missing',
trackId: trackId || 'Missing',
hasEventListeners: button.onclick !== null || button._listeners
});
});
return buttons.length;
};
function shareTrack(trackId, title, artist) {
const shareUrl = `${window.location.origin}/artist_profile.php?id=${trackId}`;
const shareText = `Check out "${title}" by ${artist} on SoundStudioPro! 🎵`;
// Track share first
fetch('/api_social.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'share', track_id: trackId })
}).then(() => {
// Update share count
const shareBtn = document.querySelector(`[onclick="shareTrack(${trackId}, '${title}', '${artist}')"]`);
const countSpan = shareBtn.querySelector('.social-count');
const currentCount = parseInt(countSpan.textContent);
countSpan.textContent = currentCount + 1;
});
if (navigator.share) {
navigator.share({
title: `${title} by ${artist}`,
text: shareText,
url: shareUrl
}).then(() => {
showNotification('Track shared successfully!', 'success');
}).catch(error => {
console.error('Share error:', error);
// Fallback to clipboard
copyToClipboard(shareText, shareUrl);
});
} else {
// Fallback: copy to clipboard
copyToClipboard(shareText, shareUrl);
}
}
function copyToClipboard(shareText, shareUrl) {
const shareData = `${shareText}\n${shareUrl}`;
navigator.clipboard.writeText(shareData).then(() => {
showNotification('Track link copied to clipboard!', 'success');
}).catch(error => {
console.error('Clipboard error:', error);
// Final fallback: show share data in alert
alert(`Share this track:\n\n${shareData}`);
});
}
// Track view when track card is visible
function trackView(trackId) {
fetch('/api_social.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'view', track_id: trackId })
});
}
// Track play when play button is clicked
function trackPlay(trackId) {
fetch('/api_social.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'play', track_id: trackId })
});
}
// AJAX navigation for artist profiles
function loadArtistProfile(userId) {
console.log('🎵 Loading artist profile for user ID:', userId);
// Show loading state
const pageContainer = document.getElementById('pageContainer');
if (pageContainer) {
pageContainer.innerHTML = '<div class="loading-spinner"><i class="fas fa-spinner fa-spin"></i> Loading artist profile...</div>';
}
// Load the artist profile page via AJAX
fetch(`/artist_profile.php?id=${userId}&ajax=1`)
.then(response => response.text())
.then(html => {
if (pageContainer) {
pageContainer.innerHTML = html;
}
// Update browser URL without page reload
window.history.pushState({ userId: userId }, '', `/artist_profile.php?id=${userId}`);
console.log('🎵 Artist profile loaded successfully');
})
.catch(error => {
console.error('🎵 Error loading artist profile:', error);
showNotification('Failed to load artist profile. Please try again.', 'error');
// Fallback to regular navigation
window.location.href = `/artist_profile.php?id=${userId}`;
});
}
// Notification system
function showNotification(message, type = 'info') {
// Remove existing notifications
const existingNotifications = document.querySelectorAll('.notification');
existingNotifications.forEach(notification => notification.remove());
// Create notification element
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<i class="notification-icon ${getNotificationIcon(type)}"></i>
<span class="notification-message">${message}</span>
<button class="notification-close" onclick="this.parentElement.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
</div>
`;
// Add to page
document.body.appendChild(notification);
// Show animation
setTimeout(() => {
notification.classList.add('show');
}, 100);
// Auto remove after 5 seconds
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 300);
}, 5000);
}
function getNotificationIcon(type) {
switch (type) {
case 'success': return 'fas fa-check-circle';
case 'error': return 'fas fa-exclamation-circle';
case 'warning': return 'fas fa-exclamation-triangle';
default: return 'fas fa-info-circle';
}
}
</script>
</div>
</div>
<?php
// Include footer (simplified approach like dashboard.php)
include 'includes/footer.php';
?>