BitDown commited on
Commit
3ee0737
·
verified ·
1 Parent(s): e53f223

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +202 -349
index.html CHANGED
@@ -31,6 +31,7 @@
31
  --neutral-white: #FFFFFF; /* Blanc pur du logo Fitness */
32
  --danger: #f44336; /* Rouge pour danger (inchangé) */
33
  --info: #6c757d; /* Couleur neutre/grise pour info (ancien bleu) */
 
34
  --border-color: #333333; /* Couleur des bordures */
35
 
36
  --font-primary: 'Roboto', sans-serif; /* Police par défaut */
@@ -38,312 +39,89 @@
38
  }
39
  /* ===== FIN NOUVELLES VARIABLES ===== */
40
 
41
- * {
42
- margin: 0;
43
- padding: 0;
44
- box-sizing: border-box;
45
- /* Police par défaut appliquée */
46
- font-family: var(--font-primary);
47
- }
48
-
49
- body {
50
- background-color: var(--bg-dark);
51
- color: var(--text-light);
52
- min-height: 100vh;
53
- padding-bottom: 80px; /* Espace pour nav bottom */
54
- }
55
-
56
- .container {
57
- width: 100%;
58
- max-width: 800px;
59
- margin: 0 auto;
60
- padding: 1rem;
61
- }
62
-
63
- /* Header modifié pour intégrer le logo */
64
- header {
65
- padding: 1rem 0;
66
- text-align: center;
67
- border-bottom: 1px solid var(--border-color);
68
- margin-bottom: 1rem;
69
- display: flex; /* Utilisation de flex pour centrer le logo */
70
- flex-direction: column; /* Logo au-dessus du slogan */
71
- align-items: center; /* Centrage horizontal */
72
- }
73
-
74
- header img#app-logo {
75
- max-width: 250px; /* Ajustez la taille selon vos besoins */
76
- height: auto;
77
- margin-bottom: 0.5rem; /* Espace entre logo et slogan */
78
- }
79
-
80
- header p {
81
- color: var(--text-secondary);
82
- font-size: 0.9rem;
83
- }
84
- /* Fin modif header */
85
-
86
- h1, h2, h3, h4 {
87
- /* Application de la police titre + couleur accent */
88
- font-family: var(--font-headings);
89
- font-weight: 700; /* Assurez-vous que le poids est chargé */
90
- color: var(--accent);
91
- text-transform: uppercase; /* Style COCKTAIL */
92
- letter-spacing: 0.5px; /* Léger espacement */
93
- }
94
-
95
- .btn {
96
- background-color: var(--accent);
97
- /* Texte sur bouton doit contraster avec le jaune */
98
- color: var(--text-on-accent);
99
- border: none;
100
- padding: 0.6rem 1.2rem;
101
- border-radius: 4px;
102
- cursor: pointer;
103
- font-weight: bold; /* Utilise le poids de Roboto Bold par défaut */
104
- font-family: var(--font-headings); /* Police titre sur boutons */
105
- text-transform: uppercase; /* Style COCKTAIL */
106
- transition: background-color 0.2s, color 0.2s;
107
- }
108
-
109
- .btn:hover {
110
- background-color: var(--accent-dark);
111
- color: var(--text-on-accent);
112
- }
113
-
114
- .btn:disabled {
115
- background-color: #555;
116
- color: #999;
117
- cursor: not-allowed;
118
- }
119
-
120
- .btn-outline {
121
- background-color: transparent;
122
- color: var(--accent);
123
- border: 1px solid var(--accent);
124
- }
125
- .btn-outline:hover {
126
- background-color: rgba(255, 193, 7, 0.1); /* Léger fond jaune au survol */
127
- color: var(--accent);
128
- }
129
-
130
- .btn-outline:disabled {
131
- color: #555;
132
- border-color: #555;
133
- background-color: transparent;
134
- }
135
-
136
- .btn-danger {
137
- background-color: var(--danger);
138
- color: white; /* Texte blanc sur rouge */
139
- border-color: var(--danger);
140
- }
141
- .btn-danger:hover {
142
- background-color: #d32f2f; /* Rouge plus foncé */
143
- color: white;
144
- }
145
-
146
- .btn-info {
147
- background-color: var(--info);
148
- color: var(--text-light); /* Texte clair sur fond gris */
149
- border-color: var(--info);
150
- }
151
- .btn-info:hover {
152
- background-color: #5a6268; /* Gris plus foncé */
153
- color: var(--text-light);
154
- }
155
-
156
- input, select, textarea {
157
- width: 100%;
158
- padding: 0.7rem; /* Légèrement plus de padding */
159
- margin-bottom: 1rem;
160
- background-color: #2a2a2a;
161
- border: 1px solid #444;
162
- border-radius: 4px;
163
- color: var(--text-light);
164
- font-size: 1rem; /* Taille de police standard */
165
- }
166
- input:focus, select:focus, textarea:focus {
167
- outline: none;
168
- border-color: var(--accent);
169
- box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.3); /* Ombre focus jaune */
170
- }
171
-
172
- input[type="checkbox"] {
173
- width: auto;
174
- margin-right: 0.5rem;
175
- vertical-align: middle;
176
- accent-color: var(--accent); /* Couleur de la coche */
177
- }
178
-
179
- .card {
180
- background-color: var(--bg-card);
181
- border-radius: 8px;
182
- padding: 1.2rem; /* Padding un peu plus grand */
183
- margin-bottom: 1rem;
184
- box-shadow: 0 4px 8px rgba(0,0,0,0.3); /* Ombre un peu plus prononcée */
185
- }
186
-
187
  .form-group { margin-bottom: 1rem; }
188
- .form-row { display: flex; gap: 1rem; margin-bottom: 0.5rem; flex-wrap: wrap; } /* Espace augmenté */
189
  .form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; }
190
 
191
- label {
192
- display: block;
193
- margin-bottom: 0.4rem; /* Espace label/input */
194
- color: var(--text-secondary);
195
- font-size: 0.9rem;
196
- font-weight: bold; /* Labels en gras */
197
- }
198
 
199
- /* Exercise styling */
200
- .exercise {
201
- border-left: 4px solid var(--accent); /* Bordure accentuée */
202
- padding-left: 1rem;
203
- margin-bottom: 1.5rem;
204
- display: none;
205
- background-color: rgba(0,0,0,0.1); /* Léger fond pour démarquer */
206
- padding: 1rem; /* Padding interne */
207
- border-radius: 0 4px 4px 0;
208
- }
209
  .exercise.active-exercise { display: block; animation: fadeIn 0.3s ease-in-out; }
210
  .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem;}
211
  .exercise-header input { margin-bottom: 0; }
