Finished theming
This commit is contained in:
@@ -55,17 +55,17 @@
|
|||||||
<h2 class="text-2xl font-bold mb-6">System Management</h2>
|
<h2 class="text-2xl font-bold mb-6">System Management</h2>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
|
||||||
<!-- ROM Scanning -->
|
<!-- Game Scanning -->
|
||||||
<div class="bg-secondary rounded-lg p-6 border border-theme">
|
<div class="bg-secondary rounded-lg p-6 border border-theme">
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center mb-4">
|
||||||
<svg class="w-8 h-8 text-accent mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-8 h-8 text-accent mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 class="text-lg font-semibold">ROM Scanner</h3>
|
<h3 class="text-lg font-semibold">Game Scanner</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-secondary text-sm mb-4">Scan directories for new ROM files and add them to the database</p>
|
<p class="text-secondary text-sm mb-4">Scan directories for new Game files and add them to the database</p>
|
||||||
<button onclick="triggerRomScan()" class="w-full bg-accent hover:bg-accent px-4 py-2 rounded font-medium transition-colors" id="scan-btn">
|
<button onclick="triggerGameScan()" class="w-full bg-accent hover:bg-accent px-4 py-2 rounded font-medium transition-colors" id="scan-btn">
|
||||||
<span class="scan-text">Start ROM Scan</span>
|
<span class="scan-text">Start Game Scan</span>
|
||||||
<span class="scan-loading hidden">Scanning...</span>
|
<span class="scan-loading hidden">Scanning...</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="scan-status" class="mt-2 text-sm text-secondary"></div>
|
<div id="scan-status" class="mt-2 text-sm text-secondary"></div>
|
||||||
@@ -318,8 +318,8 @@
|
|||||||
<h4 class="text-lg font-semibold text-accent">Paths</h4>
|
<h4 class="text-lg font-semibold text-accent">Paths</h4>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-secondary mb-1">ROM Directory</label>
|
<label class="block text-sm font-medium text-secondary mb-1">Game Directory</label>
|
||||||
<input type="text" id="config-rom-path" class="w-full px-3 py-2 bg-tertiary border border-theme rounded text-primary" placeholder="/path/to/roms">
|
<input type="text" id="config-rom-path" class="w-full px-3 py-2 bg-tertiary border border-theme rounded text-primary" placeholder="/path/to/games">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -415,7 +415,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function triggerRomScan() {
|
async function triggerGameScan() {
|
||||||
const btn = document.getElementById('scan-btn');
|
const btn = document.getElementById('scan-btn');
|
||||||
const status = document.getElementById('scan-status');
|
const status = document.getElementById('scan-status');
|
||||||
const scanText = btn.querySelector('.scan-text');
|
const scanText = btn.querySelector('.scan-text');
|
||||||
@@ -426,7 +426,7 @@ async function triggerRomScan() {
|
|||||||
scanLoading.classList.remove('hidden');
|
scanLoading.classList.remove('hidden');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/admin/rom-scan', {
|
const response = await fetch('/api/admin/game-scan', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${getCookie('auth_token')}`
|
'Authorization': `Bearer ${getCookie('auth_token')}`
|
||||||
@@ -436,17 +436,17 @@ async function triggerRomScan() {
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.status === 'started') {
|
if (result.status === 'started') {
|
||||||
status.textContent = 'ROM scan started...';
|
status.textContent = 'Game scan started...';
|
||||||
status.className = 'mt-2 text-sm text-accent';
|
status.className = 'mt-2 text-sm text-accent';
|
||||||
|
|
||||||
// Poll for completion
|
// Poll for completion
|
||||||
pollTaskStatus('rom_scan', status);
|
pollTaskStatus('game_scan', status);
|
||||||
} else if (result.status === 'already_running') {
|
} else if (result.status === 'already_running') {
|
||||||
status.textContent = 'ROM scan already in progress';
|
status.textContent = 'Game scan already in progress';
|
||||||
status.className = 'mt-2 text-sm text-warning-color';
|
status.className = 'mt-2 text-sm text-warning-color';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
status.textContent = 'Failed to start ROM scan';
|
status.textContent = 'Failed to start Game scan';
|
||||||
status.className = 'mt-2 text-sm text-danger-color';
|
status.className = 'mt-2 text-sm text-danger-color';
|
||||||
} finally {
|
} finally {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
@@ -674,8 +674,8 @@ async function showSystemStats() {
|
|||||||
<h4 class="text-lg font-semibold mb-3 text-primary">Running Tasks</h4>
|
<h4 class="text-lg font-semibold mb-3 text-primary">Running Tasks</h4>
|
||||||
<div class="space-y-2 text-secondary">
|
<div class="space-y-2 text-secondary">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span>ROM Scan:</span>
|
<span>Game Scan:</span>
|
||||||
<span class="${stats.running_tasks.rom_scan ? 'text-accent' : 'text-secondary'}">${stats.running_tasks.rom_scan ? 'Running' : 'Idle'}</span>
|
<span class="${stats.running_tasks.game_scan ? 'text-accent' : 'text-secondary'}">${stats.running_tasks.game_scan ? 'Running' : 'Idle'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span>Metadata Refresh:</span>
|
<span>Metadata Refresh:</span>
|
||||||
@@ -1307,7 +1307,7 @@ function populateConfigForm(config) {
|
|||||||
// Populate form fields
|
// Populate form fields
|
||||||
document.getElementById('config-host').value = config.host || '';
|
document.getElementById('config-host').value = config.host || '';
|
||||||
document.getElementById('config-port').value = config.port || '';
|
document.getElementById('config-port').value = config.port || '';
|
||||||
document.getElementById('config-rom-path').value = config.rom_path || '';
|
document.getElementById('config-rom-path').value = config.game_path || '';
|
||||||
document.getElementById('config-images-path').value = config.images_path || '';
|
document.getElementById('config-images-path').value = config.images_path || '';
|
||||||
document.getElementById('config-igdb-client-id').value = config.igdb_client_id || '';
|
document.getElementById('config-igdb-client-id').value = config.igdb_client_id || '';
|
||||||
document.getElementById('config-igdb-secret').value = config.igdb_api_key || '';
|
document.getElementById('config-igdb-secret').value = config.igdb_api_key || '';
|
||||||
@@ -1320,7 +1320,7 @@ function collectConfigFromForm() {
|
|||||||
const config = {
|
const config = {
|
||||||
host: document.getElementById('config-host').value,
|
host: document.getElementById('config-host').value,
|
||||||
port: parseInt(document.getElementById('config-port').value) || 8080,
|
port: parseInt(document.getElementById('config-port').value) || 8080,
|
||||||
rom_path: document.getElementById('config-rom-path').value,
|
game_path: document.getElementById('config-rom-path').value,
|
||||||
images_path: document.getElementById('config-images-path').value,
|
images_path: document.getElementById('config-images-path').value,
|
||||||
igdb_client_id: document.getElementById('config-igdb-client-id').value,
|
igdb_client_id: document.getElementById('config-igdb-client-id').value,
|
||||||
igdb_api_key: document.getElementById('config-igdb-secret').value
|
igdb_api_key: document.getElementById('config-igdb-secret').value
|
||||||
|
|||||||
@@ -197,6 +197,8 @@
|
|||||||
.bg-tertiary { background-color: var(--tertiary-bg); }
|
.bg-tertiary { background-color: var(--tertiary-bg); }
|
||||||
.bg-accent { background-color: var(--accent-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-accent-hover { background-color: var(--accent-hover); }
|
||||||
|
.bg-warning { background-color: var(--warning-color) !important; }
|
||||||
.text-primary { color: var(--text-primary); }
|
.text-primary { color: var(--text-primary); }
|
||||||
.text-secondary { color: var(--text-secondary); }
|
.text-secondary { color: var(--text-secondary); }
|
||||||
.text-accent { color: var(--text-accent); }
|
.text-accent { color: var(--text-accent); }
|
||||||
@@ -259,7 +261,7 @@
|
|||||||
<div class="hidden md:block">
|
<div class="hidden md:block">
|
||||||
<div class="ml-10 flex items-baseline space-x-4">
|
<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">
|
<a href="/" class="hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||||
Browse ROMs
|
Browse Games
|
||||||
</a>
|
</a>
|
||||||
{% if current_user and current_user.role != "demo" %}
|
{% 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">
|
<a href="/favorites" class="hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||||
@@ -317,28 +319,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if current_user %}
|
{% if current_user %}
|
||||||
<span class="text-sm">
|
<span class="text-sm text-primary">
|
||||||
Welcome, {{ current_user.username }}
|
Welcome, {{ current_user.username }}
|
||||||
{% if current_user.role == "demo" %}
|
|
||||||
<span class="bg-yellow-600 px-2 py-1 rounded text-xs">DEMO</span>
|
|
||||||
{% elif current_user.role == "super" %}
|
|
||||||
<span class="bg-red-600 px-2 py-1 rounded text-xs">ADMIN</span>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
</span>
|
||||||
<button onclick="logout()" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded text-sm">
|
<button onclick="logout()" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded text-sm transition-colors">
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button onclick="showLogin()" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm">
|
<button onclick="showLogin()" class="bg-accent hover:bg-accent-hover px-4 py-2 rounded text-sm text-primary transition-colors">
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
<span class="bg-yellow-600 px-2 py-1 rounded text-xs">DEMO MODE</span>
|
<span class="bg-warning px-2 py-1 rounded text-xs text-black font-medium">DEMO MODE</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
<div class="md:hidden">
|
<div class="md:hidden">
|
||||||
<button onclick="toggleMobileMenu()" class="bg-gray-700 inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
<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">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -354,7 +351,7 @@
|
|||||||
<div id="mobile-menu" class="md:hidden hidden">
|
<div id="mobile-menu" class="md:hidden hidden">
|
||||||
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
|
<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">
|
<a href="/" class="hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">
|
||||||
Browse ROMs
|
Browse Games
|
||||||
</a>
|
</a>
|
||||||
{% if current_user and current_user.role != "demo" %}
|
{% 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">
|
<a href="/favorites" class="hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">
|
||||||
@@ -406,24 +403,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if current_user %}
|
{% if current_user %}
|
||||||
<div class="text-base font-medium text-white">{{ current_user.username }}</div>
|
<div class="text-base font-medium text-primary">{{ current_user.username }}</div>
|
||||||
<div class="text-sm text-gray-400 mb-3">
|
<button onclick="logout()" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded text-sm w-full mt-3 transition-colors">
|
||||||
{% if current_user.role == "demo" %}
|
|
||||||
<span class="bg-yellow-600 px-2 py-1 rounded text-xs">DEMO USER</span>
|
|
||||||
{% elif current_user.role == "super" %}
|
|
||||||
<span class="bg-red-600 px-2 py-1 rounded text-xs">ADMIN</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="bg-green-600 px-2 py-1 rounded text-xs">USER</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<button onclick="logout()" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded text-sm w-full">
|
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button onclick="showLogin()" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm w-full mb-2">
|
<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
|
Login
|
||||||
</button>
|
</button>
|
||||||
<span class="bg-yellow-600 px-2 py-1 rounded text-xs">DEMO MODE</span>
|
<span class="bg-warning px-2 py-1 rounded text-xs text-black font-medium">DEMO MODE</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -431,14 +419,14 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Genres Sidebar -->
|
<!-- Genres Sidebar -->
|
||||||
<div id="genresSidebar" class="fixed inset-y-0 left-0 w-80 bg-gray-800 border-r border-gray-700 transform -translate-x-full transition-transform duration-300 ease-in-out z-50">
|
<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="h-full flex flex-col p-4">
|
||||||
<div class="flex justify-between items-center mb-4 flex-shrink-0">
|
<div class="flex justify-between items-center mb-4 flex-shrink-0">
|
||||||
<h3 class="text-lg font-medium text-white">Browse by Genre</h3>
|
<h3 class="text-lg font-medium text-primary">Browse by Genre</h3>
|
||||||
<button onclick="toggleGenresSidebar()" class="text-gray-400 hover:text-white">×</button>
|
<button onclick="toggleGenresSidebar()" class="text-secondary hover:text-primary">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="genresContainer" class="space-y-2 flex-1 overflow-y-auto">
|
<div id="genresContainer" class="space-y-2 flex-1 overflow-y-auto">
|
||||||
<p class="text-gray-400 text-sm">Loading genres...</p>
|
<p class="text-secondary text-sm">Loading genres...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -451,27 +439,27 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Login Modal -->
|
<!-- Login Modal -->
|
||||||
<div id="loginModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
<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-gray-800 border-gray-700">
|
<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="mt-3">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h3 class="text-lg font-medium text-white">Login</h3>
|
<h3 class="text-lg font-medium text-primary">Login</h3>
|
||||||
<button onclick="hideLogin()" class="text-gray-400 hover:text-white">×</button>
|
<button onclick="hideLogin()" class="text-secondary hover:text-primary">×</button>
|
||||||
</div>
|
</div>
|
||||||
<form onsubmit="handleLogin(event)" class="space-y-4">
|
<form onsubmit="handleLogin(event)" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<input type="text" id="username" placeholder="Username" required
|
<input type="text" id="username" placeholder="Username" required
|
||||||
class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
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>
|
||||||
<div>
|
<div>
|
||||||
<input type="password" id="password" placeholder="Password" required
|
<input type="password" id="password" placeholder="Password" required
|
||||||
class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
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>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<button type="button" onclick="hideLogin()" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 rounded-md">
|
<button type="button" onclick="hideLogin()" class="px-4 py-2 bg-tertiary hover:bg-accent rounded-md text-primary transition-colors">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md">
|
<button type="submit" class="px-4 py-2 bg-accent hover:bg-accent-hover rounded-md text-primary transition-colors">
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -613,7 +601,7 @@
|
|||||||
const container = document.getElementById('genresContainer');
|
const container = document.getElementById('genresContainer');
|
||||||
|
|
||||||
if (genres.length === 0) {
|
if (genres.length === 0) {
|
||||||
container.innerHTML = '<p class="text-gray-400 text-sm">No genres found</p>';
|
container.innerHTML = '<p class="text-secondary text-sm">No genres found</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,8 +609,8 @@
|
|||||||
.sort((a, b) => b.count - a.count)
|
.sort((a, b) => b.count - a.count)
|
||||||
.map(genre => `
|
.map(genre => `
|
||||||
<a href="/browse/genres/${encodeURIComponent(genre.name)}"
|
<a href="/browse/genres/${encodeURIComponent(genre.name)}"
|
||||||
class="block p-2 bg-gray-700 hover:bg-gray-600 rounded text-sm text-white">
|
class="block p-2 bg-tertiary hover:bg-accent rounded text-sm text-primary transition-colors">
|
||||||
${genre.name} <span class="text-gray-400">(${genre.count})</span>
|
${genre.name} <span class="text-secondary">(${genre.count})</span>
|
||||||
</a>
|
</a>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold mb-2">My Favorites</h1>
|
<h1 class="text-3xl font-bold mb-2">My Favorites</h1>
|
||||||
<p class="text-gray-400">Your personally selected ROM collection</p>
|
<p class="text-gray-400">Your personally selected game collection</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if games %}
|
{% if games %}
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
<h2 class="text-2xl font-bold mb-2">No favorites yet</h2>
|
<h2 class="text-2xl font-bold mb-2">No favorites yet</h2>
|
||||||
<p class="text-gray-400 mb-6">Start browsing and add games to your favorites collection!</p>
|
<p class="text-gray-400 mb-6">Start browsing and add games to your favorites collection!</p>
|
||||||
<a href="/" class="bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded-lg text-white">
|
<a href="/" class="bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded-lg text-white">
|
||||||
Browse ROMs
|
Browse Games
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -130,7 +130,10 @@
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
// Get filename from Content-Disposition header, removing quotes and underscores
|
||||||
|
let filename = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
||||||
|
filename = filename.replace(/^["_]+|["_]+$/g, ''); // Remove quotes and underscores from start and end
|
||||||
|
a.download = filename;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<nav class="text-sm text-gray-400 mb-4">
|
<nav class="text-sm text-gray-400 mb-4">
|
||||||
<a href="/" class="hover:text-white">Browse ROMs</a>
|
<a href="/" class="hover:text-white">Browse Games</a>
|
||||||
<span class="mx-2">/</span>
|
<span class="mx-2">/</span>
|
||||||
<span class="text-white">{{ game.metadata_obj.title or game.title }}</span>
|
<span class="text-white">{{ game.metadata_obj.title or game.title }}</span>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
{% if can_download %}
|
{% if can_download %}
|
||||||
<button onclick="downloadGame({{ game.id }})"
|
<button onclick="downloadGame({{ game.id }})"
|
||||||
class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded">
|
class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded">
|
||||||
Download ROM
|
Download Game
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="bg-gray-600 px-4 py-2 rounded cursor-not-allowed">
|
<span class="bg-gray-600 px-4 py-2 rounded cursor-not-allowed">
|
||||||
@@ -268,7 +268,10 @@
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
// Get filename from Content-Disposition header, removing quotes and underscores
|
||||||
|
let filename = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
||||||
|
filename = filename.replace(/^["_]+|["_]+$/g, ''); // Remove quotes and underscores from start and end
|
||||||
|
a.download = filename;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}ROM Library - DOS Frontend{% endblock %}
|
{% block title %}Game Library - DOS Frontend{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4">
|
||||||
<h1 class="text-3xl font-bold mb-2 sm:mb-0">ROM Library</h1>
|
<h1 class="text-3xl font-bold mb-2 sm:mb-0">Game Library</h1>
|
||||||
<div class="flex flex-col sm:flex-row gap-2">
|
<div class="flex flex-col sm:flex-row gap-2">
|
||||||
<!-- Search Form -->
|
<!-- Search Form -->
|
||||||
<form method="GET" class="flex gap-2">
|
<form method="GET" class="flex gap-2">
|
||||||
@@ -15,26 +15,26 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input type="text" name="search" placeholder="Search games..."
|
<input type="text" name="search" placeholder="Search games..."
|
||||||
value="{% if search and not search.startswith('genre:') and not search.startswith('tag:') %}{{ search }}{% endif %}"
|
value="{% if search and not search.startswith('genre:') and not search.startswith('tag:') %}{{ search }}{% endif %}"
|
||||||
class="w-full sm:w-64 px-4 py-2 pl-10 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full sm:w-64 px-4 py-2 pl-10 bg-tertiary border border-theme rounded-lg text-primary placeholder-secondary focus:outline-none focus:ring-2 focus:ring-accent">
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white">Search</button>
|
<button type="submit" class="px-4 py-2 bg-accent hover:bg-accent-hover rounded-lg text-primary transition-colors">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Controls Bar -->
|
<!-- Controls Bar -->
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
|
||||||
<div class="text-gray-400">
|
<div class="text-secondary">
|
||||||
{% if is_demo %}
|
{% if is_demo %}
|
||||||
<span class="text-yellow-400">Demo Mode:</span> You can browse ROMs but cannot download or favorite them.
|
<span class="text-yellow-400">Demo Mode:</span> You can browse games but cannot download or favorite them.
|
||||||
<button onclick="showLogin()" class="text-blue-400 hover:text-blue-300 underline">Login</button> for full access.
|
<button onclick="showLogin()" class="text-blue-400 hover:text-blue-300 underline">Login</button> for full access.
|
||||||
{% else %}
|
{% else %}
|
||||||
Showing {{ games|length }} of {{ total_games }} ROMs
|
Showing {{ games|length }} of {{ total_games }} games
|
||||||
{% if search %} for "{{ search }}"{% endif %}
|
{% if search %} for "{{ search }}"{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
<div class="flex flex-col sm:flex-row gap-2 sm:gap-4 items-center">
|
<div class="flex flex-col sm:flex-row gap-2 sm:gap-4 items-center">
|
||||||
<!-- Results per page -->
|
<!-- Results per page -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label class="text-gray-400 text-sm">Show:</label>
|
<label class="text-secondary text-sm">Show:</label>
|
||||||
<select onchange="changePerPage(this.value)" class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-white text-sm min-w-16">
|
<select onchange="changePerPage(this.value)" class="bg-tertiary border border-theme rounded px-2 py-1 text-primary text-sm min-w-16">
|
||||||
<option value="20" {% if per_page == 20 %}selected{% endif %}>20</option>
|
<option value="20" {% if per_page == 20 %}selected{% endif %}>20</option>
|
||||||
<option value="50" {% if per_page == 50 %}selected{% endif %}>50</option>
|
<option value="50" {% if per_page == 50 %}selected{% endif %}>50</option>
|
||||||
<option value="100" {% if per_page == 100 %}selected{% endif %}>100</option>
|
<option value="100" {% if per_page == 100 %}selected{% endif %}>100</option>
|
||||||
@@ -51,16 +51,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- View Toggle -->
|
<!-- View Toggle -->
|
||||||
<div class="flex bg-gray-700 rounded-lg p-1">
|
<div class="flex bg-tertiary rounded-lg p-1">
|
||||||
<button onclick="changeView('grid')"
|
<button onclick="changeView('grid')"
|
||||||
class="px-4 py-2 rounded text-sm touch-manipulation {{ 'bg-blue-600 text-white' if view == 'grid' else 'text-gray-400 hover:text-white active:bg-gray-600' }}">
|
class="px-4 py-2 rounded text-sm touch-manipulation transition-colors {{ 'bg-accent text-primary' if view == 'grid' else 'text-secondary hover:text-primary active:bg-secondary' }}">
|
||||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path>
|
<path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="ml-1 hidden sm:inline">Grid</span>
|
<span class="ml-1 hidden sm:inline">Grid</span>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="changeView('list')"
|
<button onclick="changeView('list')"
|
||||||
class="px-4 py-2 rounded text-sm touch-manipulation {{ 'bg-blue-600 text-white' if view == 'list' else 'text-gray-400 hover:text-white active:bg-gray-600' }}">
|
class="px-4 py-2 rounded text-sm touch-manipulation transition-colors {{ 'bg-accent text-primary' if view == 'list' else 'text-secondary hover:text-primary active:bg-secondary' }}">
|
||||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -75,29 +75,29 @@
|
|||||||
{% if view == 'grid' %}
|
{% if view == 'grid' %}
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||||
{% for game in games %}
|
{% for game in games %}
|
||||||
<div class="bg-gray-800 rounded-lg border border-gray-700 hover:border-gray-600 transition-colors overflow-hidden hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
<div class="bg-secondary rounded-lg border border-theme hover:border-accent transition-colors overflow-hidden hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
||||||
<!-- Clickable overlay for the card -->
|
<!-- Clickable overlay for the card -->
|
||||||
<div onclick="showGameDetail({{ game.id }})" class="absolute inset-0 z-10 cursor-pointer" aria-label="View {{ game.metadata_obj.title or game.title }} details"></div>
|
<div onclick="showGameDetail({{ game.id }})" class="absolute inset-0 z-10 cursor-pointer" aria-label="View {{ game.metadata_obj.title or game.title }} details"></div>
|
||||||
|
|
||||||
<!-- Cover Image -->
|
<!-- Cover Image -->
|
||||||
<div class="aspect-[3/4] bg-gray-900 relative overflow-hidden">
|
<div class="aspect-[3/4] bg-primary relative overflow-hidden">
|
||||||
{% if game.metadata_obj and (game.metadata_obj.cover_image_path or (game.metadata_obj.cover_image and game.metadata_obj.cover_image.startswith('http'))) %}
|
{% if game.metadata_obj and (game.metadata_obj.cover_image_path or (game.metadata_obj.cover_image and game.metadata_obj.cover_image.startswith('http'))) %}
|
||||||
<img data-game-id="{{ game.id }}"
|
<img data-game-id="{{ game.id }}"
|
||||||
{% if game.metadata_obj.cover_image_path %}src="/images/{{ game.metadata_obj.cover_image_path.name }}"{% else %}src="{{ game.metadata_obj.cover_image }}"{% endif %}
|
{% if game.metadata_obj.cover_image_path %}src="/images/{{ game.metadata_obj.cover_image_path.name }}"{% else %}src="{{ game.metadata_obj.cover_image }}"{% endif %}
|
||||||
alt="{{ game.metadata_obj.title or game.title }}"
|
alt="{{ game.metadata_obj.title or game.title }}"
|
||||||
class="w-full h-full object-cover transition-transform duration-200 group-hover:scale-105"
|
class="w-full h-full object-cover transition-transform duration-200 group-hover:scale-105"
|
||||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||||||
<div class="w-full h-full bg-gradient-to-br from-gray-800 to-gray-900 hidden items-center justify-center">
|
<div class="w-full h-full bg-gradient-theme hidden items-center justify-center absolute inset-0">
|
||||||
<div class="text-gray-400 text-center p-4">
|
<div class="text-secondary text-center p-4">
|
||||||
<svg class="w-16 h-16 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-16 h-16 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2h2a2 2 0 002-2z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2h2a2 2 0 002-2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="text-sm font-medium">{{ (game.metadata_obj.title or game.title)[:20] }}{% if (game.metadata_obj.title or game.title)|length > 20 %}...{% endif %}</p>
|
<p class="text-sm font-medium">{{ (game.metadata_obj.title or game.title)[:20] }}{% if (game.metadata_obj.title or game.title)|length > 20 %}...{% endif %}</p>
|
||||||
<p class="text-xs text-gray-500 mt-1">DOS Game</p>
|
<p class="text-xs text-secondary opacity-75 mt-1">DOS Game</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden">
|
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden absolute inset-0">
|
||||||
<!-- Background Pattern -->
|
<!-- Background Pattern -->
|
||||||
<div class="absolute inset-0 opacity-10">
|
<div class="absolute inset-0 opacity-10">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="100%" height="100%" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="text-center p-3 relative z-10">
|
<div class="text-center p-3 relative z-10 w-full h-full flex flex-col justify-center items-center">
|
||||||
<!-- DosVault Logo -->
|
<!-- DosVault Logo -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<svg class="w-20 h-20 mx-auto text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-20 h-20 mx-auto text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -188,23 +188,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Game Info -->
|
<!-- Game Info -->
|
||||||
<div class="p-3">
|
<div class="p-3 bg-tertiary border-t border-theme">
|
||||||
<h3 class="font-semibold text-blue-400 truncate mb-1 text-sm">
|
<h3 class="font-semibold text-accent truncate mb-1 text-sm">
|
||||||
{{ game.metadata_obj.title or game.title }}
|
{{ game.metadata_obj.title or game.title }}
|
||||||
</h3>
|
</h3>
|
||||||
{% if game.metadata_obj and game.metadata_obj.year %}
|
{% if game.metadata_obj and game.metadata_obj.year %}
|
||||||
<p class="text-xs text-gray-400 mb-2">{{ game.metadata_obj.year }}</p>
|
<p class="text-xs text-secondary mb-2">{{ game.metadata_obj.year }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-xs text-gray-500">Click to view details</span>
|
<span class="text-xs text-secondary">Click to view details</span>
|
||||||
{% if not is_demo %}
|
{% if not is_demo %}
|
||||||
<button onclick="event.stopPropagation(); downloadGame({{ game.id }})"
|
<button onclick="event.stopPropagation(); downloadGame({{ game.id }})"
|
||||||
class="bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-xs z-20 relative">
|
class="bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-xs z-20 relative transition-colors">
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="bg-gray-600 px-2 py-1 rounded text-xs cursor-not-allowed">
|
<span class="bg-tertiary border border-theme px-2 py-1 rounded text-xs cursor-not-allowed text-secondary">
|
||||||
Login
|
Login
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -219,20 +219,20 @@
|
|||||||
{% if view == 'list' %}
|
{% if view == 'list' %}
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
{% for game in games %}
|
{% for game in games %}
|
||||||
<div class="bg-gray-800 rounded-lg border border-gray-700 hover:border-gray-600 transition-colors hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
<div class="bg-secondary rounded-lg border border-theme hover:border-accent transition-colors hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
||||||
<!-- Clickable overlay for the card -->
|
<!-- Clickable overlay for the card -->
|
||||||
<div onclick="showGameDetail({{ game.id }})" class="absolute inset-0 z-10 cursor-pointer" aria-label="View {{ game.metadata_obj.title or game.title }} details"></div>
|
<div onclick="showGameDetail({{ game.id }})" class="absolute inset-0 z-10 cursor-pointer" aria-label="View {{ game.metadata_obj.title or game.title }} details"></div>
|
||||||
|
|
||||||
<div class="p-4 flex items-center gap-4">
|
<div class="p-4 flex items-center gap-4">
|
||||||
<!-- Cover Image -->
|
<!-- Cover Image -->
|
||||||
<div class="w-16 h-20 bg-gray-900 rounded overflow-hidden flex-shrink-0 relative">
|
<div class="w-16 h-20 bg-primary rounded overflow-hidden flex-shrink-0 relative">
|
||||||
{% if game.metadata_obj and (game.metadata_obj.cover_image_path or (game.metadata_obj.cover_image and game.metadata_obj.cover_image.startswith('http'))) %}
|
{% if game.metadata_obj and (game.metadata_obj.cover_image_path or (game.metadata_obj.cover_image and game.metadata_obj.cover_image.startswith('http'))) %}
|
||||||
<img data-game-id="{{ game.id }}"
|
<img data-game-id="{{ game.id }}"
|
||||||
{% if game.metadata_obj.cover_image_path %}src="/images/{{ game.metadata_obj.cover_image_path.name }}"{% else %}src="{{ game.metadata_obj.cover_image }}"{% endif %}
|
{% if game.metadata_obj.cover_image_path %}src="/images/{{ game.metadata_obj.cover_image_path.name }}"{% else %}src="{{ game.metadata_obj.cover_image }}"{% endif %}
|
||||||
alt="{{ game.metadata_obj.title or game.title }}"
|
alt="{{ game.metadata_obj.title or game.title }}"
|
||||||
class="w-full h-full object-cover"
|
class="w-full h-full object-cover"
|
||||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||||||
<div class="w-full h-full bg-gradient-theme hidden items-center justify-center relative overflow-hidden">
|
<div class="w-full h-full bg-gradient-theme hidden items-center justify-center relative overflow-hidden absolute inset-0">
|
||||||
<!-- Background Pattern -->
|
<!-- Background Pattern -->
|
||||||
<div class="absolute inset-0 opacity-10">
|
<div class="absolute inset-0 opacity-10">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="100%" height="100%" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -246,10 +246,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="text-center p-3 relative z-10">
|
<div class="text-center p-1 relative z-10 w-full h-full flex flex-col justify-center items-center">
|
||||||
<!-- DosVault Logo -->
|
<!-- DosVault Logo -->
|
||||||
<div class="mb-4">
|
<div class="mb-1">
|
||||||
<svg class="w-20 h-20 mx-auto text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-6 h-6 mx-auto text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<!-- Vault door with enhanced styling -->
|
<!-- Vault door with enhanced styling -->
|
||||||
<circle cx="16" cy="16" r="15" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/>
|
<circle cx="16" cy="16" r="15" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/>
|
||||||
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.8"/>
|
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.8"/>
|
||||||
@@ -273,18 +273,9 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Game Title -->
|
|
||||||
<div class="mb-2">
|
|
||||||
<h3 class="text-accent font-bold text-sm leading-tight mb-1">
|
|
||||||
{{ (game.metadata_obj.title or game.title)[:18] }}{% if (game.metadata_obj.title or game.title)|length > 18 %}...{% endif %}
|
|
||||||
</h3>
|
|
||||||
<div class="w-12 h-0.5 bg-accent mx-auto opacity-60"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Branding -->
|
<!-- Branding -->
|
||||||
<div class="text-xs text-secondary opacity-75 font-medium">
|
<div class="text-[6px] text-accent font-bold tracking-wide opacity-75">
|
||||||
<div class="mb-1">CLASSIC DOS GAME</div>
|
DOSVAULT
|
||||||
<div class="text-accent text-[10px] font-bold tracking-wider">DOSVAULT</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Decorative Corner Elements -->
|
<!-- Decorative Corner Elements -->
|
||||||
@@ -311,9 +302,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden">
|
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden absolute inset-0">
|
||||||
<!-- Compact Background Pattern -->
|
<!-- Compact Background Pattern -->
|
||||||
<div class="absolute inset-0 opacity-8">
|
<div class="absolute inset-0 opacity-10">
|
||||||
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none">
|
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="pixel-grid-small" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
|
<pattern id="pixel-grid-small" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
|
||||||
@@ -325,8 +316,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Compact Logo -->
|
<!-- Compact Logo -->
|
||||||
<div class="text-center relative z-10">
|
<div class="text-center relative z-10 w-full h-full flex flex-col justify-center items-center p-1">
|
||||||
<svg class="w-8 h-8 mx-auto text-accent mb-1" viewBox="0 0 32 32" fill="none">
|
<svg class="w-6 h-6 mx-auto text-accent mb-1" viewBox="0 0 32 32" fill="none">
|
||||||
<!-- Simplified vault door -->
|
<!-- Simplified vault door -->
|
||||||
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="1.5" fill="currentColor" opacity="0.1"/>
|
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="1.5" fill="currentColor" opacity="0.1"/>
|
||||||
<circle cx="16" cy="16" r="8" stroke="currentColor" stroke-width="1" opacity="0.6"/>
|
<circle cx="16" cy="16" r="8" stroke="currentColor" stroke-width="1" opacity="0.6"/>
|
||||||
@@ -342,7 +333,7 @@
|
|||||||
<rect x="20" y="14" width="6" height="4" rx="2" fill="currentColor" opacity="0.7"/>
|
<rect x="20" y="14" width="6" height="4" rx="2" fill="currentColor" opacity="0.7"/>
|
||||||
<circle cx="22" cy="16" r="0.8" fill="var(--primary-bg)"/>
|
<circle cx="22" cy="16" r="0.8" fill="var(--primary-bg)"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="text-accent text-[8px] font-bold tracking-wide opacity-75">DOSVAULT</div>
|
<div class="text-accent text-[6px] font-bold tracking-wide opacity-75">DOSVAULT</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -401,7 +392,7 @@
|
|||||||
<nav class="flex items-center space-x-1">
|
<nav class="flex items-center space-x-1">
|
||||||
{% if current_page > 1 %}
|
{% if current_page > 1 %}
|
||||||
<a href="javascript:void(0)" onclick="goToPage({{ current_page - 1 }})"
|
<a href="javascript:void(0)" onclick="goToPage({{ current_page - 1 }})"
|
||||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||||
Previous
|
Previous
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -409,21 +400,21 @@
|
|||||||
<!-- First page -->
|
<!-- First page -->
|
||||||
{% if current_page > 6 %}
|
{% if current_page > 6 %}
|
||||||
<a href="javascript:void(0)" onclick="goToPage(1)"
|
<a href="javascript:void(0)" onclick="goToPage(1)"
|
||||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||||
1
|
1
|
||||||
</a>
|
</a>
|
||||||
{% if current_page > 7 %}
|
{% if current_page > 7 %}
|
||||||
<span class="px-3 py-2 text-gray-400">...</span>
|
<span class="px-3 py-2 text-secondary">...</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Page numbers around current page -->
|
<!-- Page numbers around current page -->
|
||||||
{% for page_num in range(max(1, current_page - 4), min(total_pages + 1, current_page + 5)) %}
|
{% for page_num in range(max(1, current_page - 4), min(total_pages + 1, current_page + 5)) %}
|
||||||
{% if page_num == current_page %}
|
{% if page_num == current_page %}
|
||||||
<span class="px-3 py-2 bg-blue-600 rounded text-sm">{{ page_num }}</span>
|
<span class="px-3 py-2 bg-accent rounded text-sm text-primary">{{ page_num }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="javascript:void(0)" onclick="goToPage({{ page_num }})"
|
<a href="javascript:void(0)" onclick="goToPage({{ page_num }})"
|
||||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||||
{{ page_num }}
|
{{ page_num }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -432,17 +423,17 @@
|
|||||||
<!-- Last page -->
|
<!-- Last page -->
|
||||||
{% if current_page < total_pages - 5 %}
|
{% if current_page < total_pages - 5 %}
|
||||||
{% if current_page < total_pages - 6 %}
|
{% if current_page < total_pages - 6 %}
|
||||||
<span class="px-3 py-2 text-gray-400">...</span>
|
<span class="px-3 py-2 text-secondary">...</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="javascript:void(0)" onclick="goToPage({{ total_pages }})"
|
<a href="javascript:void(0)" onclick="goToPage({{ total_pages }})"
|
||||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||||
{{ total_pages }}
|
{{ total_pages }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_page < total_pages %}
|
{% if current_page < total_pages %}
|
||||||
<a href="javascript:void(0)" onclick="goToPage({{ current_page + 1 }})"
|
<a href="javascript:void(0)" onclick="goToPage({{ current_page + 1 }})"
|
||||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||||
Next
|
Next
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -454,7 +445,7 @@
|
|||||||
<div id="gameDetailOverlay" class="hidden fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4 overflow-y-auto backdrop-blur-sm">
|
<div id="gameDetailOverlay" class="hidden fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4 overflow-y-auto backdrop-blur-sm">
|
||||||
<div class="bg-secondary rounded-lg max-w-6xl max-h-full w-full overflow-hidden relative shadow-2xl border border-theme animate-fade-in">
|
<div class="bg-secondary rounded-lg max-w-6xl max-h-full w-full overflow-hidden relative shadow-2xl border border-theme animate-fade-in">
|
||||||
<!-- Close Button -->
|
<!-- Close Button -->
|
||||||
<button onclick="closeGameDetail()" class="absolute top-4 right-4 text-primary hover:text-secondary text-2xl z-20 bg-primary bg-opacity-20 hover:bg-opacity-30 rounded-full w-10 h-10 flex items-center justify-center transition-all">
|
<button onclick="closeGameDetail()" class="absolute top-4 right-4 text-primary hover:text-secondary text-2xl z-30 bg-primary bg-opacity-20 hover:bg-opacity-30 rounded-full w-10 h-10 flex items-center justify-center transition-all">
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -486,7 +477,7 @@
|
|||||||
|
|
||||||
<button id="gameDownloadBtn" onclick="downloadGameFromOverlay()"
|
<button id="gameDownloadBtn" onclick="downloadGameFromOverlay()"
|
||||||
class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded hidden">
|
class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded hidden">
|
||||||
Download ROM
|
Download Game
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span id="gameDownloadDisabled" class="bg-gray-600 px-4 py-2 rounded cursor-not-allowed hidden">
|
<span id="gameDownloadDisabled" class="bg-gray-600 px-4 py-2 rounded cursor-not-allowed hidden">
|
||||||
@@ -648,7 +639,10 @@
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
// Get filename from Content-Disposition header, removing quotes and underscores
|
||||||
|
let filename = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
||||||
|
filename = filename.replace(/^["\_]+|["\_]+$/g, ''); // Remove quotes and underscores from start and end
|
||||||
|
a.download = filename;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
|
|||||||
Reference in New Issue
Block a user