BitDown commited on
Commit
b2c6b16
·
verified ·
1 Parent(s): b59ef08

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +54 -144
index.html CHANGED
@@ -4,18 +4,16 @@
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 (Superset enlevé) --- */
11
- :root {
12
  --bg-dark: #121212;
13
  --bg-card: #1e1e1e;
14
  --text-light: #e0e0e0;
15
  --accent: #4CAF50;
16
  --accent-dark: #3a8a3d;
17
  --danger: #f44336;
18
- /* --superset-border: #4a90e2; enlevé */
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; }
@@ -39,11 +37,11 @@
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
  .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem;}
49
  .exercise-header input {margin-bottom: 0;}
@@ -65,7 +63,6 @@
65
  #app-container > div:not(.active) { display: none !important; }
66
  .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;}
67
  .badge { background-color: var(--accent); color: white; padding: 0.2rem 0.5rem; border-radius: 10px; font-size: 0.8rem; white-space: nowrap; }
68
- /* .badge-superset enlevé */
69
  .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; }
70
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
71
  .exercise-summary { margin: 0.3rem 0; padding: 0.3rem 0; border-bottom: 1px solid #333; font-size: 0.9rem;}
@@ -73,14 +70,20 @@
73
  .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
74
  .satisfaction-value { font-size: 2rem; color: var(--accent); margin-top: 0.5rem; }
75
  .exercise-navigation { display: flex; justify-content: space-between; margin-top: 1rem; margin-bottom: 1rem; }
76
- /* Styles détail superset enlevés */
77
 
78
- /* Animation d'apparition/zoom pour nouvelle séance */
79
  @keyframes fadeInZoom {
80
- from { opacity: 0; transform: scale(0.95); }
81
- to { opacity: 1; transform: scale(1); }
 
 
 
 
 
 
82
  }
83
  #new-workout-page.active {
 
84
  animation: fadeInZoom 0.3s ease-out;
85
  }
86
 
@@ -92,7 +95,7 @@
92
  .user-info strong { color: var(--text-light); }
93
  .user-info button { margin-left: 0.5rem; padding: 0.2rem 0.5rem; font-size: 0.8rem; }
94
 
95
- @media (max-width: 600px) {
96
  .form-row { flex-direction: column; gap: 0; }
97
  .container { padding: 0.5rem; }
98
  h1 { font-size: 1.5rem; }
@@ -102,7 +105,7 @@
102
  .user-info button { display: block; margin: 0.5rem auto 0;}
103
  .exercise-navigation button { padding: 0.5rem; font-size: 0.9rem;}
104
  }
105
- /* Animation fade in standard */
106
  @keyframes fadeIn {
107
  from { opacity: 0; }
108
  to { opacity: 1; }
@@ -110,7 +113,9 @@
110
  </style>
111
  </head>
112
  <body>
113
- <div class="container">
 
 
114
  <header>
115
  <h1>LiftTrack</h1>
116
  <p>Suivi de vos séances de musculation</p>
@@ -221,6 +226,7 @@
221
  let workoutExercisesForm = []; // Référence aux divs .exercise dans le formulaire
222
 
223
  // --- Éléments DOM ---
 
224
  const loginPage = document.getElementById('login-page');
225
  const mainAppContent = document.getElementById('main-app-content');
226
  const usernameInput = document.getElementById('username');
@@ -247,6 +253,7 @@
247
  const nextExerciseBtn = document.getElementById('next-exercise-btn');
248
  const currentExerciseIndicator = document.getElementById('current-exercise-indicator');
249
 
 
250
  // --- Initialisation ---
251
  document.addEventListener('DOMContentLoaded', () => {
252
  const rememberedUser = sessionStorage.getItem('liftTrackCurrentUser');
@@ -271,6 +278,7 @@
271
  }
272
 
273
  // --- Écouteurs d'Événements ---
 
274
  function initEventListeners() {
275
  loginBtn.addEventListener('click', handleLogin);
276
  logoutBtn.addEventListener('click', handleLogout);
@@ -294,7 +302,9 @@
294
  satisfactionRange.addEventListener('input', () => { satisfactionValue.textContent = `${satisfactionRange.value}%`; });
295
  }
296
 
 
297
  // --- Logique Principale ---
 
298
  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 = ''; } } }