212
- .series-container { margin-left: 0rem; margin-top: 1rem; } /* Pas de marge gauche superflue */
213
- .series { background-color: #282828; padding: 0.8rem; border-radius: 4px; margin-bottom: 0.8rem; border: 1px solid #333; } /* Style série amélioré */
214
 
215
  /* Nav Bottom */
216
- .nav-bottom {
217
- position: fixed; bottom: 0; left: 0; width: 100%;
218
- background-color: #1a1a1a;
219
- display: flex; justify-content: space-around;
220
- padding: 0.7rem 0;
221
- box-shadow: 0 -3px 10px rgba(0,0,0,0.4);
222
- z-index: 10;
223
- }
224
- .nav-item {
225
- text-align: center; color: var(--text-secondary);
226
- text-decoration: none; font-size: 0.85rem;
227
- transition: color 0.2s; padding: 0 0.5rem;
228
- }
229
  .nav-item.active { color: var(--accent); font-weight: bold; }
230
- .nav-icon { font-size: 1.5rem; margin-bottom: 0.2rem; } /* Icônes plus grandes */
231
-
232
- /* ===== CSS MODIFIÉ POUR LA CARTE SÉANCE ===== */
233
- .workout-card {
234
- border-left: 4px solid var(--accent);
235
- cursor: pointer;
236
- transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
237
- }
238
- .workout-card:hover {
239
- transform: translateY(-3px);
240
- box-shadow: 0 6px 12px rgba(0,0,0,0.4);
241
- }
242
- .workout-header {
243
- display: flex;
244
- align-items: center; /* Centre verticalement titre et badge */
245
- gap: 0.75rem; /* Espace entre le titre et le badge */
246
- margin-bottom: 0.5rem; /* Espace sous l'en-tête, avant les détails */
247
- }
248
- .workout-header h3 {
249
- color: var(--text-light);
250
- font-family: var(--font-primary);
251
- text-transform: none;
252
- letter-spacing: normal;
253
- font-weight: bold;
254
- flex-grow: 1; /* Permet au titre de prendre l'espace disponible */
255
- min-width: 0; /* Important pour permettre au titre de rétrécir */
256
- position: static; /* Assure positionnement normal */
257
- margin-bottom: 0; /* Retiré car géré par gap */
258
- }
259
- /* Cible spécifiquement le badge DANS le .workout-header (le badge de date) */
260
- .workout-header > .badge {
261
- background-color: #2a2a2a; /* Fond plus discret, correspond aux inputs */
262
- color: var(--text-secondary); /* Texte gris clair */
263
- padding: 0.3rem 0.6rem;
264
- border-radius: 4px; /* Moins arrondi, plus sobre */
265
- font-size: 0.8rem;
266
- font-weight: normal; /* Date pas forcément en gras */
267
- white-space: nowrap; /* Garde la date sur une ligne */
268
- flex-shrink: 0; /* Empêche le badge de rétrécir */
269
- margin-left: auto; /* Pousse le badge vers la droite après que le h3 ait pris sa place */
270
- position: static; /* Assure positionnement normal */
271
- }
272
- .workout-details {
273
- font-size: 0.9rem;
274
- color: var(--text-secondary);
275
- margin-top: 0.8rem; /* Rétabli ou ajusté */
276
- }
277
- .workout-details span {
278
- margin-right: 0.8rem; /* Espacement entre les détails */
279
- }
280
- /* Assure que le badge de type à l'intérieur du h3 garde son style */
281
- .workout-header h3 .badge.type-badge {
282
- background-color: var(--neutral-white);
283
- color: var(--bg-dark);
284
- padding: 0.3rem 0.6rem;
285
- border-radius: 12px;
286
- font-size: 0.8rem;
287
- font-weight: bold;
288
- white-space: nowrap;
289
- margin-left: 5px;
290
- /* On s'assure qu'il n'hérite pas du style du badge de date */
291
- position: static;
292
- flex-shrink: initial;
293
- margin-left: 5px; /* Garde la marge gauche du badge type */
294
- }
295
- /* ===== FIN CSS MODIFIÉ POUR LA CARTE SÉANCE ===== */
296
-
297
 
298
  /* Stats */
299
  .stat-card { text-align: center; padding: 1rem; }
300
- .stat-value {
301
- font-size: 2rem; /* Taille augmentée */
302
- color: var(--accent);
303
- font-weight: bold;
304
- font-family: var(--font-headings); /* Police titre */
305
- margin-bottom: 0.3rem;
306
- }
307
- .stat-card div:last-child { color: var(--text-secondary); font-size: 0.9rem; } /* Label stat */
308
- .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; } /* Min width augmentée */
309
 
310
- .hidden { display: none !important; } /* Utilise !important pour forcer */
 
311
  #app-container > div:not(.active) { display: none !important; }
312
- .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; } /* Espace augmenté */
313
-
314
- /* Badges Généraux (non date, non type) */
315
- .badge {
316
- background-color: var(--accent);
317
- color: var(--text-on-accent);
318
- padding: 0.3rem 0.6rem; /* Padding ajusté */
319
- border-radius: 12px; /* Plus arrondi */
320
- font-size: 0.8rem;
321
- font-weight: bold;
322
- white-space: nowrap;
323
- margin-left: 5px;
324
- }
325
- /* Style spécifique pour le badge de type déjà géré plus haut */
326
 
 
 
 
327
 
328
  /* Spinner */
329
- .spinner {
330
- border: 4px solid rgba(255, 255, 255, 0.1);
331
- width: 36px; height: 36px;
332
- border-radius: 50%;
333
- border-left-color: var(--accent); /* Couleur accent pour spinner */
334
- animation: spin 1s linear infinite;
335
- margin: 2rem auto; display: none;
336
- }
337
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
338
 
339
  /* Exercise Summary in Details */
340
- .exercise-summary {
341
- margin: 0.3rem 0; padding: 0.5rem 0;
342
- border-bottom: 1px solid var(--border-color);
343
- font-size: 0.9rem;
344
- }
345
  .exercise-summary:last-child { border-bottom: none; }
346
- .series-summary-container .badge { font-size: 0.7rem; padding: 0.2rem 0.4rem; background-color: var(--info); color: var(--text-light); } /* Style badge Dégr/Unilat dans détails */
347
 
348
  /* Satisfaction Slider */
349
  .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
@@ -357,10 +135,16 @@
357
  /* Login Page */
358
  #login-page .card { max-width: 400px; margin: 3rem auto; }
359
  #login-page h2 { text-align: center; margin-bottom: 1.5rem; color: var(--accent); }
360
- #login-error { color: var(--danger); text-align: center; margin-top: 1rem; font-size: 0.9rem; display: none; min-height: 1.2em; font-weight: bold; }
 
 
 
361
  #login-form button { margin-top: 1rem; }
362
- .auth-switch { text-align: center; margin-top: 1.5rem; font-size: 0.9rem; color: var(--text-secondary); }
363
- .auth-switch a { color: var(--accent); cursor: pointer; text-decoration: underline; font-weight: bold; }
 
 
 
364
 
365
  /* User Info */
366
  .user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-secondary);}
@@ -368,13 +152,7 @@
368
  .user-info button { margin-left: 0.8rem; padding: 0.3rem 0.7rem; font-size: 0.8rem; }
369
 
370
  /* Manage Types Page */
371
- #workout-types-list li {
372
- display: flex; justify-content: space-between; align-items: center;
373
- background-color: #2a2a2a; padding: 0.7rem 1rem; /* Padding augmenté */
374
- margin-bottom: 0.5rem; border-radius: 4px;
375
- border-left: 4px solid var(--neutral-white); /* Bordure blanche */
376
- color: var(--text-light); font-weight: bold;
377
- }
378
  #add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;}
379
  #add-type-form input { margin-bottom: 0; flex-grow: 1;}
380
  #add-type-form button { flex-shrink: 0; }
@@ -382,23 +160,17 @@
382
 
383
  /* Select Workout Type Page */
384
  #select-workout-type-page .card { text-align: center; }
385
- #select-workout-type-page .btn { margin-top: 1rem; width: 80%; } /* Boutons plus larges */
386
  #select-workout-type { margin-bottom: 1rem;}
387
 
388
  /* Type Trend Card */
389
  .type-trend-card { margin-bottom: 1.5rem; }
390
- .type-trend-card h4 {
391
- /* Style titre appliqué par défaut */
392
- color: var(--neutral-white); /* Titre Tendance en Blanc */
393
- border-bottom: 1px solid var(--border-color);
394
- padding-bottom: 0.5rem; margin-bottom: 1rem;
395
- text-transform: uppercase; /* Garde uppercase pour section */
396
- }
397
  .type-trend-card ul { list-style: none; padding: 0; font-size: 0.95rem; }
