feat: complete UI redesign with DaisyUI + Tailwind CSS v4

Design system overhaul using DaisyUI v5 on Tailwind CSS v4:

- Custom 'ohmstream' dark theme with orange primary (#FF9F1C),
  magenta secondary, gold accent matching existing palette
- Tailwind CSS-first config (input.css source, style.css built output)
- DaisyUI components: navbar, drawer, cards, badges, alerts, tables,
  progress bars, tabs, toggles, stats, form controls, tooltips
- Mobile-first responsive layout with drawer navigation
- Eliminated ~500+ lines of embedded CSS across 15+ template files
- Removed all inline style spam from admin_panel and settings_section
- Preserved all HTMX triggers, Alpine.js state, and Jinja2 logic
- Updated auth-ui.js for DaisyUI tab-active class compatibility

Build: npm run build:css (minified) / npm run watch:css (dev)
This commit is contained in:
root
2026-04-11 19:46:52 +00:00
parent 87f245d3fc
commit 4101d98a41
28 changed files with 2534 additions and 2808 deletions
+76 -119
View File
@@ -1,130 +1,87 @@
<div class="episode-list-container section-container" x-data="{ view: 'grid' }">
<div class="section-header">
<div>
<h2 style="border: none; padding: 0; margin-bottom: 5px;">{{ anime_title }}</h2>
<span class="badge">{{ episodes|length }} épisodes disponibles</span>
<div class="card bg-base-200 border border-primary/30 mt-8" x-data="{ view: 'grid' }">
<!-- Header -->
<div class="card-body p-6">
<div class="flex justify-between items-center">
<div class="flex items-center gap-3">
<h2 class="text-xl font-bold border-none p-0 m-0">{{ anime_title }}</h2>
<span class="badge badge-outline">{{ episodes|length }} épisodes disponibles</span>
</div>
<div class="flex gap-2">
<button class="btn btn-circle btn-sm btn-ghost" @click="view = 'grid'" :class="{ 'btn-primary': view === 'grid' }">
<i class="fas fa-th"></i>
</button>
<button class="btn btn-circle btn-sm btn-ghost" @click="view = 'list'" :class="{ 'btn-primary': view === 'list' }">
<i class="fas fa-list"></i>
</button>
<button class="btn btn-circle btn-sm btn-error" onclick="document.getElementById('player-container').innerHTML = ''">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="header-actions" style="display: flex; gap: 10px;">
<button class="btn btn-icon" @click="view = 'grid'" :class="{ 'btn-primary': view === 'grid' }">
<i class="fas fa-th"></i>
</button>
<button class="btn btn-icon" @click="view = 'list'" :class="{ 'btn-primary': view === 'list' }">
<i class="fas fa-list"></i>
</button>
<button class="btn btn-icon danger" onclick="document.getElementById('player-container').innerHTML = ''">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<!-- Zone d'affichage du player vidéo (Placé en haut pour une meilleure visibilité lors de la sélection) -->
<div id="video-player-display"></div>
<!-- Video player display area -->
<div id="video-player-display" x-ref="playerArea"></div>
<div class="episodes-content" :class="'view-' + view" style="margin-top: 25px;">
<!-- Episodes content -->
{% if episodes %}
{% for ep in episodes %}
<div class="episode-item">
<div class="ep-number">EP {{ ep.episode_number or loop.index }}</div>
<div class="ep-title" title="{{ ep.title or 'Épisode ' ~ (ep.episode_number or loop.index) }}">
{{ ep.title or 'Épisode ' ~ (ep.episode_number or loop.index) }}
</div>
<div class="ep-actions">
<button class="btn btn-primary btn-small"
hx-get="/api/player/embed?url={{ ep.url | urlencode }}"
hx-target="#video-player-display"
hx-swap="innerHTML"
onclick="document.getElementById('video-player-display').scrollIntoView({behavior: 'smooth'})">
<i class="fas fa-play"></i> Regarder
</button>
<button class="btn btn-secondary btn-icon btn-small"
hx-post="/api/anime/download?url={{ ep.url | urlencode }}"
hx-swap="none"
title="Télécharger cet épisode">
<i class="fas fa-download"></i>
</button>
</div>
<!-- Grid View -->
<div x-show="view === 'grid'" x-transition class="mt-6">
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 lg:grid-cols-7 gap-3">
{% for ep in episodes %}
<div class="bg-base-300 rounded-lg p-4 text-center hover:bg-base-100 transition-colors border border-transparent hover:border-primary flex flex-col gap-2">
<div class="text-primary font-bold text-xl">EP {{ ep.episode_number or loop.index }}</div>
<button class="btn btn-xs btn-primary w-full"
hx-get="/api/player/embed?url={{ ep.url | urlencode }}"
hx-target="#video-player-display"
hx-swap="innerHTML"
onclick="document.getElementById('video-player-display').scrollIntoView({behavior: 'smooth'})">
<i class="fas fa-play"></i> Regarder
</button>
<button class="btn btn-xs btn-ghost w-full"
hx-post="/api/anime/download?url={{ ep.url | urlencode }}"
hx-swap="none"
title="Télécharger cet épisode">
<i class="fas fa-download"></i> Télécharger
</button>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
<!-- List View -->
<div x-show="view === 'list'" x-transition class="mt-6">
<div class="flex flex-col gap-2">
{% for ep in episodes %}
<div class="flex items-center gap-4 bg-base-300 rounded-lg px-4 py-3 hover:bg-base-100 transition-colors">
<span class="font-bold text-primary w-12 shrink-0">EP {{ ep.episode_number or loop.index }}</span>
<span class="flex-1 truncate text-base-content/80 font-medium"
title="{{ ep.title or 'Épisode ' ~ (ep.episode_number or loop.index) }}">
{{ ep.title or 'Épisode ' ~ (ep.episode_number or loop.index) }}
</span>
<div class="flex gap-2 shrink-0">
<button class="btn btn-xs btn-primary"
hx-get="/api/player/embed?url={{ ep.url | urlencode }}"
hx-target="#video-player-display"
hx-swap="innerHTML"
onclick="document.getElementById('video-player-display').scrollIntoView({behavior: 'smooth'})">
<i class="fas fa-play"></i> Regarder
</button>
<button class="btn btn-xs btn-ghost"
hx-post="/api/anime/download?url={{ ep.url | urlencode }}"
hx-swap="none"
title="Télécharger cet épisode">
<i class="fas fa-download"></i>
</button>
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div class="no-results">
<i class="fas fa-exclamation-circle"></i>
<div class="text-center py-12 text-base-content/40">
<i class="fas fa-exclamation-circle text-3xl mb-3 block"></i>
<p>Aucun épisode trouvé pour cette source.</p>
</div>
{% endif %}
</div>
</div>
<style>
.episode-list-container {
margin-top: 30px;
background: var(--bg-card);
border-radius: var(--card-radius);
padding: 30px;
border: 1px solid var(--secondary);
animation: fadeIn 0.3s ease-out;
}
.episodes-content.view-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 15px;
}
.view-grid .episode-item {
background: var(--bg-elevated);
padding: 20px 15px;
border-radius: 4px;
text-align: center;
transition: var(--transition);
border: 1px solid var(--secondary);
display: flex;
flex-direction: column;
gap: 12px;
}
.view-grid .episode-item:hover {
background: var(--text-dim);
border-color: var(--primary);
}
.view-grid .ep-title { display: none; }
.view-grid .ep-number { font-weight: 800; font-size: 1.2rem; color: var(--primary); }
.view-grid .ep-actions { display: flex; flex-direction: column; gap: 8px; }
.view-grid .ep-actions .btn { width: 100%; }
.episodes-content.view-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.view-list .episode-item {
display: flex;
align-items: center;
gap: 20px;
background: var(--bg-elevated);
padding: 12px 20px;
border-radius: 4px;
border: 1px solid var(--secondary);
transition: var(--transition);
}
.view-list .episode-item:hover {
background: var(--text-dim);
border-color: var(--primary);
}
.view-list .ep-number { font-weight: 800; width: 60px; color: var(--primary); }
.view-list .ep-title { flex: 1; color: var(--text-main); font-weight: 500; }
.view-list .ep-actions { display: flex; gap: 10px; }
#video-player-display:not(:empty) {
margin: 20px 0 30px 0;
padding: 25px;
background: #000;
border-radius: 4px;
border: 1px solid var(--primary);
}
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
</style>