BitDown commited on
Commit
840f62b
·
verified ·
1 Parent(s): ddd7255

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +339 -518
index.html CHANGED
@@ -4,8 +4,10 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>LiftTrack - Suivi Musculation Multi-Utilisateurs</title>
 
 
7
  <style>
8
- /* --- Styles CSS --- */
9
  :root {
10
  --bg-dark: #121212;
11
  --bg-card: #1e1e1e;
@@ -13,26 +15,42 @@
13
  --accent: #4CAF50;
14
  --accent-dark: #3a8a3d;
15
  --danger: #f44336;
 
16
  }
17
  * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
18
- body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; /* Espace pour nav bottom */ }
19
  .container { width: 100%; max-width: 800px; margin: 0 auto; padding: 1rem; }
20
  header { padding: 1rem 0; text-align: center; border-bottom: 1px solid #333; margin-bottom: 1rem; }
21
  h1, h2, h3 { color: var(--accent); }
22
  .btn { background-color: var(--accent); color: white; border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background-color 0.2s; }
23
  .btn:hover { background-color: var(--accent-dark); }
 
24
  .btn-outline { background-color: transparent; color: var(--accent); border: 1px solid var(--accent); }
 
25
  .btn-danger { background-color: var(--danger); }
26
  input, select, textarea { width: 100%; padding: 0.6rem; margin-bottom: 1rem; background-color: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: var(--text-light); }
27
- input[type="checkbox"] { width: auto; margin-right: 0.5rem; vertical-align: middle; } /* Align checkbox */
28
  .card { background-color: var(--bg-card); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
29
  .form-group { margin-bottom: 1rem; }
30
- .form-row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; /* Allow wrapping on small screens */ }
31
- .form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; /* Prevent items from becoming too small */ }
32
  label { display: block; margin-bottom: 0.3rem; color: #bbb; font-size: 0.9rem; }
33
- .exercise { border-left: 3px solid var(--accent); padding-left: 1rem; margin-bottom: 1.5rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem;}
35
- .exercise-header input {margin-bottom: 0;} /* Remove margin on input in header */
36
  .series-container { margin-left: 0.5rem; margin-top: 0.5rem; }
37
  .series { background-color: #252525; padding: 0.7rem; border-radius: 4px; margin-bottom: 0.5rem; }
38
  .nav-bottom { position: fixed; bottom: 0; left: 0; width: 100%; background-color: #1a1a1a; display: flex; justify-content: space-around; padding: 0.7rem 0; box-shadow: 0 -2px 10px rgba(0,0,0,0.3); z-index: 10; }
@@ -45,40 +63,65 @@
45
  .stat-card { text-align: center; padding: 1rem; }
46
  .stat-value { font-size: 1.8rem; color: var(--accent); font-weight: bold; }
47
  .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 1rem; }
48
-
49
- /* --- Règles d'Affichage Cruciales --- */
50
- .hidden { display: none; } /* Utilitaire pour cacher des éléments spécifiques */
51
- #login-page { display: block; } /* Montrer la page de login au début */
52
- #main-app-content { display: none; } /* Cacher le contenu principal au début */
53
- #app-container > div:not(.active) {
54
- display: none !important; /* Cache les divs enfants non actives de app-container (CORRIGÉ avec !important) */
55
- }
56
- /* --- Fin Règles d'Affichage --- */
57
-
58
- .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;} /* Allow wrapping */
59
  .badge { background-color: var(--accent); color: white; padding: 0.2rem 0.5rem; border-radius: 10px; font-size: 0.8rem; white-space: nowrap; }
 
60
  .spinner { border: 4px solid rgba(0, 0, 0, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--accent); animation: spin 1s linear infinite; margin: 2rem auto; display: none; }