398
  .type-trend-card li { margin-bottom: 0.6rem; color: var(--text-light); display: flex; justify-content: space-between; border-bottom: 1px dashed #333; padding-bottom: 0.4rem;}
399
  .type-trend-card li:last-child { border-bottom: none; }
400
- .type-trend-card li span:first-child { color: var(--text-secondary); font-size: 0.85rem; margin-right: 1rem; } /* Date */
401
- .type-trend-card li strong { color: var(--accent); } /* Tonnage en accent */
402
 
403
 
404
  /* Media Queries */
@@ -408,11 +180,9 @@
408
  .form-row { flex-direction: column; gap: 0.8rem; }
409
  .flex-between { flex-direction: column; align-items: stretch; gap: 0.8rem; }
410
  .flex-between > div { width: 100%; display: flex; justify-content: center; margin-top: 0.5rem;}
411
- /* Ajustement spécifique pour l'en-tête de carte séance sur mobile */
412
- .workout-header { flex-wrap: wrap; } /* Permet au badge de passer en dessous si manque de place */
413
- .workout-header h3 { margin-bottom: 0.3rem; /* Ajoute un peu d'espace si ça passe en dessous */ }
414
- .workout-header > .badge { margin-left: 0; /* Pas besoin de le pousser à droite si ça passe en dessous */ }
415
-
416
  .user-info { text-align: center; margin-bottom: 0.8rem;}
417
  .user-info button { display: block; margin: 0.5rem auto 0;}
418
  .exercise-navigation button { padding: 0.6rem; font-size: 0.9rem;}
@@ -426,9 +196,8 @@
426
  </head>
427
  <body>
428
  <div class="container">
429
- <!-- Header modifié avec le logo -->
430
  <header>
431
- <!-- Assurez-vous que le fichier 'input_file_0.png' est bien à la racine -->
432
  <img src="input_file_0.png" alt="Cocktail Fitness Logo" id="app-logo">
433
  <p>Votre suivi de séances personnalisé</p>
434
  </header>
@@ -437,13 +206,22 @@
437
  <div id="login-page">
438
  <div class="card">
439
  <h2 id="auth-title">Connexion</h2>
440
- <form id="login-form" novalidate> <!-- novalidate évite validation navigateur simple -->
441
  <div class="form-group"> <label for="auth-email">Email</label> <input type="email" id="auth-email" placeholder="[email protected]" required> </div>
442
  <div class="form-group"> <label for="auth-password">Mot de passe</label> <input type="password" id="auth-password" placeholder="********" required> </div>
443
- <p id="login-error" style="display: none;"></p>
 
444
  <button type="submit" id="auth-action-btn" class="btn" style="width: 100%;">Se Connecter</button>
445
  </form>
446
- <div class="auth-switch"> <span id="auth-switch-text">Pas encore de compte ?</span> <a id="auth-switch-link">Inscrivez-vous ici</a> </div>
 
 
 
 
 
 
 
 
447
  </div>
448
  </div>
449
 
@@ -455,6 +233,7 @@
455
  </div>
456
 
457
  <div id="app-container">
 
458
  <!-- Home Page -->
459
  <div id="home-page">
460
  <div class="flex-between">
@@ -601,7 +380,7 @@
601
  <!-- ================= JAVASCRIPT =================== -->
602
  <!-- ================================================== -->
603
  <script>
604
- // Firebase Init - Remplacez avec votre propre config si différente
605
  const firebaseConfig = { apiKey: "AIzaSyAkWvrRyXgrC7zbTtoh_GppsHMrz2rF7WM", authDomain: "lifttrackapp.firebaseapp.com", projectId: "lifttrackapp", storageBucket: "lifttrackapp.appspot.com", messagingSenderId: "594426771796", appId: "1:594426771796:web:789bef037ca0016c54b0c1", measurementId: "G-MXLFK0H160" };
606
  let app, auth, db;
607
  try {
@@ -616,14 +395,7 @@
616
  }
617
 
618
  // --- Variables d'État ---
619
- let workouts = [];
620
- let currentWorkoutId = null;
621
- let currentExerciseIndex = 0;
622
- let workoutExercisesForm = [];
623
- let currentFirebaseUser = null;
624
- let userWorkoutTypes = [];
625
- let selectedWorkoutTypeName = null;
626
- let isLoginMode = true;
627
 
628
  // --- Éléments DOM ---
629
  const loginPage = document.getElementById('login-page');
@@ -633,9 +405,10 @@
633
  const loginForm = document.getElementById('login-form');
634
  const authActionButton = document.getElementById('auth-action-btn');
635
  const authSwitchLink = document.getElementById('auth-switch-link');
 
636
  const authTitle = document.getElementById('auth-title');
637
  const authSwitchText = document.getElementById('auth-switch-text');
638
- const loginError = document.getElementById('login-error');
639
  const currentUserDisplay = document.getElementById('current-user-display');
640
  const logoutBtn = document.getElementById('logout-btn');
641
  const spinner = document.querySelector('#workouts-list .spinner');
@@ -681,18 +454,88 @@
681
  // --- DÉFINITION DES FONCTIONS ---
682
  // ===========================================
683
 
684
- // (Les définitions des fonctions JavaScript sont ici - identiques à la version précédente)
685
- // ... (initAuthListener, showLoginPage, showApp, handleAuthAction, etc...) ...
686
- // ... (TOUTES les fonctions JS jusqu'à la fin de la section fonctions) ...
687
-
688
- function initAuthListener() { if (!auth) { console.error("Auth object not ready in initAuthListener"); return; } auth.onAuthStateChanged(user => { console.log("Auth state changed:", user ? user.uid : 'null'); currentFirebaseUser = user; if (user) { showApp(); } else { workouts = []; userWorkoutTypes = []; showLoginPage(); } }); }
689
- function showLoginPage() { if(loginPage) loginPage.style.display = 'block'; if(mainAppContent) mainAppContent.style.display = 'none'; isLoginMode = true; authSwitchMode(); }
690
- function showApp() { if (!currentFirebaseUser || !mainAppContent) { console.error("Cannot show app: No user or main content missing."); showLoginPage(); return; } console.log("Affichage App for user:", currentFirebaseUser.email); if(loginPage) loginPage.style.display = 'none'; mainAppContent.style.display = 'block'; if(currentUserDisplay) currentUserDisplay.textContent = currentFirebaseUser.email; setTodayDate(); loadWorkouts(); loadWorkoutTypes(); showPage('home-page'); }
691
- function handleAuthAction(event) { if (!auth || !authEmailInput || !authPasswordInput || !authActionButton) { console.error("Auth or input/button elements missing in handleAuthAction"); return; } event.preventDefault(); const email = authEmailInput.value; const password = authPasswordInput.value; if (loginError) { loginError.textContent = ''; loginError.style.display = 'none'; } if (!email || !password) { if (loginError) { loginError.textContent = 'Email et Mot de passe requis.'; loginError.style.display = 'block'; } return; } authActionButton.disabled = true; authActionButton.textContent = 'Chargement...'; if (isLoginMode) { console.log("Attempting sign in for:", email); auth.signInWithEmailAndPassword(email, password).then(userCredential => { console.log("Sign in successful:", userCredential.user.uid); }).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authActionButton.textContent = 'Se Connecter'; }); } else { console.log("Attempting create user for:", email); auth.createUserWithEmailAndPassword(email, password).then(userCredential => { console.log("Create user successful:", userCredential.user.uid); }).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authActionButton.textContent = 'S\'inscrire'; authSwitchMode(); }); } }
692
- function handleLogout() { if (!auth) { console.error("Auth missing for logout"); return; } console.log("Déconnexion..."); auth.signOut().catch(error => { console.error("Logout Error:", error); alert("Erreur lors de la déconnexion."); }); }
693
- function authSwitchMode() { isLoginMode = !isLoginMode; if (loginError) { loginError.textContent = ''; loginError.style.display = 'none'; } if (authEmailInput) authEmailInput.value = ''; if (authPasswordInput) authPasswordInput.value = ''; if (authTitle) authTitle.textContent = isLoginMode ? 'Connexion' : 'Inscription'; if (authActionButton) authActionButton.textContent = isLoginMode ? 'Se Connecter' : 'S\'inscrire'; if (authSwitchText) authSwitchText.textContent = isLoginMode ? 'Pas encore de compte ?' : 'Déjà un compte ?'; if (authSwitchLink) authSwitchLink.textContent = isLoginMode ? 'Inscrivez-vous ici' : 'Connectez-vous ici'; if (authEmailInput) authEmailInput.focus(); }
694
- function handleAuthError(error) { console.error("Erreur Auth Firebase:", error.code, error.message); if (loginError) { loginError.textContent = getAuthErrorMessage(error); loginError.style.display = 'block'; } }
695
- function getAuthErrorMessage(error) { switch (error.code) { case 'auth/invalid-email': return 'Format d\'email invalide.'; case 'auth/user-disabled': return 'Ce compte utilisateur a été désactivé.'; case 'auth/user-not-found': return 'Aucun utilisateur trouvé avec cet email.'; case 'auth/wrong-password': return 'Mot de passe incorrect.'; case 'auth/email-already-in-use': return 'Cet email est déjà utilisé par un autre compte.'; case 'auth/weak-password': return 'Le mot de passe doit contenir au moins 6 caractères.'; case 'auth/operation-not-allowed': return 'Méthode de connexion non activée (vérifiez Console Firebase).'; case 'auth/missing-password': return 'Mot de passe manquant.'; default: console.error("Unhandled Auth Error Code:", error.code); return `Erreur (${error.code}). Veuillez réessayer.`; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
  function loadWorkoutTypes() { if (!currentFirebaseUser || !db || !typesSpinner) return; const userId = currentFirebaseUser.uid; console.log("Chargement types pour", userId); userWorkoutTypes = []; typesSpinner.classList.remove('hidden'); db.collection('workoutTypes').where('userId', '==', userId).orderBy('name').get().then(snapshot => { userWorkoutTypes = []; snapshot.forEach(doc => { userWorkoutTypes.push({ id: doc.id, ...doc.data() }); }); console.log("Types chargés:", userWorkoutTypes); renderWorkoutTypesList(); populateWorkoutTypeSelector(); }).catch(handleFirestoreError).finally(() => { typesSpinner.classList.add('hidden'); }); }
697
  function renderWorkoutTypesList() { if (!workoutTypesList || !emptyTypesMessage) { console.error("DOM elements for types list missing"); return; } workoutTypesList.innerHTML = ''; if (userWorkoutTypes.length === 0) { emptyTypesMessage.classList.remove('hidden'); } else { emptyTypesMessage.classList.add('hidden'); userWorkoutTypes.forEach(type => { const li = document.createElement('li'); li.textContent = type.name; workoutTypesList.appendChild(li); }); } }
698
  function handleAddWorkoutType(event) { event.preventDefault(); if (!currentFirebaseUser || !newTypeNameInput || !db || !addTypeError || !addTypeForm) { console.error("Missing elements for adding workout type"); return; } const typeName = newTypeNameInput.value.trim(); addTypeError.textContent = ''; addTypeError.style.display = 'none'; if (!typeName) { addTypeError.textContent = "Le nom du type ne peut pas être vide."; addTypeError.style.display = 'block'; return; } if (userWorkoutTypes.some(t => t.name.toLowerCase() === typeName.toLowerCase())) { addTypeError.textContent = "Ce type de séance existe déjà."; addTypeError.style.display = 'block'; return; } console.log("Ajout type:", typeName); const typeData = { userId: currentFirebaseUser.uid, name: typeName, createdAt: firebase.firestore.FieldValue.serverTimestamp() }; const addBtn = addTypeForm.querySelector('button'); if (addBtn) { addBtn.disabled = true; addBtn.textContent = '...'; } db.collection('workoutTypes').add(typeData).then(() => { console.log("Type ajouté avec succès"); newTypeNameInput.value = ''; loadWorkoutTypes(); }).catch(handleFirestoreError).finally(() => { if (addBtn) { addBtn.disabled = false; addBtn.textContent = 'Ajouter'; } }); }
@@ -717,26 +560,38 @@
717
  function renderTypeTrends(trends) { if (!typeTrendsContainer || !noTrendsMessage || !trendsSpinner) { console.error("Cannot render trends: Missing DOM elements."); return;} trendsSpinner.classList.add('hidden'); typeTrendsContainer.innerHTML = ''; const trendTypes = Object.keys(trends); if (trendTypes.length === 0) { noTrendsMessage.classList.remove('hidden'); } else { noTrendsMessage.classList.add('hidden'); trendTypes.sort().forEach(typeName => { const typeData = trends[typeName]; if (!typeData || typeData.length === 0) return; const card = document.createElement('div'); card.className = 'card type-trend-card'; let listItems = ''; typeData.forEach(session => { const formattedDate = session.date ? new Date(session.date).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' }) : '??'; const tonnageText = (session.tonnage !== undefined && session.tonnage !== null) ? `${session.tonnage.toFixed(1)} kg` : 'N/A'; listItems += `<li><span>${formattedDate}</span> <strong>${tonnageText}</strong></li>`; }); card.innerHTML = `<h4>${typeName} (3 Dernières)</h4><ul>${listItems}</ul>`; typeTrendsContainer.appendChild(card); }); } }
718
  function handleFirestoreError(error) { console.error("Erreur Firestore:", error.code, error.message); alert(`Erreur de base de données: ${error.message}`); }
719
 
 
720
  // --- ÉCOUTEURS D'ÉVÉNEMENTS ---
721
  function initEventListeners() {
722
  console.log("initEventListeners: Attachement...");
 
723
  if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("!loginForm");
724
  if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("!authSwitchLink");
 
725
  if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("!logoutBtn");
 
726
  navItems.forEach((item) => { if (item) item.addEventListener('click', (e) => { e.preventDefault(); const targetPageId = item.getAttribute('data-page'); if (!currentFirebaseUser && targetPageId !== 'login-page') { showLoginPage(); return; } if (targetPageId) showPage(targetPageId); }); else console.error(`!navItem`); });
 
727
  if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('select-workout-type-page'); }); else console.error("!newWorkoutBtn");
728
  if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('manage-types-page'); }); else console.error("!manageTypesBtn");
 
729
  if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType); else console.error("!addTypeForm");
 
730
  if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("!selectWorkoutTypeDropdown");
731
  if (startStructuredWorkoutBtn) startStructuredWorkoutBtn.addEventListener('click', () => { if (selectWorkoutTypeDropdown) { const type = selectWorkoutTypeDropdown.value; if (type) { clearNewWorkoutForm(type); showPage('new-workout-page'); } else { alert("Veuillez sélectionner un type de séance."); } } }); else console.error("!startStructuredWorkoutBtn");
732
  if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("!startFreeWorkoutBtn");
 
733
  backToHomeBtns.forEach(btn => { if (btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("!backToHomeBtn"); });
 
734
  if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => { if (confirm("Annuler cette séance ? Les données non enregistrées seront perdues.")) showPage('home-page'); }); else console.error("!cancelNewWorkoutBtn");
735
  if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("!saveWorkoutBtn");
736
  if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("!addExerciseBtn");
 
737
  if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', () => navigateExerciseForm(-1)); else console.error("!prevExerciseBtn");
738
  if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', () => navigateExerciseForm(1)); else console.error("!nextExerciseBtn");
 
739
  if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout); else console.error("!deleteWorkoutBtn");
 
740
  if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if (satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; }); else console.error("!satisfactionRange");
741
  console.log("initEventListeners: Fin attachement.");
742
  }
@@ -749,35 +604,33 @@
749
  document.addEventListener('DOMContentLoaded', () => {
750
  console.log("DOM chargé. Initialisation de l'application...");
751
  if (typeof firebase !== 'undefined' && typeof firebase.initializeApp === 'function' && typeof firebase.auth === 'function' && typeof firebase.firestore === 'function') {
752
- if (!auth) auth = firebase.auth(); // Ensure auth is assigned
753
- if (!db) db = firebase.firestore(); // Ensure db is assigned
754
-
755
  if (auth && db) {
756
  console.log("Firebase prêt. Initialisation des listeners...");
757
  initEventListeners(); // Initialize event listeners first
758
  initAuthListener(); // Initialize auth listener to handle state changes and initial check
759
- // Initial check after listeners are set up
760
- const user = auth.currentUser;
761
  if (user) {
762
  console.log("Utilisateur déjà connecté trouvé:", user.uid);
763
  currentFirebaseUser = user;
764
- showApp(); // Show app directly
765
  } else {
766
  console.log("Utilisateur non connecté au chargement.");
767
- showLoginPage(); // Show login page
768
  }
769
  } else {
770
- console.error("ERREUR CRITIQUE: Firebase Auth ou Firestore non initialisé correctement après tentative.");
771
  alert("Erreur critique : Services Firebase non disponibles.");
772
  }
773
  } else {
774
  console.error("ERREUR CRITIQUE: Firebase SDK non chargé ou incomplet !");
775
  alert("Erreur critique : Impossible de charger les composants Firebase.");
776
- document.body.innerHTML = '<div style="color: red; padding: 30px; text-align: center; font-family: sans-serif; background-color: #333;"><h1>Erreur Critique</h1><p>Impossible de charger les composants nécessaires. Vérifiez votre connexion et la console (F12) pour plus de détails.</p></div>';
777
  }
778
- }); // FIN DU Listener DOMContentLoaded
779
 
780
- </script> <!-- FIN de la balise SCRIPT -->
781
 
782
- </body> <!-- FIN de la balise BODY -->
783
- </html> <!-- FIN de la balise HTML -->
 
31
  --neutral-white: #FFFFFF; /* Blanc pur du logo Fitness */
32
  --danger: #f44336; /* Rouge pour danger (inchangé) */
33
  --info: #6c757d; /* Couleur neutre/grise pour info (ancien bleu) */
34
+ --success: #4CAF50; /* Vert pour succès */
35
  --border-color: #333333; /* Couleur des bordures */
36
 
37
  --font-primary: 'Roboto', sans-serif; /* Police par défaut */
 
39
  }
40
  /* ===== FIN NOUVELLES VARIABLES ===== */
41
 
42
+ * { margin: 0; padding: 0; box-sizing: border-box; font-family: var(--font-primary); }
43
+ body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; }
44
+ .container { width: 100%; max-width: 800px; margin: 0 auto; padding: 1rem; }
45
+
46
+ /* Header */
47
+ header { padding: 1rem 0; text-align: center; border-bottom: 1px solid var(--border-color); margin-bottom: 1rem; display: flex; flex-direction: column; align-items: center; }
48
+ header img#app-logo { max-width: 250px; height: auto; margin-bottom: 0.5rem; }
49
+ header p { color: var(--text-secondary); font-size: 0.9rem; }
50
+
51
+ /* Typography */
52
+ h1, h2, h3, h4 { font-family: var(--font-headings); font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.5px; }
53
+
54
+ /* Buttons */
55
+ .btn { background-color: var(--accent); color: var(--text-on-accent); border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: var(--font-headings); text-transform: uppercase; transition: background-color 0.2s, color 0.2s; }
56
+ .btn:hover { background-color: var(--accent-dark); color: var(--text-on-accent); }
57
+ .btn:disabled { background-color: #555; color: #999; cursor: not-allowed; }
58
+ .btn-outline { background-color: transparent; color: var(--accent); border: 1px solid var(--accent); }
59
+ .btn-outline:hover { background-color: rgba(255, 193, 7, 0.1); color: var(--accent); }
60
+ .btn-outline:disabled { color: #555; border-color: #555; background-color: transparent; }
61
+ .btn-danger { background-color: var(--danger); color: white; border-color: var(--danger); }
62
+ .btn-danger:hover { background-color: #d32f2f; color: white; }
63
+ .btn-info { background-color: var(--info); color: var(--text-light); border-color: var(--info); }
64
+ .btn-info:hover { background-color: #5a6268; color: var(--text-light); }
65
+
66
+ /* Forms */
67
+ input, select, textarea { width: 100%; padding: 0.7rem; margin-bottom: 1rem; background-color: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: var(--text-light); font-size: 1rem; }
68
+ input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.3); }
69
+ input[type="checkbox"] { width: auto; margin-right: 0.5rem; vertical-align: middle; accent-color: var(--accent); }
70
+ label { display: block; margin-bottom: 0.4rem; color: var(--text-secondary); font-size: 0.9rem; font-weight: bold; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  .form-group { margin-bottom: 1rem; }
72
+ .form-row { display: flex; gap: 1rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
73
  .form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; }
74
 
75
+ /* Cards */
76
+ .card { background-color: var(--bg-card); border-radius: 8px; padding: 1.2rem; margin-bottom: 1rem; box-shadow: 0 4px 8px rgba(0,0,0,0.3); }
 
 
 
 
 
77
 
78
+ /* Exercise Styling */
79
+ .exercise { border-left: 4px solid var(--accent); padding-left: 1rem; margin-bottom: 1.5rem; display: none; background-color: rgba(0,0,0,0.1); padding: 1rem; border-radius: 0 4px 4px 0; }
 
 
 
 
 
 
 
 
80
  .exercise.active-exercise { display: block; animation: fadeIn 0.3s ease-in-out; }
81
  .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem;}
82
  .exercise-header input { margin-bottom: 0; }
83
+ .series-container { margin-left: 0rem; margin-top: 1rem; }
84
+ .series { background-color: #282828; padding: 0.8rem; border-radius: 4px; margin-bottom: 0.8rem; border: 1px solid #333; }
85
 
86
  /* Nav Bottom */
87
+ .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 -3px 10px rgba(0,0,0,0.4); z-index: 10; }
88
+ .nav-item { text-align: center; color: var(--text-secondary); text-decoration: none; font-size: 0.85rem; transition: color 0.2s; padding: 0 0.5rem; }
 
 
 
 
 
 
 
 
 
 
 
89
  .nav-item.active { color: var(--accent); font-weight: bold; }
90
+ .nav-icon { font-size: 1.5rem; margin-bottom: 0.2rem; }
91
+
92
+ /* Workout Card */
93
+ .workout-card { border-left: 4px solid var(--accent); cursor: pointer; transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; }
94
+ .workout-card:hover { transform: translateY(-3px); box-shadow: 0 6px 12px rgba(0,0,0,0.4); }
95
+ .workout-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }
96
+ .workout-header h3 { color: var(--text-light); font-family: var(--font-primary); text-transform: none; letter-spacing: normal; font-weight: bold; flex-grow: 1; min-width: 0; position: static; margin-bottom: 0; }
97
+ .workout-header > .badge { background-color: #2a2a2a; color: var(--text-secondary); padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.8rem; font-weight: normal; white-space: nowrap; flex-shrink: 0; margin-left: auto; position: static; }
98
+ .workout-details { font-size: 0.9rem; color: var(--text-secondary); margin-top: 0.8rem; }
99
+ .workout-details span { margin-right: 0.8rem; }
100
+ .workout-header h3 .badge.type-badge { background-color: var(--neutral-white); color: var(--bg-dark); padding: 0.3rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: bold; white-space: nowrap; margin-left: 5px; position: static; flex-shrink: initial; margin-left: 5px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  /* Stats */
103
  .stat-card { text-align: center; padding: 1rem; }
104
+ .stat-value { font-size: 2rem; color: var(--accent); font-weight: bold; font-family: var(--font-headings); margin-bottom: 0.3rem; }
105
+ .stat-card div:last-child { color: var(--text-secondary); font-size: 0.9rem; }
106
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; }
 
 
 
 
 
 
107
 
108
+ /* Utility */
109
+ .hidden { display: none !important; }
110
  #app-container > div:not(.active) { display: none !important; }
111
+ .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ /* Badges (General) */
114
+ .badge { background-color: var(--accent); color: var(--text-on-accent); padding: 0.3rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: bold; white-space: nowrap; margin-left: 5px; }
115
+ /* .badge.type-badge is handled in .workout-header h3 .badge.type-badge */
116
 
117
  /* Spinner */
118
+ .spinner { border: 4px solid rgba(255, 255, 255, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--accent); animation: spin 1s linear infinite; margin: 2rem auto; display: none; }
 
 
 
 
 
 
 
119
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
120
 
121
  /* Exercise Summary in Details */
122
+ .exercise-summary { margin: 0.3rem 0; padding: 0.5rem 0; border-bottom: 1px solid var(--border-color); font-size: 0.9rem; }
 
 
 
 
123
  .exercise-summary:last-child { border-bottom: none; }
124
+ .series-summary-container .badge { font-size: 0.7rem; padding: 0.2rem 0.4rem; background-color: var(--info); color: var(--text-light); }
125
 
126
  /* Satisfaction Slider */
127
  .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
 
135
  /* Login Page */
136
  #login-page .card { max-width: 400px; margin: 3rem auto; }
137
  #login-page h2 { text-align: center; margin-bottom: 1.5rem; color: var(--accent); }
138
+ /* Error/Success message styling */
139
+ #login-message { text-align: center; margin-top: 1rem; font-size: 0.9rem; min-height: 1.2em; font-weight: bold; display: none; }
140
+ #login-message.error { color: var(--danger); }
141
+ #login-message.success { color: var(--success); }
142
  #login-form button { margin-top: 1rem; }
143
+ .auth-options { margin-top: 1.5rem; font-size: 0.9rem; text-align: center; } /* Container for switch and forgot password */
144
+ .auth-switch { margin-bottom: 0.5rem; color: var(--text-secondary); } /* Style for "Pas de compte..." */
145
+ .auth-options a { color: var(--accent); cursor: pointer; text-decoration: underline; font-weight: bold; }
146
+ #forgot-password-link { display: block; /* Place it below the switch link */ }
147
+
148
 
149
  /* User Info */
150
  .user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-secondary);}
 
152
  .user-info button { margin-left: 0.8rem; padding: 0.3rem 0.7rem; font-size: 0.8rem; }
153
 
154
  /* Manage Types Page */
155
+ #workout-types-list li { display: flex; justify-content: space-between; align-items: center; background-color: #2a2a2a; padding: 0.7rem 1rem; margin-bottom: 0.5rem; border-radius: 4px; border-left: 4px solid var(--neutral-white); color: var(--text-light); font-weight: bold; }
 
 
 
 
 
 
156
  #add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;}
157
  #add-type-form input { margin-bottom: 0; flex-grow: 1;}
158
  #add-type-form button { flex-shrink: 0; }
 
160
 
161
  /* Select Workout Type Page */
162
  #select-workout-type-page .card { text-align: center; }
163
+ #select-workout-type-page .btn { margin-top: 1rem; width: 80%; }
164
  #select-workout-type { margin-bottom: 1rem;}
165
 
166
  /* Type Trend Card */
167
  .type-trend-card { margin-bottom: 1.5rem; }
168
+ .type-trend-card h4 { color: var(--neutral-white); border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; margin-bottom: 1rem; text-transform: uppercase; }
 
 
 
 
 
 
169
  .type-trend-card ul { list-style: none; padding: 0; font-size: 0.95rem; }
170
  .type-trend-card li { margin-bottom: 0.6rem; color: var(--text-light); display: flex; justify-content: space-between; border-bottom: 1px dashed #333; padding-bottom: 0.4rem;}
171
  .type-trend-card li:last-child { border-bottom: none; }
172
+ .type-trend-card li span:first-child { color: var(--text-secondary); font-size: 0.85rem; margin-right: 1rem; }
173
+ .type-trend-card li strong { color: var(--accent); }
174
 
175
 
176
  /* Media Queries */
 
180
  .form-row { flex-direction: column; gap: 0.8rem; }
181
  .flex-between { flex-direction: column; align-items: stretch; gap: 0.8rem; }
182
  .flex-between > div { width: 100%; display: flex; justify-content: center; margin-top: 0.5rem;}
183
+ .workout-header { flex-wrap: wrap; }
184
+ .workout-header h3 { margin-bottom: 0.3rem; }
185
+ .workout-header > .badge { margin-left: 0; }
 
 
186
  .user-info { text-align: center; margin-bottom: 0.8rem;}
187
  .user-info button { display: block; margin: 0.5rem auto 0;}
188
  .exercise-navigation button { padding: 0.6rem; font-size: 0.9rem;}
 
196
  </head>
197
  <body>
198
  <div class="container">
199
+ <!-- Header -->
200
  <header>
 
201
  <img src="input_file_0.png" alt="Cocktail Fitness Logo" id="app-logo">
202
  <p>Votre suivi de séances personnalisé</p>
203
  </header>
 
206
  <div id="login-page">
207
  <div class="card">
208
  <h2 id="auth-title">Connexion</h2>
209
+ <form id="login-form" novalidate>
210
  <div class="form-group"> <label for="auth-email">Email</label> <input type="email" id="auth-email" placeholder="[email protected]" required> </div>
211
  <div class="form-group"> <label for="auth-password">Mot de passe</label> <input type="password" id="auth-password" placeholder="********" required> </div>
212
+ <!-- Message pour erreurs OU succès (ex: email reset envoyé) -->
213
+ <p id="login-message" style="display: none;"></p>
214
  <button type="submit" id="auth-action-btn" class="btn" style="width: 100%;">Se Connecter</button>
215
  </form>
216
+ <!-- Options supplémentaires sous le formulaire -->
217
+ <div class="auth-options">
218
+ <div class="auth-switch">
219
+ <span id="auth-switch-text">Pas encore de compte ?</span>
220
+ <a id="auth-switch-link">Inscrivez-vous ici</a>
221
+ </div>
222
+ <!-- Lien Mot de passe oublié -->
223
+ <a href="#" id="forgot-password-link">Mot de passe oublié ?</a>
224
+ </div>
225
  </div>
226
  </div>
227
 
 
233
  </div>
234
 
235
  <div id="app-container">
236
+ <!-- Contenu des différentes pages (Home, Manage Types, etc.) -->
237
  <!-- Home Page -->
238
  <div id="home-page">
239
  <div class="flex-between">
 
380
  <!-- ================= JAVASCRIPT =================== -->
381
  <!-- ================================================== -->
382
  <script>
383
+ // Firebase Init
384
  const firebaseConfig = { apiKey: "AIzaSyAkWvrRyXgrC7zbTtoh_GppsHMrz2rF7WM", authDomain: "lifttrackapp.firebaseapp.com", projectId: "lifttrackapp", storageBucket: "lifttrackapp.appspot.com", messagingSenderId: "594426771796", appId: "1:594426771796:web:789bef037ca0016c54b0c1", measurementId: "G-MXLFK0H160" };
385
  let app, auth, db;
386
  try {
 
395
  }
396
 
397
  // --- Variables d'État ---
398
+ let workouts = []; let currentWorkoutId = null; let currentExerciseIndex = 0; let workoutExercisesForm = []; let currentFirebaseUser = null; let userWorkoutTypes = []; let selectedWorkoutTypeName = null; let isLoginMode = true;
 
 
 
 
 
 
 
399
 
400
  // --- Éléments DOM ---
401
  const loginPage = document.getElementById('login-page');
 
405
  const loginForm = document.getElementById('login-form');
406
  const authActionButton = document.getElementById('auth-action-btn');
407
  const authSwitchLink = document.getElementById('auth-switch-link');
408
+ const forgotPasswordLink = document.getElementById('forgot-password-link'); // **NOUVEAU**
409
  const authTitle = document.getElementById('auth-title');
410
  const authSwitchText = document.getElementById('auth-switch-text');
411
+ const loginMessage = document.getElementById('login-message'); // **MODIFIÉ** (remplace loginError)
412
  const currentUserDisplay = document.getElementById('current-user-display');
413
  const logoutBtn = document.getElementById('logout-btn');
414
  const spinner = document.querySelector('#workouts-list .spinner');
 
454
  // --- DÉFINITION DES FONCTIONS ---
455
  // ===========================================
456
 
457
+ function showMessage(message, isError = true) {
458
+ if (loginMessage) {
459
+ loginMessage.textContent = message;
460
+ loginMessage.className = isError ? 'error' : 'success'; // Applique la classe CSS
461
+ loginMessage.style.display = 'block';
462
+ }
463
+ }
464
+
465
+ function clearMessage() {
466
+ if (loginMessage) {
467
+ loginMessage.textContent = '';
468
+ loginMessage.style.display = 'none';
469
+ }
470
+ }
471
+
472
+
473
+ function initAuthListener() { /* ... identique ... */ if (!auth) { console.error("Auth object not ready in initAuthListener"); return; } auth.onAuthStateChanged(user => { console.log("Auth state changed:", user ? user.uid : 'null'); currentFirebaseUser = user; if (user) { showApp(); } else { workouts = []; userWorkoutTypes = []; showLoginPage(); } }); }
474
+ function showLoginPage() { /* ... identique ... */ if(loginPage) loginPage.style.display = 'block'; if(mainAppContent) mainAppContent.style.display = 'none'; isLoginMode = true; authSwitchMode(); }
475
+ function showApp() { /* ... identique ... */ if (!currentFirebaseUser || !mainAppContent) { console.error("Cannot show app: No user or main content missing."); showLoginPage(); return; } console.log("Affichage App for user:", currentFirebaseUser.email); if(loginPage) loginPage.style.display = 'none'; mainAppContent.style.display = 'block'; if(currentUserDisplay) currentUserDisplay.textContent = currentFirebaseUser.email; setTodayDate(); loadWorkouts(); loadWorkoutTypes(); showPage('home-page'); }
476
+
477
+ function handleAuthAction(event) {
478
+ if (!auth || !authEmailInput || !authPasswordInput || !authActionButton) return;
479
+ event.preventDefault();
480
+ const email = authEmailInput.value;
481
+ const password = authPasswordInput.value;
482
+ clearMessage(); // **MODIFIÉ**
483
+ if (!email || !password) { showMessage('Email et Mot de passe requis.'); return; } // **MODIFIÉ**
484
+ authActionButton.disabled = true; authActionButton.textContent = 'Chargement...';
485
+ if (isLoginMode) { auth.signInWithEmailAndPassword(email, password).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authActionButton.textContent = 'Se Connecter'; }); }
486
+ else { auth.createUserWithEmailAndPassword(email, password).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authActionButton.textContent = 'S\'inscrire'; }); }
487
+ }
488
+
489
+ // **NOUVELLE FONCTION** pour la réinitialisation du mot de passe
490
+ function handlePasswordReset(event) {
491
+ event.preventDefault(); // Empêche le lien de naviguer
492
+ if (!auth) { alert("Service d'authentification non disponible."); return; }
493
+ clearMessage(); // Efface les messages précédents
494
+
495
+ const email = prompt("Veuillez entrer votre adresse email pour recevoir le lien de réinitialisation :");
496
+
497
+ if (!email) { // Si l'utilisateur annule ou ne saisit rien
498
+ showMessage("Saisie de l'email annulée.", true);
499
+ return;
500
+ }
501
+
502
+ console.log("Tentative d'envoi de l'email de réinitialisation à :", email);
503
+ auth.sendPasswordResetEmail(email)
504
+ .then(() => {
505
+ console.log("Email de réinitialisation envoyé.");
506
+ showMessage("Si un compte existe pour " + email + ", un email de réinitialisation a été envoyé.", false); // Message succès (vert)
507
+ })
508
+ .catch((error) => {
509
+ console.error("Erreur sendPasswordResetEmail:", error);
510
+ // Utilise handleAuthError pour afficher un message standardisé si possible
511
+ handleAuthError(error); // Affiche l'erreur dans la zone de message
512
+ });
513
+ }
514
+
515
+
516
+ function handleLogout() { /* ... identique ... */ if (!auth) { console.error("Auth missing for logout"); return; } console.log("Déconnexion..."); auth.signOut().catch(error => { console.error("Logout Error:", error); alert("Erreur lors de la déconnexion."); }); }
517
+
518
+ function authSwitchMode() {
519
+ isLoginMode = !isLoginMode;
520
+ clearMessage(); // **MODIFIÉ**
521
+ if (authEmailInput) authEmailInput.value = '';
522
+ if (authPasswordInput) authPasswordInput.value = '';
523
+ if (authTitle) authTitle.textContent = isLoginMode ? 'Connexion' : 'Inscription';
524
+ if (authActionButton) authActionButton.textContent = isLoginMode ? 'Se Connecter' : 'S\'inscrire';
525
+ if (authSwitchText) authSwitchText.textContent = isLoginMode ? 'Pas encore de compte ?' : 'Déjà un compte ?';
526
+ if (authSwitchLink) authSwitchLink.textContent = isLoginMode ? 'Inscrivez-vous ici' : 'Connectez-vous ici';
527
+ if (authEmailInput) authEmailInput.focus();
528
+ }
529
+
530
+ function handleAuthError(error) {
531
+ console.error("Erreur Auth Firebase:", error.code, error.message);
532
+ // Utilise la fonction showMessage pour afficher l'erreur
533
+ showMessage(getAuthErrorMessage(error), true); // true indique que c'est une erreur (rouge)
534
+ }
535
+
536
+ function getAuthErrorMessage(error) { /* ... identique ... */ switch (error.code) { case 'auth/invalid-email': return 'Format d\'email invalide.'; case 'auth/user-disabled': return 'Ce compte utilisateur a été désactivé.'; case 'auth/user-not-found': return 'Aucun utilisateur trouvé avec cet email.'; case 'auth/wrong-password': return 'Mot de passe incorrect.'; case 'auth/email-already-in-use': return 'Cet email est déjà utilisé par un autre compte.'; case 'auth/weak-password': return 'Le mot de passe doit contenir au moins 6 caractères.'; case 'auth/operation-not-allowed': return 'Méthode de connexion non activée (vérifiez Console Firebase).'; case 'auth/missing-password': return 'Mot de passe manquant.'; case 'auth/missing-email': return 'Adresse email manquante.'; default: console.error("Unhandled Auth Error Code:", error.code); return `Erreur (${error.code}). Veuillez réessayer.`; } } // Ajout case 'auth/missing-email'
537
+
538
+ // ... (Le reste des fonctions JS : loadWorkoutTypes, renderWorkoutTypesList, handleAddWorkoutType, ..., handleFirestoreError reste identique) ...
539
  function loadWorkoutTypes() { if (!currentFirebaseUser || !db || !typesSpinner) return; const userId = currentFirebaseUser.uid; console.log("Chargement types pour", userId); userWorkoutTypes = []; typesSpinner.classList.remove('hidden'); db.collection('workoutTypes').where('userId', '==', userId).orderBy('name').get().then(snapshot => { userWorkoutTypes = []; snapshot.forEach(doc => { userWorkoutTypes.push({ id: doc.id, ...doc.data() }); }); console.log("Types chargés:", userWorkoutTypes); renderWorkoutTypesList(); populateWorkoutTypeSelector(); }).catch(handleFirestoreError).finally(() => { typesSpinner.classList.add('hidden'); }); }