299
 
300
  function showPage(pageId) {
@@ -308,10 +318,8 @@
308
  if (pageId === 'new-workout-page') { renderActiveExerciseForm(); }
309
  }
310
 
311
- // --- Logique Formulaire Nouvelle Séance ---
312
-
313
  function handleAddNewExercise() {
314
- addExercise(true); // true pour naviguer vers le nouvel exercice
315
  }
316
 
317
  function addExercise(navigateToNew = false) {
@@ -321,51 +329,27 @@
321
  exerciseDiv.className = 'card exercise';
322
  exerciseDiv.setAttribute('data-exercise-id', exerciseId);
323
 
324
- // **HTML SANS SUPERSET**
325
  exerciseDiv.innerHTML = `
326
- <div class="exercise-header">
327
- <input type="text" placeholder="Nom Exercice" class="exercise-name">
328
- <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;">×</button>
329
- </div>
330
- <div class="form-group" style="margin-top: 0.5rem; margin-bottom: 0.5rem;">
331
- <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.9rem;">
332
- <input type="checkbox" class="unilateral-checkbox"> Unilatéral
333
- </label>
334
- <!-- Checkbox Superset enlevée -->
335
- </div>
336
  <div class="series-container"></div>
337
  <button class="btn btn-outline add-series" style="width: 100%; margin-top: 0.5rem; padding: 0.4rem;">+ Ajouter Série</button>
338
  `;
339
  exercisesContainer.appendChild(exerciseDiv);
340
-
341
- // Mettre à jour la référence immédiatement
342
  workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
343
 
344
- // Écouteurs pour cet exercice
345
  const removeBtn = exerciseDiv.querySelector('.remove-exercise');
346
  removeBtn.addEventListener('click', () => {
347
  const indexToRemove = workoutExercisesForm.indexOf(exerciseDiv);
348
- if (indexToRemove > -1) {
349
- exerciseDiv.remove();
350
- workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // MAJ après suppression DOM
351
- if (currentExerciseIndex >= indexToRemove) { currentExerciseIndex = Math.max(0, currentExerciseIndex - 1); }
352
- if (currentExerciseIndex >= workoutExercisesForm.length) { currentExerciseIndex = Math.max(0, workoutExercisesForm.length - 1); }
353
- renderActiveExerciseForm();
354
- }
355
  });
356
 
357
  const addSeriesBtn = exerciseDiv.querySelector('.add-series');
358
  const seriesContainer = exerciseDiv.querySelector('.series-container');
359
  addSeriesBtn.addEventListener('click', () => addSeries(seriesContainer));
360
-
361
  addSeries(seriesContainer);
362
 
363
- if (navigateToNew) {
364
- currentExerciseIndex = workoutExercisesForm.length - 1;
365
- renderActiveExerciseForm();
366
- } else {
367
- updateExerciseNavButtons();
368
- }
369
  }
370
 
371
  function addSeries(container) {
@@ -375,135 +359,74 @@
375
  }
376
 
377
  function navigateExerciseForm(direction) {
378
- // S'assurer que la référence est à jour avant de naviguer
379
  workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
380
  const newIndex = currentExerciseIndex + direction;
381
- if (newIndex >= 0 && newIndex < workoutExercisesForm.length) {
382
- currentExerciseIndex = newIndex;
383
- renderActiveExerciseForm();
384
- }
385
  }
386
 
387
  function renderActiveExerciseForm() {
388
  workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
389
  workoutExercisesForm.forEach(ex => ex.classList.remove('active-exercise'));
390
-
391
- if (currentExerciseIndex >= 0 && currentExerciseIndex < workoutExercisesForm.length) {
392
- const currentExDiv = workoutExercisesForm[currentExerciseIndex];
393
- currentExDiv.classList.add('active-exercise');
394
- // Logique style superset enlevée ici
395
- }
396
  updateExerciseNavButtons();
397
  }
398
 
