Armario / templates /efectos.html
YoBatM's picture
Upload 4 files
c1d9028 verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>✨ Efectos Nitro - Avatar Studio</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #ff00ff;
--secondary: #00ffff;
--dark: #0a0a1a;
--light: #ffffff;
--gradient-angle: 135deg;
}
body {
background: linear-gradient(var(--gradient-angle), #0f0c29, #302b63, #24243e);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
padding: 20px;
color: var(--light);
overflow-x: hidden;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
/* ===== HEADER ===== */
header {
text-align: center;
padding: 30px 0;
margin-bottom: 30px;
position: relative;
}
header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, transparent, var(--primary), var(--secondary), transparent);
animation: glow 2s ease-in-out infinite;
}
@keyframes glow {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
h1 {
font-size: 3.5rem;
margin-bottom: 10px;
background: linear-gradient(90deg, var(--primary), var(--secondary), #ff9966, var(--primary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 300% 300%;
animation: gradientShift 8s ease infinite;
text-shadow: 0 0 20px rgba(255, 0, 255, 0.5);
letter-spacing: 2px;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.subtitle {
font-size: 1.2rem;
color: #aaa;
margin-top: 10px;
font-weight: 300;
}
/* ===== SEARCH & FILTERS ===== */
.controls {
background: rgba(20, 20, 40, 0.8);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px;
margin-bottom: 30px;
border: 1px solid rgba(255, 0, 255, 0.2);
box-shadow: 0 0 30px rgba(255, 0, 255, 0.1);
}
.search-container {
display: flex;
gap: 15px;
flex-wrap: wrap;
align-items: center;
}
.search-box {
flex: 1;
min-width: 300px;
}
.search-box input {
width: 100%;
padding: 12px 20px;
border: 2px solid var(--primary);
border-radius: 50px;
background: rgba(30, 30, 60, 0.8);
color: white;
font-size: 1rem;
transition: all 0.3s ease;
}
.search-box input:focus {
outline: none;
border-color: var(--secondary);
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
}
.search-box input::placeholder {
color: #888;
}
.filter-btn {
padding: 12px 25px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
border: none;
border-radius: 50px;
color: white;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(255, 0, 255, 0.4);
}
.filter-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(255, 0, 255, 0.6);
}
.filter-btn:active {
transform: translateY(1px);
}
/* ===== STATS BAR ===== */
.stats-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(30, 30, 60, 0.8);
padding: 15px 25px;
border-radius: 10px;
margin-bottom: 25px;
border-left: 4px solid var(--secondary);
}
.total-count {
font-size: 1.3rem;
font-weight: bold;
color: var(--secondary);
}
.view-toggle {
display: flex;
gap: 10px;
}
.view-btn {
padding: 8px 15px;
background: rgba(50, 50, 100, 0.7);
border: 2px solid var(--primary);
border-radius: 8px;
color: white;
cursor: pointer;
transition: all 0.3s ease;
}
.view-btn.active {
background: var(--primary);
border-color: white;
box-shadow: 0 0 15px rgba(255, 0, 255, 0.5);
}
.view-btn:hover:not(.active) {
background: rgba(255, 0, 255, 0.3);
}
/* ===== EFFECTS GRID ===== */
.effects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 25px;
padding: 10px;
}
.effect-card {
background: rgba(30, 30, 60, 0.7);
border-radius: 15px;
overflow: hidden;
border: 2px solid transparent;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.effect-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent, rgba(255, 0, 255, 0.1), transparent);
z-index: 1;
opacity: 0;
transition: opacity 0.3s ease;
}
.effect-card:hover {
transform: translateY(-10px) scale(1.03);
box-shadow: 0 15px 40px rgba(255, 0, 255, 0.4);
border-color: var(--primary);
}
.effect-card:hover::before {
opacity: 1;
}
.effect-card:hover .effect-img {
transform: scale(1.1);
filter: brightness(1.2) saturate(1.3);
}
.effect-card:hover .effect-info {
background: rgba(40, 40, 80, 0.95);
}
.effect-img-container {
position: relative;
aspect-ratio: 1;
overflow: hidden;
background: linear-gradient(135deg, #1a1a2e, #16213e);
display: flex;
align-items: center;
justify-content: center;
}
.effect-img {
width: 80%;
height: 80%;
object-fit: contain;
transition: all 0.4s ease;
cursor: pointer;
animation: pulse 3s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.effect-img.loading {
opacity: 0.3;
}
.effect-img.error {
display: none;
}
.loading-spinner {
position: absolute;
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.effect-info {
padding: 15px;
background: rgba(25, 25, 50, 0.9);
transition: all 0.3s ease;
}
.effect-id {
font-size: 0.9rem;
color: var(--secondary);
font-weight: bold;
margin-bottom: 5px;
text-transform: uppercase;
letter-spacing: 1px;
}
.effect-type {
font-size: 0.85rem;
color: #aaa;
background: rgba(0, 255, 255, 0.1);
padding: 3px 10px;
border-radius: 15px;
display: inline-block;
}
/* ===== EMPTY STATE ===== */
.empty-state {
grid-column: 1 / -1;
text-align: center;
padding: 60px 20px;
background: rgba(30, 30, 60, 0.5);
border-radius: 15px;
border: 2px dashed rgba(255, 0, 255, 0.3);
}
.empty-state i {
font-size: 4rem;
color: var(--secondary);
margin-bottom: 20px;
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
.empty-state h3 {
font-size: 1.8rem;
margin-bottom: 10px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.empty-state p {
color: #888;
max-width: 500px;
margin: 0 auto;
}
/* ===== LOADING SKELETON ===== */
.skeleton {
background: linear-gradient(90deg, #2a2a4a, #3a3a5a, #2a2a4a);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 15px;
overflow: hidden;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* ===== FOOTER ===== */
footer {
text-align: center;
padding: 30px;
margin-top: 40px;
color: #888;
font-size: 0.9rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
/* ===== NOTIFICATION ===== */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: rgba(30, 30, 60, 0.95);
border-left: 4px solid var(--primary);
color: white;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
transform: translateX(400px);
transition: transform 0.3s ease;
z-index: 1000;
}
.notification.show {
transform: translateX(0);
}
.notification.success {
border-left-color: #00ff9d;
}
.notification.error {
border-left-color: #ff4d4d;
}
/* ===== MODAL ===== */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
z-index: 2000;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.modal.show {
display: flex;
opacity: 1;
}
.modal-content {
background: rgba(30, 30, 60, 0.95);
border: 3px solid var(--secondary);
border-radius: 20px;
padding: 30px;
max-width: 90%;
max-height: 90%;
position: relative;
}
.modal-close {
position: absolute;
top: 15px;
right: 15px;
background: rgba(255, 0, 255, 0.2);
border: none;
color: white;
width: 35px;
height: 35px;
border-radius: 50%;
cursor: pointer;
font-size: 1.2rem;
transition: all 0.3s ease;
}
.modal-close:hover {
background: var(--primary);
transform: rotate(90deg);
}
.modal-gif {
max-width: 80vw;
max-height: 70vh;
border: 2px solid var(--primary);
border-radius: 10px;
box-shadow: 0 0 50px rgba(255, 0, 255, 0.5);
}
/* ===== RESPONSIVE ===== */
@media (max-width: 768px) {
h1 {
font-size: 2.5rem;
}
.search-box {
min-width: 100%;
}
.effects-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.effect-img {
width: 70%;
height: 70%;
}
}
@media (max-width: 480px) {
h1 {
font-size: 2rem;
}
.controls {
padding: 15px;
}
.stats-bar {
flex-direction: column;
gap: 15px;
}
.effects-grid {
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
}
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<header>
<h1><i class="fas fa-magic"></i> NITRO EFFECTS STUDIO</h1>
<p class="subtitle">Colección de efectos animados para tu avatar</p>
</header>
<!-- Controls -->
<div class="controls">
<div class="search-container">
<div class="search-box">
<input type="text" id="searchInput" placeholder="🔍 Buscar efecto por ID o tipo..." onkeyup="filterEffects()">
</div>
<button class="filter-btn" onclick="clearFilters()">
<i class="fas fa-sync-alt"></i> Limpiar
</button>
</div>
</div>
<!-- Stats -->
<div class="stats-bar">
<div class="total-count">
<i class="fas fa-fire"></i> <span id="totalCount">{{ efectos|length }}</span> efectos disponibles
</div>
<div class="view-toggle">
<button class="view-btn active" onclick="setView('grid')">
<i class="fas fa-th-large"></i> Cuadrícula
</button>
<button class="view-btn" onclick="setView('list')">
<i class="fas fa-list"></i> Lista
</button>
</div>
</div>
<!-- Effects Grid -->
<div class="effects-grid" id="effectsContainer">
{% if efectos|length == 0 %}
<div class="empty-state">
<i class="fas fa-ghost"></i>
<h3>¡No hay efectos!</h3>
<p>Parece que no se encontraron efectos con los parámetros actuales. Intenta ajustar la configuración o verifica la URL.</p>
</div>
{% else %}
{% for efecto in efectos %}
<div class="effect-card" data-id="{{ efecto.id }}" data-type="{{ efecto.tipo }}">
<div class="effect-img-container">
<div class="loading-spinner"></div>
<img
src="{{ efecto.file }}"
alt="{{ efecto.id }}"
class="effect-img loading"
onerror="handleImageError(this)"
onload="handleImageLoad(this)"
onclick="showGifModal('{{ efecto.file }}', '{{ efecto.id }}')"
/>
</div>
<div class="effect-info">
<div class="effect-id">{{ efecto.id }}</div>
<div class="effect-type"><i class="fas fa-tag"></i> {{ efecto.tipo }}</div>
</div>
</div>
{% endfor %}
{% endif %}
</div>
</div>
<!-- Modal for GIF preview -->
<div class="modal" id="gifModal">
<div class="modal-content">
<button class="modal-close" onclick="closeModal()">&times;</button>
<img class="modal-gif" id="modalGif" src="" alt="Preview">
</div>
</div>
<!-- Notification -->
<div class="notification" id="notification"></div>
<script>
// ========== IMAGE HANDLING ==========
function handleImageLoad(img) {
img.classList.remove('loading');
}
function handleImageError(img) {
img.classList.add('error');
img.parentElement.querySelector('.loading-spinner').style.display = 'none';
img.parentElement.innerHTML = '<i class="fas fa-times-circle" style="font-size: 3rem; color: #ff4d4d;"></i>';
}
// ========== SEARCH & FILTER ==========
function filterEffects() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const cards = document.querySelectorAll('.effect-card');
let visibleCount = 0;
cards.forEach(card => {
const id = card.dataset.id.toLowerCase();
const type = card.dataset.type.toLowerCase();
if (id.includes(searchTerm) || type.includes(searchTerm)) {
card.style.display = '';
visibleCount++;
} else {
card.style.display = 'none';
}
});
document.getElementById('totalCount').textContent = visibleCount;
// Show empty state if needed
const grid = document.getElementById('effectsContainer');
if (visibleCount === 0 && cards.length > 0) {
if (!grid.querySelector('.empty-state')) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
emptyState.innerHTML = `
<i class="fas fa-search"></i>
<h3>¡No se encontraron resultados!</h3>
<p>Intenta con otro término de búsqueda</p>
`;
emptyState.style.gridColumn = '1 / -1';
grid.appendChild(emptyState);
}
} else {
const emptyState = grid.querySelector('.empty-state');
if (emptyState) emptyState.remove();
}
}
function clearFilters() {
document.getElementById('searchInput').value = '';
filterEffects();
showNotification('Filtros limpiados', 'success');
}
// ========== VIEW MODE ==========
function setView(mode) {
const grid = document.getElementById('effectsContainer');
const buttons = document.querySelectorAll('.view-btn');
buttons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
if (mode === 'list') {
grid.style.gridTemplateColumns = '1fr';
} else {
grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(220px, 1fr))';
}
showNotification(`Vista: ${mode === 'list' ? 'Lista' : 'Cuadrícula'}`, 'success');
}
// ========== MODAL ==========
function showGifModal(src, id) {
const modal = document.getElementById('gifModal');
const gif = document.getElementById('modalGif');
gif.src = src;
modal.classList.add('show');
// Log for analytics
console.log(`Previewing effect: ${id}`);
}
function closeModal() {
const modal = document.getElementById('gifModal');
modal.classList.remove('show');
document.getElementById('modalGif').src = '';
}
// Close modal on click outside
window.onclick = function(event) {
const modal = document.getElementById('gifModal');
if (event.target === modal) {
closeModal();
}
}
// Close modal on Escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
// ========== NOTIFICATIONS ==========
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type} show`;
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// ========== PAGE LOAD ==========
window.addEventListener('load', function() {
// Show welcome message
setTimeout(() => {
showNotification(`✨ ${document.getElementById('totalCount').textContent} efectos cargados`, 'success');
}, 500);
});
</script>
</body>
</html>