626 lines
29 KiB
HTML
626 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}DosVault{% endblock %}</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
<style>
|
|
:root {
|
|
/* Default Dark Theme */
|
|
--primary-bg: #111827;
|
|
--secondary-bg: #1f2937;
|
|
--tertiary-bg: #374151;
|
|
--accent-bg: #2563eb;
|
|
--accent-hover: #1d4ed8;
|
|
--text-primary: #ffffff;
|
|
--text-secondary: #9ca3af;
|
|
--text-accent: #60a5fa;
|
|
--border-color: #4b5563;
|
|
--success-color: #059669;
|
|
--warning-color: #d97706;
|
|
--danger-color: #dc2626;
|
|
--gradient-from: #1f2937;
|
|
--gradient-to: #111827;
|
|
}
|
|
|
|
/* Tokyo Night Theme */
|
|
.theme-tokyo-night {
|
|
--primary-bg: #1a1b26;
|
|
--secondary-bg: #24283b;
|
|
--tertiary-bg: #414868;
|
|
--accent-bg: #7aa2f7;
|
|
--accent-hover: #565f89;
|
|
--text-primary: #c0caf5;
|
|
--text-secondary: #9aa5ce;
|
|
--text-accent: #7aa2f7;
|
|
--border-color: #414868;
|
|
--success-color: #9ece6a;
|
|
--warning-color: #e0af68;
|
|
--danger-color: #f7768e;
|
|
--gradient-from: #24283b;
|
|
--gradient-to: #1a1b26;
|
|
}
|
|
|
|
/* Cyberpunk Theme */
|
|
.theme-cyberpunk {
|
|
--primary-bg: #0d0208;
|
|
--secondary-bg: #1a0e1a;
|
|
--tertiary-bg: #2d1b2e;
|
|
--accent-bg: #ff006e;
|
|
--accent-hover: #d90856;
|
|
--text-primary: #00f5ff;
|
|
--text-secondary: #c77dff;
|
|
--text-accent: #ff006e;
|
|
--border-color: #7209b7;
|
|
--success-color: #39ff14;
|
|
--warning-color: #ffbe0b;
|
|
--danger-color: #ff1744;
|
|
--gradient-from: #1a0e1a;
|
|
--gradient-to: #0d0208;
|
|
}
|
|
|
|
/* Ultra Retro Theme */
|
|
.theme-ultra-retro {
|
|
--primary-bg: #000000;
|
|
--secondary-bg: #001100;
|
|
--tertiary-bg: #003300;
|
|
--accent-bg: #00ff00;
|
|
--accent-hover: #00cc00;
|
|
--text-primary: #00ff00;
|
|
--text-secondary: #00cc00;
|
|
--text-accent: #00ff00;
|
|
--border-color: #005500;
|
|
--success-color: #00ff00;
|
|
--warning-color: #ffff00;
|
|
--danger-color: #ff0000;
|
|
--gradient-from: #001100;
|
|
--gradient-to: #000000;
|
|
}
|
|
|
|
/* Ocean Theme */
|
|
.theme-ocean {
|
|
--primary-bg: #0f172a;
|
|
--secondary-bg: #1e293b;
|
|
--tertiary-bg: #334155;
|
|
--accent-bg: #0ea5e9;
|
|
--accent-hover: #0284c7;
|
|
--text-primary: #f1f5f9;
|
|
--text-secondary: #94a3b8;
|
|
--text-accent: #38bdf8;
|
|
--border-color: #475569;
|
|
--success-color: #10b981;
|
|
--warning-color: #f59e0b;
|
|
--danger-color: #ef4444;
|
|
--gradient-from: #1e293b;
|
|
--gradient-to: #0f172a;
|
|
}
|
|
|
|
/* Sunset Theme */
|
|
.theme-sunset {
|
|
--primary-bg: #451a03;
|
|
--secondary-bg: #7c2d12;
|
|
--tertiary-bg: #9a3412;
|
|
--accent-bg: #ea580c;
|
|
--accent-hover: #c2410c;
|
|
--text-primary: #fed7aa;
|
|
--text-secondary: #fdba74;
|
|
--text-accent: #fb923c;
|
|
--border-color: #9a3412;
|
|
--success-color: #22c55e;
|
|
--warning-color: #eab308;
|
|
--danger-color: #ef4444;
|
|
--gradient-from: #7c2d12;
|
|
--gradient-to: #451a03;
|
|
}
|
|
|
|
/* Light Theme */
|
|
.theme-light {
|
|
--primary-bg: #ffffff;
|
|
--secondary-bg: #f8fafc;
|
|
--tertiary-bg: #e2e8f0;
|
|
--accent-bg: #3b82f6;
|
|
--accent-hover: #2563eb;
|
|
--text-primary: #1e293b;
|
|
--text-secondary: #64748b;
|
|
--text-accent: #3b82f6;
|
|
--border-color: #cbd5e1;
|
|
--success-color: #059669;
|
|
--warning-color: #d97706;
|
|
--danger-color: #dc2626;
|
|
--gradient-from: #f8fafc;
|
|
--gradient-to: #ffffff;
|
|
}
|
|
|
|
/* Light Blue Theme */
|
|
.theme-light-blue {
|
|
--primary-bg: #f0f9ff;
|
|
--secondary-bg: #e0f2fe;
|
|
--tertiary-bg: #bae6fd;
|
|
--accent-bg: #0ea5e9;
|
|
--accent-hover: #0284c7;
|
|
--text-primary: #0c4a6e;
|
|
--text-secondary: #075985;
|
|
--text-accent: #0ea5e9;
|
|
--border-color: #7dd3fc;
|
|
--success-color: #059669;
|
|
--warning-color: #d97706;
|
|
--danger-color: #dc2626;
|
|
--gradient-from: #e0f2fe;
|
|
--gradient-to: #f0f9ff;
|
|
}
|
|
|
|
/* Light Green Theme */
|
|
.theme-light-green {
|
|
--primary-bg: #f0fdf4;
|
|
--secondary-bg: #dcfce7;
|
|
--tertiary-bg: #bbf7d0;
|
|
--accent-bg: #22c55e;
|
|
--accent-hover: #16a34a;
|
|
--text-primary: #14532d;
|
|
--text-secondary: #166534;
|
|
--text-accent: #22c55e;
|
|
--border-color: #86efac;
|
|
--success-color: #059669;
|
|
--warning-color: #d97706;
|
|
--danger-color: #dc2626;
|
|
--gradient-from: #dcfce7;
|
|
--gradient-to: #f0fdf4;
|
|
}
|
|
|
|
/* Light Purple Theme */
|
|
.theme-light-purple {
|
|
--primary-bg: #faf5ff;
|
|
--secondary-bg: #f3e8ff;
|
|
--tertiary-bg: #e9d5ff;
|
|
--accent-bg: #a855f7;
|
|
--accent-hover: #9333ea;
|
|
--text-primary: #581c87;
|
|
--text-secondary: #7c3aed;
|
|
--text-accent: #a855f7;
|
|
--border-color: #c4b5fd;
|
|
--success-color: #059669;
|
|
--warning-color: #d97706;
|
|
--danger-color: #dc2626;
|
|
--gradient-from: #f3e8ff;
|
|
--gradient-to: #faf5ff;
|
|
}
|
|
|
|
/* Apply CSS custom properties */
|
|
body {
|
|
background-color: var(--primary-bg);
|
|
color: var(--text-primary);
|
|
}
|
|
.bg-primary { background-color: var(--primary-bg); }
|
|
.bg-secondary { background-color: var(--secondary-bg); }
|
|
.bg-tertiary { background-color: var(--tertiary-bg); }
|
|
.bg-accent { background-color: var(--accent-bg); }
|
|
.bg-accent:hover { background-color: var(--accent-hover); }
|
|
.bg-accent-hover { background-color: var(--accent-hover); }
|
|
.bg-warning { background-color: var(--warning-color) !important; }
|
|
.text-primary { color: var(--text-primary); }
|
|
.text-secondary { color: var(--text-secondary); }
|
|
.text-accent { color: var(--text-accent); }
|
|
.border-theme { border-color: var(--border-color); }
|
|
.bg-gradient-theme { background: linear-gradient(to bottom right, var(--gradient-from), var(--gradient-to)); }
|
|
|
|
/* Fade-in animation for overlays */
|
|
.animate-fade-in {
|
|
animation: fadeIn 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.95) translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Smooth transitions for overlay elements */
|
|
.transition-all-smooth {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-primary text-primary min-h-screen">
|
|
<nav class="bg-secondary border-b border-theme">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex items-center justify-between h-16">
|
|
<!-- Logo -->
|
|
<div class="flex-shrink-0">
|
|
<a href="/" class="flex items-center space-x-3 text-xl font-bold text-blue-400 hover:text-blue-300 transition-colors">
|
|
<svg class="w-8 h-8" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<!-- Vault door -->
|
|
<circle cx="16" cy="16" r="14" fill="currentColor" opacity="0.1"/>
|
|
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
<circle cx="16" cy="16" r="8" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
<circle cx="16" cy="16" r="4" fill="currentColor" opacity="0.3"/>
|
|
|
|
<!-- DOS-style pixels -->
|
|
<rect x="6" y="6" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
|
<rect x="10" y="6" width="2" height="2" fill="currentColor" opacity="0.8"/>
|
|
<rect x="20" y="6" width="2" height="2" fill="currentColor" opacity="0.8"/>
|
|
<rect x="24" y="6" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
|
<rect x="6" y="24" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
|
<rect x="24" y="24" width="2" height="2" fill="currentColor" opacity="0.6"/>
|
|
|
|
<!-- Handle -->
|
|
<rect x="20" y="14" width="6" height="4" rx="2" fill="currentColor" opacity="0.7"/>
|
|
<circle cx="24" cy="16" r="1" fill="currentColor"/>
|
|
</svg>
|
|
<span>DosVault</span>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Desktop Navigation -->
|
|
<div class="hidden md:block">
|
|
<div class="ml-10 flex items-baseline space-x-4">
|
|
<a href="/" class="hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
|
Browse Games
|
|
</a>
|
|
{% if current_user and current_user.role != "demo" %}
|
|
<a href="/favorites" class="hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
|
My Favorites
|
|
</a>
|
|
{% endif %}
|
|
<button onclick="toggleGenresSidebar()" class="hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
|
Browse Genres
|
|
</button>
|
|
{% if current_user and current_user.role == "super" %}
|
|
<a href="/admin" class="hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
|
Admin
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Desktop User Info -->
|
|
<div class="hidden md:flex items-center space-x-4">
|
|
<!-- Theme Picker -->
|
|
<div class="relative">
|
|
<button onclick="toggleThemeMenu()" class="bg-gray-700 hover:bg-gray-600 p-2 rounded text-sm">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zM21 5a2 2 0 00-2-2h-4a2 2 0 00-2 2v12a4 4 0 004 4h4a2 2 0 002-2V5z"></path>
|
|
</svg>
|
|
</button>
|
|
<div id="theme-menu" class="hidden absolute right-0 mt-2 w-48 bg-gray-800 rounded-md shadow-lg z-50 border border-gray-700">
|
|
<div class="py-1">
|
|
<button onclick="setTheme('default')" class="flex items-center w-full px-4 py-2 text-sm text-white hover:bg-gray-700">
|
|
<div class="w-4 h-4 rounded-full bg-gray-700 mr-3"></div>
|
|
Default Dark
|
|
</button>
|
|
<button onclick="setTheme('tokyo-night')" class="flex items-center w-full px-4 py-2 text-sm text-white hover:bg-gray-700">
|
|
<div class="w-4 h-4 rounded-full bg-purple-600 mr-3"></div>
|
|
Tokyo Night
|
|
</button>
|
|
<button onclick="setTheme('cyberpunk')" class="flex items-center w-full px-4 py-2 text-sm text-white hover:bg-gray-700">
|
|
<div class="w-4 h-4 rounded-full bg-pink-500 mr-3"></div>
|
|
Cyberpunk
|
|
</button>
|
|
<button onclick="setTheme('ultra-retro')" class="flex items-center w-full px-4 py-2 text-sm text-white hover:bg-gray-700">
|
|
<div class="w-4 h-4 rounded-full bg-green-500 mr-3"></div>
|
|
Ultra Retro
|
|
</button>
|
|
<button onclick="setTheme('ocean')" class="flex items-center w-full px-4 py-2 text-sm text-white hover:bg-gray-700">
|
|
<div class="w-4 h-4 rounded-full bg-blue-500 mr-3"></div>
|
|
Ocean
|
|
</button>
|
|
<button onclick="setTheme('sunset')" class="flex items-center w-full px-4 py-2 text-sm text-white hover:bg-gray-700">
|
|
<div class="w-4 h-4 rounded-full bg-orange-500 mr-3"></div>
|
|
Sunset
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if current_user %}
|
|
<span class="text-sm text-primary">
|
|
Welcome, {{ current_user.username }}
|
|
</span>
|
|
<button onclick="logout()" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded text-sm transition-colors">
|
|
Logout
|
|
</button>
|
|
{% else %}
|
|
<button onclick="showLogin()" class="bg-accent hover:bg-accent-hover px-4 py-2 rounded text-sm text-primary transition-colors">
|
|
Login
|
|
</button>
|
|
<span class="bg-warning px-2 py-1 rounded text-xs text-black font-medium">DEMO MODE</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Mobile menu button -->
|
|
<div class="md:hidden">
|
|
<button onclick="toggleMobileMenu()" class="bg-tertiary inline-flex items-center justify-center p-2 rounded-md text-secondary hover:text-primary hover:bg-accent focus:outline-none focus:ring-2 focus:ring-inset focus:ring-accent transition-colors">
|
|
<svg id="hamburger-icon" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
</svg>
|
|
<svg id="close-icon" class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile menu -->
|
|
<div id="mobile-menu" class="md:hidden hidden">
|
|
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
|
|
<a href="/" class="hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">
|
|
Browse Games
|
|
</a>
|
|
{% if current_user and current_user.role != "demo" %}
|
|
<a href="/favorites" class="hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">
|
|
My Favorites
|
|
</a>
|
|
{% endif %}
|
|
<button onclick="toggleGenresSidebar()" class="hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium w-full text-left">
|
|
Browse Genres
|
|
</button>
|
|
{% if current_user and current_user.role == "super" %}
|
|
<a href="/admin" class="hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">
|
|
Admin
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Mobile User Info -->
|
|
<div class="pt-4 pb-3 border-t border-gray-700">
|
|
<div class="px-5">
|
|
<!-- Mobile Theme Picker -->
|
|
<div class="mb-4">
|
|
<p class="text-sm text-gray-400 mb-2">Theme</p>
|
|
<div class="grid grid-cols-3 gap-2">
|
|
<button onclick="setTheme('default')" class="flex flex-col items-center p-2 bg-gray-700 hover:bg-gray-600 rounded text-xs">
|
|
<div class="w-4 h-4 rounded-full bg-gray-700 mb-1"></div>
|
|
Default
|
|
</button>
|
|
<button onclick="setTheme('tokyo-night')" class="flex flex-col items-center p-2 bg-gray-700 hover:bg-gray-600 rounded text-xs">
|
|
<div class="w-4 h-4 rounded-full bg-purple-600 mb-1"></div>
|
|
Tokyo
|
|
</button>
|
|
<button onclick="setTheme('cyberpunk')" class="flex flex-col items-center p-2 bg-gray-700 hover:bg-gray-600 rounded text-xs">
|
|
<div class="w-4 h-4 rounded-full bg-pink-500 mb-1"></div>
|
|
Cyber
|
|
</button>
|
|
<button onclick="setTheme('ultra-retro')" class="flex flex-col items-center p-2 bg-gray-700 hover:bg-gray-600 rounded text-xs">
|
|
<div class="w-4 h-4 rounded-full bg-green-500 mb-1"></div>
|
|
Retro
|
|
</button>
|
|
<button onclick="setTheme('ocean')" class="flex flex-col items-center p-2 bg-gray-700 hover:bg-gray-600 rounded text-xs">
|
|
<div class="w-4 h-4 rounded-full bg-blue-500 mb-1"></div>
|
|
Ocean
|
|
</button>
|
|
<button onclick="setTheme('sunset')" class="flex flex-col items-center p-2 bg-gray-700 hover:bg-gray-600 rounded text-xs">
|
|
<div class="w-4 h-4 rounded-full bg-orange-500 mb-1"></div>
|
|
Sunset
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{% if current_user %}
|
|
<div class="text-base font-medium text-primary">{{ current_user.username }}</div>
|
|
<button onclick="logout()" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded text-sm w-full mt-3 transition-colors">
|
|
Logout
|
|
</button>
|
|
{% else %}
|
|
<button onclick="showLogin()" class="bg-accent hover:bg-accent-hover px-4 py-2 rounded text-sm w-full mb-2 text-primary transition-colors">
|
|
Login
|
|
</button>
|
|
<span class="bg-warning px-2 py-1 rounded text-xs text-black font-medium">DEMO MODE</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Genres Sidebar -->
|
|
<div id="genresSidebar" class="fixed inset-y-0 left-0 w-80 bg-secondary border-r border-theme transform -translate-x-full transition-transform duration-300 ease-in-out z-50">
|
|
<div class="h-full flex flex-col p-4">
|
|
<div class="flex justify-between items-center mb-4 flex-shrink-0">
|
|
<h3 class="text-lg font-medium text-primary">Browse by Genre</h3>
|
|
<button onclick="toggleGenresSidebar()" class="text-secondary hover:text-primary">×</button>
|
|
</div>
|
|
<div id="genresContainer" class="space-y-2 flex-1 overflow-y-auto">
|
|
<p class="text-secondary text-sm">Loading genres...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overlay -->
|
|
<div id="genresOverlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden" onclick="toggleGenresSidebar()"></div>
|
|
|
|
<main class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
<!-- Login Modal -->
|
|
<div id="loginModal" class="hidden fixed inset-0 bg-black bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
|
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-secondary border-theme">
|
|
<div class="mt-3">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-medium text-primary">Login</h3>
|
|
<button onclick="hideLogin()" class="text-secondary hover:text-primary">×</button>
|
|
</div>
|
|
<form onsubmit="handleLogin(event)" class="space-y-4">
|
|
<div>
|
|
<input type="text" id="username" placeholder="Username" required
|
|
class="w-full px-3 py-2 bg-tertiary border border-theme rounded-md text-primary placeholder-secondary focus:outline-none focus:ring-2 focus:ring-accent">
|
|
</div>
|
|
<div>
|
|
<input type="password" id="password" placeholder="Password" required
|
|
class="w-full px-3 py-2 bg-tertiary border border-theme rounded-md text-primary placeholder-secondary focus:outline-none focus:ring-2 focus:ring-accent">
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<button type="button" onclick="hideLogin()" class="px-4 py-2 bg-tertiary hover:bg-accent rounded-md text-primary transition-colors">
|
|
Cancel
|
|
</button>
|
|
<button type="submit" class="px-4 py-2 bg-accent hover:bg-accent-hover rounded-md text-primary transition-colors">
|
|
Login
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let authToken = localStorage.getItem('authToken');
|
|
|
|
function toggleMobileMenu() {
|
|
const mobileMenu = document.getElementById('mobile-menu');
|
|
const hamburgerIcon = document.getElementById('hamburger-icon');
|
|
const closeIcon = document.getElementById('close-icon');
|
|
|
|
mobileMenu.classList.toggle('hidden');
|
|
hamburgerIcon.classList.toggle('hidden');
|
|
closeIcon.classList.toggle('hidden');
|
|
}
|
|
|
|
function toggleThemeMenu() {
|
|
const themeMenu = document.getElementById('theme-menu');
|
|
themeMenu.classList.toggle('hidden');
|
|
}
|
|
|
|
function setTheme(themeName) {
|
|
// Remove all existing theme classes
|
|
document.body.classList.remove('theme-tokyo-night', 'theme-cyberpunk', 'theme-ultra-retro', 'theme-ocean', 'theme-sunset');
|
|
|
|
// Add the selected theme class
|
|
if (themeName !== 'default') {
|
|
document.body.classList.add(`theme-${themeName}`);
|
|
}
|
|
|
|
// Save theme preference
|
|
localStorage.setItem('dosvault-theme', themeName);
|
|
|
|
// Hide theme menu
|
|
document.getElementById('theme-menu').classList.add('hidden');
|
|
}
|
|
|
|
// Load saved theme on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const savedTheme = localStorage.getItem('dosvault-theme');
|
|
if (savedTheme && savedTheme !== 'default') {
|
|
document.body.classList.add(`theme-${savedTheme}`);
|
|
}
|
|
});
|
|
|
|
// Close theme menu when clicking outside
|
|
document.addEventListener('click', function(event) {
|
|
const themeMenu = document.getElementById('theme-menu');
|
|
const themeButton = event.target.closest('[onclick="toggleThemeMenu()"]');
|
|
|
|
if (!themeButton && !themeMenu.contains(event.target)) {
|
|
themeMenu.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
function showLogin() {
|
|
document.getElementById('loginModal').classList.remove('hidden');
|
|
}
|
|
|
|
function hideLogin() {
|
|
document.getElementById('loginModal').classList.add('hidden');
|
|
}
|
|
|
|
async function handleLogin(event) {
|
|
event.preventDefault();
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
|
|
const formData = new FormData();
|
|
formData.append('username', username);
|
|
formData.append('password', password);
|
|
|
|
try {
|
|
const response = await fetch('/login', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
localStorage.setItem('authToken', data.access_token);
|
|
window.location.reload();
|
|
} else {
|
|
alert('Login failed. Please check your credentials.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
alert('Login failed. Please try again.');
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
try {
|
|
await fetch('/logout', { method: 'POST' });
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
}
|
|
localStorage.removeItem('authToken');
|
|
window.location.reload();
|
|
}
|
|
|
|
// Set auth header if token exists
|
|
if (authToken) {
|
|
fetch.defaults = {
|
|
headers: {
|
|
'Authorization': `Bearer ${authToken}`
|
|
}
|
|
};
|
|
}
|
|
|
|
let genresLoaded = false;
|
|
|
|
function toggleGenresSidebar() {
|
|
const sidebar = document.getElementById('genresSidebar');
|
|
const overlay = document.getElementById('genresOverlay');
|
|
const isOpen = !sidebar.classList.contains('-translate-x-full');
|
|
|
|
if (isOpen) {
|
|
sidebar.classList.add('-translate-x-full');
|
|
overlay.classList.add('hidden');
|
|
} else {
|
|
sidebar.classList.remove('-translate-x-full');
|
|
overlay.classList.remove('hidden');
|
|
if (!genresLoaded) {
|
|
loadGenres();
|
|
}
|
|
}
|
|
}
|
|
|
|
async function loadGenres() {
|
|
try {
|
|
const response = await fetch('/api/genres');
|
|
const genres = await response.json();
|
|
const container = document.getElementById('genresContainer');
|
|
|
|
if (genres.length === 0) {
|
|
container.innerHTML = '<p class="text-secondary text-sm">No genres found</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = genres
|
|
.sort((a, b) => b.count - a.count)
|
|
.map(genre => `
|
|
<a href="/browse/genres/${encodeURIComponent(genre.name)}"
|
|
class="block p-2 bg-tertiary hover:bg-accent rounded text-sm text-primary transition-colors">
|
|
${genre.name} <span class="text-secondary">(${genre.count})</span>
|
|
</a>
|
|
`).join('');
|
|
|
|
genresLoaded = true;
|
|
} catch (error) {
|
|
console.error('Error loading genres:', error);
|
|
document.getElementById('genresContainer').innerHTML =
|
|
'<p class="text-red-400 text-sm">Error loading genres</p>';
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |