801e6a050b
- Documentation archivée et réorganisée - Backend: Ajout tests, migrations, library service, rate limiting - Frontend: Suppression Flutter, focus sur interface web HTML/JS - Tailwind CSS ajouté pour le style - Améliorations UX et corrections bugs Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
783 lines
46 KiB
HTML
783 lines
46 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr" class="dark">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AudiOhm - Web Player</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
|
<script>
|
|
tailwind.config = {
|
|
darkMode: 'class',
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
primary: {
|
|
50: '#f0f9ff',
|
|
100: '#e0f2fe',
|
|
200: '#bae6fd',
|
|
300: '#7dd3fc',
|
|
400: '#38bdf8',
|
|
500: '#0ea5e9',
|
|
600: '#0284c7',
|
|
700: '#0369a1',
|
|
800: '#075985',
|
|
900: '#0c4a6e',
|
|
},
|
|
accent: {
|
|
50: '#fdf2f8',
|
|
100: '#fce7f3',
|
|
200: '#fbcfe8',
|
|
300: '#f9a8d4',
|
|
400: '#f472b6',
|
|
500: '#ec4899',
|
|
600: '#db2777',
|
|
700: '#be185d',
|
|
800: '#9d174d',
|
|
900: '#831843',
|
|
},
|
|
success: '#10b981',
|
|
warning: '#f59e0b',
|
|
error: '#ef4444',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style>
|
|
/* Custom animations */
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes slideIn {
|
|
from { transform: translateX(100%); }
|
|
to { transform: translateX(0); }
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
.animate-spin {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
.animate-fadeIn {
|
|
animation: fadeIn 0.3s ease-out;
|
|
}
|
|
|
|
.animate-slideIn {
|
|
animation: slideIn 0.3s ease-out;
|
|
}
|
|
|
|
.animate-pulse {
|
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
}
|
|
|
|
/* Custom scrollbar */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: #1f2937;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #4b5563;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #6b7280;
|
|
}
|
|
|
|
/* Glassmorphism */
|
|
.glass {
|
|
background: rgba(17, 24, 39, 0.8);
|
|
backdrop-filter: blur(12px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.glass-card {
|
|
background: rgba(31, 41, 55, 0.95);
|
|
backdrop-filter: blur(12px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
/* Range slider styling */
|
|
input[type="range"] {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-track {
|
|
background: #374151;
|
|
height: 4px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
margin-top: -4px;
|
|
background-color: #0ea5e9;
|
|
height: 12px;
|
|
width: 12px;
|
|
border-radius: 50%;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb:hover {
|
|
background-color: #38bdf8;
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
/* Larger slider for desktop */
|
|
@media (min-width: 640px) {
|
|
input[type="range"]::-webkit-slider-track {
|
|
height: 6px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
height: 14px;
|
|
width: 14px;
|
|
}
|
|
}
|
|
|
|
/* Screen reader only utility */
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
white-space: nowrap;
|
|
border-width: 0;
|
|
}
|
|
|
|
.sr-only.focusable:focus {
|
|
position: static;
|
|
width: auto;
|
|
height: auto;
|
|
padding: inherit;
|
|
margin: inherit;
|
|
overflow: visible;
|
|
clip: auto;
|
|
white-space: normal;
|
|
}
|
|
|
|
/* Focus visible styles for keyboard navigation */
|
|
:focus-visible {
|
|
outline: 2px solid #0ea5e9;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Better focus styles for interactive elements */
|
|
button:focus-visible,
|
|
a:focus-visible,
|
|
input:focus-visible,
|
|
[tabindex]:focus-visible {
|
|
outline: 2px solid #0ea5e9;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Library Tabs Styles */
|
|
.library-tab {
|
|
background: rgba(31, 41, 55, 0.6);
|
|
color: #9ca3af;
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.library-tab:hover {
|
|
background: rgba(55, 65, 81, 0.6);
|
|
color: #e5e7eb;
|
|
}
|
|
|
|
.library-tab.active {
|
|
background: rgba(14, 165, 233, 0.2);
|
|
color: #38bdf8;
|
|
border-color: rgba(56, 189, 248, 0.3);
|
|
}
|
|
|
|
.tab-panel {
|
|
display: none;
|
|
}
|
|
|
|
.tab-panel.active {
|
|
display: block;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gradient-to-br from-gray-900 via-slate-900 to-gray-900 min-h-screen text-white font-sans">
|
|
<!-- Skip Link for Accessibility -->
|
|
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary-600 focus:text-white focus:rounded-lg">
|
|
Aller au contenu principal
|
|
</a>
|
|
|
|
<!-- Toast Container -->
|
|
<div id="toast-container" class="fixed top-4 right-4 z-50 flex flex-col gap-2" role="status" aria-live="polite" aria-atomic="true"></div>
|
|
|
|
<!-- App Container -->
|
|
<div id="app" class="min-h-screen">
|
|
<!-- Loading Screen -->
|
|
<div id="loading-screen" class="fixed inset-0 bg-gray-900 flex flex-col items-center justify-center z-50" role="status" aria-live="polite" aria-busy="true">
|
|
<div class="relative w-16 h-16 mb-6">
|
|
<div class="absolute inset-0 border-4 border-primary-500/30 rounded-full"></div>
|
|
<div class="absolute inset-0 border-4 border-transparent border-t-primary-500 rounded-full animate-spin"></div>
|
|
</div>
|
|
<h2 class="text-2xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
|
|
Chargement de AudiOhm...
|
|
</h2>
|
|
<p class="text-gray-400 mt-2">Préparation de votre expérience musicale</p>
|
|
</div>
|
|
|
|
<!-- Login Screen -->
|
|
<div id="login-screen" class="hidden fixed inset-0 bg-gradient-to-br from-gray-900 via-slate-900 to-gray-900 flex items-center justify-center p-4" role="dialog" aria-modal="true" aria-labelledby="login-title">
|
|
<div class="glass-card rounded-2xl p-8 w-full max-w-md animate-fadeIn">
|
|
<!-- Logo -->
|
|
<div class="text-center mb-8">
|
|
<div class="inline-flex items-center justify-center w-20 h-20 rounded-2xl bg-gradient-to-br from-primary-500 to-accent-500 mb-4 shadow-lg shadow-primary-500/25" aria-hidden="true">
|
|
<i class="fas fa-headphones text-4xl text-white"></i>
|
|
</div>
|
|
<h1 id="login-title" class="text-3xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
|
|
AudiOhm
|
|
</h1>
|
|
<p class="text-gray-400 mt-2">Votre musique, illimitée</p>
|
|
</div>
|
|
|
|
<!-- Login Form -->
|
|
<form id="login-form" class="space-y-4" aria-label="Formulaire de connexion">
|
|
<div>
|
|
<label for="login-email" class="block text-sm font-medium text-gray-300 mb-2">Email</label>
|
|
<div class="relative">
|
|
<i class="fas fa-envelope absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
|
|
<input type="email" id="login-email" required
|
|
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
|
|
placeholder="vous@example.com" autocomplete="email" aria-describedby="login-email-hint">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="login-password" class="block text-sm font-medium text-gray-300 mb-2">Mot de passe</label>
|
|
<div class="relative">
|
|
<i class="fas fa-lock absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
|
|
<input type="password" id="login-password" required
|
|
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
|
|
placeholder="••••••••" autocomplete="current-password">
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit"
|
|
class="w-full py-3 px-4 bg-gradient-to-r from-primary-600 to-primary-500 hover:from-primary-500 hover:to-primary-400 rounded-xl font-semibold transition-all transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-gray-900 shadow-lg shadow-primary-500/25">
|
|
<i class="fas fa-sign-in-alt mr-2" aria-hidden="true"></i>
|
|
Se connecter
|
|
</button>
|
|
|
|
<div class="text-center">
|
|
<p class="text-gray-400 text-sm">
|
|
Pas encore de compte ?
|
|
<button type="button" id="show-register" class="text-primary-400 hover:text-primary-300 font-medium transition-colors focus:outline-none focus:underline focus:ring-2 focus:ring-primary-400 rounded">
|
|
Créer un compte
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Register Form -->
|
|
<form id="register-form" class="hidden space-y-4" aria-label="Formulaire d'inscription">
|
|
<div>
|
|
<label for="register-username" class="block text-sm font-medium text-gray-300 mb-2">Nom d'utilisateur</label>
|
|
<div class="relative">
|
|
<i class="fas fa-user absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
|
|
<input type="text" id="register-username" required
|
|
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
|
|
placeholder="votre_pseudo" autocomplete="username">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="register-email" class="block text-sm font-medium text-gray-300 mb-2">Email</label>
|
|
<div class="relative">
|
|
<i class="fas fa-envelope absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
|
|
<input type="email" id="register-email" required
|
|
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
|
|
placeholder="vous@example.com" autocomplete="email">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="register-password" class="block text-sm font-medium text-gray-300 mb-2">Mot de passe</label>
|
|
<div class="relative">
|
|
<i class="fas fa-lock absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
|
|
<input type="password" id="register-password" required minlength="8"
|
|
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
|
|
placeholder="Min. 8 caractères" autocomplete="new-password" aria-describedby="password-requirements">
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit"
|
|
class="w-full py-3 px-4 bg-gradient-to-r from-accent-600 to-accent-500 hover:from-accent-500 hover:to-accent-400 rounded-xl font-semibold transition-all transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-accent-400 focus:ring-offset-2 focus:ring-offset-gray-900 shadow-lg shadow-accent-500/25">
|
|
<i class="fas fa-user-plus mr-2" aria-hidden="true"></i>
|
|
Créer un compte
|
|
</button>
|
|
|
|
<div class="text-center">
|
|
<p class="text-gray-400 text-sm">
|
|
Déjà un compte ?
|
|
<button type="button" id="show-login" class="text-accent-400 hover:text-accent-300 font-medium transition-colors focus:outline-none focus:underline focus:ring-2 focus:ring-accent-400 rounded">
|
|
Se connecter
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</form>
|
|
|
|
<div id="auth-error" class="hidden mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-400 text-sm"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main App -->
|
|
<div id="main-app" class="hidden">
|
|
<!-- Mobile Menu Button -->
|
|
<button id="mobile-menu-btn" class="lg:hidden fixed top-4 left-4 z-40 p-3 glass rounded-xl hover:bg-gray-800/50 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-all" aria-label="Ouvrir le menu" aria-expanded="false" aria-controls="sidebar">
|
|
<i class="fas fa-bars text-xl" aria-hidden="true"></i>
|
|
</button>
|
|
|
|
<!-- Sidebar -->
|
|
<aside id="sidebar" class="fixed left-0 top-0 h-full w-64 glass border-r border-gray-800 z-30 transform -translate-x-full lg:translate-x-0 transition-transform duration-300" aria-label="Navigation principale">
|
|
<div class="p-6">
|
|
<!-- Logo -->
|
|
<div class="flex items-center gap-3 mb-8">
|
|
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-accent-500 flex items-center justify-center shadow-lg" aria-hidden="true">
|
|
<i class="fas fa-headphones text-white"></i>
|
|
</div>
|
|
<h1 class="text-xl font-bold">AudiOhm</h1>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="space-y-2" aria-label="Navigation principale">
|
|
<a href="#" data-page="home" class="nav-item active flex items-center gap-3 px-4 py-3 rounded-xl bg-primary-500/10 text-primary-400 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500" role="button" aria-current="page">
|
|
<i class="fas fa-home w-5" aria-hidden="true"></i>
|
|
<span>Accueil</span>
|
|
</a>
|
|
<a href="#" data-page="search" class="nav-item flex items-center gap-3 px-4 py-3 rounded-xl text-gray-400 hover:bg-gray-800/50 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500" role="button">
|
|
<i class="fas fa-search w-5" aria-hidden="true"></i>
|
|
<span>Rechercher</span>
|
|
</a>
|
|
<a href="#" data-page="library" class="nav-item flex items-center gap-3 px-4 py-3 rounded-xl text-gray-400 hover:bg-gray-800/50 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500" role="button">
|
|
<i class="fas fa-music w-5" aria-hidden="true"></i>
|
|
<span>Bibliothèque</span>
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Logout -->
|
|
<div class="absolute bottom-0 left-0 right-0 p-6 border-t border-gray-800">
|
|
<button id="logout-btn" class="w-full flex items-center justify-center gap-2 px-4 py-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-accent-500" aria-label="Se déconnecter">
|
|
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
|
<span>Déconnexion</span>
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main id="main-content" class="lg:ml-64 min-h-screen pb-20 sm:pb-32" tabindex="-1">
|
|
<!-- Home Page -->
|
|
<div id="home-page" class="page active p-4 sm:p-6 lg:p-10 pt-16 sm:pt-6 lg:pt-10">
|
|
<!-- Header -->
|
|
<div class="mb-6 sm:mb-8">
|
|
<h1 class="text-2xl sm:text-3xl lg:text-4xl font-bold mb-2">
|
|
<span class="bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
|
|
Bienvenue sur AudiOhm
|
|
</span>
|
|
<span class="text-xl sm:text-2xl"> 🎵</span>
|
|
</h1>
|
|
<p class="text-sm sm:text-base text-gray-400">Votre alternative à Spotify avec streaming YouTube</p>
|
|
</div>
|
|
|
|
<!-- Quick Search -->
|
|
<section class="mb-8 sm:mb-10" aria-labelledby="quick-search-heading">
|
|
<h2 id="quick-search-heading" class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
|
|
<i class="fas fa-bolt text-primary-400" aria-hidden="true"></i>
|
|
Recherche rapide
|
|
</h2>
|
|
<div class="flex flex-col sm:flex-row gap-2 sm:gap-3">
|
|
<label for="quick-search" class="sr-only">Rechercher une musique</label>
|
|
<input type="search" id="quick-search"
|
|
class="flex-1 px-3 sm:px-4 py-2 sm:py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all text-sm"
|
|
placeholder="Rechercher une musique, un artiste..." aria-describedby="quick-search-hint">
|
|
<button id="quick-search-btn" class="px-4 sm:px-6 py-2 sm:py-3 bg-primary-600 hover:bg-primary-500 rounded-xl font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-gray-900 min-h-[44px] sm:min-h-[48px]" aria-label="Lancer la recherche">
|
|
<i class="fas fa-search text-sm sm:text-base" aria-hidden="true"></i>
|
|
<span class="sr-only">Rechercher</span>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Trending -->
|
|
<section class="mb-8 sm:mb-10">
|
|
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
|
|
<i class="fas fa-fire text-accent-400"></i>
|
|
Musiques tendance
|
|
</h2>
|
|
<div id="trending-tracks" class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3 sm:gap-4">
|
|
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-primary-500/30 border-t-primary-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
|
|
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Recent -->
|
|
<section>
|
|
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
|
|
<i class="fas fa-clock text-primary-400"></i>
|
|
Récemment écoutées
|
|
</h2>
|
|
<div id="recent-tracks" class="text-sm sm:text-base text-gray-400">
|
|
<p>Aucune écoute récente</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Search Page -->
|
|
<div id="search-page" class="page hidden p-4 sm:p-6 lg:p-10 pt-16 sm:pt-6 lg:pt-10">
|
|
<h1 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6 flex items-center gap-2 sm:gap-3">
|
|
<i class="fas fa-search text-primary-400" aria-hidden="true"></i>
|
|
Recherche
|
|
</h1>
|
|
|
|
<div class="flex flex-col sm:flex-row gap-2 sm:gap-3 mb-6 sm:mb-8">
|
|
<label for="search-input" class="sr-only">Rechercher de la musique</label>
|
|
<input type="search" id="search-input"
|
|
class="flex-1 px-3 sm:px-4 py-2 sm:py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all text-sm"
|
|
placeholder="Que voulez-vous écouter ?" aria-describedby="search-hint">
|
|
<button id="search-btn" class="px-4 sm:px-8 py-2 sm:py-3 bg-gradient-to-r from-primary-600 to-primary-500 hover:from-primary-500 hover:to-primary-400 rounded-xl font-semibold transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-gray-900 min-h-[44px] sm:min-h-[48px] text-sm sm:text-base">
|
|
<i class="fas fa-search mr-0 sm:mr-2" aria-hidden="true"></i>
|
|
<span class="hidden sm:inline">Rechercher</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="search-results" role="list" aria-label="Résultats de recherche" class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3 sm:gap-4"></div>
|
|
</div>
|
|
|
|
<!-- Library Page -->
|
|
<div id="library-page" class="page hidden p-4 sm:p-6 lg:p-10 pt-16 sm:pt-6 lg:pt-10">
|
|
<h1 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6 flex items-center gap-2 sm:gap-3">
|
|
<i class="fas fa-music text-accent-400"></i>
|
|
Ma Bibliothèque
|
|
</h1>
|
|
|
|
<!-- Tabs Navigation -->
|
|
<div class="flex gap-2 mb-6 overflow-x-auto pb-2" role="tablist" aria-label="Onglets de la bibliothèque">
|
|
<button id="tab-playlists" class="library-tab active px-4 py-2 rounded-lg font-medium transition-all whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
role="tab"
|
|
aria-selected="true"
|
|
aria-controls="library-playlists"
|
|
onclick="switchLibraryTab('playlists')">
|
|
<i class="fas fa-list mr-2"></i>
|
|
<span class="hidden sm:inline">Playlists</span>
|
|
<span class="sm:hidden">Playlists</span>
|
|
</button>
|
|
<button id="tab-liked" class="library-tab px-4 py-2 rounded-lg font-medium transition-all whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
role="tab"
|
|
aria-selected="false"
|
|
aria-controls="library-liked"
|
|
onclick="switchLibraryTab('liked')">
|
|
<i class="fas fa-heart mr-2"></i>
|
|
<span class="hidden sm:inline">Titres likés</span>
|
|
<span class="sm:hidden">Likés</span>
|
|
</button>
|
|
<button id="tab-history" class="library-tab px-4 py-2 rounded-lg font-medium transition-all whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
role="tab"
|
|
aria-selected="false"
|
|
aria-controls="library-history"
|
|
onclick="switchLibraryTab('history')">
|
|
<i class="fas fa-history mr-2"></i>
|
|
<span class="hidden sm:inline">Historique</span>
|
|
<span class="sm:hidden">Historique</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab Panels -->
|
|
<div class="tab-panels">
|
|
<!-- Playlists Tab -->
|
|
<div id="library-playlists" class="tab-panel active" role="tabpanel" aria-labelledby="tab-playlists">
|
|
<section class="mb-8 sm:mb-10">
|
|
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
|
<h2 class="text-lg sm:text-xl font-semibold flex items-center gap-2">
|
|
<i class="fas fa-list text-primary-400"></i>
|
|
Mes Playlists
|
|
</h2>
|
|
<button id="create-playlist-btn" class="px-3 sm:px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded-lg font-medium transition-all text-sm flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-primary-400" aria-label="Créer une nouvelle playlist">
|
|
<i class="fas fa-plus"></i>
|
|
<span class="hidden sm:inline">Créer</span>
|
|
</button>
|
|
</div>
|
|
<div id="my-playlists" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
|
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-primary-500/30 border-t-primary-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
|
|
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Liked Tracks Tab -->
|
|
<div id="library-liked" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab-liked">
|
|
<section>
|
|
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
|
|
<i class="fas fa-heart text-accent-400"></i>
|
|
Titres likés
|
|
</h2>
|
|
<div id="liked-tracks" class="space-y-2 max-w-4xl">
|
|
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-accent-500/30 border-t-accent-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
|
|
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Listening History Tab -->
|
|
<div id="library-history" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab-history">
|
|
<section>
|
|
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
|
|
<i class="fas fa-history text-primary-400"></i>
|
|
Historique d'écoute
|
|
</h2>
|
|
<div id="listening-history" class="space-y-2 max-w-4xl">
|
|
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
|
|
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-primary-500/30 border-t-primary-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
|
|
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Player -->
|
|
<div id="player" class="hidden fixed bottom-0 left-0 right-0 glass border-t border-gray-800 px-2 sm:px-4 py-2 sm:py-3 z-40" role="region" aria-label="Lecteur audio">
|
|
<!-- Mobile Compact View -->
|
|
<div class="sm:hidden flex items-center gap-2">
|
|
<!-- Track Info (Mobile) -->
|
|
<div class="flex items-center gap-2 flex-1 min-w-0">
|
|
<img id="player-cover" src="/static/img/default-cover.png" alt=""
|
|
class="w-10 h-10 rounded-lg object-cover bg-gray-800 flex-shrink-0" aria-hidden="true">
|
|
<button id="mobile-play-btn" class="p-2 bg-primary-600 rounded-full flex-shrink-0" aria-label="Lecture/Pause">
|
|
<i class="fas fa-play text-xs"></i>
|
|
</button>
|
|
<div class="min-w-0 flex-1">
|
|
<div id="player-title" class="font-medium text-xs truncate" aria-live="polite">Aucun titre</div>
|
|
<div id="player-artist" class="text-xs text-gray-400 truncate" aria-live="polite">-</div>
|
|
</div>
|
|
</div>
|
|
<!-- Actions (Mobile) -->
|
|
<div class="flex items-center gap-1 flex-shrink-0">
|
|
<button id="mobile-like-btn" class="p-2 text-gray-400 hover:text-accent-400 transition-all" aria-label="J'aime">
|
|
<i class="far fa-heart text-sm"></i>
|
|
</button>
|
|
<button id="mobile-expand-btn" class="p-2 text-gray-400 hover:text-white transition-all" aria-label="Agrandir le player">
|
|
<i class="fas fa-chevron-up text-sm"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Desktop Full View -->
|
|
<div class="hidden sm:flex items-center gap-2 lg:gap-4 max-w-screen-2xl mx-auto">
|
|
<!-- Track Info -->
|
|
<div class="flex items-center gap-2 lg:gap-3 flex-shrink-0 w-32 lg:w-64">
|
|
<img id="player-cover-desktop" src="/static/img/default-cover.png" alt=""
|
|
class="w-10 h-10 lg:w-14 lg:h-14 rounded-lg object-cover bg-gray-800 flex-shrink-0" aria-hidden="true">
|
|
<div class="min-w-0 flex-1 hidden sm:block">
|
|
<div id="player-title-desktop" class="font-medium text-xs lg:text-sm truncate" aria-live="polite">Aucun titre</div>
|
|
<div id="player-artist-desktop" class="text-xs text-gray-400 truncate" aria-live="polite">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Controls -->
|
|
<div class="flex-1 flex flex-col items-center gap-1 lg:gap-2">
|
|
<!-- Main Controls -->
|
|
<div class="flex items-center gap-1 lg:gap-2">
|
|
<button id="shuffle-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Mode aléatoire" aria-pressed="false">
|
|
<i class="fas fa-random text-sm lg:text-base" aria-hidden="true"></i>
|
|
</button>
|
|
<button id="prev-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Piste précédente">
|
|
<i class="fas fa-step-backward text-sm lg:text-base" aria-hidden="true"></i>
|
|
</button>
|
|
<button id="play-btn" class="p-2 lg:p-4 bg-primary-600 hover:bg-primary-500 rounded-full transition-all transform hover:scale-110 focus:outline-none focus:ring-4 focus:ring-primary-500/50 min-w-[40px] lg:min-w-[52px] min-h-[40px] lg:min-h-[52px] flex items-center justify-center" aria-label="Lecture" aria-pressed="false">
|
|
<i class="fas fa-play text-sm lg:text-base" aria-hidden="true"></i>
|
|
</button>
|
|
<button id="next-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Piste suivante">
|
|
<i class="fas fa-step-forward text-sm lg:text-base" aria-hidden="true"></i>
|
|
</button>
|
|
<button id="repeat-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Répéter" aria-pressed="false">
|
|
<i class="fas fa-redo text-sm lg:text-base" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Progress -->
|
|
<div class="flex items-center gap-2 lg:gap-3 w-full max-w-xl px-2">
|
|
<span id="current-time" class="text-xs text-gray-400 w-8 lg:w-10 text-right flex-shrink-0" aria-live="off" aria-label="Temps écoulé">0:00</span>
|
|
<label for="progress-bar" class="sr-only">Barre de progression</label>
|
|
<input type="range" id="progress-bar" min="0" max="100" value="0"
|
|
class="flex-1 h-1" aria-label="Progression de la lecture" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-valuetext="0%">
|
|
<span id="total-time" class="text-xs text-gray-400 w-8 lg:w-10 flex-shrink-0" aria-live="off" aria-label="Durée totale">0:00</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Volume & Actions -->
|
|
<div class="flex items-center gap-1 lg:gap-2 flex-shrink-0">
|
|
<button id="mute-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Couper le son" aria-pressed="false">
|
|
<i class="fas fa-volume-up text-sm lg:text-base" aria-hidden="true"></i>
|
|
</button>
|
|
<label for="volume-bar" class="sr-only">Volume</label>
|
|
<input type="range" id="volume-bar" min="0" max="100" value="100"
|
|
class="w-12 lg:w-20 hidden md:block" aria-label="Volume" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100" aria-valuetext="100%">
|
|
<div class="w-px h-6 lg:h-8 bg-gray-700 mx-1 lg:mx-2 hidden md:block" aria-hidden="true"></div>
|
|
<button id="like-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-accent-400 transition-all focus:outline-none focus:ring-2 focus:ring-accent-500 rounded-lg min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="J'aime" aria-pressed="false">
|
|
<i class="far fa-heart text-sm lg:text-base" aria-hidden="true"></i>
|
|
</button>
|
|
<button id="queue-open-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center relative" aria-label="File d'attente" aria-expanded="false">
|
|
<i class="fas fa-list-ul text-sm lg:text-base" aria-hidden="true"></i>
|
|
<span id="queue-count" class="absolute -top-1 -right-1 bg-primary-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold">0</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<audio id="audio-player" preload="none" class="hidden"></audio>
|
|
</div>
|
|
|
|
<!-- Create Playlist Modal -->
|
|
<div id="create-playlist-modal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" role="dialog" aria-labelledby="create-playlist-title" aria-modal="true" aria-hidden="true">
|
|
<div class="glass-card rounded-2xl p-6 w-full max-w-md animate-fadeIn">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 id="create-playlist-title" class="text-xl font-bold">Créer une playlist</h2>
|
|
<button id="close-create-playlist-modal" class="p-2 text-gray-400 hover:text-white transition-all rounded-lg hover:bg-gray-700/50 focus:outline-none focus:ring-2 focus:ring-primary-500" aria-label="Fermer">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<form id="create-playlist-form" aria-label="Formulaire de création de playlist">
|
|
<div class="mb-4">
|
|
<label for="playlist-name" class="block text-sm font-medium text-gray-300 mb-2">Nom de la playlist *</label>
|
|
<input type="text" id="playlist-name" required
|
|
class="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
|
|
placeholder="Ma nouvelle playlist" aria-describedby="playlist-name-hint">
|
|
</div>
|
|
|
|
<div class="mb-6">
|
|
<label for="playlist-description" class="block text-sm font-medium text-gray-300 mb-2">Description (optionnel)</label>
|
|
<textarea id="playlist-description" rows="3"
|
|
class="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all resize-none"
|
|
placeholder="Décrivez votre playlist..."></textarea>
|
|
</div>
|
|
|
|
<div class="flex gap-3">
|
|
<button type="button" id="cancel-create-playlist"
|
|
class="flex-1 px-4 py-3 bg-gray-700 hover:bg-gray-600 rounded-xl font-medium transition-all focus:outline-none focus:ring-2 focus:ring-gray-500">
|
|
Annuler
|
|
</button>
|
|
<button type="submit"
|
|
class="flex-1 px-4 py-3 bg-gradient-to-r from-primary-600 to-primary-500 hover:from-primary-500 hover:to-primary-400 rounded-xl font-semibold transition-all transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-primary-400 shadow-lg">
|
|
Créer
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Playlist Details Modal -->
|
|
<div id="playlist-details-modal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" role="dialog" aria-labelledby="playlist-details-title" aria-modal="true" aria-hidden="true">
|
|
<div class="glass-card rounded-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden animate-fadeIn flex flex-col">
|
|
<!-- Header -->
|
|
<div class="p-6 border-b border-gray-800">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 id="playlist-details-title" class="text-xl font-bold truncate flex-1">Titre de la playlist</h2>
|
|
<button id="close-playlist-details" class="p-2 text-gray-400 hover:text-white transition-all rounded-lg hover:bg-gray-700/50 focus:outline-none focus:ring-2 focus:ring-primary-500 ml-2" aria-label="Fermer">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<p id="playlist-details-description" class="text-gray-400 text-sm mb-4"></p>
|
|
<div class="flex items-center gap-3">
|
|
<button id="play-playlist-btn" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded-lg font-medium transition-all flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-primary-400">
|
|
<i class="fas fa-play"></i>
|
|
Lecture
|
|
</button>
|
|
<button id="shuffle-playlist-btn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg font-medium transition-all flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-gray-500">
|
|
<i class="fas fa-random"></i>
|
|
Aléatoire
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tracks -->
|
|
<div id="playlist-tracks" class="flex-1 overflow-y-auto p-4">
|
|
<div class="flex flex-col items-center justify-center py-12 text-gray-400">
|
|
<i class="fas fa-music text-4xl mb-4"></i>
|
|
<p class="text-lg">Aucune piste</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Queue Panel -->
|
|
<div id="queue-panel" class="fixed inset-y-0 right-0 w-full sm:w-96 glass border-l border-gray-800 z-50 transform translate-x-full transition-transform duration-300 ease-out" role="dialog" aria-labelledby="queue-title" aria-hidden="true">
|
|
<!-- Header -->
|
|
<div class="p-4 sm:p-6 border-b border-gray-800">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 id="queue-title" class="text-lg sm:text-xl font-bold flex items-center gap-2">
|
|
<i class="fas fa-list-ul text-primary-400"></i>
|
|
File d'attente
|
|
<span id="queue-count-badge" class="text-sm font-normal text-gray-400">(0)</span>
|
|
</h2>
|
|
<button id="queue-close-btn" class="p-2 text-gray-400 hover:text-white transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg" aria-label="Fermer la file d'attente">
|
|
<i class="fas fa-times text-lg"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Queue Actions -->
|
|
<div class="flex items-center gap-2">
|
|
<button id="queue-shuffle-btn" class="flex-1 px-4 py-2 bg-gray-800/50 hover:bg-gray-700/50 text-gray-300 hover:text-white rounded-lg transition-all text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary-500 flex items-center justify-center gap-2" aria-label="Mélanger la file d'attente">
|
|
<i class="fas fa-random"></i>
|
|
Mélanger
|
|
</button>
|
|
<button id="queue-clear-btn" class="flex-1 px-4 py-2 bg-gray-800/50 hover:bg-red-600/30 text-gray-300 hover:text-red-400 rounded-lg transition-all text-sm font-medium focus:outline-none focus:ring-2 focus:ring-red-500 flex items-center justify-center gap-2" aria-label="Vider la file d'attente">
|
|
<i class="fas fa-trash-alt"></i>
|
|
Vider
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Queue List -->
|
|
<div id="queue-list" class="p-4 overflow-y-auto" style="max-height: calc(100vh - 200px);" role="list" aria-label="Pistes dans la file d'attente">
|
|
<!-- Queue items will be dynamically inserted here -->
|
|
<div class="flex flex-col items-center justify-center py-12 text-gray-400">
|
|
<i class="fas fa-list-ul text-4xl mb-4"></i>
|
|
<p class="text-lg">File d'attente vide</p>
|
|
<p class="text-sm mt-2">Cliquez sur une piste pour l'ajouter</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/static/js/app.js"></script>
|
|
</body>
|
|
</html>
|