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);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.7; }
|
||||
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 */
|
||||
@media (max-width: 992px) {
|
||||
.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;">
|
||||
<i class="bi bi-emoji-smile"></i>
|
||||
</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>
|
||||
<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>
|
||||
<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="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;">
|
||||
@ -806,58 +823,50 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
|
||||
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;
|
||||
for(let i=0; i<bufferLength; i++) sum += dataArray[i];
|
||||
const average = sum / bufferLength;
|
||||
|
||||
|
||||
|
||||
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;
|
||||
const dominantHue = (average * 2 + colorOffset) % 360;
|
||||
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)`);
|
||||
}
|
||||
@ -988,6 +997,7 @@ $facebook_link = "https://www.facebook.com/profile.php?id=61587890927489";
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
sendChatMessage(result.url, 'image');
|
||||
updatePhotoCounter();
|
||||
} else {
|
||||
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();
|
||||
});
|
||||
|
||||
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
|
||||
fetchMessages();
|
||||
updatePhotoCounter();
|
||||
setInterval(fetchMessages, 3000);
|
||||
setInterval(updatePhotoCounter, 30000); // Check limit status every 30s
|
||||
// --- End Chat Functionality ---
|
||||
|
||||
async function likeSong() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user