61
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
62
  .exercise-summary { margin: 0.3rem 0; padding: 0.3rem 0; border-bottom: 1px solid #333; font-size: 0.9rem;}
63
  .exercise-summary:last-child { border-bottom: none; }
64
  .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
65
  .satisfaction-value { font-size: 2rem; color: var(--accent); margin-top: 0.5rem; }
66
- @media (max-width: 600px) {
67
- .form-row { flex-direction: column; gap: 0; }
68
- .container { padding: 0.5rem; }
69
- h1 { font-size: 1.5rem; }
70
- .flex-between { flex-direction: column; align-items: stretch; } /* Stack items vertically */
71
- .flex-between > div { width: 100%; display: flex; justify-content: flex-end; margin-top: 0.5rem;} /* Push buttons to the right */
72
- .user-info { text-align: center; margin-bottom: 0.5rem;}
73
- .user-info button { display: block; margin: 0.5rem auto 0;} /* Center logout button */
74
  }
75
- /* Styles page de connexion */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  #login-page .card { max-width: 400px; margin: 2rem auto; }
77
  #login-page h2 { text-align: center; margin-bottom: 1.5rem; }
78
  #login-error { color: var(--danger); text-align: center; margin-top: 1rem; display: none; }
79
  .user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: #bbb;}
80
  .user-info strong { color: var(--text-light); }
81
  .user-info button { margin-left: 0.5rem; padding: 0.2rem 0.5rem; font-size: 0.8rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  </style>
83
  </head>
84
  <body>
@@ -90,7 +133,8 @@
90
 
91
  <!-- Écran de Connexion -->
92
  <div id="login-page">
93
- <div class="card">
 
94
  <h2>Accès Utilisateur</h2>
95
  <div class="form-group">
96
  <label for="username">Nom d'utilisateur</label>
@@ -104,7 +148,7 @@
104
  </div>
105
  </div>
106
 
107
- <!-- Contenu Principal de l'Application (initiallement caché) -->
108
  <div id="main-app-content">
109
  <div class="user-info">
110
  Connecté: <strong id="current-user-display"></strong>
@@ -113,143 +157,84 @@
113
 
114
  <div id="app-container">
115
  <!-- Page Accueil / Liste Séances -->
116
- <!-- Seule cette page a la classe 'active' au début -->
117
  <div id="home-page" class="active">
118
- <div class="flex-between">
119
  <h2>Mes séances</h2>
120
  <button id="new-workout-btn" class="btn">Nouvelle séance</button>
121
  </div>
122
  <div id="workouts-list" class="workout-list" style="margin-top: 1rem;">
123
- <!-- Liste des séances ici -->
124
- <div class="spinner hidden"></div> <!-- Spinner caché au début -->
125
  <p id="empty-workout-message" class="hidden" style="text-align: center; margin-top: 2rem;">
126
- Aucune séance enregistrée pour cet utilisateur.
127
  </p>
128
  </div>
129
  </div>
130
 
131
- <!-- Page Nouvelle Séance (PAS de classe 'hidden' ici) -->
132
  <div id="new-workout-page">
133
  <div class="flex-between">
134
- <h2>Nouvelle séance</h2>
135
- <div> <!-- Conteneur pour les boutons -->
136
  <button id="cancel-new-workout-btn" class="btn btn-outline" style="margin-right: 0.5rem;">Annuler</button>
137
- <button id="save-workout-btn" class="btn">Enregistrer</button>
138
  </div>
139
  </div>
 
140
  <div class="card">
141
- <div class="form-group">
142
- <label for="workout-name">Nom de la séance</label>
143
- <input type="text" id="workout-name" placeholder="Ex: Push, Legs, Full Body...">
144
- </div>
145
- <div class="form-row">
146
- <div class="form-group">
147
- <label for="workout-date">Date</label>
148
- <input type="date" id="workout-date">
149
- </div>
150
- <div class="form-group">
151
- <label for="workout-duration">Durée (min)</label>
152
- <input type="number" id="workout-duration" min="1" placeholder="60">
153
- </div>
154
- </div>
155
  </div>
 
156
  <h3 style="margin: 1rem 0;">Exercices</h3>
 
 
 
 
 
 
 
157
  <div id="exercises-container">
158
  <!-- Exercices ajoutés dynamiquement ici -->
159
  </div>
160
- <button id="add-exercise-btn" class="btn btn-outline" style="width: 100%; margin-top: 1rem;">+ Ajouter Exercice</button>
 
 
161
  <div class="card" style="margin-top: 2rem;">
162
- <div class="form-group">
163
- <label for="satisfaction">Niveau de satisfaction (1-100%)</label>
164
- <input type="range" id="satisfaction" min="1" max="100" value="75">
165
- <div class="satisfaction">
166
- <span>Satisfaction</span>
167
- <div class="satisfaction-value">75%</div>
168
- </div>
169
- </div>
170
  </div>
171
  </div>
172
 
173
- <!-- Page Détail Séance (PAS de classe 'hidden' ici) -->
174
  <div id="workout-details-page">
175
- <div class="flex-between">
176
- <h2 id="detail-workout-name">Détail Séance</h2>
177
- <button id="back-to-home" class="btn btn-outline">Retour</button>
178
- </div>
179
- <div class="card">
180
- <div class="workout-details-info">
181
- <div class="form-row">
182
- <p><strong>Date:</strong> <span id="detail-date"></span></p>
183
- <p><strong>Durée:</strong> <span id="detail-duration"></span> min</p>
184
- </div>
185
- </div>
186
- </div>
187
- <div class="stats-grid" style="margin-top: 1rem;">
188
- <div class="card stat-card">
189
- <div class="stat-value" id="detail-tonnage">0</div>
190
- <div>Tonnage Total (kg)</div>
191
- </div>
192
- <div class="card stat-card">
193
- <div class="stat-value" id="detail-satisfaction">0%</div>
194
- <div>Satisfaction</div>
195
- </div>
196
- <div class="card stat-card">
197
- <div class="stat-value" id="detail-exercises-count">0</div>
198
- <div>Exercices</div>
199
- </div>
200
- </div>
201
  <h3 style="margin: 1.5rem 0 1rem;">Exercices Réalisés</h3>
202
- <div id="detail-exercises-container">
203
- <!-- Détails des exercices affichés ici -->
204
- </div>
205
  <button id="delete-workout-btn" class="btn btn-danger" style="width: 100%; margin-top: 2rem;">Supprimer cette séance</button>
206
  </div>
207
 
208
- <!-- Page Statistiques (PAS de classe 'hidden' ici) -->
209
  <div id="stats-page">
210
- <h2>Statistiques</h2>
211
- <div class="stats-grid">
212
- <div class="card stat-card">
213
- <div class="stat-value" id="stats-workout-count">0</div>
214
- <div>Séances Totales</div>
215
- </div>
216
- <div class="card stat-card">
217
- <div class="stat-value" id="stats-avg-tonnage">0</div>
218
- <div>Tonnage Moyen</div>
219
- </div>
220
- <div class="card stat-card">
221
- <div class="stat-value" id="stats-avg-satisfaction">0%</div>
222
- <div>Satisfaction Moyenne</div>
223
- </div>
224
- </div>
225
  <h3 style="margin: 1.5rem 0 1rem;">Tendances Récentes</h3>
226
- <div class="card">
227
- <p style="text-align: center; margin: 1rem 0;">
228
- Les statistiques détaillées seront affichées après plusieurs séances enregistrées.
229
- </p>
230
- </div>
231
  </div>
232
  </div>
233
 
234
  <!-- Menu Navigation Bas -->
235
- <nav class="nav-bottom">
236
- <a href="#" class="nav-item active" data-page="home-page">
237
- <div class="nav-icon">📋</div>
238
- <div>Séances</div>
239
- </a>
240
- <a href="#" class="nav-item" data-page="stats-page">
241
- <div class="nav-icon">📊</div>
242
- <div>Stats</div>
243
- </a>
244
- </nav>
245
- </div> <!-- Fin de #main-app-content -->
246
- </div> <!-- Fin de .container -->
247
 
248
  <script>
249
  // --- Variables d'État ---
250
- let workouts = []; // Séances pour l'utilisateur courant
251
- let currentUser = null; // Nom de l'utilisateur connecté
252
- let currentWorkoutId = null; // ID de la séance en cours de visualisation/modification
 
 
253
 
254
  // --- Éléments DOM ---
255
  const loginPage = document.getElementById('login-page');
@@ -260,238 +245,122 @@
260
  const currentUserDisplay = document.getElementById('current-user-display');
261
  const logoutBtn = document.getElementById('logout-btn');
262
  const spinner = document.querySelector('#workouts-list .spinner');
263
-
264
  const appContainer = document.getElementById('app-container');
265
  const navItems = document.querySelectorAll('.nav-item');
266
  const newWorkoutBtn = document.getElementById('new-workout-btn');
267
  const saveWorkoutBtn = document.getElementById('save-workout-btn');
268
  const cancelNewWorkoutBtn = document.getElementById('cancel-new-workout-btn');
269
  const addExerciseBtn = document.getElementById('add-exercise-btn');
270
- const exercisesContainer = document.getElementById('exercises-container'); // Pour la page nouvelle séance
271
- const workoutsList = document.getElementById('workouts-list'); // Pour afficher la liste
272
  const backToHomeBtn = document.getElementById('back-to-home');
273
  const deleteWorkoutBtn = document.getElementById('delete-workout-btn');
274
  const satisfactionRange = document.getElementById('satisfaction');
275
  const satisfactionValue = document.querySelector('.satisfaction-value');
276
  const emptyWorkoutMessage = document.getElementById('empty-workout-message');
277
  const workoutDateInput = document.getElementById('workout-date');
 
 
 
278
 
279
 
280
  // --- Initialisation ---
281
  document.addEventListener('DOMContentLoaded', () => {
282
  const rememberedUser = sessionStorage.getItem('liftTrackCurrentUser');
283
- if (rememberedUser) {
284
- loginUser(rememberedUser);
285
- } else {
286
- showLoginPage();
287
- }
288
  initEventListeners();
289
- // setTodayDate sera appelé dans showApp après la connexion
290
  });
291
 
292
- // --- Fonctions d'Authentification ---
293
- function showLoginPage() {
294
- loginPage.style.display = 'block';
295
- mainAppContent.style.display = 'none';
296
- loginError.style.display = 'none';
297
- usernameInput.value = '';
298
- currentUser = null; // Assurer que l'utilisateur est bien déconnecté
299
- }
300
-
301
- function showApp() {
302
- if (!currentUser) return; // Ne rien faire si pas d'utilisateur défini
303
- loginPage.style.display = 'none';
304
- mainAppContent.style.display = 'block';
305
- currentUserDisplay.textContent = currentUser;
306
- setTodayDate(); // Mettre la date du jour par défaut
307
- loadWorkouts(); // Charger les données de l'utilisateur
308
- renderWorkoutsList(); // Afficher ses séances
309
- showPage('home-page'); // Afficher la page d'accueil par défaut
310
- }
311
-
312
- function handleLogin() {
313
- const username = usernameInput.value.trim();
314
- // Validation simple: non vide
315
- if (username && username.length > 0) {
316
- loginUser(username);
317
- } else {
318
- loginError.textContent = "Veuillez entrer un nom d'utilisateur.";
319
- loginError.style.display = 'block';
320
- }
321
- }
322
-
323
- function loginUser(username) {
324
- currentUser = username;
325
- sessionStorage.setItem('liftTrackCurrentUser', currentUser); // Mémoriser pour la session
326
- showApp(); // Afficher l'application principale
327
- }
328
-
329
- function handleLogout() {
330
- currentUser = null;
331
- sessionStorage.removeItem('liftTrackCurrentUser');
332
- workouts = []; // Vider les données en mémoire
333
- showLoginPage(); // Revenir à la page de connexion
334
- }
335
-
336
- // --- Fonctions de Persistance des Données (Spécifique à l'utilisateur) ---
337
- function getStorageKey() {
338
- if (!currentUser) return null;
339
- const safeUsername = currentUser.replace(/[^a-zA-Z0-9_-]/g, '_'); // Rendre le nom sûr pour une clé
340
- return `liftTrackData_${safeUsername}`;
341
- }
342
-
343
- function loadWorkouts() {
344
- const storageKey = getStorageKey();
345
- if (!storageKey) {
346
- workouts = []; // Pas d'utilisateur, pas de données
347
- return;
348
- }
349
- // Simuler un petit chargement
350
- if (spinner) spinner.classList.remove('hidden');
351
- emptyWorkoutMessage.classList.add('hidden'); // Cacher le message pendant le chargement
352
- workoutsList.innerHTML = ''; // Vider la liste actuelle
353
-
354
- setTimeout(() => { // Simule un délai réseau ou de traitement
355
- try {
356
- const savedData = localStorage.getItem(storageKey);
357
- workouts = savedData ? JSON.parse(savedData) : [];
358
- console.log(`Chargé ${workouts.length} séances pour ${currentUser}`);
359
- } catch (e) {
360
- console.error("Erreur lors du parsing des données depuis localStorage:", e);
361
- workouts = []; // En cas d'erreur de parsing, repartir de zéro
362
- alert("Erreur lors du chargement de vos données. Certaines données pourraient être corrompues.");
363
- } finally {
364
- if (spinner) spinner.classList.add('hidden');
365
- renderWorkoutsList(); // Afficher la liste (même si vide) après chargement
366
- }
367
- }, 150); // Petit délai pour voir le spinner
368
  }
369
-
370
-
371
- function saveWorkouts() {
372
- const storageKey = getStorageKey();
373
- if (!storageKey) {
374
- console.error("Sauvegarde impossible: aucun utilisateur connecté.");
375
- return;
376
- }
377
- try {
378
- localStorage.setItem(storageKey, JSON.stringify(workouts));
379
- console.log(`Sauvegardé ${workouts.length} séances pour ${currentUser}`);
380
- } catch (e) {
381
- console.error("Erreur lors de la sauvegarde dans localStorage:", e);
382
- alert("Erreur lors de la sauvegarde de vos données. Le stockage local est peut-être plein ou indisponible.");
383
- }
384
  }
385
 
386
  // --- Écouteurs d'Événements ---
387
  function initEventListeners() {
388
  loginBtn.addEventListener('click', handleLogin);
389
  logoutBtn.addEventListener('click', handleLogout);
390
- usernameInput.addEventListener('keypress', (e) => {
391
- if (e.key === 'Enter') {
392
- e.preventDefault(); // Empêche soumission formulaire (si jamais il y en avait un)
393
- handleLogin();
394
- }
395
- });
396
 
397
- // Navigation principale (menu du bas)
398
- navItems.forEach(item => {
399
- item.addEventListener('click', (e) => {
400
- e.preventDefault();
401
- if (!currentUser) return; // Sécurité
402
- const targetPageId = item.getAttribute('data-page');
403
- showPage(targetPageId);
404
- // Mettre à jour l'état actif du menu
405
- navItems.forEach(nav => nav.classList.remove('active'));
406
- item.classList.add('active');
407
- // Mettre à jour les stats si on va sur la page stats
408
- if (targetPageId === 'stats-page') {
409
- updateStats();
410
- }
411
- });
412
  });
413
 
414
- // Boutons de l'application principale
415
  newWorkoutBtn.addEventListener('click', () => {
416
  if (!currentUser) return;
417
- showPage('new-workout-page');
418
- clearNewWorkoutForm();
419
- currentWorkoutId = null; // Important pour indiquer une nouvelle séance
 
 
420
  });
421
 
422
- cancelNewWorkoutBtn.addEventListener('click', () => {
423
- showPage('home-page'); // Retour simple à l'accueil
424
- });
 
 
 
 
425
 
426
- saveWorkoutBtn.addEventListener('click', saveWorkout);
427
- addExerciseBtn.addEventListener('click', addExercise); // Note: addExercise ajoute aussi la première série
428
  backToHomeBtn.addEventListener('click', () => showPage('home-page'));
429
  deleteWorkoutBtn.addEventListener('click', deleteWorkout);
430
-
431
- // Slider satisfaction
432
- satisfactionRange.addEventListener('input', () => {
433
- satisfactionValue.textContent = `${satisfactionRange.value}%`;
434
- });
435
  }
436
 
437
- // --- Logique Principale de l'Application ---
438
- function setTodayDate() {
439
- if(workoutDateInput) {
440
- try {
441
- const today = new Date().toISOString().split('T')[0];
442
- workoutDateInput.value = today;
443
- } catch (e) {
444
- console.error("Impossible de définir la date d'aujourd'hui:", e);
445
- // Fallback si toISOString n'est pas supporté (très improbable)
446
- workoutDateInput.value = '';
447
- }
448
- }
449
- }
450
 
451
- function showPage(pageId) {
452
- if (!currentUser) { showLoginPage(); return; } // Rediriger si pas connecté
453
 
454
- // Cacher toutes les pages
455
  document.querySelectorAll('#app-container > div').forEach(page => {
456
  page.classList.remove('active');
457
  });
458
 
459
- // Afficher la page cible
460
  const pageToShow = document.getElementById(pageId);
461
  if (pageToShow) {
462
  pageToShow.classList.add('active');
463
  } else {
464
- console.error(`Page ID "${pageId}" non trouvée. Affichage de home-page.`);
465
  document.getElementById('home-page').classList.add('active');
466
- pageId = 'home-page'; // Corriger pageId pour la nav
467
  }
468
 
469
- // Mettre à jour la nav du bas
470
- navItems.forEach(item => {
471
- if (item.getAttribute('data-page') === pageId) {
472
- item.classList.add('active');
473
- } else {
474
- item.classList.remove('active');
475
- }
476
- });
477
 
478
- // Si la page cible est 'home-page', on s'assure que la nav 'Séances' est active
479
- if (pageId === 'home-page' || pageId === 'new-workout-page' || pageId === 'workout-details-page') {
480
- document.querySelector('.nav-item[data-page="home-page"]').classList.add('active');
481
- document.querySelector('.nav-item[data-page="stats-page"]').classList.remove('active');
482
- } else if (pageId === 'stats-page') {
483
- document.querySelector('.nav-item[data-page="home-page"]').classList.remove('active');
484
- document.querySelector('.nav-item[data-page="stats-page"]').classList.add('active');
485
  }
 
486
 
 
 
 
 
487
  }
488
 
489
- function addExercise() {
490
  if (!currentUser) return;
491
  const exerciseId = `exercise-${Date.now()}`;
492
  const exerciseDiv = document.createElement('div');
493
- // Utiliser 'card exercise' pour le style et l'identification
494
- exerciseDiv.className = 'card exercise';
495
  exerciseDiv.setAttribute('data-exercise-id', exerciseId);
496
 
497
  exerciseDiv.innerHTML = `
@@ -499,310 +368,262 @@
499
  <input type="text" placeholder="Nom Exercice" class="exercise-name">
500
  <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;">×</button>
501
  </div>
502
- <div class="form-group" style="margin-top: 0.5rem; margin-bottom: 0.5rem;">
503
- <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.9rem;">
504
  <input type="checkbox" class="unilateral-checkbox"> Unilatéral
505
  </label>
 
 
 
506
  </div>
507
- <div class="series-container">
508
- <!-- Séries ajoutées ici -->
509
- </div>
510
  <button class="btn btn-outline add-series" style="width: 100%; margin-top: 0.5rem; padding: 0.4rem;">+ Ajouter Série</button>
511
  `;
512
- exercisesContainer.appendChild(exerciseDiv);
513
 
514
- // Écouteurs pour cet exercice spécifique
 
 
 
515
  const removeBtn = exerciseDiv.querySelector('.remove-exercise');
516
- removeBtn.addEventListener('click', () => exerciseDiv.remove());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  const addSeriesBtn = exerciseDiv.querySelector('.add-series');
519
  const seriesContainer = exerciseDiv.querySelector('.series-container');
520
  addSeriesBtn.addEventListener('click', () => addSeries(seriesContainer));
521
 
522
- // Ajouter la première série automatiquement
523
- addSeries(seriesContainer);
 
 
 
 
 
 
 
524
  }
525
 
526
- function addSeries(container) {
527
- if (!currentUser || !container) return;
528
- const seriesId = `series-${Date.now()}`;
529
- const seriesDiv = document.createElement('div');
530
- seriesDiv.className = 'series';
531
- seriesDiv.setAttribute('data-series-id', seriesId);
532
-
533
- seriesDiv.innerHTML = `
534
- <div class="form-row" style="align-items: flex-end; gap: 0.8rem;"> <!-- Ajout gap -->
535
- <div class="form-group" style="flex: 1.2;">
536
- <label>Reps</label>
537
- <input type="number" class="reps" min="1" placeholder="10" style="padding: 0.4rem;">
538
- </div>
539
- <div class="form-group" style="flex: 1.2;">
540
- <label>Charge (kg)</label>
541
- <input type="number" class="weight" min="0" step="0.1" placeholder="20" style="padding: 0.4rem;">
542
- </div>
543
- <div class="form-group" style="flex: 1; display: flex; align-items: center; padding-bottom: 0.6rem; min-width: 100px;">
544
- <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.8rem; margin-bottom: 0; white-space: nowrap;">
545
- <input type="checkbox" class="degressive-checkbox" style="margin-right: 0.3rem;"> Dégressive
546
- </label>
547
- </div>
548
- <button class="btn btn-danger remove-series" style="padding: 0.3rem 0.6rem; margin-bottom: 0.6rem; flex-basis: 30px; flex-grow: 0; align-self: center;">×</button>
549
- </div>
550
- `;
551
- container.appendChild(seriesDiv);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
 
553
- const removeBtn = seriesDiv.querySelector('.remove-series');
554
- removeBtn.addEventListener('click', () => seriesDiv.remove());
 
 
 
 
 
 
 
 
 
 
 
 
555
  }
556
 
557
  function saveWorkout() {
558
  if (!currentUser) return;
559
 
560
- // --- Récupération Données ---
561
  const workoutName = document.getElementById('workout-name').value.trim();
562
  const workoutDate = document.getElementById('workout-date').value;
563
  const workoutDuration = parseInt(document.getElementById('workout-duration').value) || 0;
564
  const satisfaction = parseInt(document.getElementById('satisfaction').value);
565
 
566
- // --- Validation Données Séance ---
567
- if (!workoutName) { alert("Veuillez entrer un nom pour la séance."); return; }
568
- if (!workoutDate) { alert("Veuillez choisir une date pour la séance."); return; }
569
- if (workoutDuration <= 0) { alert("Veuillez entrer une durée valide (en minutes)."); return; }
570
 
571
- // --- Récupération Exercices ---
 
572
  const exercises = [];
573
- const exerciseElements = exercisesContainer.querySelectorAll('.exercise');
574
-
575
- if (exerciseElements.length === 0) { alert("Veuillez ajouter au moins un exercice à la séance."); return; }
576
-
577
- let validationError = null; // Pour stocker le message d'erreur
578
-
579
- exerciseElements.forEach(exerciseEl => {
580
- if (validationError) return; // Arrêter si une erreur précédente a eu lieu
581
 
 
 
 
582
  const exerciseName = exerciseEl.querySelector('.exercise-name').value.trim();
583
- const isUnilateral = exerciseEl.querySelector('.unilateral-checkbox').checked;
 
584
 
585
- if (!exerciseName) {
586
- validationError = "Veuillez nommer tous les exercices.";
587
- return;
588
- }
589
 
590
  const series = [];
591
  const seriesElements = exerciseEl.querySelectorAll('.series');
592
-
593
- if (seriesElements.length === 0) {
594
- validationError = `L'exercice "${exerciseName}" doit contenir au moins une série.`;
595
- return;
596
- }
597
 
598
  seriesElements.forEach(seriesEl => {
599
- if (validationError) return;
600
-
601
- const repsInput = seriesEl.querySelector('.reps');
602
- const weightInput = seriesEl.querySelector('.weight');
603
- const reps = parseInt(repsInput.value) || 0;
604
- const weight = parseFloat(weightInput.value) || 0; // Permet 0 pour poids du corps
605
  const isDegressive = seriesEl.querySelector('.degressive-checkbox').checked;
606
-
607
- if (reps <= 0) {
608
- validationError = `Nombre de répétitions invalide pour une série de "${exerciseName}".`;
609
- repsInput.focus(); // Focus sur le champ erroné
610
- return;
611
- }
612
- if (weight < 0) { // Poids négatif n'est pas permis
613
- validationError = `Charge invalide pour une série de "${exerciseName}".`;
614
- weightInput.focus();
615
- return;
616
- }
617
  series.push({ reps, weight, isDegressive });
618
  });
619
 
620
  if (!validationError) {
621
- exercises.push({ name: exerciseName, isUnilateral, series });
 
622
  }
623
  });
624
 
625
- // Afficher l'erreur et arrêter si nécessaire
626
- if (validationError) {
627
- alert(validationError);
628
- return;
629
- }
630
 
631
  // --- Calcul Tonnage ---
632
  let totalTonnage = 0;
633
  exercises.forEach(exercise => {
634
  exercise.series.forEach(serie => {
635
- const weightFactor = exercise.isUnilateral ? 2 : 1; // Compte double pour unilatéral
636
  totalTonnage += serie.reps * serie.weight * weightFactor;
637
  });
638
  });
639
 
640
- // --- Création Objet Séance ---
641
  const workout = {
642
- id: currentWorkoutId || `workout-${Date.now()}`, // Utilise l'ID existant si modification
643
- name: workoutName,
644
- date: workoutDate,
645
- duration: workoutDuration,
646
- exercises: exercises, // Utilise la variable locale remplie
647
- totalTonnage: totalTonnage,
648
- satisfaction: satisfaction
649
  };
650
 
651
- // --- Ajout ou Mise à Jour ---
652
  const existingIndex = workouts.findIndex(w => w.id === workout.id);
653
- if (existingIndex > -1) {
654
- workouts[existingIndex] = workout; // Mise à jour
655
- console.log("Séance mise à jour:", workout.id);
656
- } else {
657
- workouts.push(workout); // Ajout nouvelle séance
658
- console.log("Nouvelle séance ajoutée:", workout.id);
659
- }
660
 
661
- // --- Sauvegarde et Affichage ---
662
  saveWorkouts();
663
- showPage('home-page');
664
- renderWorkoutsList(); // Mettre à jour la liste immédiatement
665
- }
666
 
667
- function renderWorkoutsList() {
668
- if (!currentUser) return; // Sécurité
669
- workoutsList.innerHTML = ''; // Vider avant de remplir
670
 
671
- if (workouts.length === 0) {
672
- emptyWorkoutMessage.classList.remove('hidden'); // Afficher message si vide
673
- updateStats(); // Mettre à jour les stats (qui seront à 0)
674
- return;
675
- }
676
-
677
- emptyWorkoutMessage.classList.add('hidden'); // Cacher message si pas vide
678
 
679
- // Trier par date (plus récent en premier)
 
 
680
  const sortedWorkouts = [...workouts].sort((a, b) => new Date(b.date) - new Date(a.date));
681
-
682
- sortedWorkouts.forEach(workout => {
683
- const workoutDate = new Date(workout.date).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' });
684
- const workoutDiv = document.createElement('div');
685
- workoutDiv.className = 'card workout-card';
686
- workoutDiv.setAttribute('data-workout-id', workout.id);
687
-
688
- // Simplification de l'affichage pour plus de clarté
689
- workoutDiv.innerHTML = `
690
- <div class="workout-header">
691
- <h3 style="margin-bottom: 0.5rem;">${workout.name}</h3>
692
- <div class="badge">${workoutDate}</div>
693
- </div>
694
- <div class="workout-details" style="font-size: 0.9rem; color: #ccc; margin-top: 0.5rem;">
695
- <span>${workout.duration} min</span> |
696
- <span>${workout.exercises.length} exo${workout.exercises.length > 1 ? 's' : ''}</span> |
697
- <span>${workout.totalTonnage.toFixed(1)} kg</span> |
698
- <span>${workout.satisfaction}%</span>
699
- </div>
700
- `;
701
- workoutDiv.addEventListener('click', () => displayWorkoutDetails(workout.id));
702
- workoutsList.appendChild(workoutDiv);
703
- });
704
-
705
- updateStats(); // Mettre à jour les stats globales
706
- }
707
 
708
  function displayWorkoutDetails(workoutId) {
709
  if (!currentUser) return;
710
  const workout = workouts.find(w => w.id === workoutId);
711
- if (!workout) {
712
- console.error("Séance non trouvée:", workoutId);
713
- showPage('home-page'); // Retour à l'accueil si non trouvée
714
- return;
715
- }
716
 
717
- // MAJ Infos générales
718
- document.getElementById('detail-workout-name').textContent = workout.name;
719
- document.getElementById('detail-date').textContent = new Date(workout.date).toLocaleDateString('fr-FR');
720
- document.getElementById('detail-duration').textContent = workout.duration;
721
- document.getElementById('detail-tonnage').textContent = workout.totalTonnage.toFixed(1);
722
- document.getElementById('detail-satisfaction').textContent = `${workout.satisfaction}%`;
723
- document.getElementById('detail-exercises-count').textContent = workout.exercises.length;
724
 
725
- // MAJ Liste des exercices détaillés
726
  const detailExercisesContainer = document.getElementById('detail-exercises-container');
727
- detailExercisesContainer.innerHTML = ''; // Vider
728
 
729
- workout.exercises.forEach(exercise => {
 
730
  const exerciseDiv = document.createElement('div');
731
- exerciseDiv.className = 'card'; // Style carte pour chaque exo
 
 
 
 
 
 
 
 
 
 
 
 
732
  let seriesHtml = '';
733
- exercise.series.forEach((serie, index) => {
734
- const degressiveLabel = serie.isDegressive ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--accent-dark);">Dégr.</span>' : '';
735
- const weightFactor = exercise.isUnilateral ? 2 : 1;
736
- const seriesTonnage = serie.reps * serie.weight * weightFactor;
737
- seriesHtml += `
738
- <div class="exercise-summary">
739
- <div class="flex-between">
740
- <span>Série ${index + 1}: ${serie.reps} reps × ${serie.weight} kg${degressiveLabel}</span>
741
- <span style="color: #aaa;">(${seriesTonnage.toFixed(1)} kg)</span>
742
- </div>
743
- </div>
744
- `;
745
- });
746
 
747
  const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem;">Unilat.</span>' : '';
 
 
 
748
  exerciseDiv.innerHTML = `
749
- <h3 style="font-size: 1.1rem; margin-bottom: 0.5rem;">${exercise.name}${unilateralLabel}</h3>
750
  <div class="series-summary-container">
751
  ${seriesHtml}
752
  </div>`;
753
  detailExercisesContainer.appendChild(exerciseDiv);
754
  });
755
 
756
- currentWorkoutId = workoutId; // Stocker pour suppression éventuelle
757
- showPage('workout-details-page'); // Afficher la page détail
758
  }
759
 
760
- function deleteWorkout() {
761
- if (!currentUser || !currentWorkoutId) return;
762
- const workoutToDelete = workouts.find(w => w.id === currentWorkoutId);
763
- if (!workoutToDelete) return; // Sécurité
764
-
765
- const confirmDelete = confirm(`Êtes-vous sûr de vouloir supprimer la séance "${workoutToDelete.name}" du ${new Date(workoutToDelete.date).toLocaleDateString('fr-FR')} ?\nCette action est irréversible.`);
766
- if (!confirmDelete) return;
767
-
768
- // Filtrer la séance
769
- workouts = workouts.filter(w => w.id !== currentWorkoutId);
770
- saveWorkouts(); // Sauvegarder la liste mise à jour
771
- currentWorkoutId = null; // Réinitialiser l'ID courant
772
- showPage('home-page');
773
- renderWorkoutsList(); // Rafraîchir la liste
774
  }
775
 
776
- function clearNewWorkoutForm() {
777
- if (!currentUser) return;
778
- document.getElementById('workout-name').value = '';
779
- document.getElementById('workout-duration').value = '';
780
- setTodayDate(); // Remettre date du jour
781
- exercisesContainer.innerHTML = ''; // Vider les exercices ajoutés
782
- satisfactionRange.value = 75; // Reset satisfaction
783
- satisfactionValue.textContent = '75%';
784
- currentWorkoutId = null; // Assurer qu'on est bien en mode création
785
- }
786
-
787
- function updateStats() {
788
- if (!currentUser) return;
789
- const workoutCount = workouts.length;
790
-
791
- document.getElementById('stats-workout-count').textContent = workoutCount;
792
-
793
- if (workoutCount === 0) {
794
- document.getElementById('stats-avg-tonnage').textContent = '0';
795
- document.getElementById('stats-avg-satisfaction').textContent = '0%';
796
- return;
797
- }
798
-
799
- const totalTonnageAll = workouts.reduce((sum, workout) => sum + (workout.totalTonnage || 0), 0);
800
- const avgTonnage = totalTonnageAll / workoutCount;
801
- document.getElementById('stats-avg-tonnage').textContent = avgTonnage.toFixed(1);
802
-
803
- const totalSatisfaction = workouts.reduce((sum, workout) => sum + (workout.satisfaction || 0), 0);
804
- const avgSatisfaction = totalSatisfaction / workoutCount;
805
- document.getElementById('stats-avg-satisfaction').textContent = `${Math.round(avgSatisfaction)}%`;
806
  }
807
 
808
  </script>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>LiftTrack - Suivi Musculation Multi-Utilisateurs</title>
7
+ <!-- Ajout de la librairie pour les confettis -->
8
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
9
  <style>
10
+ /* --- Styles CSS (avec ajouts/modifications) --- */
11
  :root {
12
  --bg-dark: #121212;
13
  --bg-card: #1e1e1e;
 
15
  --accent: #4CAF50;
16
  --accent-dark: #3a8a3d;
17
  --danger: #f44336;
18
+ --superset-border: #4a90e2; /* Couleur pour indiquer superset */
19
  }
20
  * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
21
+ body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; }
22
  .container { width: 100%; max-width: 800px; margin: 0 auto; padding: 1rem; }