399
  function updateExerciseNavButtons() {
400
  const totalExercises = workoutExercisesForm.length;
401
- if (totalExercises === 0) {
402
- currentExerciseIndicator.textContent = "(Aucun exercice)";
403
- prevExerciseBtn.disabled = true;
404
- nextExerciseBtn.disabled = true;
405
- } else {
406
- currentExerciseIndicator.textContent = `(${currentExerciseIndex + 1}/${totalExercises})`;
407
- prevExerciseBtn.disabled = currentExerciseIndex === 0;
408
- nextExerciseBtn.disabled = currentExerciseIndex === totalExercises - 1;
409
- }
410
  }
411
 
412
  function clearNewWorkoutForm() {
413
- if (!currentUser) return;
414
- document.getElementById('workout-name').value = ''; document.getElementById('workout-duration').value = '';
415
  setTodayDate(); exercisesContainer.innerHTML = ''; workoutExercisesForm = [];
416
  satisfactionRange.value = 75; satisfactionValue.textContent = '75%';
417
- currentWorkoutId = null; currentExerciseIndex = 0;
418
- addExercise(false); // Ajoute le premier vide
419
- renderActiveExerciseForm(); // Assure l'affichage correct
420
  }
421
 
422
  function saveWorkout() {
423
  if (!currentUser) return;
424
-
425
- // **CORRECTION : Rafraîchir la référence juste avant la validation**
426
- workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
427
 
428
  const workoutName = document.getElementById('workout-name').value.trim();
429
  const workoutDate = document.getElementById('workout-date').value;
430
  const workoutDuration = parseInt(document.getElementById('workout-duration').value) || 0;
431
  const satisfaction = parseInt(document.getElementById('satisfaction').value);
432
 
433
- if (!workoutName) { alert("Veuillez entrer un nom pour la séance."); return; }
434
- if (!workoutDate) { alert("Veuillez choisir une date pour la séance."); return; }
435
- if (workoutDuration <= 0) { alert("Veuillez entrer une durée valide (en minutes)."); return; }
436
 
437
- // Utiliser la référence fraîchement mise à jour
438
- const exerciseElements = workoutExercisesForm;
439
- const exercises = [];
440
- if (exerciseElements.length === 0) { alert("Veuillez ajouter au moins un exercice à la séance."); return; }
441
 
442
  let validationError = null;
443
- exerciseElements.forEach((exerciseEl, index) => { // Ajout index pour debug éventuel
444
  if (validationError) return;
445
-
446
- const nameInput = exerciseEl.querySelector('.exercise-name');
447
- const exerciseName = nameInput ? nameInput.value.trim() : ''; // Vérifier si l'input existe
448
  const isUnilateral = exerciseEl.querySelector('.unilateral-checkbox').checked;
449
- // isSupersetStart enlevé
450
-
451
- // **Point crucial de la validation du nom**
452
- if (!exerciseName) {
453
- // Donner un indice (numéro de l'exercice basé sur l'ordre actuel)
454
- validationError = `Veuillez nommer l'exercice #${index + 1}.`;
455
- // Essayer de mettre le focus sur le champ vide si possible
456
- if(nameInput) nameInput.focus();
457
- return;
458
- }
459
-
460
- const series = [];
461
- const seriesElements = exerciseEl.querySelectorAll('.series');
462
- if (seriesElements.length === 0) { validationError = `L'exercice "${exerciseName}" doit contenir au moins une série.`; return; }
463
-
464
  seriesElements.forEach(seriesEl => {
465
  if (validationError) return;
466
  const repsInput = seriesEl.querySelector('.reps'); const weightInput = seriesEl.querySelector('.weight');
467
  const reps = parseInt(repsInput.value) || 0; const weight = parseFloat(weightInput.value) || 0;
468
  const isDegressive = seriesEl.querySelector('.degressive-checkbox').checked;
469
- if (reps <= 0) { validationError = `Reps invalides pour une série de "${exerciseName}".`; if(repsInput) repsInput.focus(); return; }
470
- if (weight < 0) { validationError = `Charge invalide pour une série de "${exerciseName}".`; if(weightInput) weightInput.focus(); return; }
471
  series.push({ reps, weight, isDegressive });
472
  });
473
-
474
- if (!validationError) {
475
- // **Objet exercice SANS isSupersetStart**
476
- exercises.push({ name: exerciseName, isUnilateral, series });
477
- }
478
  });
479
 
480
  if (validationError) { alert(validationError); return; }
481
 
482
- // --- Calcul Tonnage ---
483
- let totalTonnage = 0;
484
- exercises.forEach(exercise => {
485
- exercise.series.forEach(serie => {
486
- const weightFactor = exercise.isUnilateral ? 2 : 1;
487
- totalTonnage += serie.reps * serie.weight * weightFactor;
488
- });
489
- });
490
-
491
- // --- Création/MAJ Objet Workout ---
492
- const workout = {
493
- id: currentWorkoutId || `workout-${Date.now()}`, name: workoutName, date: workoutDate, duration: workoutDuration,
494
- exercises: exercises, // Utilise le tableau local `exercises`
495
- totalTonnage: totalTonnage, satisfaction: satisfaction
496
- };
497
  const existingIndex = workouts.findIndex(w => w.id === workout.id);
498
  if (existingIndex > -1) { workouts[existingIndex] = workout; } else { workouts.push(workout); }
499
 
500
- saveWorkouts();
501
- confetti({ particleCount: 150, spread: 90, origin: { y: 0.6 } }); // Confettis!
502
- showPage('home-page');
503
- renderWorkoutsList();
504
  }