540
  function renderWorkoutTypesList() { if (!workoutTypesList || !emptyTypesMessage) { console.error("DOM elements for types list missing"); return; } workoutTypesList.innerHTML = ''; if (userWorkoutTypes.length === 0) { emptyTypesMessage.classList.remove('hidden'); } else { emptyTypesMessage.classList.add('hidden'); userWorkoutTypes.forEach(type => { const li = document.createElement('li'); li.textContent = type.name; workoutTypesList.appendChild(li); }); } }
541
  function handleAddWorkoutType(event) { event.preventDefault(); if (!currentFirebaseUser || !newTypeNameInput || !db || !addTypeError || !addTypeForm) { console.error("Missing elements for adding workout type"); return; } const typeName = newTypeNameInput.value.trim(); addTypeError.textContent = ''; addTypeError.style.display = 'none'; if (!typeName) { addTypeError.textContent = "Le nom du type ne peut pas être vide."; addTypeError.style.display = 'block'; return; } if (userWorkoutTypes.some(t => t.name.toLowerCase() === typeName.toLowerCase())) { addTypeError.textContent = "Ce type de séance existe déjà."; addTypeError.style.display = 'block'; return; } console.log("Ajout type:", typeName); const typeData = { userId: currentFirebaseUser.uid, name: typeName, createdAt: firebase.firestore.FieldValue.serverTimestamp() }; const addBtn = addTypeForm.querySelector('button'); if (addBtn) { addBtn.disabled = true; addBtn.textContent = '...'; } db.collection('workoutTypes').add(typeData).then(() => { console.log("Type ajouté avec succès"); newTypeNameInput.value = ''; loadWorkoutTypes(); }).catch(handleFirestoreError).finally(() => { if (addBtn) { addBtn.disabled = false; addBtn.textContent = 'Ajouter'; } }); }
 