23
  header { padding: 1rem 0; text-align: center; border-bottom: 1px solid #333; margin-bottom: 1rem; }
24
  h1, h2, h3 { color: var(--accent); }
25
  .btn { background-color: var(--accent); color: white; border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background-color 0.2s; }
26
  .btn:hover { background-color: var(--accent-dark); }
27
+ .btn:disabled { background-color: #555; cursor: not-allowed; }
28
  .btn-outline { background-color: transparent; color: var(--accent); border: 1px solid var(--accent); }
29
+ .btn-outline:disabled { color: #555; border-color: #555; }
30
  .btn-danger { background-color: var(--danger); }
31
  input, select, textarea { width: 100%; padding: 0.6rem; margin-bottom: 1rem; background-color: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: var(--text-light); }
32
+ input[type="checkbox"] { width: auto; margin-right: 0.5rem; vertical-align: middle; }
33
  .card { background-color: var(--bg-card); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
34
  .form-group { margin-bottom: 1rem; }
35
+ .form-row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
36
+ .form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; }
37
  label { display: block; margin-bottom: 0.3rem; color: #bbb; font-size: 0.9rem; }
38
+ .exercise {
39
+ border-left: 3px solid var(--accent);
40
+ padding-left: 1rem;
41
+ margin-bottom: 1.5rem;
42
+ display: none; /* Caché par défaut, montré par JS */
43
+ }
44
+ .exercise.active-exercise {
45
+ display: block; /* Afficher l'exercice actif */
46
+ animation: fadeIn 0.3s ease-in-out;
47
+ }
48
+ /* Style pour exercice débutant un superset (dans le formulaire) */
49
+ .exercise.is-superset-start {
50
+ border-left-color: var(--superset-border);
51
+ }
52
  .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem;}
53
+ .exercise-header input {margin-bottom: 0;}
54
  .series-container { margin-left: 0.5rem; margin-top: 0.5rem; }
55
  .series { background-color: #252525; padding: 0.7rem; border-radius: 4px; margin-bottom: 0.5rem; }
56
  .nav-bottom { position: fixed; bottom: 0; left: 0; width: 100%; background-color: #1a1a1a; display: flex; justify-content: space-around; padding: 0.7rem 0; box-shadow: 0 -2px 10px rgba(0,0,0,0.3); z-index: 10; }
 
63
  .stat-card { text-align: center; padding: 1rem; }
64
  .stat-value { font-size: 1.8rem; color: var(--accent); font-weight: bold; }
65
  .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 1rem; }
66
+ .hidden { display: none; }
67
+ #login-page { display: block; }
68
+ #main-app-content { display: none; }
69
+ #app-container > div:not(.active) { display: none !important; }
70
+ .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;}
 
 
 
 
 
 
71
  .badge { background-color: var(--accent); color: white; padding: 0.2rem 0.5rem; border-radius: 10px; font-size: 0.8rem; white-space: nowrap; }
72
+ .badge-superset { background-color: var(--superset-border); }
73
  .spinner { border: 4px solid rgba(0, 0, 0, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--accent); animation: spin 1s linear infinite; margin: 2rem auto; display: none; }
74
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
75
  .exercise-summary { margin: 0.3rem 0; padding: 0.3rem 0; border-bottom: 1px solid #333; font-size: 0.9rem;}
76
  .exercise-summary:last-child { border-bottom: none; }
77
  .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
78
  .satisfaction-value { font-size: 2rem; color: var(--accent); margin-top: 0.5rem; }
79
+ .exercise-navigation { display: flex; justify-content: space-between; margin-top: 1rem; margin-bottom: 1rem; }
80
+ .detail-exercise-card.is-superset-start {
81
+ border-left: 3px solid var(--superset-border);
82
+ margin-bottom: 0.2rem; /* Réduire l'espace avant le suivant */
 
 
 
 
83
  }
84
+ .detail-exercise-card.is-superset-continuation {
85
+ border-left: 3px solid var(--superset-border);
86
+ padding-top: 0.5rem;
87
+ margin-top: -0.8rem; /* Rapprocher visuellement */
88
+ border-top-left-radius: 0; /* Pour lier visuellement */
89
+ border-bottom-left-radius: 8px;
90
+ }
91
+
92
+
93
+ /* Animation d'apparition/zoom pour nouvelle séance */
94
+ @keyframes fadeInZoom {
95
+ from { opacity: 0; transform: scale(0.95); }
96
+ to { opacity: 1; transform: scale(1); }
97
+ }
98
+ #new-workout-page.active {
99
+ animation: fadeInZoom 0.3s ease-out;
100
+ }
101
+
102
+ /* Styles page de connexion & user info */
103
  #login-page .card { max-width: 400px; margin: 2rem auto; }
104
  #login-page h2 { text-align: center; margin-bottom: 1.5rem; }
105
  #login-error { color: var(--danger); text-align: center; margin-top: 1rem; display: none; }
106
  .user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: #bbb;}
107
  .user-info strong { color: var(--text-light); }
108
  .user-info button { margin-left: 0.5rem; padding: 0.2rem 0.5rem; font-size: 0.8rem; }
109
+
110
+ @media (max-width: 600px) {
111
+ .form-row { flex-direction: column; gap: 0; }
112
+ .container { padding: 0.5rem; }
113
+ h1 { font-size: 1.5rem; }
114
+ .flex-between { flex-direction: column; align-items: stretch; }
115
+ .flex-between > div { width: 100%; display: flex; justify-content: flex-end; margin-top: 0.5rem;}
116
+ .user-info { text-align: center; margin-bottom: 0.5rem;}
117
+ .user-info button { display: block; margin: 0.5rem auto 0;}
118
+ .exercise-navigation button { padding: 0.5rem; font-size: 0.9rem;} /* Ajuster boutons nav exercice */
119
+ }
120
+ /* Animation fade in standard */
121
+ @keyframes fadeIn {
122
+ from { opacity: 0; }
123
+ to { opacity: 1; }
124
+ }
125
  </style>
126
  </head>
127
  <body>
 
133
 
134
  <!-- Écran de Connexion -->
135
  <div id="login-page">
136
+ <!-- Contenu page connexion -->
137
+ <div class="card">
138
  <h2>Accès Utilisateur</h2>
139
  <div class="form-group">
140
  <label for="username">Nom d'utilisateur</label>
 
148
  </div>
149
  </div>
150
 
151
+ <!-- Contenu Principal de l'Application -->
152
  <div id="main-app-content">
153
  <div class="user-info">
154
  Connecté: <strong id="current-user-display"></strong>
 
157
 
158
  <div id="app-container">
159
  <!-- Page Accueil / Liste Séances -->
 
160
  <div id="home-page" class="active">
161
+ <div class="flex-between">
162
  <h2>Mes séances</h2>
163
  <button id="new-workout-btn" class="btn">Nouvelle séance</button>
164
  </div>
165
  <div id="workouts-list" class="workout-list" style="margin-top: 1rem;">
166
+ <div class="spinner hidden"></div>
 
167
  <p id="empty-workout-message" class="hidden" style="text-align: center; margin-top: 2rem;">
168
+ Aucune séance enregistrée.
169
  </p>
170
  </div>
171
  </div>
172
 
173
+ <!-- Page Nouvelle Séance -->
174
  <div id="new-workout-page">
175
  <div class="flex-between">
176
+ <h2>Nouvelle séance <span id="current-exercise-indicator" style="font-size: 0.9rem; color: #ccc;"></span></h2>
177
+ <div>
178
  <button id="cancel-new-workout-btn" class="btn btn-outline" style="margin-right: 0.5rem;">Annuler</button>
179
+ <button id="save-workout-btn" class="btn">Enregistrer Séance</button>
180
  </div>
181
  </div>
182
+ <!-- Infos Séance -->
183
  <div class="card">
184
+ <div class="form-group"> <label for="workout-name">Nom de la séance</label> <input type="text" id="workout-name" placeholder="Ex: Push, Legs, Full Body..."> </div>
185
+ <div class="form-row"> <div class="form-group"> <label for="workout-date">Date</label> <input type="date" id="workout-date"> </div> <div class="form-group"> <label for="workout-duration">Durée (min)</label> <input type="number" id="workout-duration" min="1" placeholder="60"> </div> </div>
 
 
 
 
 
 
 
 
 
 
 
 
186
  </div>
187
+
188
  <h3 style="margin: 1rem 0;">Exercices</h3>
189
+ <!-- Navigation entre exercices -->
190
+ <div class="exercise-navigation card">
191
+ <button id="prev-exercise-btn" class="btn btn-outline">< Précédent</button>
192
+ <span style="align-self: center; color: #ccc;">Exercice Actif</span>
193
+ <button id="next-exercise-btn" class="btn btn-outline">Suivant ></button>
194
+ </div>
195
+ <!-- Conteneur pour les exercices (un seul visible à la fois) -->
196
  <div id="exercises-container">
197
  <!-- Exercices ajoutés dynamiquement ici -->
198
  </div>
199
+ <button id="add-exercise-btn" class="btn btn-outline" style="width: 100%; margin-top: 1rem;">+ Ajouter un Nouvel Exercice</button>
200
+
201
+ <!-- Satisfaction -->
202
  <div class="card" style="margin-top: 2rem;">
203
+ <div class="form-group"> <label for="satisfaction">Niveau de satisfaction (1-100%)</label> <input type="range" id="satisfaction" min="1" max="100" value="75"> <div class="satisfaction"> <span>Satisfaction</span> <div class="satisfaction-value">75%</div> </div> </div>
 
 
 
 
 
 
 
204
  </div>
205
  </div>
206
 
207
+ <!-- Page Détail Séance -->
208
  <div id="workout-details-page">
209
+ <div class="flex-between"> <h2 id="detail-workout-name">Détail Séance</h2> <button id="back-to-home" class="btn btn-outline">Retour</button> </div>
210
+ <div class="card"> <div class="workout-details-info"> <div class="form-row"> <p><strong>Date:</strong> <span id="detail-date"></span></p> <p><strong>Durée:</strong> <span id="detail-duration"></span> min</p> </div> </div> </div>
211
+ <div class="stats-grid" style="margin-top: 1rem;"> <div class="card stat-card"> <div class="stat-value" id="detail-tonnage">0</div> <div>Tonnage Total (kg)</div> </div> <div class="card stat-card"> <div class="stat-value" id="detail-satisfaction">0%</div> <div>Satisfaction</div> </div> <div class="card stat-card"> <div class="stat-value" id="detail-exercises-count">0</div> <div>Exercices</div> </div> </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  <h3 style="margin: 1.5rem 0 1rem;">Exercices Réalisés</h3>
213
+ <div id="detail-exercises-container"> <!-- Détails des exercices affichés ici --> </div>
 
 
214
  <button id="delete-workout-btn" class="btn btn-danger" style="width: 100%; margin-top: 2rem;">Supprimer cette séance</button>
215
  </div>
216
 
217
+ <!-- Page Statistiques -->
218
  <div id="stats-page">
219
+ <h2>Statistiques</h2>
220
+ <div class="stats-grid"> <div class="card stat-card"> <div class="stat-value" id="stats-workout-count">0</div> <div>Séances Totales</div> </div> <div class="card stat-card"> <div class="stat-value" id="stats-avg-tonnage">0</div> <div>Tonnage Moyen</div> </div> <div class="card stat-card"> <div class="stat-value" id="stats-avg-satisfaction">0%</div> <div>Satisfaction Moyenne</div> </div> </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  <h3 style="margin: 1.5rem 0 1rem;">Tendances Récentes</h3>
222
+ <div class="card"> <p style="text-align: center; margin: 1rem 0;"> Les statistiques détaillées seront affichées après plusieurs séances enregistrées. </p> </div>
 
 
 
 
223
  </div>
224
  </div>
225
 
226
  <!-- Menu Navigation Bas -->
227
+ <nav class="nav-bottom"> <a href="#" class="nav-item active" data-page="home-page"> <div class="nav-icon">📋</div> <div>Séances</div> </a> <a href="#" class="nav-item" data-page="stats-page"> <div class="nav-icon">📊</div> <div>Stats</div> </a> </nav>
228
+ </div>
229
+ </div>
 
 
 
 
 
 
 
 
 
230
 
231
  <script>
232
  // --- Variables d'État ---
233
+ let workouts = [];
234
+ let currentUser = null;
235
+ let currentWorkoutId = null;
236
+ let currentExerciseIndex = 0; // Index de l'exercice affiché dans le formulaire Nouvelle Séance
237
+ let workoutExercisesForm = []; // Tableau temporaire pour les divs d'exercice dans le form
238
 
239
  // --- Éléments DOM ---
240
  const loginPage = document.getElementById('login-page');
 
245
  const currentUserDisplay = document.getElementById('current-user-display');
246
  const logoutBtn = document.getElementById('logout-btn');
247
  const spinner = document.querySelector('#workouts-list .spinner');
 
248
  const appContainer = document.getElementById('app-container');
249
  const navItems = document.querySelectorAll('.nav-item');
250
  const newWorkoutBtn = document.getElementById('new-workout-btn');
251
  const saveWorkoutBtn = document.getElementById('save-workout-btn');
252
  const cancelNewWorkoutBtn = document.getElementById('cancel-new-workout-btn');
253
  const addExerciseBtn = document.getElementById('add-exercise-btn');
254
+ const exercisesContainer = document.getElementById('exercises-container');
255
+ const workoutsList = document.getElementById('workouts-list');
256
  const backToHomeBtn = document.getElementById('back-to-home');
257
  const deleteWorkoutBtn = document.getElementById('delete-workout-btn');
258
  const satisfactionRange = document.getElementById('satisfaction');
259
  const satisfactionValue = document.querySelector('.satisfaction-value');
260
  const emptyWorkoutMessage = document.getElementById('empty-workout-message');
261
  const workoutDateInput = document.getElementById('workout-date');
262
+ const prevExerciseBtn = document.getElementById('prev-exercise-btn');
263
+ const nextExerciseBtn = document.getElementById('next-exercise-btn');
264
+ const currentExerciseIndicator = document.getElementById('current-exercise-indicator');
265
 
266
 
267
  // --- Initialisation ---
268
  document.addEventListener('DOMContentLoaded', () => {
269
  const rememberedUser = sessionStorage.getItem('liftTrackCurrentUser');
270
+ if (rememberedUser) { loginUser(rememberedUser); } else { showLoginPage(); }
 
 
 
 
271
  initEventListeners();
 
272
  });
273
 
274
+ // --- Authentification & Persistance (inchangées) ---
275
+ function showLoginPage() { /* ... */ loginPage.style.display = 'block'; mainAppContent.style.display = 'none'; loginError.style.display = 'none'; usernameInput.value = ''; currentUser = null; }
276
+ function showApp() { /* ... */ if (!currentUser) return; loginPage.style.display = 'none'; mainAppContent.style.display = 'block'; currentUserDisplay.textContent = currentUser; setTodayDate(); loadWorkouts(); showPage('home-page'); }
277
+ function handleLogin() { /* ... */ const username = usernameInput.value.trim(); if (username && username.length > 0) { loginUser(username); } else { loginError.textContent = "Veuillez entrer un nom d'utilisateur."; loginError.style.display = 'block'; }}
278
+ function loginUser(username) { /* ... */ currentUser = username; sessionStorage.setItem('liftTrackCurrentUser', currentUser); showApp(); }
279
+ function handleLogout() { /* ... */ currentUser = null; sessionStorage.removeItem('liftTrackCurrentUser'); workouts = []; showLoginPage(); }
280
+ function getStorageKey() { /* ... */ if (!currentUser) return null; const safeUsername = currentUser.replace(/[^a-zA-Z0-9_-]/g, '_'); return `liftTrackData_${safeUsername}`; }
281
+ function loadWorkouts() { /* ... (avec spinner) */
282
+ const storageKey = getStorageKey(); if (!storageKey) { workouts = []; return; }
283
+ if (spinner) spinner.classList.remove('hidden'); emptyWorkoutMessage.classList.add('hidden'); workoutsList.innerHTML = '';
284
+ setTimeout(() => { try { const savedData = localStorage.getItem(storageKey); workouts = savedData ? JSON.parse(savedData) : []; console.log(`Chargé ${workouts.length} séances pour ${currentUser}`); } catch (e) { console.error("Erreur parsing localStorage:", e); workouts = []; alert("Erreur chargement données."); } finally { if (spinner) spinner.classList.add('hidden'); renderWorkoutsList(); } }, 150);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  }
286
+ function saveWorkouts() { /* ... (avec try/catch) */
287
+ const storageKey = getStorageKey(); if (!storageKey) { console.error("Sauvegarde impossible: non connecté."); return; } try { localStorage.setItem(storageKey, JSON.stringify(workouts)); console.log(`Sauvegardé ${workouts.length} séances pour ${currentUser}`); } catch (e) { console.error("Erreur sauvegarde localStorage:", e); alert("Erreur sauvegarde données."); }
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  }
289
 
290
  // --- Écouteurs d'Événements ---
291
  function initEventListeners() {
292
  loginBtn.addEventListener('click', handleLogin);
293
  logoutBtn.addEventListener('click', handleLogout);
294
+ usernameInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); handleLogin(); } });
 
 
 
 
 