505
 
506
- // --- Affichage et Stats ---
507
  function renderWorkoutsList() {
508
  if (!currentUser) return; workoutsList.innerHTML = ''; if (workouts.length === 0) { emptyWorkoutMessage.classList.remove('hidden'); updateStats(); return; } emptyWorkoutMessage.classList.add('hidden');
509
  const sortedWorkouts = [...workouts].sort((a, b) => new Date(b.date) - new Date(a.date));
@@ -516,28 +439,15 @@
516
  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;
517
  const detailExercisesContainer = document.getElementById('detail-exercises-container'); detailExercisesContainer.innerHTML = '';
518
 
519
- // **Affichage détail SANS logique Superset**
520
  workout.exercises.forEach((exercise) => {
521
- const exerciseDiv = document.createElement('div');
522
- exerciseDiv.className = 'card detail-exercise-card'; // Juste la classe de base
523
-
524
  let seriesHtml = '';
525
- exercise.series.forEach((serie, sIndex) => {
526
- 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;
527
- 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>`;
528
- });
529
-
530
  const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem;">Unilat.</span>' : '';
531
- // Pas de supersetLabel ici
532
-
533
- exerciseDiv.innerHTML = `
534
- <h3 style="font-size: 1.1rem; margin-bottom: 0.5rem;">${exercise.name}${unilateralLabel}</h3>
535
- <div class="series-summary-container"> ${seriesHtml} </div>`;
536
  detailExercisesContainer.appendChild(exerciseDiv);
537
  });
538
-
539
- currentWorkoutId = workoutId;
540
- showPage('workout-details-page');
541
  }
542
 
543
  function deleteWorkout() {
 
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
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
8
  <style>
9
+ /* --- Styles CSS --- */
10
+ :root { /* ... variables ... */
11
  --bg-dark: #121212;
12
  --bg-card: #1e1e1e;
13
  --text-light: #e0e0e0;
14
  --accent: #4CAF50;
15
  --accent-dark: #3a8a3d;
16
  --danger: #f44336;
 
17
  }
18
  * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
19
  body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; }
 
37
  border-left: 3px solid var(--accent);
38
  padding-left: 1rem;
39
  margin-bottom: 1.5rem;
40
+ display: none;
41
  }
42
  .exercise.active-exercise {
43
+ display: block;
44
+ animation: fadeIn 0.3s ease-in-out; /* Utilise fadeIn simple pour l'exercice */
45
  }
46
  .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem;}
47
  .exercise-header input {margin-bottom: 0;}
 
63
  #app-container > div:not(.active) { display: none !important; }
64
  .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;}
65
  .badge { background-color: var(--accent); color: white; padding: 0.2rem 0.5rem; border-radius: 10px; font-size: 0.8rem; white-space: nowrap; }
 
66
  .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; }
67
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
68
  .exercise-summary { margin: 0.3rem 0; padding: 0.3rem 0; border-bottom: 1px solid #333; font-size: 0.9rem;}
 
70
  .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
71
  .satisfaction-value { font-size: 2rem; color: var(--accent); margin-top: 0.5rem; }
72
  .exercise-navigation { display: flex; justify-content: space-between; margin-top: 1rem; margin-bottom: 1rem; }
 
73
 
74
+ /* Animation d'apparition/zoom pour la page nouvelle séance */
75
  @keyframes fadeInZoom {
76
+ from {
77
+ opacity: 0;
78
+ transform: scale(0.9); /* Effet plus marqué */
79
+ }
80
+ to {
81
+ opacity: 1;
82
+ transform: scale(1);
83
+ }
84
  }
85
  #new-workout-page.active {
86
+ /* Applique l'animation à toute la page quand elle devient active */
87
  animation: fadeInZoom 0.3s ease-out;
88
  }
89
 
 
95
  .user-info strong { color: var(--text-light); }
96
  .user-info button { margin-left: 0.5rem; padding: 0.2rem 0.5rem; font-size: 0.8rem; }
97
 
98
+ @media (max-width: 600px) { /* ... media queries ... */
99
  .form-row { flex-direction: column; gap: 0; }
100
  .container { padding: 0.5rem; }
101
  h1 { font-size: 1.5rem; }
 
105
  .user-info button { display: block; margin: 0.5rem auto 0;}
106
  .exercise-navigation button { padding: 0.5rem; font-size: 0.9rem;}
107
  }
108
+ /* Animation fade in simple */
109
  @keyframes fadeIn {
110
  from { opacity: 0; }
111
  to { opacity: 1; }
 
113
  </style>
114
  </head>
115
  <body>
116
+ <!-- ... tout le contenu de <body> ... -->
117
+ <!-- (Il est identique à celui de la réponse précédente, pas besoin de le répéter ici) -->
118
+ <div class="container">
119
  <header>
120
  <h1>LiftTrack</h1>
121
  <p>Suivi de vos séances de musculation</p>
 
226
  let workoutExercisesForm = []; // Référence aux divs .exercise dans le formulaire
227
 
228
  // --- Éléments DOM ---
229
+ // (Identiques à la réponse précédente)
230
  const loginPage = document.getElementById('login-page');
231
  const mainAppContent = document.getElementById('main-app-content');
232
  const usernameInput = document.getElementById('username');
 
253
  const nextExerciseBtn = document.getElementById('next-exercise-btn');
254
  const currentExerciseIndicator = document.getElementById('current-exercise-indicator');
255
 
256
+
257
  // --- Initialisation ---
258
  document.addEventListener('DOMContentLoaded', () => {
259
  const rememberedUser = sessionStorage.getItem('liftTrackCurrentUser');
 
278
  }
279
 
280
  // --- Écouteurs d'Événements ---
281
+ // (Identiques à la réponse précédente)
282
  function initEventListeners() {
283
  loginBtn.addEventListener('click', handleLogin);
284
  logoutBtn.addEventListener('click', handleLogout);
 
302
  satisfactionRange.addEventListener('input', () => { satisfactionValue.textContent = `${satisfactionRange.value}%`; });
303
  }
304
 
305
+
306
  // --- Logique Principale ---
307
+ // (Identique à la réponse précédente, y compris showPage, addExercise, saveWorkout etc.)
308
  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 = ''; } } }
309
 
310
  function showPage(pageId) {
 
318
  if (pageId === 'new-workout-page') { renderActiveExerciseForm(); }
319
  }
320
 
 
 
321
  function handleAddNewExercise() {
322
+ addExercise(true);
323
  }
324
 
325
  function addExercise(navigateToNew = false) {
 
329
  exerciseDiv.className = 'card exercise';
330
  exerciseDiv.setAttribute('data-exercise-id', exerciseId);
331
 
 
332
  exerciseDiv.innerHTML = `
333
+ <div class="exercise-header"> <input type="text" placeholder="Nom Exercice" class="exercise-name"> <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;">×</button> </div>
334
+ <div class="form-group" style="margin-top: 0.5rem; margin-bottom: 0.5rem;"> <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.9rem;"> <input type="checkbox" class="unilateral-checkbox"> Unilatéral </label> </div>
 
 
 
 
 
 
 
 
335
  <div class="series-container"></div>
336
  <button class="btn btn-outline add-series" style="width: 100%; margin-top: 0.5rem; padding: 0.4rem;">+ Ajouter Série</button>
337
  `;
338
  exercisesContainer.appendChild(exerciseDiv);
 
 
339
  workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
340
 
 
341
  const removeBtn = exerciseDiv.querySelector('.remove-exercise');
342
  removeBtn.addEventListener('click', () => {
343
  const indexToRemove = workoutExercisesForm.indexOf(exerciseDiv);
344
+ if (indexToRemove > -1) { exerciseDiv.remove(); workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); if (currentExerciseIndex >= indexToRemove) { currentExerciseIndex = Math.max(0, currentExerciseIndex - 1); } if (currentExerciseIndex >= workoutExercisesForm.length) { currentExerciseIndex = Math.max(0, workoutExercisesForm.length - 1); } renderActiveExerciseForm(); }
 
 
 
 
 
 
345
  });
346
 
347
  const addSeriesBtn = exerciseDiv.querySelector('.add-series');
348
  const seriesContainer = exerciseDiv.querySelector('.series-container');
349
  addSeriesBtn.addEventListener('click', () => addSeries(seriesContainer));
 
350
  addSeries(seriesContainer);
351
 
352
+ if (navigateToNew) { currentExerciseIndex = workoutExercisesForm.length - 1; renderActiveExerciseForm(); } else { updateExerciseNavButtons(); }
 
 
 
 
 
353
  }
354
 
355
  function addSeries(container) {
 
359
  }
360
 
361
  function navigateExerciseForm(direction) {
 
362
  workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
363
  const newIndex = currentExerciseIndex + direction;
364
+ if (newIndex >= 0 && newIndex < workoutExercisesForm.length) { currentExerciseIndex = newIndex; renderActiveExerciseForm(); }
 
 
 
365
  }
366
 
367
  function renderActiveExerciseForm() {
368
  workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise'));
369
  workoutExercisesForm.forEach(ex => ex.classList.remove('active-exercise'));
370
+ if (currentExerciseIndex >= 0 && currentExerciseIndex < workoutExercisesForm.length) { workoutExercisesForm[currentExerciseIndex].classList.add('active-exercise'); }
 
 
 
 
 
371
  updateExerciseNavButtons();
372
  }
373
 
374
  function updateExerciseNavButtons() {
375
  const totalExercises = workoutExercisesForm.length;
376
+ if (totalExercises === 0) { currentExerciseIndicator.textContent = "(Aucun exercice)"; prevExerciseBtn.disabled = true; nextExerciseBtn.disabled = true; } else { currentExerciseIndicator.textContent = `(${currentExerciseIndex + 1}/${totalExercises})`; prevExerciseBtn.disabled = currentExerciseIndex === 0; nextExerciseBtn.disabled = currentExerciseIndex === totalExercises - 1; }
 
 
 
 
 
 
 
 
377
  }
378
 
379
  function clearNewWorkoutForm() {
380
+ if (!currentUser) return; document.getElementById('workout-name').value = ''; document.getElementById('workout-duration').value = '';
 
381
  setTodayDate(); exercisesContainer.innerHTML = ''; workoutExercisesForm = [];
382
  satisfactionRange.value = 75; satisfactionValue.textContent = '75%';
383
+ currentWorkoutId = null; currentExerciseIndex = 0; addExercise(false); renderActiveExerciseForm();
 
 
384
  }
385
 
386
  function saveWorkout() {
387
  if (!currentUser) return;
388
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // Rafraîchir
 
 
389
 
390
  const workoutName = document.getElementById('workout-name').value.trim();
391
  const workoutDate = document.getElementById('workout-date').value;
392
  const workoutDuration = parseInt(document.getElementById('workout-duration').value) || 0;
393
  const satisfaction = parseInt(document.getElementById('satisfaction').value);
394
 
395
+ if (!workoutName) { alert("Nom séance requis."); return; } if (!workoutDate) { alert("Date requise."); return; } if (workoutDuration <= 0) { alert("Durée invalide."); return; }
 
 
396
 
397
+ const exerciseElements = workoutExercisesForm; const exercises = [];
398
+ if (exerciseElements.length === 0) { alert("Ajoutez au moins un exercice."); return; }
 
 
399
 
400
  let validationError = null;
401
+ exerciseElements.forEach((exerciseEl, index) => {
402
  if (validationError) return;
403
+ const nameInput = exerciseEl.querySelector('.exercise-name'); const exerciseName = nameInput ? nameInput.value.trim() : '';
 
 
404
  const isUnilateral = exerciseEl.querySelector('.unilateral-checkbox').checked;
405
+ if (!exerciseName) { validationError = `Nommez l'exercice #${index + 1}.`; if(nameInput) nameInput.focus(); return; }
406
+ const series = []; const seriesElements = exerciseEl.querySelectorAll('.series');
407
+ if (seriesElements.length === 0) { validationError = `Exercice "${exerciseName}" sans série.`; return; }
 
 
 
 
 
 
 
 
 
 
 
 
408
  seriesElements.forEach(seriesEl => {
409
  if (validationError) return;
410
  const repsInput = seriesEl.querySelector('.reps'); const weightInput = seriesEl.querySelector('.weight');
411
  const reps = parseInt(repsInput.value) || 0; const weight = parseFloat(weightInput.value) || 0;
412
  const isDegressive = seriesEl.querySelector('.degressive-checkbox').checked;
413
+ if (reps <= 0) { validationError = `Reps invalides pour "${exerciseName}".`; if(repsInput) repsInput.focus(); return; }
414
+ if (weight < 0) { validationError = `Charge invalide pour "${exerciseName}".`; if(weightInput) weightInput.focus(); return; }
415
  series.push({ reps, weight, isDegressive });
416
  });
417
+ if (!validationError) { exercises.push({ name: exerciseName, isUnilateral, series }); }
 
 
 
 
418
  });
419
 
420
  if (validationError) { alert(validationError); return; }
421
 
422
+ let totalTonnage = 0; exercises.forEach(ex => ex.series.forEach(s => totalTonnage += s.reps * s.weight * (ex.isUnilateral ? 2 : 1)));
423
+ const workout = { id: currentWorkoutId || `workout-${Date.now()}`, name: workoutName, date: workoutDate, duration: workoutDuration, exercises: exercises, totalTonnage: totalTonnage, satisfaction: satisfaction };
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  const existingIndex = workouts.findIndex(w => w.id === workout.id);
425
  if (existingIndex > -1) { workouts[existingIndex] = workout; } else { workouts.push(workout); }
426
 
427
+ saveWorkouts(); confetti({ particleCount: 150, spread: 90, origin: { y: 0.6 } }); showPage('home-page'); renderWorkoutsList();
 
 
 
428
  }
429
 
 
430
  function renderWorkoutsList() {
431
  if (!currentUser) return; workoutsList.innerHTML = ''; if (workouts.length === 0) { emptyWorkoutMessage.classList.remove('hidden'); updateStats(); return; } emptyWorkoutMessage.classList.add('hidden');
432
  const sortedWorkouts = [...workouts].sort((a, b) => new Date(b.date) - new Date(a.date));
 
439
  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;
440
  const detailExercisesContainer = document.getElementById('detail-exercises-container'); detailExercisesContainer.innerHTML = '';
441
 
 
442
  workout.exercises.forEach((exercise) => {
443
+ const exerciseDiv = document.createElement('div'); exerciseDiv.className = 'card detail-exercise-card';
 
 
444
  let seriesHtml = '';
445
+ exercise.series.forEach((serie, sIndex) => { 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; 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>`; });
 
 
 
 
446
  const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem;">Unilat.</span>' : '';
447
+ exerciseDiv.innerHTML = `<h3 style="font-size: 1.1rem; margin-bottom: 0.5rem;">${exercise.name}${unilateralLabel}</h3> <div class="series-summary-container"> ${seriesHtml} </div>`;
 
 
 
 
448
  detailExercisesContainer.appendChild(exerciseDiv);
449
  });
450
+ currentWorkoutId = workoutId; showPage('workout-details-page');
 
 
451
  }
452
 
453
  function deleteWorkout() {