535005b3d5
- settings.js: replace broken CSS vars with getThemeColor() helper - base.html: add bg-primary text-primary-content active state to drawer - All templates: btn-small -> btn-sm (DaisyUI standard) - Delete orphan templates/components/header.html - auth-utils.js: fix .show class -> use hidden (Tailwind) - login.html: remove redundant auth-* classes, keep DaisyUI only - auth-ui.js: update form selector for cleanup - watchlist.html: fix nav active class styling - 4 JS files (series-search, tabs, recommendations, anime-details): - Replace all old CSS classes with DaisyUI/Tailwind - Remove hardcoded colors, use theme-aware classes - loading-spinner -> DaisyUI loading component - no-results/search-results -> Tailwind utility layout - All badges -> DaisyUI badge variants
321 lines
17 KiB
HTML
321 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr" data-theme="ohmstream">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Ohm Stream Downloader</title>
|
|
|
|
<!-- Fonts -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
|
|
<!-- CSS: Tailwind (built from input.css via DaisyUI), Font Awesome, Plyr -->
|
|
<link rel="stylesheet" href="/static/css/style.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
|
|
|
|
<!-- x-cloak: hide elements until Alpine initializes -->
|
|
<style>
|
|
[x-cloak] { display: none !important; }
|
|
|
|
/* Inter as default font, system sans-serif fallback */
|
|
body {
|
|
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
}
|
|
</style>
|
|
|
|
<!-- HTMX (local vendor) -->
|
|
<script src="/static/vendor/htmx.min.js"></script>
|
|
|
|
<!-- Configure HTMX to include auth token in all requests -->
|
|
<script>
|
|
document.addEventListener('htmx:configRequest', (event) => {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
event.detail.headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<!-- Alpine.js (local vendor, deferred) -->
|
|
<script src="/static/vendor/alpine.min.js" defer></script>
|
|
|
|
<!-- Plyr.io JS (CDN) -->
|
|
<script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
|
|
|
|
<!-- Application JS modules -->
|
|
<script src="/static/js/auth.js?v=1.10" defer></script>
|
|
<script src="/static/js/api.js?v=1.11" defer></script>
|
|
<script src="/static/js/utils.js?v=1.11" defer></script>
|
|
<script src="/static/js/downloads.js?v=1.11" defer></script>
|
|
<script src="/static/js/watchlist.js?v=1.11" defer></script>
|
|
<script src="/static/js/main.js?v=1.11" defer></script>
|
|
<script src="/static/js/settings.js?v=1.0" defer></script>
|
|
</head>
|
|
|
|
<body x-data="globalAppState" x-cloak class="min-h-screen bg-base-100 text-base-content">
|
|
|
|
<!-- ============================================================
|
|
Toast notification container (fixed position, top-right)
|
|
============================================================ -->
|
|
{% include "components/toast_container.html" %}
|
|
|
|
<!-- ============================================================
|
|
DaisyUI Drawer: wraps the entire page layout.
|
|
The checkbox (id="ohm-drawer") toggles the mobile sidebar.
|
|
============================================================ -->
|
|
<div class="drawer">
|
|
<input id="ohm-drawer" type="checkbox" class="drawer-toggle" />
|
|
|
|
<!-- Page content area -->
|
|
<div class="drawer-content flex flex-col min-h-screen">
|
|
|
|
<!-- ====================================================
|
|
DaisyUI Navbar (top bar)
|
|
==================================================== -->
|
|
<nav class="navbar bg-base-200 border-b border-base-300 sticky top-0 z-30 px-4 lg:px-8">
|
|
<!-- Mobile menu toggle -->
|
|
<div class="flex-none lg:hidden">
|
|
<label for="ohm-drawer" class="btn btn-square btn-ghost" aria-label="Menu">
|
|
<i class="fa-solid fa-bars text-lg"></i>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Brand / Logo -->
|
|
<div class="flex-1 gap-2">
|
|
<a href="/web" class="btn btn-ghost text-xl gap-2 hover:bg-transparent">
|
|
<i class="fa-solid fa-bolt text-primary"></i>
|
|
<span class="font-bold">Ohm Stream</span>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Desktop navigation tabs (hidden on mobile, shown in drawer instead) -->
|
|
<div class="hidden lg:flex flex-none gap-1">
|
|
<button class="btn btn-sm btn-ghost gap-1.5"
|
|
:class="{ 'btn-active bg-primary/15 text-primary': activeTab === 'home' }"
|
|
@click="activeTab = 'home'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'home' } }))">
|
|
<i class="fa-solid fa-house text-xs"></i> Accueil
|
|
</button>
|
|
<button class="btn btn-sm btn-ghost gap-1.5"
|
|
:class="{ 'btn-active bg-primary/15 text-primary': activeTab === 'anime' }"
|
|
@click="activeTab = 'anime'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'anime' } }))">
|
|
<i class="fa-solid fa-film text-xs"></i> Anime
|
|
</button>
|
|
<button class="btn btn-sm btn-ghost gap-1.5"
|
|
:class="{ 'btn-active bg-primary/15 text-primary': activeTab === 'series' }"
|
|
@click="activeTab = 'series'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'series' } }))">
|
|
<i class="fa-solid fa-tv text-xs"></i> Séries
|
|
</button>
|
|
<button class="btn btn-sm btn-ghost gap-1.5"
|
|
:class="{ 'btn-active bg-primary/15 text-primary': activeTab === 'watchlist' }"
|
|
@click="activeTab = 'watchlist'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'watchlist' } }))">
|
|
<i class="fa-solid fa-clipboard-list text-xs"></i> Watchlist
|
|
</button>
|
|
<button class="btn btn-sm btn-ghost gap-1.5"
|
|
:class="{ 'btn-active bg-primary/15 text-primary': activeTab === 'downloads' }"
|
|
@click="activeTab = 'downloads'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'downloads' } }))">
|
|
<i class="fa-solid fa-download text-xs"></i> Téléchargements
|
|
</button>
|
|
<button class="btn btn-sm btn-ghost gap-1.5"
|
|
:class="{ 'btn-active bg-primary/15 text-primary': activeTab === 'settings' }"
|
|
@click="activeTab = 'settings'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'settings' } }))">
|
|
<i class="fa-solid fa-gear text-xs"></i> Paramètres
|
|
</button>
|
|
</div>
|
|
|
|
<!-- User info (desktop) -->
|
|
<div class="hidden lg:flex flex-none items-center gap-2">
|
|
<!-- Authenticated state -->
|
|
<div x-show="isAuthenticated" x-cloak class="flex items-center gap-2">
|
|
<span class="text-sm text-base-content/70">
|
|
<i class="fa-solid fa-user text-primary"></i>
|
|
<strong class="text-primary" x-text="username">-</strong>
|
|
</span>
|
|
<button class="btn btn-sm btn-ghost text-error"
|
|
onclick="removeToken(); isAuthenticated = false"
|
|
hx-post="/api/auth/logout"
|
|
hx-on::after-request="window.location.href = '/login'">
|
|
<i class="fa-solid fa-right-from-bracket"></i> Déconnexion
|
|
</button>
|
|
</div>
|
|
<!-- Unauthenticated state -->
|
|
<div x-show="!isAuthenticated" x-cloak>
|
|
<a href="/login" class="btn btn-sm btn-primary">
|
|
<i class="fa-solid fa-right-to-bracket"></i> Se connecter
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile: user icon trigger + settings dropdown -->
|
|
<div class="flex-none lg:hidden">
|
|
<div x-show="isAuthenticated" x-cloak>
|
|
<div class="dropdown dropdown-end">
|
|
<div tabindex="0" role="button" class="btn btn-square btn-ghost">
|
|
<i class="fa-solid fa-circle-user text-lg text-primary"></i>
|
|
</div>
|
|
<ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box border border-base-300 z-[1] w-56 p-2 shadow-lg mt-2">
|
|
<li class="menu-title text-xs" x-text="username"></li>
|
|
<li>
|
|
<button class="text-error"
|
|
onclick="removeToken(); isAuthenticated = false"
|
|
hx-post="/api/auth/logout"
|
|
hx-on::after-request="window.location.href = '/login'">
|
|
<i class="fa-solid fa-right-from-bracket"></i> Déconnexion
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div x-show="!isAuthenticated" x-cloak>
|
|
<a href="/login" class="btn btn-sm btn-primary">
|
|
<i class="fa-solid fa-right-to-bracket"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- ====================================================
|
|
Main content block (rendered by child templates)
|
|
==================================================== -->
|
|
<main class="flex-1">
|
|
<div class="container mx-auto px-4 py-6 max-w-7xl">
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Footer -->
|
|
<footer class="footer footer-center p-4 bg-base-200 text-base-content/50 border-t border-base-300">
|
|
<aside class="text-xs">
|
|
<p>Ohm Stream Downloader — Téléchargez vos animes et séries</p>
|
|
</aside>
|
|
</footer>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
DaisyUI Drawer sidebar (mobile navigation)
|
|
Slides in from the left on mobile (< lg).
|
|
==================================================== -->
|
|
<div class="drawer-side z-40">
|
|
<label for="ohm-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
|
<aside class="bg-base-200 min-h-full w-64 border-r border-base-300 flex flex-col">
|
|
|
|
<!-- Drawer header / brand -->
|
|
<div class="p-4 border-b border-base-300">
|
|
<a href="/web" class="flex items-center gap-2 text-xl font-bold" @click="document.getElementById('ohm-drawer').checked = false">
|
|
<i class="fa-solid fa-bolt text-primary"></i>
|
|
<span>Ohm Stream</span>
|
|
</a>
|
|
<p class="text-xs text-base-content/50 mt-1">Téléchargez vos vidéos, animes et séries</p>
|
|
</div>
|
|
|
|
<!-- Mobile navigation menu -->
|
|
<ul class="menu p-4 gap-1 flex-1">
|
|
|
|
<!-- User info (mobile drawer) -->
|
|
<li x-show="isAuthenticated" x-cloak class="mb-2">
|
|
<div class="flex items-center gap-2 px-2 py-1 rounded-lg bg-base-300/50">
|
|
<i class="fa-solid fa-user text-primary text-sm"></i>
|
|
<span class="text-sm truncate">
|
|
<span class="text-base-content/50">Connecté: </span>
|
|
<strong class="text-primary" x-text="username">-</strong>
|
|
</span>
|
|
</div>
|
|
</li>
|
|
<li x-show="!isAuthenticated" x-cloak class="mb-2">
|
|
<a href="/login" class="btn btn-primary btn-sm w-full justify-center">
|
|
<i class="fa-solid fa-right-to-bracket"></i> Se connecter
|
|
</a>
|
|
</li>
|
|
|
|
<li class="mt-2">
|
|
<button class="w-full text-left"
|
|
:class="{ 'bg-primary text-primary-content rounded-lg': activeTab === 'home' }"
|
|
@click="activeTab = 'home'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'home' } })); document.getElementById('ohm-drawer').checked = false">
|
|
<i class="fa-solid fa-house w-5 text-center"></i> Accueil
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button class="w-full text-left"
|
|
:class="{ 'bg-primary text-primary-content rounded-lg': activeTab === 'anime' }"
|
|
@click="activeTab = 'anime'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'anime' } })); document.getElementById('ohm-drawer').checked = false">
|
|
<i class="fa-solid fa-film w-5 text-center"></i> Anime
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button class="w-full text-left"
|
|
:class="{ 'bg-primary text-primary-content rounded-lg': activeTab === 'series' }"
|
|
@click="activeTab = 'series'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'series' } })); document.getElementById('ohm-drawer').checked = false">
|
|
<i class="fa-solid fa-tv w-5 text-center"></i> Séries
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button class="w-full text-left"
|
|
:class="{ 'bg-primary text-primary-content rounded-lg': activeTab === 'watchlist' }"
|
|
@click="activeTab = 'watchlist'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'watchlist' } })); document.getElementById('ohm-drawer').checked = false">
|
|
<i class="fa-solid fa-clipboard-list w-5 text-center"></i> Watchlist
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button class="w-full text-left"
|
|
:class="{ 'bg-primary text-primary-content rounded-lg': activeTab === 'downloads' }"
|
|
@click="activeTab = 'downloads'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'downloads' } })); document.getElementById('ohm-drawer').checked = false">
|
|
<i class="fa-solid fa-download w-5 text-center"></i> Téléchargements
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button class="w-full text-left"
|
|
:class="{ 'bg-primary text-primary-content rounded-lg': activeTab === 'settings' }"
|
|
@click="activeTab = 'settings'; window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: 'settings' } })); document.getElementById('ohm-drawer').checked = false">
|
|
<i class="fa-solid fa-gear w-5 text-center"></i> Paramètres
|
|
</button>
|
|
</li>
|
|
|
|
<!-- Mobile logout -->
|
|
<li x-show="isAuthenticated" x-cloak class="mt-auto border-t border-base-300 pt-2">
|
|
<button class="text-error"
|
|
onclick="removeToken(); isAuthenticated = false"
|
|
hx-post="/api/auth/logout"
|
|
hx-on::after-request="window.location.href = '/login'">
|
|
<i class="fa-solid fa-right-from-bracket w-5 text-center"></i> Déconnexion
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</aside>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================
|
|
Alpine.js global state initialization
|
|
============================================================ -->
|
|
<script>
|
|
document.addEventListener('alpine:init', () => {
|
|
console.log('Alpine.js initializing...');
|
|
|
|
Alpine.data('globalAppState', () => ({
|
|
activeTab: 'home',
|
|
isAuthenticated: true,
|
|
username: '',
|
|
|
|
init() {
|
|
// Auth state listeners
|
|
window.addEventListener('auth-success', (e) => {
|
|
this.isAuthenticated = true;
|
|
this.username = e.detail.username;
|
|
});
|
|
window.addEventListener('auth-logout', () => {
|
|
this.isAuthenticated = false;
|
|
this.username = '';
|
|
});
|
|
|
|
// Tab switching via custom events (SPA hash routing support)
|
|
window.addEventListener('set-tab', (e) => {
|
|
this.activeTab = e.detail.tab;
|
|
});
|
|
}
|
|
}));
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|