295
 
296
+ navItems.forEach(item => { /* ... navigation menu bas ... */
297
+ item.addEventListener('click', (e) => { e.preventDefault(); if (!currentUser) return; const targetPageId = item.getAttribute('data-page'); showPage(targetPageId); navItems.forEach(nav => nav.classList.remove('active')); item.classList.add('active'); if (targetPageId === 'stats-page') { updateStats(); } });
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  });
299
 
300
+ // Bouton Nouvelle Séance
301
  newWorkoutBtn.addEventListener('click', () => {
302
  if (!currentUser) return;
303
+ currentWorkoutId = null; // C'est une nouvelle séance
304
+ clearNewWorkoutForm(); // Réinitialise le formulaire et ajoute le premier exercice vide
305
+ showPage('new-workout-page');
306
+ // Déclencher l'animation d'entrée (la classe 'active' est déjà ajoutée par showPage)
307
+ // L'animation CSS sur #new-workout-page.active s'en charge
308
  });
309
 
310
+ cancelNewWorkoutBtn.addEventListener('click', () => showPage('home-page'));
311
+ saveWorkoutBtn.addEventListener('click', saveWorkout); // Sauvegarde tous les exercices ajoutés
312
+ addExerciseBtn.addEventListener('click', handleAddNewExercise); // Ajoute un exo et navigue vers lui
313
+
314
+ // Navigation formulaire exercices
315
+ prevExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, -1));
316
+ nextExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, 1));
317
 
 
 
