Auto commit: 2026-02-16T13:04:02.148Z
This commit is contained in:
parent
9ac1e3b52d
commit
400e93e9fd
38
api/photo_status.php
Normal file
38
api/photo_status.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
|
||||||
|
$ip = explode(',', $ip)[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare("SELECT COUNT(*) FROM messages WHERE ip_address = ? AND type = 'image' AND created_at > DATE_SUB(NOW(), INTERVAL 6 HOUR)");
|
||||||
|
$stmt->execute([$ip]);
|
||||||
|
$count = (int)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
$limit = 5;
|
||||||
|
$remaining = max(0, $limit - $count);
|
||||||
|
|
||||||
|
$reset_in_seconds = 0;
|
||||||
|
if ($count >= $limit) {
|
||||||
|
// Find the oldest message in the 6-hour window
|
||||||
|
$stmt = db()->prepare("SELECT created_at FROM messages WHERE ip_address = ? AND type = 'image' AND created_at > DATE_SUB(NOW(), INTERVAL 6 HOUR) ORDER BY created_at ASC LIMIT 1");
|
||||||
|
$stmt->execute([$ip]);
|
||||||
|
$oldest = $stmt->fetchColumn();
|
||||||
|
if ($oldest) {
|
||||||
|
$oldest_time = strtotime($oldest);
|
||||||
|
$reset_time = $oldest_time + (6 * 3600);
|
||||||
|
$reset_in_seconds = max(0, $reset_time - time());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'count' => $count,
|
||||||
|
'limit' => $limit,
|
||||||
|
'remaining' => $remaining,
|
||||||
|
'reset_in_seconds' => $reset_in_seconds
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
165
index.php
165
index.php
@ -579,12 +579,27 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
|||||||
box-shadow: 0 0 20px rgba(24, 119, 242, 0.5);
|
box-shadow: 0 0 20px rgba(24, 119, 242, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-5px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0% { transform: scale(1); opacity: 1; }
|
0% { transform: scale(1); opacity: 1; }
|
||||||
50% { transform: scale(1.1); opacity: 0.7; }
|
50% { transform: scale(1.1); opacity: 0.7; }
|
||||||
100% { transform: scale(1); opacity: 1; }
|
100% { transform: scale(1); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes alert-pulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.3); background: #facc15 !important; }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
.counter-alert {
|
||||||
|
animation: alert-pulse 1s infinite ease-in-out;
|
||||||
|
box-shadow: 0 0 10px #facc15;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
.app-container {
|
.app-container {
|
||||||
@ -725,9 +740,11 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
|||||||
<button onclick="toggleEmojiPicker()" style="background: rgba(255,255,255,0.1); border: none; border-radius: 8px; color: white; padding: 0 0.5rem; cursor: pointer;">
|
<button onclick="toggleEmojiPicker()" style="background: rgba(255,255,255,0.1); border: none; border-radius: 8px; color: white; padding: 0 0.5rem; cursor: pointer;">
|
||||||
<i class="bi bi-emoji-smile"></i>
|
<i class="bi bi-emoji-smile"></i>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="document.getElementById('chat-file').click()" style="background: rgba(255,255,255,0.1); border: none; border-radius: 8px; color: white; padding: 0 0.5rem; cursor: pointer;">
|
<button id="camera-btn" onclick="document.getElementById('chat-file').click()" title="Subir foto" style="background: rgba(255,255,255,0.1); border: none; border-radius: 8px; color: white; padding: 0 0.5rem; cursor: pointer; position: relative; transition: all 0.3s;">
|
||||||
<i class="bi bi-camera-fill"></i>
|
<i class="bi bi-camera-fill"></i>
|
||||||
|
<span id="photo-counter" style="position: absolute; top: -10px; right: -5px; background: var(--primary-color); color: white; font-size: 0.6rem; padding: 1px 4px; border-radius: 10px; font-weight: bold; border: 1px solid rgba(255,255,255,0.3); display: none;">0/5</span>
|
||||||
</button>
|
</button>
|
||||||
|
<div id="photo-limit-msg" style="display: none; position: absolute; bottom: -20px; left: 0; width: 100%; text-align: center; font-size: 0.65rem; color: #ff4444; font-weight: bold; animation: fadeIn 0.3s;"></div>
|
||||||
<input type="file" id="chat-file" style="display: none;" accept="image/*" onchange="uploadImage(this)">
|
<input type="file" id="chat-file" style="display: none;" accept="image/*" onchange="uploadImage(this)">
|
||||||
<input type="text" id="chat-msg" placeholder="Escribe un mensaje..." style="flex: 1; font-size: 0.8rem; padding: 0.5rem; border-radius: 8px; border: none; background: rgba(255,255,255,0.1); color: white;">
|
<input type="text" id="chat-msg" placeholder="Escribe un mensaje..." style="flex: 1; font-size: 0.8rem; padding: 0.5rem; border-radius: 8px; border: none; background: rgba(255,255,255,0.1); color: white;">
|
||||||
<button onclick="sendChatMessage()" style="background: var(--primary-color); border: none; border-radius: 8px; color: white; padding: 0 1rem; cursor: pointer;">
|
<button onclick="sendChatMessage()" style="background: var(--primary-color); border: none; border-radius: 8px; color: white; padding: 0 1rem; cursor: pointer;">
|
||||||
@ -806,58 +823,50 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
|||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const barCount = 40; // Número de barras fijas para un look más limpio
|
||||||
|
const logicalWidth = canvas.width / window.devicePixelRatio;
|
||||||
|
const logicalHeight = canvas.height / window.devicePixelRatio;
|
||||||
|
const gap = 4;
|
||||||
|
const barWidth = (logicalWidth / barCount) - gap;
|
||||||
|
|
||||||
|
colorOffset += 2;
|
||||||
|
|
||||||
|
for (let i = 0; i < barCount; i++) {
|
||||||
|
// Mapear el índice de la barra a la frecuencia (usando una escala logarítmica para mejor visual)
|
||||||
|
const frequencyIndex = Math.floor(Math.pow(i / barCount, 1.5) * (bufferLength / 2));
|
||||||
|
const barHeight = (dataArray[frequencyIndex] / 255) * logicalHeight * 0.8 + 5;
|
||||||
|
|
||||||
|
const x = i * (barWidth + gap);
|
||||||
|
const barHue = (colorOffset + (i / barCount) * 200) % 360;
|
||||||
|
|
||||||
|
const gradient = ctx.createLinearGradient(0, logicalHeight, 0, logicalHeight - barHeight);
|
||||||
|
gradient.addColorStop(0, `hsla(${barHue}, 100%, 50%, 0.2)`);
|
||||||
|
gradient.addColorStop(0.5, `hsla(${barHue}, 100%, 50%, 0.8)`);
|
||||||
|
gradient.addColorStop(1, `hsla(${(barHue + 60) % 360}, 100%, 70%, 1)`);
|
||||||
|
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
|
||||||
|
// Dibujar barra con bordes redondeados superiores
|
||||||
|
ctx.beginPath();
|
||||||
|
if (ctx.roundRect) {
|
||||||
|
ctx.roundRect(x, logicalHeight - barHeight, barWidth, barHeight, [5, 5, 0, 0]);
|
||||||
|
} else {
|
||||||
|
ctx.rect(x, logicalHeight - barHeight, barWidth, barHeight);
|
||||||
|
}
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Añadir un "cap" o punto brillante arriba de la barra
|
||||||
|
ctx.fillStyle = `hsla(${(barHue + 60) % 360}, 100%, 80%, 1)`;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x + barWidth/2, logicalHeight - barHeight, barWidth/3, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Efecto de brillo dinámico en la tarjeta basado en el volumen medio
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for(let i=0; i<bufferLength; i++) sum += dataArray[i];
|
for(let i=0; i<bufferLength; i++) sum += dataArray[i];
|
||||||
const average = sum / bufferLength;
|
const average = sum / bufferLength;
|
||||||
|
const dominantHue = (average * 2 + colorOffset) % 360;
|
||||||
|
|
||||||
|
|
||||||
const barCount = bufferLength / 1.5;
|
|
||||||
const logicalWidth = canvas.width / window.devicePixelRatio;
|
|
||||||
const logicalHeight = canvas.height / window.devicePixelRatio;
|
|
||||||
const barWidth = (logicalWidth / barCount) * 0.7;
|
|
||||||
let x = logicalWidth / 2;
|
|
||||||
let xLeft = logicalWidth / 2;
|
|
||||||
|
|
||||||
colorOffset += 1;
|
|
||||||
|
|
||||||
for (let i = 0; i < barCount; i++) {
|
|
||||||
const barHeight = (dataArray[i] / 255) * logicalHeight * 0.9;
|
|
||||||
const barHue = (colorOffset + (i / barCount) * 360) % 360;
|
|
||||||
const saturation = 100;
|
|
||||||
const lightness = 60 + (dataArray[i] / 255) * 15;
|
|
||||||
|
|
||||||
const gradient = ctx.createLinearGradient(0, logicalHeight, 0, logicalHeight - barHeight);
|
|
||||||
gradient.addColorStop(0, `hsla(${barHue}, ${saturation}%, ${lightness}%, 0.1)`);
|
|
||||||
gradient.addColorStop(0.4, `hsla(${barHue}, ${saturation}%, ${lightness}%, 0.8)`);
|
|
||||||
gradient.addColorStop(1, `hsla(${(barHue + 40) % 360}, ${saturation}%, ${lightness + 20}%, 1)`);
|
|
||||||
|
|
||||||
ctx.fillStyle = gradient;
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.roundRect(x, logicalHeight - barHeight, barWidth, barHeight, [10, 10, 0, 0]);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
ctx.fillStyle = `hsla(${(barHue + 40) % 360}, 100%, 80%, 0.8)`;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(x + barWidth/2, logicalHeight - barHeight, barWidth/2, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
ctx.fillStyle = gradient;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.roundRect(xLeft - barWidth, logicalHeight - barHeight, barWidth, barHeight, [10, 10, 0, 0]);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
ctx.fillStyle = `hsla(${(barHue + 40) % 360}, 100%, 80%, 0.8)`;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(xLeft - barWidth/2, logicalHeight - barHeight, barWidth/2, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
x += barWidth + 3;
|
|
||||||
xLeft -= barWidth + 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dominantHue = (average * 2.5 + colorOffset) % 360;
|
|
||||||
document.documentElement.style.setProperty('--dynamic-glow', `hsla(${dominantHue}, 100%, 60%, 0.8)`);
|
document.documentElement.style.setProperty('--dynamic-glow', `hsla(${dominantHue}, 100%, 60%, 0.8)`);
|
||||||
document.documentElement.style.setProperty('--dynamic-glow-dim', `hsla(${dominantHue}, 100%, 60%, 0.3)`);
|
document.documentElement.style.setProperty('--dynamic-glow-dim', `hsla(${dominantHue}, 100%, 60%, 0.3)`);
|
||||||
}
|
}
|
||||||
@ -988,6 +997,7 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
sendChatMessage(result.url, 'image');
|
sendChatMessage(result.url, 'image');
|
||||||
|
updatePhotoCounter();
|
||||||
} else {
|
} else {
|
||||||
alert(result.error || 'Error al subir la imagen');
|
alert(result.error || 'Error al subir la imagen');
|
||||||
}
|
}
|
||||||
@ -1087,9 +1097,64 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
|||||||
if (e.key === 'Enter') sendChatMessage();
|
if (e.key === 'Enter') sendChatMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function updatePhotoCounter() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('api/photo_status.php');
|
||||||
|
const data = await response.json();
|
||||||
|
const counter = document.getElementById('photo-counter');
|
||||||
|
const cameraBtn = document.getElementById('camera-btn');
|
||||||
|
const fileInput = document.getElementById('chat-file');
|
||||||
|
const limitMsg = document.getElementById('photo-limit-msg');
|
||||||
|
|
||||||
|
if (data && typeof data.count !== 'undefined') {
|
||||||
|
counter.innerText = `${data.count}/${data.limit}`;
|
||||||
|
counter.style.display = 'block';
|
||||||
|
|
||||||
|
// Alerta cuando solo queda 1 foto (4 de 5)
|
||||||
|
if (data.count === data.limit - 1) {
|
||||||
|
counter.classList.add('counter-alert');
|
||||||
|
} else {
|
||||||
|
counter.classList.remove('counter-alert');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.count >= data.limit) {
|
||||||
|
counter.style.background = '#ff4444';
|
||||||
|
// Bloquear visualmente el botón
|
||||||
|
cameraBtn.style.opacity = '0.4';
|
||||||
|
cameraBtn.style.filter = 'grayscale(1)';
|
||||||
|
cameraBtn.style.pointerEvents = 'none';
|
||||||
|
cameraBtn.title = 'Límite de fotos alcanzado';
|
||||||
|
fileInput.disabled = true;
|
||||||
|
|
||||||
|
// Mostrar tiempo restante
|
||||||
|
if (data.reset_in_seconds > 0) {
|
||||||
|
const hours = Math.floor(data.reset_in_seconds / 3600);
|
||||||
|
const minutes = Math.floor((data.reset_in_seconds % 3600) / 60);
|
||||||
|
const timeStr = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||||
|
limitMsg.innerText = `Disponible en ${timeStr}`;
|
||||||
|
limitMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
counter.style.background = 'var(--primary-color)';
|
||||||
|
// Restaurar el botón
|
||||||
|
cameraBtn.style.opacity = '1';
|
||||||
|
cameraBtn.style.filter = 'none';
|
||||||
|
cameraBtn.style.pointerEvents = 'auto';
|
||||||
|
cameraBtn.title = 'Subir foto';
|
||||||
|
fileInput.disabled = false;
|
||||||
|
limitMsg.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating photo counter:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initial fetch and poll
|
// Initial fetch and poll
|
||||||
fetchMessages();
|
fetchMessages();
|
||||||
|
updatePhotoCounter();
|
||||||
setInterval(fetchMessages, 3000);
|
setInterval(fetchMessages, 3000);
|
||||||
|
setInterval(updatePhotoCounter, 30000); // Check limit status every 30s
|
||||||
// --- End Chat Functionality ---
|
// --- End Chat Functionality ---
|
||||||
|
|
||||||
async function likeSong() {
|
async function likeSong() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user