Auto commit: 2026-02-15T18:45:30.299Z
This commit is contained in:
parent
9eb2d9d88c
commit
f86c3be51e
20
api/get_likes.php
Normal file
20
api/get_likes.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$song_title = $_GET['song_title'] ?? '';
|
||||
|
||||
if (empty($song_title)) {
|
||||
echo json_encode(['likes_count' => 0]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = db()->prepare("SELECT likes_count FROM song_likes WHERE song_title = ?");
|
||||
$stmt->execute([$song_title]);
|
||||
$count = $stmt->fetchColumn();
|
||||
echo json_encode(['likes_count' => (int)($count ?: 0)]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
14
api/get_top_fans.php
Normal file
14
api/get_top_fans.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT username, total_likes FROM user_likes ORDER BY total_likes DESC LIMIT 5");
|
||||
$fans = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'fans' => $fans]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
39
api/like_song.php
Normal file
39
api/like_song.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$username = $input['username'] ?? '';
|
||||
$song_title = $input['song_title'] ?? '';
|
||||
|
||||
if (empty($username) || empty($song_title)) {
|
||||
echo json_encode(['error' => 'Faltan datos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// 1. Update/Insert into song_likes
|
||||
$stmt = $pdo->prepare("INSERT INTO song_likes (song_title, likes_count) VALUES (?, 1) ON DUPLICATE KEY UPDATE likes_count = likes_count + 1");
|
||||
$stmt->execute([$song_title]);
|
||||
|
||||
// 2. Update/Insert into user_likes
|
||||
$stmtUser = $pdo->prepare("INSERT INTO user_likes (username, total_likes) VALUES (?, 1) ON DUPLICATE KEY UPDATE total_likes = total_likes + 1");
|
||||
$stmtUser->execute([$username]);
|
||||
|
||||
// 3. Post to chat
|
||||
$message = "¡Le dio un ❤️ a la canción: $song_title!";
|
||||
$stmtChat = $pdo->prepare("INSERT INTO messages (username, message) VALUES (?, ?)");
|
||||
$stmtChat->execute([$username, $message]);
|
||||
|
||||
// 3. Get total likes for this song
|
||||
$stmtCount = $pdo->prepare("SELECT likes_count FROM song_likes WHERE song_title = ?");
|
||||
$stmtCount->execute([$song_title]);
|
||||
$count = $stmtCount->fetchColumn();
|
||||
|
||||
echo json_encode(['success' => true, 'likes_count' => $count]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
6
db/migrations/002_create_song_likes.sql
Normal file
6
db/migrations/002_create_song_likes.sql
Normal file
@ -0,0 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS song_likes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
song_title VARCHAR(255) NOT NULL UNIQUE,
|
||||
likes_count INT DEFAULT 0,
|
||||
last_liked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
5
db/migrations/003_create_user_likes.sql
Normal file
5
db/migrations/003_create_user_likes.sql
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE TABLE IF NOT EXISTS user_likes (
|
||||
username VARCHAR(255) PRIMARY KEY,
|
||||
total_likes INT DEFAULT 0,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
204
index.php
204
index.php
@ -584,6 +584,16 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.fan-1 { color: #facc15 !important; font-weight: 800 !important; text-shadow: 0 0 10px rgba(250, 204, 21, 0.5); }
|
||||
.fan-2 { color: #cbd5e1 !important; font-weight: 700 !important; }
|
||||
.fan-3 { color: #fb923c !important; font-weight: 700 !important; }
|
||||
|
||||
@keyframes fan-entry {
|
||||
0% { transform: scale(0.8); opacity: 0; }
|
||||
50% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 992px) {
|
||||
.app-container {
|
||||
@ -599,6 +609,9 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
.interaction-center {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
.chat-window, .ranking-window {
|
||||
height: 400px !important;
|
||||
}
|
||||
.image-section {
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
@ -607,7 +620,6 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
@ -648,6 +660,10 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
<div id="track-title" class="track-title">Cargando stream...</div>
|
||||
<div class="track-status">EN VIVO</div>
|
||||
</div>
|
||||
<button id="like-song-btn" onclick="likeSong()" style="background: none; border: none; color: #ff4444; font-size: 1.8rem; cursor: pointer; transition: transform 0.2s; display: flex; align-items: center; gap: 5px;">
|
||||
<i class="bi bi-heart"></i>
|
||||
<span id="like-count" style="font-size: 0.9rem; color: white; opacity: 0.8; font-weight: bold;"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
@ -702,8 +718,8 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- Interaction Center: Live Chat -->
|
||||
<section class="interaction-center" style="width: 100%; max-width: 850px; display: grid; grid-template-columns: 1fr; gap: 2rem; z-index: 10;">
|
||||
<!-- Interaction Center: Live Chat & Rankings -->
|
||||
<section class="interaction-center" style="width: 100%; max-width: 850px; display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; z-index: 10;">
|
||||
<!-- Live Web Chat -->
|
||||
<div class="glass-card chat-window" style="height: 500px; display: flex; flex-direction: column;">
|
||||
<h3 style="font-size: 1.2rem; margin-bottom: 1rem; color: var(--primary-color);">
|
||||
@ -721,6 +737,20 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Fans / Rankings -->
|
||||
<div class="glass-card ranking-window" style="height: 500px; display: flex; flex-direction: column;">
|
||||
<h3 style="font-size: 1.2rem; margin-bottom: 1rem; color: #facc15;">
|
||||
<i class="bi bi-trophy-fill"></i> TOP FANS
|
||||
</h3>
|
||||
<div id="top-fans-list" style="flex: 1; display: flex; flex-direction: column; gap: 1rem;">
|
||||
<!-- Fans se cargarán aquí -->
|
||||
<div style="opacity: 0.5; font-size: 0.9rem; text-align: center; margin-top: 2rem;">Calculando ranking...</div>
|
||||
</div>
|
||||
<div style="font-size: 0.75rem; opacity: 0.6; text-align: center; margin-top: 1rem; padding: 0.5rem; background: rgba(255,255,255,0.05); border-radius: 8px;">
|
||||
¡Dales "Like" a tus canciones favoritas para subir en el ranking!
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
@ -738,7 +768,9 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
|
||||
<audio id="radio-audio" src="https://listen.radioking.com/radio/828046/stream/897251" preload="none" crossorigin="anonymous"></audio>
|
||||
<audio id="welcome-sound" src="https://assets.mixkit.co/active_storage/sfx/2013/2013-preview.mp3" preload="auto"></audio>
|
||||
|
||||
<script>
|
||||
const audio = document.getElementById('radio-audio');
|
||||
@ -936,32 +968,92 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
const chatUser = document.getElementById('chat-user');
|
||||
const chatMsg = document.getElementById('chat-msg');
|
||||
let lastMessageCount = 0;
|
||||
let topFans = [];
|
||||
let welcomeSoundPlayed = false;
|
||||
|
||||
async function fetchTopFans() {
|
||||
try {
|
||||
const response = await fetch('api/get_top_fans.php');
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
topFans = data.fans.map(f => f.username);
|
||||
const list = document.getElementById('top-fans-list');
|
||||
list.innerHTML = '';
|
||||
|
||||
const currentUserName = document.getElementById('user-name').value.trim();
|
||||
|
||||
data.fans.forEach((fan, index) => {
|
||||
const div = document.createElement('div');
|
||||
const medal = index === 0 ? '🥇' : (index === 1 ? '🥈' : (index === 2 ? '🥉' : '👤'));
|
||||
const scale = index === 0 ? '1.1' : '1';
|
||||
const opacity = 1 - (index * 0.1);
|
||||
|
||||
// Welcome sound for #1 fan
|
||||
if (index === 0 && fan.username === currentUserName && !welcomeSoundPlayed && currentUserName !== "") {
|
||||
document.getElementById('welcome-sound').play().catch(e => console.log("Audio play blocked"));
|
||||
welcomeSoundPlayed = true;
|
||||
}
|
||||
|
||||
div.style.background = 'rgba(255,255,255,0.05)';
|
||||
div.style.padding = '0.8rem';
|
||||
div.style.borderRadius = '12px';
|
||||
div.style.display = 'flex';
|
||||
div.style.alignItems = 'center';
|
||||
div.style.gap = '10px';
|
||||
div.style.transform = `scale(${scale})`;
|
||||
div.style.opacity = opacity;
|
||||
div.style.border = index === 0 ? '1px solid #facc15' : '1px solid transparent';
|
||||
div.style.animation = fan.username === currentUserName ? 'fan-entry 0.5s ease-out' : 'none';
|
||||
|
||||
div.innerHTML = `
|
||||
<div style="font-size: 1.5rem;">${medal}</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 700; font-size: 0.9rem; color: ${index === 0 ? '#facc15' : 'white'}">${fan.username}</div>
|
||||
<div style="font-size: 0.75rem; opacity: 0.6;">${fan.total_likes} Corazones repartidos</div>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ranking error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMessages() {
|
||||
try {
|
||||
const response = await fetch('api/chat.php');
|
||||
const messages = await response.json();
|
||||
|
||||
if (messages.length !== lastMessageCount) {
|
||||
const currentUserName = document.getElementById('user-name').value.trim() || 'Anónimo';
|
||||
chatMessages.innerHTML = '';
|
||||
messages.forEach(msg => {
|
||||
const div = document.createElement('div');
|
||||
div.style.background = 'rgba(255,255,255,0.05)';
|
||||
div.style.padding = '0.8rem';
|
||||
div.style.borderRadius = '12px';
|
||||
div.style.borderLeft = `4px solid ${msg.username === currentUserName ? 'var(--primary-color)' : 'rgba(255,255,255,0.2)'}`;
|
||||
|
||||
div.innerHTML = `
|
||||
<div style="font-size: 0.7rem; font-weight: bold; color: var(--primary-color); margin-bottom: 2px;">${msg.username}</div>
|
||||
<div style="font-size: 0.85rem; line-height: 1.4;">${msg.message}</div>
|
||||
<div style="font-size: 0.6rem; opacity: 0.4; margin-top: 4px; text-align: right;">${new Date(msg.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</div>
|
||||
`;
|
||||
chatMessages.appendChild(div);
|
||||
});
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
lastMessageCount = messages.length;
|
||||
}
|
||||
const currentUserName = document.getElementById('user-name').value.trim() || 'Anónimo';
|
||||
chatMessages.innerHTML = '';
|
||||
messages.forEach(msg => {
|
||||
const div = document.createElement('div');
|
||||
const isLike = msg.message.includes('❤️');
|
||||
|
||||
// Determine fan class
|
||||
let fanClass = '';
|
||||
const fanIndex = topFans.indexOf(msg.username);
|
||||
if (fanIndex === 0) fanClass = 'fan-1';
|
||||
else if (fanIndex === 1) fanClass = 'fan-2';
|
||||
else if (fanIndex === 2) fanClass = 'fan-3';
|
||||
|
||||
div.style.background = isLike ? 'rgba(255, 68, 68, 0.15)' : 'rgba(255,255,255,0.05)';
|
||||
div.style.padding = '0.8rem';
|
||||
div.style.borderRadius = '12px';
|
||||
div.style.borderLeft = `4px solid ${isLike ? '#ff4444' : (msg.username === currentUserName ? 'var(--primary-color)' : 'rgba(255,255,255,0.2)')}`;
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="${fanClass}" style="font-size: 0.7rem; font-weight: bold; color: ${isLike ? '#ff4444' : 'var(--primary-color)'}; margin-bottom: 2px;">
|
||||
${fanIndex === 0 ? '👑 ' : ''}${msg.username}
|
||||
</div>
|
||||
<div style="font-size: 0.85rem; line-height: 1.4; ${isLike ? 'font-weight: 600;' : ''}">${msg.message}</div>
|
||||
<div style="font-size: 0.6rem; opacity: 0.4; margin-top: 4px; text-align: right;">${new Date(msg.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</div>
|
||||
`;
|
||||
chatMessages.appendChild(div);
|
||||
});
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
lastMessageCount = messages.length;
|
||||
} catch (error) {
|
||||
console.error('Chat error:', error);
|
||||
}
|
||||
@ -1016,10 +1108,57 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
});
|
||||
|
||||
// Initial fetch and poll
|
||||
fetchMessages();
|
||||
fetchTopFans().then(() => fetchMessages());
|
||||
setInterval(fetchMessages, 3000);
|
||||
setInterval(fetchTopFans, 10000);
|
||||
// --- End Chat Functionality ---
|
||||
|
||||
async function likeSong() {
|
||||
const nameInput = document.getElementById('user-name');
|
||||
const name = nameInput.value.trim();
|
||||
const song = document.getElementById('track-title').innerText.replace(/\s{2,}/g, ' ').trim();
|
||||
|
||||
if (name.length < 3) {
|
||||
alert('Por favor, ingresa tu nombre arriba para dar Like.');
|
||||
nameInput.style.borderColor = '#ff4444';
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('like-song-btn');
|
||||
const icon = btn.querySelector('i');
|
||||
|
||||
icon.classList.remove('bi-heart');
|
||||
icon.classList.add('bi-heart-fill');
|
||||
btn.style.transform = 'scale(1.3)';
|
||||
|
||||
try {
|
||||
const response = await fetch('api/like_song.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: name, song_title: song })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// Confetti explosion!
|
||||
confetti({
|
||||
particleCount: 100,
|
||||
spread: 70,
|
||||
origin: { y: 0.6 },
|
||||
colors: ['#ff4444', '#00e676', '#38bdf8', '#facc15']
|
||||
});
|
||||
|
||||
document.getElementById('like-count').innerText = result.likes_count;
|
||||
setTimeout(() => {
|
||||
btn.style.transform = 'scale(1)';
|
||||
}, 200);
|
||||
fetchMessages();
|
||||
fetchTopFans(); // Update ranking
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error liking song:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch Now Playing Metadata from RadioKing
|
||||
async function updateMetadata() {
|
||||
try {
|
||||
@ -1036,11 +1175,26 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
if (trackTitle.textContent !== fullDisplay) {
|
||||
trackTitle.style.opacity = '0';
|
||||
|
||||
// Reset Like Button
|
||||
const likeIcon = document.querySelector('#like-song-btn i');
|
||||
likeIcon.classList.remove('bi-heart-fill');
|
||||
likeIcon.classList.add('bi-heart');
|
||||
document.getElementById('like-count').innerText = '';
|
||||
|
||||
trackTitle.classList.remove('scrolling');
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
trackTitle.textContent = fullDisplay;
|
||||
trackTitle.style.opacity = '1';
|
||||
|
||||
// Fetch likes for this song
|
||||
try {
|
||||
const lResp = await fetch(`api/get_likes.php?song_title=${encodeURIComponent(fullDisplay)}`);
|
||||
const lData = await lResp.json();
|
||||
if (lData.likes_count > 0) {
|
||||
document.getElementById('like-count').innerText = lData.likes_count;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Check if scrolling is needed
|
||||
if (trackTitle.scrollWidth > trackTitle.clientWidth) {
|
||||
trackTitle.classList.add('scrolling');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user