318
  backToHomeBtn.addEventListener('click', () => showPage('home-page'));
319
  deleteWorkoutBtn.addEventListener('click', deleteWorkout);
320
+ satisfactionRange.addEventListener('input', () => { satisfactionValue.textContent = `${satisfactionRange.value}%`; });
 
 
 
 
321
  }
322
 
323
+ // --- Logique Principale ---
324
+ function setTodayDate() { /* ... */ if(workoutDateInput) { try { const today = new Date().toISOString().split('T')[0]; workoutDateInput.value = today; } catch (e) { console.error("Impossible définir date:", e); workoutDateInput.value = ''; } } }
 
 
 
 
 
 
 
 
 
 
 
325
 
326
+ function showPage(pageId) {
327
+ if (!currentUser) { showLoginPage(); return; }
328
 
 
329
  document.querySelectorAll('#app-container > div').forEach(page => {
330
  page.classList.remove('active');
331
  });
332
 
 
333
  const pageToShow = document.getElementById(pageId);
334
  if (pageToShow) {
335
  pageToShow.classList.add('active');
336
  } else {
337
+ console.error(`Page ID "${pageId}" non trouvée. Affichage home-page.`);
338
  document.getElementById('home-page').classList.add('active');
339
+ pageId = 'home-page';
340
  }
341
 
342
+ // Gérer la nav active
343
+ navItems.forEach(item => { item.classList.remove('active'); });
344
+ const activeNavItem = document.querySelector(`.nav-item[data-page="${pageId === 'stats-page' ? 'stats-page' : 'home-page'}"]`);
345
+ if(activeNavItem) activeNavItem.classList.add('active');
 
 
 
 
346
 
347
+ // Si on affiche la page nouvelle séance, s'assurer que le premier exercice est visible
348
+ if (pageId === 'new-workout-page') {
349
+ renderActiveExerciseForm(); // Affichera l'exercice à currentExerciseIndex
 
 
 
 
350
  }
351
+ }
352
 