560
  function renderTypeTrends(trends) { if (!typeTrendsContainer || !noTrendsMessage || !trendsSpinner) { console.error("Cannot render trends: Missing DOM elements."); return;} trendsSpinner.classList.add('hidden'); typeTrendsContainer.innerHTML = ''; const trendTypes = Object.keys(trends); if (trendTypes.length === 0) { noTrendsMessage.classList.remove('hidden'); } else { noTrendsMessage.classList.add('hidden'); trendTypes.sort().forEach(typeName => { const typeData = trends[typeName]; if (!typeData || typeData.length === 0) return; const card = document.createElement('div'); card.className = 'card type-trend-card'; let listItems = ''; typeData.forEach(session => { const formattedDate = session.date ? new Date(session.date).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' }) : '??'; const tonnageText = (session.tonnage !== undefined && session.tonnage !== null) ? `${session.tonnage.toFixed(1)} kg` : 'N/A'; listItems += `<li><span>${formattedDate}</span> <strong>${tonnageText}</strong></li>`; }); card.innerHTML = `<h4>${typeName} (3 Dernières)</h4><ul>${listItems}</ul>`; typeTrendsContainer.appendChild(card); }); } }
561
  function handleFirestoreError(error) { console.error("Erreur Firestore:", error.code, error.message); alert(`Erreur de base de données: ${error.message}`); }
562
 
563
+
564
  // --- ÉCOUTEURS D'ÉVÉNEMENTS ---
565
  function initEventListeners() {
566
  console.log("initEventListeners: Attachement...");
567
+ // Auth
568
  if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("!loginForm");
569
  if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("!authSwitchLink");
570
+ if (forgotPasswordLink) forgotPasswordLink.addEventListener('click', handlePasswordReset); else console.error("!forgotPasswordLink"); // **NOUVEAU**
571
  if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("!logoutBtn");
572
+ // Nav
573
  navItems.forEach((item) => { if (item) item.addEventListener('click', (e) => { e.preventDefault(); const targetPageId = item.getAttribute('data-page'); if (!currentFirebaseUser && targetPageId !== 'login-page') { showLoginPage(); return; } if (targetPageId) showPage(targetPageId); }); else console.error(`!navItem`); });
574
+ // Workout List Actions
575
  if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('select-workout-type-page'); }); else console.error("!newWorkoutBtn");
576
  if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('manage-types-page'); }); else console.error("!manageTypesBtn");
577
+ // Type Management
578
  if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType); else console.error("!addTypeForm");
579
+ // Select Type Page
580
  if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("!selectWorkoutTypeDropdown");
581
  if (startStructuredWorkoutBtn) startStructuredWorkoutBtn.addEventListener('click', () => { if (selectWorkoutTypeDropdown) { const type = selectWorkoutTypeDropdown.value; if (type) { clearNewWorkoutForm(type); showPage('new-workout-page'); } else { alert("Veuillez sélectionner un type de séance."); } } }); else console.error("!startStructuredWorkoutBtn");
582
  if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("!startFreeWorkoutBtn");
583
+ // General Nav
584
  backToHomeBtns.forEach(btn => { if (btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("!backToHomeBtn"); });
585
+ // New Workout Actions
586
  if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => { if (confirm("Annuler cette séance ? Les données non enregistrées seront perdues.")) showPage('home-page'); }); else console.error("!cancelNewWorkoutBtn");
587
  if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("!saveWorkoutBtn");
588
  if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("!addExerciseBtn");
589
+ // Exercise Nav
590
  if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', () => navigateExerciseForm(-1)); else console.error("!prevExerciseBtn");
591
  if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', () => navigateExerciseForm(1)); else console.error("!nextExerciseBtn");
592
+ // Details Page
593
  if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout); else console.error("!deleteWorkoutBtn");
594
+ // Satisfaction
595
  if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if (satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; }); else console.error("!satisfactionRange");
596
  console.log("initEventListeners: Fin attachement.");
597
  }
 
604
  document.addEventListener('DOMContentLoaded', () => {
605
  console.log("DOM chargé. Initialisation de l'application...");
606
  if (typeof firebase !== 'undefined' && typeof firebase.initializeApp === 'function' && typeof firebase.auth === 'function' && typeof firebase.firestore === 'function') {
607
+ if (!auth) auth = firebase.auth();
608
+ if (!db) db = firebase.firestore();
 
609
  if (auth && db) {
610
  console.log("Firebase prêt. Initialisation des listeners...");
611
  initEventListeners(); // Initialize event listeners first
612
  initAuthListener(); // Initialize auth listener to handle state changes and initial check
613
+ const user = auth.currentUser; // Check initial state synchronously IF possible
 
614
  if (user) {
615
  console.log("Utilisateur déjà connecté trouvé:", user.uid);
616
  currentFirebaseUser = user;
617
+ showApp();
618
  } else {
619
  console.log("Utilisateur non connecté au chargement.");
620
+ showLoginPage();
621
  }
622
  } else {
623
+ console.error("ERREUR CRITIQUE: Firebase Auth ou Firestore non initialisé.");
624
  alert("Erreur critique : Services Firebase non disponibles.");
625
  }
626
  } else {
627
  console.error("ERREUR CRITIQUE: Firebase SDK non chargé ou incomplet !");
628
  alert("Erreur critique : Impossible de charger les composants Firebase.");
629
+ document.body.innerHTML = '<div style="color: red; padding: 30px; text-align: center; font-family: sans-serif; background-color: #333;"><h1>Erreur Critique</h1><p>Impossible de charger les composants nécessaires. Vérifiez votre connexion et la console (F12).</p></div>';
630
  }
631
+ }); // FIN DOMContentLoaded
632
 
633
+ </script> <!-- FIN SCRIPT -->
634
 
635
+ </body> <!-- FIN BODY -->
636
+ </html> <!-- FIN HTML -->