353
+ // --- Logique Formulaire Nouvelle Séance ---
354
+
355
+ function handleAddNewExercise() {
356
+ addExercise(true); // true pour naviguer vers le nouvel exercice
357
  }
358
 
359
+ function addExercise(navigateToNew = false) {
360
  if (!currentUser) return;
361
  const exerciseId = `exercise-${Date.now()}`;
362
  const exerciseDiv = document.createElement('div');
363
+ exerciseDiv.className = 'card exercise'; // Pas 'active-exercise' ici
 
364
  exerciseDiv.setAttribute('data-exercise-id', exerciseId);
365
 
366
  exerciseDiv.innerHTML = `
 
368
  <input type="text" placeholder="Nom Exercice" class="exercise-name">
369
  <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;">×</button>
370
  </div>
371
+ <div class="form-row" style="margin-top: 0.5rem; margin-bottom: 0.5rem; gap: 1rem;">
372
+ <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.9rem;">
373
  <input type="checkbox" class="unilateral-checkbox"> Unilatéral
374
  </label>
375
+ <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.9rem;">
376
+ <input type="checkbox" class="superset-checkbox"> Début de Superset
377
+ </label>
378
  </div>
379
+ <div class="series-container"></div>
 
 
380
  <button class="btn btn-outline add-series" style="width: 100%; margin-top: 0.5rem; padding: 0.4rem;">+ Ajouter Série</button>
381
  `;
382
+ exercisesContainer.appendChild(exerciseDiv); // Ajouter au DOM
383
 
384
+ // Garder une référence (important pour la navigation)
385
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
386
+
387
+ // Écouteurs pour cet exercice
388
  const removeBtn = exerciseDiv.querySelector('.remove-exercise');
389
+ // Gérer la suppression d'exercice dans le formulaire
390
+ removeBtn.addEventListener('click', () => {
391
+ const indexToRemove = workoutExercisesForm.indexOf(exerciseDiv);
392
+ if (indexToRemove > -1) {
393
+ exerciseDiv.remove(); // Supprimer du DOM
394
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // Mettre à jour la liste
395
+ // Ajuster l'index courant si on supprime l'actuel ou un précédent
396
+ if (currentExerciseIndex >= indexToRemove) {
397
+ currentExerciseIndex = Math.max(0, currentExerciseIndex - 1);
398
+ }
399
+ // Si on supprime le dernier, l'index doit pointer sur le nouveau dernier (ou 0 si vide)
400
+ if (currentExerciseIndex >= workoutExercisesForm.length) {
401
+ currentExerciseIndex = Math.max(0, workoutExercisesForm.length - 1);
402
+ }
403
+ renderActiveExerciseForm(); // Afficher le bon exercice après suppression
404
+ }
405
+ });
406
+
407
 
408
  const addSeriesBtn = exerciseDiv.querySelector('.add-series');
409
  const seriesContainer = exerciseDiv.querySelector('.series-container');
410
  addSeriesBtn.addEventListener('click', () => addSeries(seriesContainer));
411
 
412
+ addSeries(seriesContainer); // Ajouter la première série
413
+
414
+ if (navigateToNew) {
415
+ currentExerciseIndex = workoutExercisesForm.length - 1; // Aller au nouvel exercice ajouté
416
+ renderActiveExerciseForm();
417
+ } else {
418
+ updateExerciseNavButtons(); // Juste mettre à jour les boutons si on n'y navigue pas
419
+ }
420
+
421
  }
422
 
423
+ function addSeries(container) { /* ... (inchangé) ... */
424
+ if (!currentUser || !container) return; const seriesId = `series-${Date.now()}`; const seriesDiv = document.createElement('div'); seriesDiv.className = 'series'; seriesDiv.setAttribute('data-series-id', seriesId);
425
+ seriesDiv.innerHTML = `<div class="form-row" style="align-items: flex-end; gap: 0.8rem;"> <div class="form-group" style="flex: 1.2;"> <label>Reps</label> <input type="number" class="reps" min="1" placeholder="10" style="padding: 0.4rem;"> </div> <div class="form-group" style="flex: 1.2;"> <label>Charge (kg)</label> <input type="number" class="weight" min="0" step="0.1" placeholder="20" style="padding: 0.4rem;"> </div> <div class="form-group" style="flex: 1; display: flex; align-items: center; padding-bottom: 0.6rem; min-width: 100px;"> <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.8rem; margin-bottom: 0; white-space: nowrap;"> <input type="checkbox" class="degressive-checkbox" style="margin-right: 0.3rem;"> Dégressive </label> </div> <button class="btn btn-danger remove-series" style="padding: 0.3rem 0.6rem; margin-bottom: 0.6rem; flex-basis: 30px; flex-grow: 0; align-self: center;">×</button> </div>`;
426
+ container.appendChild(seriesDiv); const removeBtn = seriesDiv.querySelector('.remove-series'); removeBtn.addEventListener('click', () => seriesDiv.remove());
427
+ }
428
+
429
+ function navigateExerciseForm(direction) {
430
+ const newIndex = currentExerciseIndex + direction;
431
+ if (newIndex >= 0 && newIndex < workoutExercisesForm.length) {
432
+ currentExerciseIndex = newIndex;
433
+ renderActiveExerciseForm();
434
+ }
435
+ }
436
+
437
+ function renderActiveExerciseForm() {
438
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // MAJ référence
439
+ // Cacher tous les exercices du formulaire
440
+ workoutExercisesForm.forEach(ex => ex.classList.remove('active-exercise'));
441
+
442
+ // Afficher l'exercice courant s'il existe
443
+ if (currentExerciseIndex >= 0 && currentExerciseIndex < workoutExercisesForm.length) {
444
+ const currentExDiv = workoutExercisesForm[currentExerciseIndex];
445
+ currentExDiv.classList.add('active-exercise');
446
+ // Appliquer style superset si coché
447
+ const isSuperset = currentExDiv.querySelector('.superset-checkbox').checked;
448
+ if (isSuperset) {
449
+ currentExDiv.classList.add('is-superset-start');
450
+ } else {
451
+ currentExDiv.classList.remove('is-superset-start');
452
+ }
453
+
454
+ }
455
+
456
+ // Mettre à jour l'indicateur et les boutons
457
+ updateExerciseNavButtons();
458
+ }
459
+
460
+ function updateExerciseNavButtons() {
461
+ const totalExercises = workoutExercisesForm.length;
462
+ if (totalExercises === 0) {
463
+ currentExerciseIndicator.textContent = "(Aucun exercice)";
464
+ prevExerciseBtn.disabled = true;
465
+ nextExerciseBtn.disabled = true;
466
+ } else {
467
+ currentExerciseIndicator.textContent = `(${currentExerciseIndex + 1}/${totalExercises})`;
468
+ prevExerciseBtn.disabled = currentExerciseIndex === 0;
469
+ nextExerciseBtn.disabled = currentExerciseIndex === totalExercises - 1;
470
+ }
471
+ }
472
 
473
+
474
+ function clearNewWorkoutForm() {
475
+ if (!currentUser) return;
476
+ document.getElementById('workout-name').value = '';
477
+ document.getElementById('workout-duration').value = '';
478
+ setTodayDate();
479
+ exercisesContainer.innerHTML = ''; // Vider le conteneur
480
+ workoutExercisesForm = []; // Vider la référence
481
+ satisfactionRange.value = 75;
482
+ satisfactionValue.textContent = '75%';
483
+ currentWorkoutId = null;
484
+ currentExerciseIndex = 0; // Revenir au premier potentiel exercice
485
+ addExercise(false); // Ajouter le premier bloc d'exercice vide mais ne pas naviguer (render s'en charge)
486
+ renderActiveExerciseForm(); // Afficher le premier exercice et MAJ boutons
487
  }
488
 
489
  function saveWorkout() {
490
  if (!currentUser) return;
491
 
 
492
  const workoutName = document.getElementById('workout-name').value.trim();
493
  const workoutDate = document.getElementById('workout-date').value;
494
  const workoutDuration = parseInt(document.getElementById('workout-duration').value) || 0;
495
  const satisfaction = parseInt(document.getElementById('satisfaction').value);
496
 
497
+ if (!workoutName) { alert("Nom séance requis."); return; }
498
+ if (!workoutDate) { alert("Date requise."); return; }
499
+ if (workoutDuration <= 0) { alert("Durée invalide."); return; }
 
500
 
501
+ // Lire depuis les divs d'exercices présents dans le conteneur du formulaire
502
+ const exerciseElements = workoutExercisesForm; // Utiliser notre référence à jour
503
  const exercises = [];
504
+ if (exerciseElements.length === 0) { alert("Ajoutez au moins un exercice."); return; }
 
 
 
 
 
 
 
505
 
506
+ let validationError = null;
507
+ exerciseElements.forEach(exerciseEl => {
508
+ if (validationError) return;
509
  const exerciseName = exerciseEl.querySelector('.exercise-name').value.trim();
510
+ const isUnilateral = exerciseEl.querySelector('.unilateral-checkbox').checked;
511
+ const isSupersetStart = exerciseEl.querySelector('.superset-checkbox').checked; // Récupérer état superset
512
 
513
+ if (!exerciseName) { validationError = "Nommez tous les exercices."; return; }
 
 
 
514
 
515
  const series = [];
516
  const seriesElements = exerciseEl.querySelectorAll('.series');
517
+ if (seriesElements.length === 0) { validationError = `Exercice "${exerciseName}" sans série.`; return; }
 
 
 
 
518
 
519
  seriesElements.forEach(seriesEl => {
520
+ if (validationError) return;
521
+ const repsInput = seriesEl.querySelector('.reps'); const weightInput = seriesEl.querySelector('.weight');
522
+ const reps = parseInt(repsInput.value) || 0; const weight = parseFloat(weightInput.value) || 0;
 
 
 
523
  const isDegressive = seriesEl.querySelector('.degressive-checkbox').checked;
524
+ if (reps <= 0) { validationError = `Reps invalides pour "${exerciseName}".`; repsInput.focus(); return; }
525
+ if (weight < 0) { validationError = `Charge invalide pour "${exerciseName}".`; weightInput.focus(); return; }
 
 
 
 
 
 
 
 
 
526
  series.push({ reps, weight, isDegressive });
527
  });
528
 
529
  if (!validationError) {
530
+ // Ajouter isSupersetStart à l'objet exercice
531
+ exercises.push({ name: exerciseName, isUnilateral, isSupersetStart, series });
532
  }
533
  });
534
 
535
+ if (validationError) { alert(validationError); return; }
 
 
 
 
536
 
537
  // --- Calcul Tonnage ---
538
  let totalTonnage = 0;
539
  exercises.forEach(exercise => {
540
  exercise.series.forEach(serie => {
541
+ const weightFactor = exercise.isUnilateral ? 2 : 1;
542
  totalTonnage += serie.reps * serie.weight * weightFactor;
543
  });
544
  });
545
 
 
546
  const workout = {
547
+ id: currentWorkoutId || `workout-${Date.now()}`, name: workoutName, date: workoutDate, duration: workoutDuration,
548
+ exercises: exercises, // Tableau d'exercices avec potentiellement isSupersetStart
549
+ totalTonnage: totalTonnage, satisfaction: satisfaction
 
 
 
 
550
  };
551
 
 
552
  const existingIndex = workouts.findIndex(w => w.id === workout.id);
553
+ if (existingIndex > -1) { workouts[existingIndex] = workout; } else { workouts.push(workout); }
 
 
 
 
 
 
554
 
 
555
  saveWorkouts();
 
 
 
556
 
557
+ // Animation Confettis !
558
+ confetti({ particleCount: 150, spread: 90, origin: { y: 0.6 } });
 
559
 
560
+ showPage('home-page');
561
+ renderWorkoutsList();
562
+ }
 
 
 
 
563
 
564
+ // --- Affichage et Stats (avec gestion superset dans l'affichage détail) ---
565
+ function renderWorkoutsList() { /* ... (inchangé sauf appel updateStats) ... */
566
+ if (!currentUser) return; workoutsList.innerHTML = ''; if (workouts.length === 0) { emptyWorkoutMessage.classList.remove('hidden'); updateStats(); return; } emptyWorkoutMessage.classList.add('hidden');
567
  const sortedWorkouts = [...workouts].sort((a, b) => new Date(b.date) - new Date(a.date));
568
+ sortedWorkouts.forEach(workout => { const workoutDate = new Date(workout.date).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }); const workoutDiv = document.createElement('div'); workoutDiv.className = 'card workout-card'; workoutDiv.setAttribute('data-workout-id', workout.id); workoutDiv.innerHTML = `<div class="workout-header"><h3 style="margin-bottom: 0.5rem;">${workout.name}</h3><div class="badge">${workoutDate}</div></div><div class="workout-details" style="font-size: 0.9rem; color: #ccc; margin-top: 0.5rem;"><span>${workout.duration} min</span> | <span>${workout.exercises.length} exo${workout.exercises.length > 1 ? 's' : ''}</span> | <span>${workout.totalTonnage.toFixed(1)} kg</span> | <span>${workout.satisfaction}%</span></div>`; workoutDiv.addEventListener('click', () => displayWorkoutDetails(workout.id)); workoutsList.appendChild(workoutDiv); });
569
+ updateStats();
570
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
 
572
  function displayWorkoutDetails(workoutId) {
573
  if (!currentUser) return;
574
  const workout = workouts.find(w => w.id === workoutId);
575
+ if (!workout) { console.error("Séance non trouvée:", workoutId); showPage('home-page'); return; }
 
 
 
 
576
 
577
+ // MAJ Infos générales (inchangé)
578
+ document.getElementById('detail-workout-name').textContent = workout.name; document.getElementById('detail-date').textContent = new Date(workout.date).toLocaleDateString('fr-FR'); document.getElementById('detail-duration').textContent = workout.duration; document.getElementById('detail-tonnage').textContent = workout.totalTonnage.toFixed(1); document.getElementById('detail-satisfaction').textContent = `${workout.satisfaction}%`; document.getElementById('detail-exercises-count').textContent = workout.exercises.length;
 
 
 
 
 
579
 
 
580
  const detailExercisesContainer = document.getElementById('detail-exercises-container');
581
+ detailExercisesContainer.innerHTML = '';
582
 
583
+ // MAJ Exercices avec indicateur Superset
584
+ workout.exercises.forEach((exercise, index) => {
585
  const exerciseDiv = document.createElement('div');
586
+ // Ajouter des classes pour le style Superset
587
+ exerciseDiv.classList.add('card', 'detail-exercise-card'); // Classe de base
588
+ let isContinuation = false;
589
+ if (exercise.isSupersetStart) {
590
+ exerciseDiv.classList.add('is-superset-start');
591
+ }
592
+ // Vérifier si l'exercice *précédent* était un début de superset
593
+ if (index > 0 && workout.exercises[index - 1].isSupersetStart) {
594
+ exerciseDiv.classList.add('is-superset-continuation');
595
+ isContinuation = true;
596
+ }
597
+
598
+
599
  let seriesHtml = '';
600
+ exercise.series.forEach((serie, sIndex) => { /* ... (inchangé) ... */
601
+ const degressiveLabel = serie.isDegressive ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--accent-dark);">Dégr.</span>' : ''; const weightFactor = exercise.isUnilateral ? 2 : 1; const seriesTonnage = serie.reps * serie.weight * weightFactor;
602
+ seriesHtml += `<div class="exercise-summary"><div class="flex-between"><span>Série ${sIndex + 1}: ${serie.reps} reps × ${serie.weight} kg${degressiveLabel}</span><span style="color: #aaa;">(${seriesTonnage.toFixed(1)} kg)</span></div></div>`;
603
+ });
 
 
 
 
 
 
 
 
 
604
 
605
  const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem;">Unilat.</span>' : '';
606
+ // Ajout badge Superset si c'est le début
607
+ const supersetLabel = exercise.isSupersetStart ? ` <span class="badge badge-superset" style="font-size: 0.7rem;">Superset 🔽</span>` : '';
608
+
609
  exerciseDiv.innerHTML = `
610
+ <h3 style="font-size: 1.1rem; margin-bottom: 0.5rem;">${exercise.name}${unilateralLabel}${supersetLabel}</h3>
611
  <div class="series-summary-container">
612
  ${seriesHtml}
613
  </div>`;
614
  detailExercisesContainer.appendChild(exerciseDiv);
615
  });
616
 
617
+ currentWorkoutId = workoutId;
618
+ showPage('workout-details-page');
619
  }
620
 
621
+ function deleteWorkout() { /* ... (inchangé) ... */
622
+ if (!currentUser || !currentWorkoutId) return; const workoutToDelete = workouts.find(w => w.id === currentWorkoutId); if (!workoutToDelete) return; const confirmDelete = confirm(`Supprimer séance "${workoutToDelete.name}" du ${new Date(workoutToDelete.date).toLocaleDateString('fr-FR')} ?`); if (!confirmDelete) return; workouts = workouts.filter(w => w.id !== currentWorkoutId); saveWorkouts(); currentWorkoutId = null; showPage('home-page'); renderWorkoutsList();
 
 
 
 
 
 
 
 
 
 
 
 
623
  }
624
 
625
+ function updateStats() { /* ... (inchangé) ... */
626
+ if (!currentUser) return; const workoutCount = workouts.length; document.getElementById('stats-workout-count').textContent = workoutCount; if (workoutCount === 0) { document.getElementById('stats-avg-tonnage').textContent = '0'; document.getElementById('stats-avg-satisfaction').textContent = '0%'; return; } const totalTonnageAll = workouts.reduce((sum, workout) => sum + (workout.totalTonnage || 0), 0); const avgTonnage = totalTonnageAll / workoutCount; document.getElementById('stats-avg-tonnage').textContent = avgTonnage.toFixed(1); const totalSatisfaction = workouts.reduce((sum, workout) => sum + (workout.satisfaction || 0), 0); const avgSatisfaction = totalSatisfaction / workoutCount; document.getElementById('stats-avg-satisfaction').textContent = `${Math.round(avgSatisfaction)}%`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  }
628
 
629
  </script>