BitDown commited on
Commit
37a4cdd
·
verified ·
1 Parent(s): 1e2a7da

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1582 -80
index.html CHANGED
@@ -1,108 +1,1610 @@
1
-
2
  <!DOCTYPE html>
3
  <html lang="fr">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Cocktail Fitness - Connexion</title>
 
 
 
 
 
 
 
 
 
 
 
8
  <link href="https://fonts.googleapis.com/css2?family=Oswald:wght@700&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
 
 
9
  <style>
10
- body {
11
- background-color: #101010;
12
- color: #f0f0f0;
13
- font-family: 'Roboto', sans-serif;
14
- display: flex;
15
- align-items: center;
16
- justify-content: center;
17
- height: 100vh;
 
 
 
 
 
 
 
 
 
 
 
 
18
  margin: 0;
 
 
 
 
19
  }
20
- .card {
21
- background-color: #1e1e1e;
22
- padding: 2rem;
23
- border-radius: 8px;
 
 
 
 
 
24
  width: 100%;
25
- max-width: 400px;
26
- box-shadow: 0 0 20px rgba(0,0,0,0.5);
 
27
  }
28
- h2 {
29
- font-family: 'Oswald', sans-serif;
30
- color: #FFC107;
 
31
  text-align: center;
 
32
  margin-bottom: 1rem;
 
 
 
33
  }
34
- input {
35
- width: 100%;
36
- padding: 0.8rem;
37
- margin-bottom: 1rem;
38
- border: 1px solid #444;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  border-radius: 4px;
40
- background-color: #2a2a2a;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  color: white;
42
  }
43
- button {
 
 
 
 
 
 
 
 
 
 
 
44
  width: 100%;
45
- padding: 0.8rem;
46
- background-color: #FFC107;
47
- border: none;
 
48
  border-radius: 4px;
49
- color: #101010;
50
- font-weight: bold;
51
- cursor: pointer;
52
  }
53
- #login-error {
54
- color: #f44336;
55
- text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  display: none;
57
- margin-top: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </style>
60
  </head>
61
  <body>
62
- <div class="card">
63
- <h2>Connexion</h2>
64
- <form id="login-form">
65
- <input type="email" id="auth-email" placeholder="Votre email" required>
66
- <input type="password" id="auth-password" placeholder="Mot de passe" required>
67
- <p id="login-error"></p>
68
- <button type="submit">Se connecter</button>
69
- </form>
70
- </div>
71
-
72
- <!-- Firebase SDK -->
73
- <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
74
- <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  <script>
76
- const firebaseConfig = {
77
- apiKey: "AIzaSyAkWvrRyXgrC7zbTtoh_GppsHMrz2rF7WM",
78
- authDomain: "lifttrackapp.firebaseapp.com",
79
- projectId: "lifttrackapp",
80
- storageBucket: "lifttrackapp.appspot.com",
81
- messagingSenderId: "594426771796",
82
- appId: "1:594426771796:web:789bef037ca0016c54b0c1",
83
- measurementId: "G-MXLFK0H160"
84
- };
85
-
86
- firebase.initializeApp(firebaseConfig);
87
- const auth = firebase.auth();
88
-
89
- const form = document.getElementById('login-form');
90
- const errorMsg = document.getElementById('login-error');
91
-
92
- form.addEventListener('submit', (e) => {
93
- e.preventDefault();
94
- const email = document.getElementById('auth-email').value;
95
- const password = document.getElementById('auth-password').value;
96
-
97
- auth.signInWithEmailAndPassword(email, password)
98
- .then(userCredential => {
99
- alert('Connexion réussie pour ' + userCredential.user.email);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  })
101
- .catch(error => {
102
- errorMsg.style.display = 'block';
103
- errorMsg.textContent = 'Erreur : ' + error.message;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  });
105
- });
106
- </script>
107
- </body>
108
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
  <html lang="fr">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <!-- Titre mis à jour -->
7
+ <title>Cocktail Fitness - Suivi</title>
8
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
9
+
10
+ <!-- SDK Firebase (v8) - Inchangé -->
11
+ <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
12
+ <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
13
+ <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
14
+
15
+ <!-- Importation de Google Fonts (Oswald) -->
16
+ <link rel="preconnect" href="https://fonts.googleapis.com">
17
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
18
  <link href="https://fonts.googleapis.com/css2?family=Oswald:wght@700&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
19
+
20
+
21
  <style>
22
+ /* ===== NOUVELLES VARIABLES DE COULEUR & POLICES ===== */
23
+ :root {
24
+ --bg-dark: #101010; /* Fond très sombre, proche du noir */
25
+ --bg-card: #1e1e1e; /* Fond des cartes légèrement plus clair */
26
+ --text-light: #f0f0f0; /* Texte principal légèrement off-white */
27
+ --text-secondary: #aaaaaa; /* Texte secondaire (labels, etc.) */
28
+ --accent: #FFC107; /* Jaune/Or du logo Cocktail */
29
+ --accent-dark: #E0A800; /* Jaune plus sombre pour hover */
30
+ --text-on-accent: #101010; /* Texte sur fond jaune (assez foncé pour contraste) */
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 */
37
+ --font-headings: 'Oswald', sans-serif; /* Police pour titres/boutons (style COCKTAIL) */
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
+ /* Workout Card */
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
+ position: relative; /* Pour positionner le badge date */
238
+ }
239
+ .workout-card:hover {
240
+ transform: translateY(-3px); /* Légère lévitation */
241
+ box-shadow: 0 6px 12px rgba(0,0,0,0.4);
242
+ }
243
+ .workout-header { display: flex; justify-content: space-between; align-items: flex-start; }
244
+ .workout-header h3 { color: var(--text-light); margin-bottom: 0.3rem; font-family: var(--font-primary); text-transform: none; letter-spacing: normal; font-weight: bold; } /* Nom séance normal */
245
+ .workout-header .badge { position: absolute; top: 1rem; right: 1rem; background-color: var(--info); color: var(--text-light); } /* Badge date repositionné */
246
+ .workout-details { font-size: 0.9rem; color: var(--text-secondary); margin-top: 0.8rem; }
247
+ .workout-details span { margin-right: 0.8rem; }
248
+
249
+ /* Stats */
250
+ .stat-card { text-align: center; padding: 1rem; }
251
+ .stat-value {
252
+ font-size: 2rem; /* Taille augmentée */
253
+ color: var(--accent);
254
+ font-weight: bold;
255
+ font-family: var(--font-headings); /* Police titre */
256
+ margin-bottom: 0.3rem;
257
+ }
258
+ .stat-card div:last-child { color: var(--text-secondary); font-size: 0.9rem; } /* Label stat */
259
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; } /* Min width augmentée */
260
+
261
+ .hidden { display: none !important; } /* Utilise !important pour forcer */
262
+ /* Gérer l'affichage initial via JS plutôt que CSS pour éviter les flashs */
263
+ /* #login-page { display: block; } */
264
+ /* #main-app-content { display: none; } */
265
+ #app-container > div:not(.active) { display: none !important; }
266
+ .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; } /* Espace augmenté */
267
+
268
+ /* Badges */
269
+ .badge {
270
+ background-color: var(--accent);
271
+ color: var(--text-on-accent);
272
+ padding: 0.3rem 0.6rem; /* Padding ajusté */
273
+ border-radius: 12px; /* Plus arrondi */
274
+ font-size: 0.8rem;
275
+ font-weight: bold;
276
+ white-space: nowrap;
277
+ margin-left: 5px;
278
+ }
279
+ .badge.type-badge { background-color: var(--neutral-white); color: var(--bg-dark); } /* Badge type en blanc (logo Fitness) */
280
+
281
+ /* Spinner */
282
+ .spinner {
283
+ border: 4px solid rgba(255, 255, 255, 0.1);
284
+ width: 36px; height: 36px;
285
+ border-radius: 50%;
286
+ border-left-color: var(--accent); /* Couleur accent pour spinner */
287
+ animation: spin 1s linear infinite;
288
+ margin: 2rem auto; display: none;
289
+ }
290
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
291
+
292
+ /* Exercise Summary in Details */
293
+ .exercise-summary {
294
+ margin: 0.3rem 0; padding: 0.5rem 0;
295
+ border-bottom: 1px solid var(--border-color);
296
+ font-size: 0.9rem;
297
  }
298
+ .exercise-summary:last-child { border-bottom: none; }
299
+ .series-summary-container .badge { font-size: 0.7rem; padding: 0.2rem 0.4rem; } /* Badges plus petits dans résumé */
300
+
301
+ /* Satisfaction Slider */
302
+ .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
303
+ input[type="range"] { accent-color: var(--accent); cursor: pointer;}
304
+ .satisfaction-value { font-size: 2.2rem; color: var(--accent); margin-top: 0.5rem; font-weight: bold; font-family: var(--font-headings); }
305
+
306
+ /* Exercise Navigation */
307
+ .exercise-navigation { display: flex; justify-content: space-between; margin-top: 1.5rem; margin-bottom: 1.5rem; align-items: center;}
308
+ .exercise-navigation span { color: var(--text-secondary); font-style: italic; }
309
+
310
+ /* Login Page */
311
+ #login-page .card { max-width: 400px; margin: 3rem auto; }
312
+ #login-page h2 { text-align: center; margin-bottom: 1.5rem; color: var(--accent); }
313
+ #login-error { color: var(--danger); text-align: center; margin-top: 1rem; font-size: 0.9rem; display: none; min-height: 1.2em; font-weight: bold; }
314
+ #login-form button { margin-top: 1rem; }
315
+ .auth-switch { text-align: center; margin-top: 1.5rem; font-size: 0.9rem; color: var(--text-secondary); }
316
+ .auth-switch a { color: var(--accent); cursor: pointer; text-decoration: underline; font-weight: bold; }
317
+
318
+ /* User Info */
319
+ .user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-secondary);}
320
+ .user-info strong { color: var(--text-light); margin: 0 5px; }
321
+ .user-info button { margin-left: 0.8rem; padding: 0.3rem 0.7rem; font-size: 0.8rem; }
322
+
323
+ /* Manage Types Page */
324
+ #workout-types-list li {
325
+ display: flex; justify-content: space-between; align-items: center;
326
+ background-color: #2a2a2a; padding: 0.7rem 1rem; /* Padding augmenté */
327
+ margin-bottom: 0.5rem; border-radius: 4px;
328
+ border-left: 4px solid var(--neutral-white); /* Bordure blanche */
329
+ color: var(--text-light); font-weight: bold;
330
+ }
331
+ #add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;}
332
+ #add-type-form input { margin-bottom: 0; flex-grow: 1;}
333
+ #add-type-form button { flex-shrink: 0; }
334
+ #add-type-error { color: var(--danger); font-size: 0.9rem; margin-top: 0.5rem; font-weight: bold; display: none; }
335
+
336
+ /* Select Workout Type Page */
337
+ #select-workout-type-page .card { text-align: center; }
338
+ #select-workout-type-page .btn { margin-top: 1rem; width: 80%; } /* Boutons plus larges */
339
+ #select-workout-type { margin-bottom: 1rem;}
340
+
341
+ /* Type Trend Card */
342
+ .type-trend-card { margin-bottom: 1.5rem; }
343
+ .type-trend-card h4 {
344
+ /* Style titre appliqué par défaut */
345
+ color: var(--neutral-white); /* Titre Tendance en Blanc */
346
+ border-bottom: 1px solid var(--border-color);
347
+ padding-bottom: 0.5rem; margin-bottom: 1rem;
348
+ text-transform: uppercase; /* Garde uppercase pour section */
349
+ }
350
+ .type-trend-card ul { list-style: none; padding: 0; font-size: 0.95rem; }
351
+ .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;}
352
+ .type-trend-card li:last-child { border-bottom: none; }
353
+ .type-trend-card li span:first-child { color: var(--text-secondary); font-size: 0.85rem; margin-right: 1rem; } /* Date */
354
+ .type-trend-card li strong { color: var(--accent); } /* Tonnage en accent */
355
+
356
+
357
+ /* Media Queries */
358
+ @media (max-width: 600px) {
359
+ .container { padding: 0.8rem; }
360
+ h1,h2 { font-size: 1.6rem; }
361
+ .form-row { flex-direction: column; gap: 0.8rem; }
362
+ .flex-between { flex-direction: column; align-items: stretch; gap: 0.8rem; }
363
+ .flex-between > div { width: 100%; display: flex; justify-content: center; margin-top: 0.5rem;}
364
+ .user-info { text-align: center; margin-bottom: 0.8rem;}
365
+ .user-info button { display: block; margin: 0.5rem auto 0;}
366
+ .exercise-navigation button { padding: 0.6rem; font-size: 0.9rem;}
367
+ #add-type-form { flex-direction: column; align-items: stretch;}
368
+ header img#app-logo { max-width: 200px; }
369
+ }
370
+
371
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
372
+
373
  </style>
374
  </head>
375
  <body>
376
+ <div class="container">
377
+ <!-- Header modifié avec le logo -->
378
+ <header>
379
+ <!-- Assurez-vous que le fichier 'input_file_0.png' est bien à la racine -->
380
+ <img src="input_file_0.png" alt="Cocktail Fitness Logo" id="app-logo">
381
+ <p>Votre suivi de séances personnalisé</p>
382
+ </header>
383
+
384
+ <!-- Login Page -->
385
+ <div id="login-page">
386
+ <div class="card">
387
+ <h2 id="auth-title">Connexion</h2>
388
+ <form id="login-form" novalidate> <!-- novalidate évite validation navigateur simple -->
389
+ <div class="form-group"> <label for="auth-email">Email</label> <input type="email" id="auth-email" placeholder="[email protected]" required> </div>
390
+ <div class="form-group"> <label for="auth-password">Mot de passe</label> <input type="password" id="auth-password" placeholder="********" required> </div>
391
+ <p id="login-error" style="display: none;"></p>
392
+ <button type="submit" id="auth-action-btn" class="btn" style="width: 100%;">Se Connecter</button>
393
+ </form>
394
+ <div class="auth-switch"> <span id="auth-switch-text">Pas encore de compte ?</span> <a id="auth-switch-link">Inscrivez-vous ici</a> </div>
395
+ </div>
396
+ </div>
397
+
398
+ <!-- Main App Content -->
399
+ <div id="main-app-content" style="display: none;">
400
+ <div class="user-info">
401
+ Connecté: <strong id="current-user-display"></strong>
402
+ <button id="logout-btn" class="btn btn-outline">Déconnexion</button>
403
+ </div>
404
+
405
+ <div id="app-container">
406
+ <!-- Home Page -->
407
+ <div id="home-page">
408
+ <div class="flex-between">
409
+ <h2>Mes séances</h2>
410
+ <div>
411
+ <button id="manage-types-btn" class="btn btn-outline" style="margin-right: 0.5rem;">Gérer Types</button>
412
+ <button id="new-workout-btn" class="btn">Nouvelle séance</button>
413
+ </div>
414
+ </div>
415
+ <div id="workouts-list" class="workout-list" style="margin-top: 1.5rem;">
416
+ <div class="spinner hidden"></div>
417
+ <p id="empty-workout-message" class="hidden" style="text-align: center; margin-top: 2rem; color: var(--text-secondary);"> Aucune séance enregistrée. </p>
418
+ </div>
419
+ </div>
420
+
421
+ <!-- Manage Types Page -->
422
+ <div id="manage-types-page">
423
+ <div class="flex-between">
424
+ <h2>Gérer les Types</h2>
425
+ <button class="btn btn-outline back-to-home-btn">Retour</button>
426
+ </div>
427
+ <div class="card">
428
+ <h3>Types Existants</h3>
429
+ <ul id="workout-types-list" style="list-style: none; padding: 0; margin-top: 1rem;">
430
+ <li class="hidden" id="empty-types-message" style="color: var(--text-secondary); background: none; border: none; padding-left: 0;">Aucun type défini.</li>
431
+ </ul>
432
+ <div class="spinner hidden" id="types-spinner"></div>
433
+ </div>
434
+ <div class="card">
435
+ <h3>Ajouter un Type</h3>
436
+ <form id="add-type-form" novalidate>
437
+ <input type="text" id="new-type-name" placeholder="Nom (ex: Push, Pull, Legs)" required>
438
+ <button type="submit" class="btn">Ajouter</button>
439
+ </form>
440
+ <p id="add-type-error" style="display: none;"></p>
441
+ </div>
442
+ </div>
443
+
444
+ <!-- Select Workout Type Page -->
445
+ <div id="select-workout-type-page">
446
+ <div class="flex-between">
447
+ <h2>Démarrer Séance</h2>
448
+ <button class="btn btn-outline back-to-home-btn">Annuler</button>
449
+ </div>
450
+ <div class="card">
451
+ <h3>Séance Structurée</h3>
452
+ <select id="select-workout-type">
453
+ <option value="">-- Sélectionner Type --</option>
454
+ </select>
455
+ <button id="start-structured-workout-btn" class="btn" disabled>Démarrer</button>
456
+ </div>
457
+ <div class="card">
458
+ <h3>Séance Libre</h3>
459
+ <button id="start-free-workout-btn" class="btn btn-info">Démarrer Libre</button>
460
+ </div>
461
+ </div>
462
+
463
+ <!-- New Workout Page -->
464
+ <div id="new-workout-page">
465
+ <div class="flex-between">
466
+ <h2>Nouvelle séance <span id="workout-type-indicator" class="badge type-badge hidden"></span> <span id="current-exercise-indicator" style="font-size: 0.9rem; color: var(--text-secondary); text-transform: none; font-family: var(--font-primary);"></span></h2>
467
+ <div>
468
+ <button id="cancel-new-workout-btn" class="btn btn-outline" style="margin-right: 0.5rem;">Annuler</button>
469
+ <button id="save-workout-btn" class="btn">Enregistrer</button>
470
+ </div>
471
+ </div>
472
+ <div class="card">
473
+ <div class="form-group"> <label for="workout-name">Nom Séance (optionnel)</label> <input type="text" id="workout-name" placeholder="Ex: Séance Haut du Corps..."> </div>
474
+ <div class="form-row">
475
+ <div class="form-group"> <label for="workout-date">Date</label> <input type="date" id="workout-date"> </div>
476
+ <div class="form-group"> <label for="workout-duration">Durée (min)</label> <input type="number" id="workout-duration" min="1" placeholder="60"> </div>
477
+ </div>
478
+ </div>
479
+ <h3 style="margin: 1.5rem 0;">Exercices</h3>
480
+ <div class="exercise-navigation card">
481
+ <button id="prev-exercise-btn" class="btn btn-outline">< Précédent</button>
482
+ <span style="align-self: center; color: var(--text-secondary);">Exercice Actif</span>
483
+ <button id="next-exercise-btn" class="btn btn-outline">Suivant ></button>
484
+ </div>
485
+ <div id="exercises-container"> <!-- Exercices injected here --> </div>
486
+ <button id="add-exercise-btn" class="btn btn-outline" style="width: 100%; margin-top: 1rem;">+ Ajouter Exercice</button>
487
+ <div class="card" style="margin-top: 2rem;">
488
+ <div class="form-group">
489
+ <label for="satisfaction">Satisfaction (1-100%)</label>
490
+ <input type="range" id="satisfaction" min="1" max="100" value="75">
491
+ <div class="satisfaction"> <div class="satisfaction-value">75%</div> </div>
492
+ </div>
493
+ </div>
494
+ </div>
495
+
496
+ <!-- Workout Details Page -->
497
+ <div id="workout-details-page">
498
+ <div class="flex-between">
499
+ <h2>Détail Séance <span id="detail-workout-type" class="badge type-badge hidden"></span></h2>
500
+ <button class="btn btn-outline back-to-home-btn">Retour</button>
501
+ </div>
502
+ <div class="card">
503
+ <div class="workout-details-info">
504
+ <p><strong>Nom:</strong> <span id="detail-workout-name" style="color: var(--text-light); font-weight: normal;"></span></p>
505
+ <div class="form-row" style="margin-top: 0.5rem;">
506
+ <p><strong>Date:</strong> <span id="detail-date" style="color: var(--text-light); font-weight: normal;"></span></p>
507
+ <p><strong>Durée:</strong> <span id="detail-duration" style="color: var(--text-light); font-weight: normal;"></span> min</p>
508
+ </div>
509
+ </div>
510
+ </div>
511
+ <div class="stats-grid" style="margin-top: 1.5rem;">
512
+ <div class="card stat-card"> <div class="stat-value" id="detail-tonnage">0</div> <div>Tonnage (kg)</div> </div>
513
+ <div class="card stat-card"> <div class="stat-value" id="detail-satisfaction">0%</div> <div>Satisfaction</div> </div>
514
+ <div class="card stat-card"> <div class="stat-value" id="detail-exercises-count">0</div> <div>Exercices</div> </div>
515
+ </div>
516
+ <h3 style="margin: 1.5rem 0 1rem;">Exercices Réalisés</h3>
517
+ <div id="detail-exercises-container"> <!-- Details injected here --> </div>
518
+ <button id="delete-workout-btn" class="btn btn-danger" style="width: 100%; margin-top: 2rem;">Supprimer Séance</button>
519
+ </div>
520
+
521
+ <!-- Stats Page -->
522
+ <div id="stats-page">
523
+ <h2>Statistiques Générales</h2>
524
+ <div class="stats-grid">
525
+ <div class="card stat-card"> <div class="stat-value" id="stats-workout-count">0</div> <div>Séances Totales</div> </div>
526
+ <div class="card stat-card"> <div class="stat-value" id="stats-avg-tonnage">0</div> <div>Tonnage Moyen / Séance</div> </div>
527
+ <div class="card stat-card"> <div class="stat-value" id="stats-avg-satisfaction">0%</div> <div>Satisfaction Moyenne</div> </div>
528
+ </div>
529
+ <h3 style="margin: 2rem 0 1rem;">Tendances par Type</h3>
530
+ <div id="type-trends-container">
531
+ <p id="no-trends-message" style="text-align: center; color: var(--text-secondary);" class="hidden"> Pas assez de données pour afficher les tendances par type. </p>
532
+ <div class="spinner hidden" id="trends-spinner"></div>
533
+ <!-- Trends injected here -->
534
+ </div>
535
+ </div>
536
+
537
+ </div> <!-- Fin #app-container -->
538
+
539
+ <!-- Bottom Navigation -->
540
+ <nav class="nav-bottom">
541
+ <a href="#" class="nav-item active" data-page="home-page"> <div class="nav-icon">📋</div> <div>Séances</div> </a>
542
+ <a href="#" class="nav-item" data-page="stats-page"> <div class="nav-icon">📊</div> <div>Stats</div> </a>
543
+ </nav>
544
+
545
+ </div> <!-- Fin #main-app-content -->
546
+ </div> <!-- Fin .container -->
547
+
548
+ <!-- ================================================== -->
549
+ <!-- ================= JAVASCRIPT =================== -->
550
+ <!-- ================================================== -->
551
  <script>
552
+ // Firebase Init - Remplacez avec votre propre config si différente
553
+ 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" };
554
+ let app, auth, db;
555
+ try {
556
+ app = firebase.initializeApp(firebaseConfig);
557
+ auth = firebase.auth();
558
+ db = firebase.firestore();
559
+ console.log("Firebase Initialisé !");
560
+ } catch (e) {
561
+ console.error("Erreur Init Firebase:", e);
562
+ alert("Erreur critique: Impossible d'initialiser Firebase.");
563
+ // Optionnel: Afficher l'erreur dans le body
564
+ document.body.innerHTML = '<div style="color: red; padding: 20px;">Erreur critique: Impossible d\'initialiser Firebase. Vérifiez la configuration et la connexion.</div>';
565
+ }
566
+
567
+ // --- Variables d'État ---
568
+ let workouts = [];
569
+ let currentWorkoutId = null;
570
+ let currentExerciseIndex = 0;
571
+ let workoutExercisesForm = [];
572
+ let currentFirebaseUser = null;
573
+ let userWorkoutTypes = [];
574
+ let selectedWorkoutTypeName = null;
575
+ let isLoginMode = true; // Start in login mode
576
+
577
+ // --- Éléments DOM --- (On suppose qu'ils existent tous)
578
+ const loginPage = document.getElementById('login-page');
579
+ const mainAppContent = document.getElementById('main-app-content');
580
+ const authEmailInput = document.getElementById('auth-email');
581
+ const authPasswordInput = document.getElementById('auth-password');
582
+ const loginForm = document.getElementById('login-form');
583
+ const authActionButton = document.getElementById('auth-action-btn');
584
+ const authSwitchLink = document.getElementById('auth-switch-link');
585
+ const authTitle = document.getElementById('auth-title');
586
+ const authSwitchText = document.getElementById('auth-switch-text');
587
+ const loginError = document.getElementById('login-error');
588
+ const currentUserDisplay = document.getElementById('current-user-display');
589
+ const logoutBtn = document.getElementById('logout-btn');
590
+ const spinner = document.querySelector('#workouts-list .spinner');
591
+ const appContainer = document.getElementById('app-container');
592
+ const navItems = document.querySelectorAll('.nav-item');
593
+ const newWorkoutBtn = document.getElementById('new-workout-btn');
594
+ const saveWorkoutBtn = document.getElementById('save-workout-btn');
595
+ const cancelNewWorkoutBtn = document.getElementById('cancel-new-workout-btn');
596
+ const addExerciseBtn = document.getElementById('add-exercise-btn');
597
+ const exercisesContainer = document.getElementById('exercises-container');
598
+ const workoutsList = document.getElementById('workouts-list');
599
+ const backToHomeBtns = document.querySelectorAll('.back-to-home-btn');
600
+ const deleteWorkoutBtn = document.getElementById('delete-workout-btn');
601
+ const satisfactionRange = document.getElementById('satisfaction');
602
+ const satisfactionValue = document.querySelector('.satisfaction-value');
603
+ const emptyWorkoutMessage = document.getElementById('empty-workout-message');
604
+ const workoutDateInput = document.getElementById('workout-date');
605
+ const prevExerciseBtn = document.getElementById('prev-exercise-btn');
606
+ const nextExerciseBtn = document.getElementById('next-exercise-btn');
607
+ const currentExerciseIndicator = document.getElementById('current-exercise-indicator');
608
+ const workoutTypeIndicator = document.getElementById('workout-type-indicator');
609
+ const detailWorkoutType = document.getElementById('detail-workout-type');
610
+ const manageTypesBtn = document.getElementById('manage-types-btn');
611
+ const manageTypesPage = document.getElementById('manage-types-page');
612
+ const workoutTypesList = document.getElementById('workout-types-list');
613
+ const emptyTypesMessage = document.getElementById('empty-types-message');
614
+ const typesSpinner = document.getElementById('types-spinner');
615
+ const addTypeForm = document.getElementById('add-type-form');
616
+ const newTypeNameInput = document.getElementById('new-type-name');
617
+ const addTypeError = document.getElementById('add-type-error');
618
+ const selectWorkoutTypePage = document.getElementById('select-workout-type-page');
619
+ const selectWorkoutTypeDropdown = document.getElementById('select-workout-type');
620
+ const startStructuredWorkoutBtn = document.getElementById('start-structured-workout-btn');
621
+ const startFreeWorkoutBtn = document.getElementById('start-free-workout-btn');
622
+ const statsWorkoutCountEl = document.getElementById('stats-workout-count');
623
+ const statsAvgTonnageEl = document.getElementById('stats-avg-tonnage');
624
+ const statsAvgSatisfactionEl = document.getElementById('stats-avg-satisfaction');
625
+ const typeTrendsContainer = document.getElementById('type-trends-container');
626
+ const noTrendsMessage = document.getElementById('no-trends-message');
627
+ const trendsSpinner = document.getElementById('trends-spinner');
628
+
629
+ // ===========================================
630
+ // --- DÉFINITION DES FONCTIONS ---
631
+ // ===========================================
632
+
633
+ function initAuthListener() {
634
+ if (!auth) { console.error("Auth object not ready in initAuthListener"); return; }
635
+ auth.onAuthStateChanged(user => {
636
+ console.log("Auth state changed:", user ? user.uid : 'null');
637
+ currentFirebaseUser = user;
638
+ if (user) {
639
+ showApp();
640
+ } else {
641
+ workouts = []; // Clear data on logout
642
+ userWorkoutTypes = [];
643
+ showLoginPage();
644
+ }
645
+ });
646
+ }
647
+
648
+ function showLoginPage() {
649
+ if (loginPage) loginPage.style.display = 'block';
650
+ if (mainAppContent) mainAppContent.style.display = 'none';
651
+ isLoginMode = true; // Reset to login mode
652
+ authSwitchMode(); // Update UI for login mode
653
+ }
654
+
655
+ function showApp() {
656
+ if (!currentFirebaseUser || !mainAppContent) {
657
+ console.error("Cannot show app: No user or main content missing.");
658
+ showLoginPage(); // Fallback to login page
659
+ return;
660
+ }
661
+ console.log("Affichage App for user:", currentFirebaseUser.email);
662
+ if (loginPage) loginPage.style.display = 'none';
663
+ mainAppContent.style.display = 'block';
664
+ if (currentUserDisplay) currentUserDisplay.textContent = currentFirebaseUser.email;
665
+ setTodayDate();
666
+ loadWorkouts();
667
+ loadWorkoutTypes();
668
+ showPage('home-page'); // Start at home page
669
+ }
670
+
671
+ function handleAuthAction(event) {
672
+ if (!auth || !authEmailInput || !authPasswordInput || !authActionButton) {
673
+ console.error("Auth or input/button elements missing in handleAuthAction"); return;
674
+ }
675
+ event.preventDefault(); // Stop form submission
676
+ const email = authEmailInput.value;
677
+ const password = authPasswordInput.value;
678
+
679
+ // Clear previous error
680
+ if (loginError) {
681
+ loginError.textContent = '';
682
+ loginError.style.display = 'none';
683
+ }
684
+
685
+ // Basic validation
686
+ if (!email || !password) {
687
+ if (loginError) {
688
+ loginError.textContent = 'Email et Mot de passe requis.';
689
+ loginError.style.display = 'block';
690
+ }
691
+ return;
692
+ }
693
+
694
+ // Disable button and show loading state
695
+ authActionButton.disabled = true;
696
+ authActionButton.textContent = 'Chargement...';
697
+
698
+ if (isLoginMode) {
699
+ console.log("Attempting sign in for:", email);
700
+ auth.signInWithEmailAndPassword(email, password)
701
+ .then(userCredential => {
702
+ console.log("Sign in successful:", userCredential.user.uid);
703
+ // Auth state change will handle UI update via initAuthListener
704
+ })
705
+ .catch(handleAuthError) // Use dedicated error handler
706
+ .finally(() => {
707
+ // Re-enable button regardless of success/failure
708
+ authActionButton.disabled = false;
709
+ authActionButton.textContent = 'Se Connecter';
710
+ });
711
+ } else {
712
+ console.log("Attempting create user for:", email);
713
+ auth.createUserWithEmailAndPassword(email, password)
714
+ .then(userCredential => {
715
+ console.log("Create user successful:", userCredential.user.uid);
716
+ // Auth state change will handle UI update via initAuthListener
717
+ })
718
+ .catch(handleAuthError) // Use dedicated error handler
719
+ .finally(() => {
720
+ // Re-enable button
721
+ authActionButton.disabled = false;
722
+ authActionButton.textContent = 'S\'inscrire';
723
+ // Reset to login mode visually after attempting signup
724
+ authSwitchMode();
725
+ });
726
+ }
727
+ }
728
+
729
+ function handleLogout() {
730
+ if (!auth) { console.error("Auth missing for logout"); return; }
731
+ console.log("Déconnexion...");
732
+ auth.signOut().catch(error => {
733
+ console.error("Logout Error:", error);
734
+ alert("Erreur lors de la déconnexion.");
735
+ });
736
+ }
737
+
738
+ function authSwitchMode() {
739
+ isLoginMode = !isLoginMode;
740
+ // Clear errors and inputs
741
+ if (loginError) { loginError.textContent = ''; loginError.style.display = 'none'; }
742
+ if (authEmailInput) authEmailInput.value = '';
743
+ if (authPasswordInput) authPasswordInput.value = '';
744
+ // Update UI elements
745
+ if (authTitle) authTitle.textContent = isLoginMode ? 'Connexion' : 'Inscription';
746
+ if (authActionButton) authActionButton.textContent = isLoginMode ? 'Se Connecter' : 'S\'inscrire';
747
+ if (authSwitchText) authSwitchText.textContent = isLoginMode ? 'Pas encore de compte ?' : 'Déjà un compte ?';
748
+ if (authSwitchLink) authSwitchLink.textContent = isLoginMode ? 'Inscrivez-vous ici' : 'Connectez-vous ici';
749
+ // Set focus to email input for convenience
750
+ if (authEmailInput) authEmailInput.focus();
751
+ }
752
+
753
+ function handleAuthError(error) {
754
+ console.error("Erreur Auth Firebase:", error.code, error.message);
755
+ if (loginError) {
756
+ loginError.textContent = getAuthErrorMessage(error);
757
+ loginError.style.display = 'block'; // Make sure error is visible
758
+ }
759
+ }
760
+
761
+ function getAuthErrorMessage(error) {
762
+ // Provide user-friendly messages based on error codes
763
+ switch (error.code) {
764
+ case 'auth/invalid-email': return 'Format d\'email invalide.';
765
+ case 'auth/user-disabled': return 'Ce compte utilisateur a été désactivé.';
766
+ case 'auth/user-not-found': return 'Aucun utilisateur trouvé avec cet email.';
767
+ case 'auth/wrong-password': return 'Mot de passe incorrect.';
768
+ case 'auth/email-already-in-use': return 'Cet email est déjà utilisé par un autre compte.';
769
+ case 'auth/weak-password': return 'Le mot de passe doit contenir au moins 6 caractères.';
770
+ case 'auth/operation-not-allowed': return 'Méthode de connexion non activée (vérifiez Console Firebase).';
771
+ case 'auth/missing-password': return 'Mot de passe manquant.';
772
+ default:
773
+ console.error("Unhandled Auth Error Code:", error.code); // Log unhandled codes
774
+ return `Erreur (${error.code}). Veuillez réessayer.`;
775
+ }
776
+ }
777
+
778
+ function loadWorkoutTypes() {
779
+ if (!currentFirebaseUser || !db || !typesSpinner) return;
780
+ const userId = currentFirebaseUser.uid;
781
+ console.log("Chargement types pour", userId);
782
+ userWorkoutTypes = []; // Reset before loading
783
+ typesSpinner.classList.remove('hidden');
784
+ db.collection('workoutTypes').where('userId', '==', userId).orderBy('name').get()
785
+ .then(snapshot => {
786
+ userWorkoutTypes = []; // Ensure it's fresh
787
+ snapshot.forEach(doc => {
788
+ userWorkoutTypes.push({ id: doc.id, ...doc.data() });
789
+ });
790
+ console.log("Types chargés:", userWorkoutTypes);
791
+ renderWorkoutTypesList();
792
+ populateWorkoutTypeSelector();
793
+ })
794
+ .catch(handleFirestoreError)
795
+ .finally(() => {
796
+ typesSpinner.classList.add('hidden');
797
+ });
798
+ }
799
+
800
+ function renderWorkoutTypesList() {
801
+ if (!workoutTypesList || !emptyTypesMessage) { console.error("DOM elements for types list missing"); return; }
802
+ workoutTypesList.innerHTML = ''; // Clear list
803
+ if (userWorkoutTypes.length === 0) {
804
+ emptyTypesMessage.classList.remove('hidden');
805
+ } else {
806
+ emptyTypesMessage.classList.add('hidden');
807
+ userWorkoutTypes.forEach(type => {
808
+ const li = document.createElement('li');
809
+ li.textContent = type.name;
810
+ // Future: Add delete button here if needed
811
+ // const deleteBtn = document.createElement('button'); ... li.appendChild(deleteBtn);
812
+ workoutTypesList.appendChild(li);
813
+ });
814
+ }
815
+ }
816
+
817
+ function handleAddWorkoutType(event) {
818
+ event.preventDefault(); // Prevent default form submission
819
+ if (!currentFirebaseUser || !newTypeNameInput || !db || !addTypeError || !addTypeForm) {
820
+ console.error("Missing elements for adding workout type"); return;
821
+ }
822
+ const typeName = newTypeNameInput.value.trim();
823
+
824
+ // Clear previous error
825
+ addTypeError.textContent = '';
826
+ addTypeError.style.display = 'none';
827
+
828
+ if (!typeName) {
829
+ addTypeError.textContent = "Le nom du type ne peut pas être vide.";
830
+ addTypeError.style.display = 'block';
831
+ return;
832
+ }
833
+ // Check for duplicates (case-insensitive)
834
+ if (userWorkoutTypes.some(t => t.name.toLowerCase() === typeName.toLowerCase())) {
835
+ addTypeError.textContent = "Ce type de séance existe déjà.";
836
+ addTypeError.style.display = 'block';
837
+ return;
838
+ }
839
+
840
+ console.log("Ajout type:", typeName);
841
+ const typeData = { userId: currentFirebaseUser.uid, name: typeName, createdAt: firebase.firestore.FieldValue.serverTimestamp() }; // Add timestamp
842
+
843
+ const addBtn = addTypeForm.querySelector('button');
844
+ if (addBtn) { addBtn.disabled = true; addBtn.textContent = '...'; }
845
+
846
+ db.collection('workoutTypes').add(typeData)
847
+ .then(() => {
848
+ console.log("Type ajouté avec succès");
849
+ newTypeNameInput.value = ''; // Clear input field
850
+ loadWorkoutTypes(); // Refresh the list
851
+ })
852
+ .catch(handleFirestoreError)
853
+ .finally(() => {
854
+ if (addBtn) { addBtn.disabled = false; addBtn.textContent = 'Ajouter'; }
855
+ });
856
+ }
857
+
858
+ function populateWorkoutTypeSelector() {
859
+ if (!selectWorkoutTypeDropdown) return;
860
+ selectWorkoutTypeDropdown.innerHTML = '<option value="">-- Sélectionner Type --</option>'; // Reset dropdown
861
+ userWorkoutTypes.forEach(type => {
862
+ const option = document.createElement('option');
863
+ option.value = type.name; // Use name as value
864
+ option.textContent = type.name;
865
+ selectWorkoutTypeDropdown.appendChild(option);
866
+ });
867
+ updateStartStructuredBtnState(); // Update button state after populating
868
+ }
869
+
870
+ function updateStartStructuredBtnState() {
871
+ if (!selectWorkoutTypeDropdown || !startStructuredWorkoutBtn) return;
872
+ // Enable button only if a valid type (not the default empty value) is selected
873
+ startStructuredWorkoutBtn.disabled = !selectWorkoutTypeDropdown.value;
874
+ }
875
+
876
+ function loadWorkouts() {
877
+ if (!currentFirebaseUser || !db || !spinner || !emptyWorkoutMessage || !workoutsList) {
878
+ console.warn("Cannot load workouts: Missing user, db, or DOM elements.");
879
+ // Ensure list is cleared and message shown if applicable
880
+ if(workoutsList) workoutsList.innerHTML = '';
881
+ if(emptyWorkoutMessage) emptyWorkoutMessage.classList.remove('hidden');
882
+ workouts = []; updateStats(); return;
883
+ }
884
+ const userId = currentFirebaseUser.uid;
885
+ console.log(`Chargement séances pour ${userId}...`);
886
+ spinner.classList.remove('hidden');
887
+ emptyWorkoutMessage.classList.add('hidden');
888
+ workoutsList.innerHTML = ''; // Clear previous list
889
+
890
+ db.collection('workouts').where('userId', '==', userId).orderBy('date', 'desc').get()
891
+ .then((querySnapshot) => {
892
+ workouts = []; // Reset local array
893
+ console.log(`${querySnapshot.size} séances trouvées.`);
894
+ querySnapshot.forEach((doc) => {
895
+ const workoutData = doc.data();
896
+ workoutData.id = doc.id; // Assign Firestore document ID
897
+ workouts.push(workoutData);
898
+ });
899
+ renderWorkoutsList(); // Update the UI
900
+ updateStats(); // Update stats based on new data
901
  })
902
+ .catch(handleFirestoreError)
903
+ .finally(() => {
904
+ spinner.classList.add('hidden'); // Hide spinner always
905
+ // Show empty message only if spinner is hidden AND workouts is empty
906
+ if (workouts.length === 0) {
907
+ emptyWorkoutMessage.classList.remove('hidden');
908
+ }
909
+ });
910
+ }
911
+
912
+ function saveWorkout() {
913
+ if (!currentFirebaseUser || !db || !exercisesContainer || !saveWorkoutBtn) {
914
+ alert("Erreur: Impossible de sauvegarder (manque user/db/elements)."); return;
915
+ }
916
+ const userId = currentFirebaseUser.uid;
917
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // Get current exercises from DOM
918
+
919
+ const workoutNameInput = document.getElementById('workout-name');
920
+ const workoutName = workoutNameInput ? workoutNameInput.value.trim() : '';
921
+ const workoutDate = workoutDateInput ? workoutDateInput.value : '';
922
+ const workoutDurationInput = document.getElementById('workout-duration');
923
+ const workoutDuration = workoutDurationInput ? parseInt(workoutDurationInput.value) || 0 : 0;
924
+ const satisfaction = satisfactionRange ? parseInt(satisfactionRange.value) : 75; // Default satisfaction
925
+
926
+ // Validate essential data
927
+ if (!selectedWorkoutTypeName) { alert("Erreur interne: Type de séance manquant."); showPage('home-page'); return; }
928
+ if (!workoutDate) { alert("Veuillez sélectionner une date."); if(workoutDateInput) workoutDateInput.focus(); return; }
929
+ if (workoutDuration <= 0) { alert("Veuillez entrer une durée valide (minutes)."); if(workoutDurationInput) workoutDurationInput.focus(); return; }
930
+ if (workoutExercisesForm.length === 0) { alert("Veuillez ajouter au moins un exercice."); return; }
931
+
932
+ // Process exercises and series
933
+ const exercises = [];
934
+ let validationError = null;
935
+ workoutExercisesForm.forEach((exerciseEl, index) => {
936
+ if (validationError) return; // Stop processing if error found
937
+ const nameInput = exerciseEl.querySelector('.exercise-name');
938
+ const exerciseName = nameInput ? nameInput.value.trim() : '';
939
+ const unilateralCheckbox = exerciseEl.querySelector('.unilateral-checkbox');
940
+ const isUnilateral = unilateralCheckbox ? unilateralCheckbox.checked : false;
941
+
942
+ if (!exerciseName) { validationError = `Nommez l'exercice #${index + 1}.`; if(nameInput) nameInput.focus(); return; }
943
+
944
+ const series = [];
945
+ const seriesElements = exerciseEl.querySelectorAll('.series');
946
+ if (seriesElements.length === 0) { validationError = `L'exercice "${exerciseName}" n'a pas de série.`; return; }
947
+
948
+ seriesElements.forEach((seriesEl, sIndex) => {
949
+ if (validationError) return;
950
+ const repsInput = seriesEl.querySelector('.reps');
951
+ const weightInput = seriesEl.querySelector('.weight');
952
+ const degressiveCheckbox = seriesEl.querySelector('.degressive-checkbox');
953
+ const reps = repsInput ? parseInt(repsInput.value) || 0 : 0;
954
+ const weight = weightInput ? parseFloat(weightInput.value) || 0 : 0;
955
+ const isDegressive = degressiveCheckbox ? degressiveCheckbox.checked : false;
956
+
957
+ if (reps <= 0) { validationError = `Reps invalides pour série ${sIndex + 1} (${exerciseName}).`; if(repsInput) repsInput.focus(); return; }
958
+ if (weight < 0) { validationError = `Charge invalide pour série ${sIndex + 1} (${exerciseName}).`; if(weightInput) weightInput.focus(); return; }
959
+ series.push({ reps, weight, isDegressive });
960
  });
961
+ if (!validationError) { exercises.push({ name: exerciseName, isUnilateral, series }); }
962
+ });
963
+
964
+ if (validationError) { alert(validationError); return; } // Show validation error and stop
965
+
966
+ // Calculate tonnage
967
+ let totalTonnage = 0;
968
+ exercises.forEach(ex => {
969
+ (ex.series || []).forEach(s => {
970
+ totalTonnage += (s.reps || 0) * (s.weight || 0) * (ex.isUnilateral ? 2 : 1);
971
+ });
972
+ });
973
+
974
+ const finalWorkoutName = workoutName || selectedWorkoutTypeName; // Use type name if custom name is empty
975
+ const workoutData = {
976
+ userId: userId,
977
+ workoutTypeName: selectedWorkoutTypeName,
978
+ name: finalWorkoutName,
979
+ date: workoutDate,
980
+ duration: workoutDuration,
981
+ exercises: exercises,
982
+ totalTonnage: totalTonnage,
983
+ satisfaction: satisfaction,
984
+ // Add timestamp for creation/update
985
+ lastUpdated: firebase.firestore.FieldValue.serverTimestamp()
986
+ };
987
+
988
+ let savePromise;
989
+ // Determine if creating new or updating existing based on currentWorkoutId
990
+ if (currentWorkoutId) {
991
+ console.log("Mise à jour séance:", currentWorkoutId);
992
+ workoutData.createdAt = workouts.find(w => w.id === currentWorkoutId)?.createdAt || firebase.firestore.FieldValue.serverTimestamp(); // Preserve creation date if possible
993
+ savePromise = db.collection('workouts').doc(currentWorkoutId).set(workoutData); // Use set to overwrite completely (or use update)
994
+ } else {
995
+ console.log("Création nouvelle séance");
996
+ workoutData.createdAt = firebase.firestore.FieldValue.serverTimestamp(); // Set creation date
997
+ savePromise = db.collection('workouts').add(workoutData); // Use add to get auto-ID
998
+ }
999
+
1000
+ saveWorkoutBtn.disabled = true;
1001
+ saveWorkoutBtn.textContent = 'Sauvegarde...';
1002
+
1003
+ savePromise.then((docRefOrVoid) => {
1004
+ const savedId = currentWorkoutId || (docRefOrVoid ? docRefOrVoid.id : 'unknown'); // Get ID if created
1005
+ console.log("Séance sauvegardée:", savedId);
1006
+ currentWorkoutId = null; // Reset ID after save
1007
+ try { if (typeof confetti === 'function') confetti({ particleCount: 150, spread: 90, origin: { y: 0.6 } }); } catch(e) { console.warn("Confetti error:", e)}
1008
+ showPage('home-page'); // Go back home
1009
+ loadWorkouts(); // Reload list
1010
+ }).catch(handleFirestoreError).finally(() => {
1011
+ saveWorkoutBtn.disabled = false;
1012
+ saveWorkoutBtn.textContent = 'Enregistrer Séance';
1013
+ });
1014
+ }
1015
+
1016
+ function deleteWorkout() {
1017
+ if (!currentFirebaseUser || !currentWorkoutId || !db || !deleteWorkoutBtn) return;
1018
+ const workoutToDelete = workouts.find(w => w.id === currentWorkoutId);
1019
+ if (!workoutToDelete) { console.error("Workout to delete not found locally."); return; }
1020
+
1021
+ const confirmMsg = `Supprimer la séance "${workoutToDelete.name || 'Sans nom'}" du ${new Date(workoutToDelete.date).toLocaleDateString('fr-FR')} ?`;
1022
+ if (!confirm(confirmMsg)) return; // Ask for confirmation
1023
+
1024
+ console.log("Suppression séance:", currentWorkoutId);
1025
+ deleteWorkoutBtn.disabled = true;
1026
+ deleteWorkoutBtn.textContent = 'Suppression...';
1027
+
1028
+ db.collection('workouts').doc(currentWorkoutId).delete()
1029
+ .then(() => {
1030
+ console.log("Séance supprimée avec succès:", currentWorkoutId);
1031
+ currentWorkoutId = null; // Clear the ID
1032
+ showPage('home-page'); // Go back home
1033
+ loadWorkouts(); // Refresh list and stats
1034
+ })
1035
+ .catch(handleFirestoreError)
1036
+ .finally(() => {
1037
+ deleteWorkoutBtn.disabled = false;
1038
+ deleteWorkoutBtn.textContent = 'Supprimer Séance';
1039
+ });
1040
+ }
1041
+
1042
+ function setTodayDate() {
1043
+ if (workoutDateInput) {
1044
+ try {
1045
+ const today = new Date();
1046
+ // Adjust for timezone offset to get local date in YYYY-MM-DD format
1047
+ const offset = today.getTimezoneOffset();
1048
+ const localDate = new Date(today.getTime() - (offset*60*1000));
1049
+ workoutDateInput.value = localDate.toISOString().split('T')[0];
1050
+ } catch (e) {
1051
+ console.error("Erreur setTodayDate:", e);
1052
+ // Fallback to non-timezone adjusted date if error occurs
1053
+ try { workoutDateInput.value = new Date().toISOString().split('T')[0]; } catch {}
1054
+ }
1055
+ }
1056
+ }
1057
+
1058
+ function showPage(pageId) {
1059
+ if (!appContainer) { console.error("App container not found!"); return; }
1060
+ console.log(`Affichage page: ${pageId}`);
1061
+
1062
+ // Hide all pages first
1063
+ const pages = appContainer.querySelectorAll(':scope > div[id$="-page"]'); // Select direct children ending with -page
1064
+ pages.forEach(page => page.classList.remove('active'));
1065
+
1066
+ const pageToShow = document.getElementById(pageId);
1067
+ if (pageToShow) {
1068
+ pageToShow.classList.add('active'); // Show the target page
1069
+ window.scrollTo(0, 0); // Scroll to top
1070
+
1071
+ // Page-specific actions
1072
+ if (pageId === 'new-workout-page') {
1073
+ // Ensure a type is selected before showing this page
1074
+ if (!selectedWorkoutTypeName) {
1075
+ console.warn("Tentative d'accès à new-workout-page sans type sélectionné.");
1076
+ alert("Veuillez d'abord sélectionner un type de séance.");
1077
+ showPage('select-workout-type-page'); // Redirect
1078
+ return;
1079
+ }
1080
+ renderActiveExerciseForm();
1081
+ updateWorkoutTypeIndicator();
1082
+ } else if (pageId === 'manage-types-page') {
1083
+ loadWorkoutTypes();
1084
+ } else if (pageId === 'select-workout-type-page') {
1085
+ populateWorkoutTypeSelector();
1086
+ } else if (pageId === 'stats-page') {
1087
+ updateStats();
1088
+ } else if (pageId === 'workout-details-page') {
1089
+ // Ensure an ID is set before showing details
1090
+ if (!currentWorkoutId) {
1091
+ console.warn("Tentative d'accès aux détails sans ID de séance.");
1092
+ alert("Séance non spécifiée.");
1093
+ showPage('home-page'); // Redirect
1094
+ return;
1095
+ }
1096
+ // Data for details page is loaded within displayWorkoutDetails function
1097
+ }
1098
+
1099
+ } else {
1100
+ console.error(`Page ID "${pageId}" non trouvée. Affichage de 'home-page'.`);
1101
+ const homePage = document.getElementById('home-page');
1102
+ if (homePage) homePage.classList.add('active');
1103
+ pageId = 'home-page'; // Fallback pageId for nav highlighting
1104
+ }
1105
+
1106
+ // Update bottom navigation highlighting
1107
+ navItems.forEach(item => item.classList.remove('active'));
1108
+ // Highlight 'home' for most app pages, 'stats' for the stats page
1109
+ const currentPageForNav = pageId === 'stats-page' ? 'stats-page' : 'home-page';
1110
+ const activeNavItem = document.querySelector(`.nav-item[data-page="${currentPageForNav}"]`);
1111
+ if (activeNavItem) activeNavItem.classList.add('active');
1112
+ }
1113
+
1114
+
1115
+ function handleAddNewExercise() {
1116
+ addExercise(true); // Add and navigate to the new exercise
1117
+ }
1118
+
1119
+ function addExercise(navigateToNew = false) {
1120
+ if (!currentFirebaseUser || !exercisesContainer) { console.error("Cannot add exercise"); return; }
1121
+ const exerciseId = `exercise-${Date.now()}`; // Simple unique ID
1122
+ const exerciseDiv = document.createElement('div');
1123
+ exerciseDiv.className = 'card exercise'; // Apply card styling
1124
+ exerciseDiv.setAttribute('data-exercise-id', exerciseId);
1125
+ // Simplified HTML structure for clarity
1126
+ exerciseDiv.innerHTML = `
1127
+ <div class="exercise-header">
1128
+ <input type="text" placeholder="Nom Exercice" class="exercise-name" required>
1129
+ <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;" aria-label="Supprimer exercice">×</button>
1130
+ </div>
1131
+ <div class="form-group" style="margin: 0.5rem 0;">
1132
+ <label style="display: inline-flex; align-items: center; color: var(--text-secondary); font-size: 0.9rem; font-weight: normal;">
1133
+ <input type="checkbox" class="unilateral-checkbox"> Unilatéral ?
1134
+ </label>
1135
+ </div>
1136
+ <div class="series-container"></div>
1137
+ <button class="btn btn-outline add-series" style="width: 100%; margin-top: 1rem; padding: 0.4rem;">+ Ajouter Série</button>
1138
+ `;
1139
+ exercisesContainer.appendChild(exerciseDiv);
1140
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // Update the form array
1141
+
1142
+ // --- Add Event Listeners for the new exercise ---
1143
+ const removeBtn = exerciseDiv.querySelector('.remove-exercise');
1144
+ removeBtn.addEventListener('click', () => {
1145
+ const indexToRemove = workoutExercisesForm.findIndex(el => el === exerciseDiv);
1146
+ if (indexToRemove > -1) {
1147
+ if (confirm("Supprimer cet exercice et toutes ses séries ?")) {
1148
+ exerciseDiv.remove(); // Remove from DOM
1149
+ workoutExercisesForm.splice(indexToRemove, 1); // Remove from array
1150
+ // Adjust current index if needed
1151
+ if (currentExerciseIndex >= indexToRemove && currentExerciseIndex > 0) {
1152
+ currentExerciseIndex--;
1153
+ }
1154
+ if (currentExerciseIndex >= workoutExercisesForm.length) {
1155
+ currentExerciseIndex = Math.max(0, workoutExercisesForm.length - 1);
1156
+ }
1157
+ renderActiveExerciseForm(); // Refresh view (show correct exercise, update nav)
1158
+ }
1159
+ }
1160
+ });
1161
+
1162
+ const addSeriesBtn = exerciseDiv.querySelector('.add-series');
1163
+ const seriesContainer = exerciseDiv.querySelector('.series-container');
1164
+ addSeriesBtn.addEventListener('click', () => addSeries(seriesContainer));
1165
+
1166
+ addSeries(seriesContainer); // Add one default series
1167
+
1168
+ // Navigate or just update nav buttons
1169
+ if (navigateToNew) {
1170
+ currentExerciseIndex = workoutExercisesForm.length - 1; // Go to the newly added exercise
1171
+ renderActiveExerciseForm();
1172
+ // Focus the name input of the new exercise
1173
+ const nameInput = exerciseDiv.querySelector('.exercise-name');
1174
+ if (nameInput) nameInput.focus();
1175
+ } else {
1176
+ updateExerciseNavButtons(); // Just update buttons if not navigating
1177
+ }
1178
+ }
1179
+
1180
+ function addSeries(container) {
1181
+ if (!currentFirebaseUser || !container) { console.error("Cannot add series"); return; }
1182
+ const seriesId = `series-${Date.now()}`;
1183
+ const seriesDiv = document.createElement('div');
1184
+ seriesDiv.className = 'series';
1185
+ seriesDiv.setAttribute('data-series-id', seriesId);
1186
+ // Simplified HTML for series row
1187
+ seriesDiv.innerHTML = `
1188
+ <div class="form-row" style="align-items: flex-end; gap: 0.8rem;">
1189
+ <div class="form-group" style="flex: 1.2;">
1190
+ <label style="font-weight: normal; font-size: 0.8rem;">Reps</label>
1191
+ <input type="number" class="reps" min="1" placeholder="10" style="padding: 0.5rem;" required>
1192
+ </div>
1193
+ <div class="form-group" style="flex: 1.2;">
1194
+ <label style="font-weight: normal; font-size: 0.8rem;">Charge (kg)</label>
1195
+ <input type="number" class="weight" min="0" step="any" placeholder="20" style="padding: 0.5rem;" required>
1196
+ </div>
1197
+ <div class="form-group" style="flex: 1; display: flex; align-items: center; padding-bottom: 0.8rem; min-width: 100px;">
1198
+ <label style="display: inline-flex; align-items: center; color: var(--text-secondary); font-size: 0.8rem; margin-bottom: 0; white-space: nowrap; font-weight: normal;">
1199
+ <input type="checkbox" class="degressive-checkbox" style="margin-right: 0.3rem;"> Dégressive
1200
+ </label>
1201
+ </div>
1202
+ <button class="btn btn-danger remove-series" style="padding: 0.3rem 0.6rem; margin-bottom: 0.8rem; flex-basis: 30px; flex-grow: 0; align-self: center;" aria-label="Supprimer série">×</button>
1203
+ </div>
1204
+ `;
1205
+ container.appendChild(seriesDiv);
1206
+
1207
+ // Add listener to the remove button of this specific series
1208
+ const removeBtn = seriesDiv.querySelector('.remove-series');
1209
+ removeBtn.addEventListener('click', () => {
1210
+ // Optional: Confirm before removing
1211
+ // if (confirm("Supprimer cette série ?")) {
1212
+ seriesDiv.remove();
1213
+ // }
1214
+ });
1215
+ }
1216
+
1217
+ function navigateExerciseForm(direction) {
1218
+ if (!exercisesContainer) return;
1219
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // Ensure up-to-date
1220
+ const newIndex = currentExerciseIndex + direction;
1221
+ // Check bounds
1222
+ if (newIndex >= 0 && newIndex < workoutExercisesForm.length) {
1223
+ currentExerciseIndex = newIndex;
1224
+ renderActiveExerciseForm();
1225
+ }
1226
+ }
1227
+
1228
+ function renderActiveExerciseForm() {
1229
+ if (!exercisesContainer) return;
1230
+ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); // Get current list
1231
+ // Hide all, then show the active one
1232
+ workoutExercisesForm.forEach((ex, index) => {
1233
+ if (index === currentExerciseIndex) {
1234
+ ex.classList.add('active-exercise');
1235
+ ex.style.display = 'block'; // Ensure it's visible
1236
+ } else {
1237
+ ex.classList.remove('active-exercise');
1238
+ ex.style.display = 'none'; // Hide inactive
1239
+ }
1240
+ });
1241
+ updateExerciseNavButtons(); // Update indicators and button states
1242
+ }
1243
+
1244
+ function updateExerciseNavButtons() {
1245
+ if (!currentExerciseIndicator || !prevExerciseBtn || !nextExerciseBtn) { console.warn("Exercise nav elements missing"); return; }
1246
+ const totalExercises = workoutExercisesForm.length;
1247
+ if (totalExercises === 0) {
1248
+ currentExerciseIndicator.textContent = "(Aucun exercice)";
1249
+ prevExerciseBtn.disabled = true;
1250
+ nextExerciseBtn.disabled = true;
1251
+ } else {
1252
+ currentExerciseIndicator.textContent = `(${currentExerciseIndex + 1} / ${totalExercises})`;
1253
+ prevExerciseBtn.disabled = (currentExerciseIndex === 0); // Disable if first
1254
+ nextExerciseBtn.disabled = (currentExerciseIndex === totalExercises - 1); // Disable if last
1255
+ }
1256
+ }
1257
+
1258
+ function clearNewWorkoutForm(typeName = "Libre") {
1259
+ console.log("Nettoyage formulaire pour type:", typeName);
1260
+ if (!currentFirebaseUser) { console.error("Cannot clear form: No user"); return; }
1261
+ selectedWorkoutTypeName = typeName;
1262
+
1263
+ // Reset form fields
1264
+ const workoutNameInput = document.getElementById('workout-name');
1265
+ if (workoutNameInput) workoutNameInput.value = (typeName === "Libre" ? "" : typeName); // Pre-fill name if structured
1266
+ if (workoutDateInput) setTodayDate(); // Set date to today
1267
+ const durationInput = document.getElementById('workout-duration');
1268
+ if (durationInput) durationInput.value = '60'; // Default duration
1269
+ if (exercisesContainer) exercisesContainer.innerHTML = ''; // Clear existing exercises
1270
+ workoutExercisesForm = []; // Reset exercise array
1271
+ if (satisfactionRange) satisfactionRange.value = 75; // Reset satisfaction slider
1272
+ if (satisfactionValue) satisfactionValue.textContent = '75%'; // Reset satisfaction display
1273
+
1274
+ currentWorkoutId = null; // Ensure we are creating a new workout
1275
+ currentExerciseIndex = 0; // Reset exercise index
1276
+
1277
+ addExercise(false); // Add the first empty exercise block
1278
+ updateWorkoutTypeIndicator(); // Update the type badge display
1279
+ renderActiveExerciseForm(); // Show the first exercise form and update nav
1280
+ }
1281
+
1282
+ function updateWorkoutTypeIndicator() {
1283
+ if (!workoutTypeIndicator) return;
1284
+ if (selectedWorkoutTypeName && selectedWorkoutTypeName !== "Libre") {
1285
+ workoutTypeIndicator.textContent = selectedWorkoutTypeName;
1286
+ workoutTypeIndicator.classList.remove('hidden');
1287
+ } else {
1288
+ workoutTypeIndicator.classList.add('hidden');
1289
+ }
1290
+ }
1291
+
1292
+ function renderWorkoutsList() {
1293
+ if (!workoutsList || !emptyWorkoutMessage) { console.warn("Cannot render workout list: DOM elements missing."); return; }
1294
+ workoutsList.innerHTML = ''; // Clear previous list
1295
+
1296
+ if (!workouts || workouts.length === 0) {
1297
+ emptyWorkoutMessage.classList.remove('hidden');
1298
+ } else {
1299
+ emptyWorkoutMessage.classList.add('hidden');
1300
+ // Sort workouts by date descending (already done by Firestore query, but good practice)
1301
+ const sortedWorkouts = [...workouts].sort((a, b) => new Date(b.date) - new Date(a.date));
1302
+
1303
+ sortedWorkouts.forEach(workout => {
1304
+ if (!workout || !workout.id) { // Skip if workout data is invalid
1305
+ console.warn("Skipping invalid workout data:", workout);
1306
+ return;
1307
+ }
1308
+ const workoutDate = workout.date ? new Date(workout.date).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }) : 'Date inconnue';
1309
+ const workoutDiv = document.createElement('div');
1310
+ workoutDiv.className = 'card workout-card';
1311
+ workoutDiv.setAttribute('data-workout-id', workout.id);
1312
+
1313
+ const typeBadge = workout.workoutTypeName && workout.workoutTypeName !== "Libre" ? `<span class="badge type-badge" style="margin-left: 8px;">${workout.workoutTypeName}</span>` : '';
1314
+ const exercisesCount = (workout.exercises || []).length;
1315
+
1316
+ workoutDiv.innerHTML = `
1317
+ <div class="workout-header">
1318
+ <h3 style="margin-bottom: 0.5rem;">${workout.name || 'Séance sans nom'} ${typeBadge}</h3>
1319
+ <div class="badge">${workoutDate}</div>
1320
+ </div>
1321
+ <div class="workout-details">
1322
+ <span>${workout.duration || '?'} min</span> |
1323
+ <span>${exercisesCount} exo${exercisesCount > 1 ? 's' : ''}</span> |
1324
+ <span>${(workout.totalTonnage || 0).toFixed(1)} kg</span> |
1325
+ <span>${workout.satisfaction || '?'}%</span>
1326
+ </div>`;
1327
+ workoutDiv.addEventListener('click', () => displayWorkoutDetails(workout.id));
1328
+ workoutsList.appendChild(workoutDiv);
1329
+ });
1330
+ }
1331
+ }
1332
+
1333
+ function displayWorkoutDetails(workoutId) {
1334
+ if (!currentFirebaseUser) { console.error("User not logged in for details"); return; }
1335
+ const workout = workouts.find(w => w && w.id === workoutId); // Find workout in local array
1336
+ if (!workout) { alert("Détails de la séance introuvables."); showPage('home-page'); return; }
1337
+
1338
+ console.log("Affichage détails pour:", workoutId, workout);
1339
+
1340
+ // --- Selectors for Detail Page Elements ---
1341
+ const detailNameEl = document.getElementById('detail-workout-name');
1342
+ const detailDateEl = document.getElementById('detail-date');
1343
+ const detailDurationEl = document.getElementById('detail-duration');
1344
+ const detailTonnageEl = document.getElementById('detail-tonnage');
1345
+ const detailSatisfactionEl = document.getElementById('detail-satisfaction');
1346
+ const detailExercisesCountEl = document.getElementById('detail-exercises-count');
1347
+ const detailExercisesContainer = document.getElementById('detail-exercises-container');
1348
+ const detailWorkoutTypeBadge = document.getElementById('detail-workout-type');
1349
+
1350
+ // --- Populate Detail Page Elements ---
1351
+ if (detailWorkoutTypeBadge) {
1352
+ if (workout.workoutTypeName && workout.workoutTypeName !== "Libre") {
1353
+ detailWorkoutTypeBadge.textContent = workout.workoutTypeName;
1354
+ detailWorkoutTypeBadge.classList.remove('hidden');
1355
+ } else {
1356
+ detailWorkoutTypeBadge.classList.add('hidden');
1357
+ }
1358
+ }
1359
+ if (detailNameEl) detailNameEl.textContent = workout.name || 'Séance sans nom';
1360
+ if (detailDateEl) detailDateEl.textContent = workout.date ? new Date(workout.date).toLocaleDateString('fr-FR') : 'Inconnue';
1361
+ if (detailDurationEl) detailDurationEl.textContent = workout.duration || '?';
1362
+ if (detailTonnageEl) detailTonnageEl.textContent = (workout.totalTonnage || 0).toFixed(1);
1363
+ if (detailSatisfactionEl) detailSatisfactionEl.textContent = `${workout.satisfaction || '?'}%`;
1364
+ const exercisesCount = (workout.exercises || []).length;
1365
+ if (detailExercisesCountEl) detailExercisesCountEl.textContent = exercisesCount;
1366
+
1367
+ // Populate exercises details
1368
+ if (detailExercisesContainer) {
1369
+ detailExercisesContainer.innerHTML = ''; // Clear previous details
1370
+ if (exercisesCount > 0) {
1371
+ workout.exercises.forEach((exercise) => {
1372
+ if (!exercise) return; // Skip invalid exercise data
1373
+ const exerciseDiv = document.createElement('div');
1374
+ exerciseDiv.className = 'card detail-exercise-card'; // Reuse card style
1375
+ let seriesHtml = '';
1376
+ (exercise.series || []).forEach((serie, sIndex) => {
1377
+ const degressiveLabel = serie.isDegressive ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--info); color: var(--text-light);">Dégr.</span>' : '';
1378
+ const weightFactor = exercise.isUnilateral ? 2 : 1;
1379
+ const seriesTonnage = (serie.reps || 0) * (serie.weight || 0) * weightFactor;
1380
+ seriesHtml += `
1381
+ <div class="exercise-summary">
1382
+ <div class="flex-between">
1383
+ <span>Série ${sIndex + 1}: ${serie.reps || '?'} reps × ${serie.weight || '?'} kg${degressiveLabel}</span>
1384
+ <span style="color: var(--text-secondary);">(${seriesTonnage.toFixed(1)} kg)</span>
1385
+ </div>
1386
+ </div>`;
1387
+ });
1388
+ const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--info); color: var(--text-light);">Unilat.</span>' : '';
1389
+ exerciseDiv.innerHTML = `
1390
+ <h4 style="font-size: 1.1rem; margin-bottom: 0.8rem; color: var(--text-light); text-transform: none; font-family: var(--font-primary); letter-spacing: normal;">
1391
+ ${exercise.name || 'Exercice sans nom'}${unilateralLabel}
1392
+ </h4>
1393
+ <div class="series-summary-container"> ${seriesHtml} </div>`;
1394
+ detailExercisesContainer.appendChild(exerciseDiv);
1395
+ });
1396
+ } else {
1397
+ detailExercisesContainer.innerHTML = '<p style="color: var(--text-secondary); text-align: center;">Aucun exercice enregistré pour cette séance.</p>';
1398
+ }
1399
+ }
1400
+
1401
+ currentWorkoutId = workoutId; // Set the current ID for potential deletion
1402
+ showPage('workout-details-page'); // Navigate to the details page
1403
+ }
1404
+
1405
+ // --- Fonctionnalité STATS (Avec tendances) ---
1406
+ function updateStats() {
1407
+ console.log("Mise à jour des statistiques...");
1408
+ if (!currentFirebaseUser || !statsWorkoutCountEl || !statsAvgTonnageEl || !statsAvgSatisfactionEl || !typeTrendsContainer || !noTrendsMessage) {
1409
+ console.warn("Cannot update stats: Missing user or DOM elements.");
1410
+ // Reset stats display if elements exist
1411
+ if(statsWorkoutCountEl) statsWorkoutCountEl.textContent = '0';
1412
+ if(statsAvgTonnageEl) statsAvgTonnageEl.textContent = '0';
1413
+ if(statsAvgSatisfactionEl) statsAvgSatisfactionEl.textContent = '0%';
1414
+ if(typeTrendsContainer) typeTrendsContainer.innerHTML = '';
1415
+ if(noTrendsMessage) noTrendsMessage.classList.remove('hidden');
1416
+ return;
1417
+ }
1418
+
1419
+ const workoutCount = workouts.length;
1420
+ statsWorkoutCountEl.textContent = workoutCount;
1421
+
1422
+ if (workoutCount === 0) {
1423
+ statsAvgTonnageEl.textContent = '0';
1424
+ statsAvgSatisfactionEl.textContent = '0%';
1425
+ renderTypeTrends({}); // Pass empty object to show 'no data' message
1426
+ return;
1427
+ }
1428
+
1429
+ // Calculate global stats
1430
+ const totalTonnageAll = workouts.reduce((sum, w) => sum + (w.totalTonnage || 0), 0);
1431
+ statsAvgTonnageEl.textContent = workoutCount > 0 ? (totalTonnageAll / workoutCount).toFixed(1) : '0';
1432
+ const totalSatisfaction = workouts.reduce((sum, w) => sum + (w.satisfaction || 0), 0);
1433
+ statsAvgSatisfactionEl.textContent = workoutCount > 0 ? `${Math.round(totalSatisfaction / workoutCount)}%` : '0%';
1434
+
1435
+ // Calculate trends per type
1436
+ console.log("Calcul tendances par type...");
1437
+ const trendsData = {};
1438
+ const structuredWorkouts = workouts.filter(w => w && w.workoutTypeName && w.workoutTypeName !== "Libre"); // Filter valid workouts
1439
+
1440
+ const groupedByType = structuredWorkouts.reduce((acc, workout) => {
1441
+ const type = workout.workoutTypeName;
1442
+ if (!acc[type]) acc[type] = [];
1443
+ acc[type].push(workout);
1444
+ return acc;
1445
+ }, {});
1446
+
1447
+ for (const typeName in groupedByType) {
1448
+ // Sort sessions chronologically for the type
1449
+ const sessionsOfType = groupedByType[typeName].sort((a, b) => new Date(a.date) - new Date(b.date));
1450
+ // Get the last 3 sessions
1451
+ trendsData[typeName] = sessionsOfType.slice(-3).map(s => ({
1452
+ date: s.date,
1453
+ tonnage: s.totalTonnage || 0 // Default tonnage to 0 if missing
1454
+ }));
1455
+ }
1456
+ console.log("Données tendances:", trendsData);
1457
+ renderTypeTrends(trendsData); // Render the calculated trends
1458
+ }
1459
+
1460
+ function renderTypeTrends(trends) {
1461
+ if (!typeTrendsContainer || !noTrendsMessage || !trendsSpinner) {
1462
+ console.error("Cannot render trends: Missing DOM elements."); return;
1463
+ }
1464
+ trendsSpinner.classList.add('hidden'); // Hide spinner
1465
+ typeTrendsContainer.innerHTML = ''; // Clear previous trends
1466
+ const trendTypes = Object.keys(trends);
1467
+
1468
+ if (trendTypes.length === 0) {
1469
+ noTrendsMessage.classList.remove('hidden'); // Show 'no data' message
1470
+ } else {
1471
+ noTrendsMessage.classList.add('hidden'); // Hide 'no data' message
1472
+ trendTypes.sort().forEach(typeName => { // Sort types alphabetically
1473
+ const typeData = trends[typeName];
1474
+ if (!typeData || typeData.length === 0) return; // Skip if no data for this type
1475
+
1476
+ const card = document.createElement('div');
1477
+ card.className = 'card type-trend-card';
1478
+ let listItems = '';
1479
+ typeData.forEach(session => {
1480
+ const formattedDate = session.date ? new Date(session.date).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' }) : '??';
1481
+ // Handle potential null/undefined tonnage
1482
+ const tonnageText = (session.tonnage !== undefined && session.tonnage !== null) ? `${session.tonnage.toFixed(1)} kg` : 'N/A';
1483
+ listItems += `<li><span>${formattedDate}</span> <strong>${tonnageText}</strong></li>`;
1484
+ });
1485
+
1486
+ card.innerHTML = `<h4>${typeName} (3 Dernières)</h4><ul>${listItems}</ul>`;
1487
+ typeTrendsContainer.appendChild(card);
1488
+ });
1489
+ }
1490
+ }
1491
+
1492
+ function handleFirestoreError(error) {
1493
+ console.error("Erreur Firestore:", error.code, error.message);
1494
+ alert(`Erreur de base de données: ${error.message}`);
1495
+ // Potentially disable relevant buttons or show a general error message
1496
+ }
1497
+
1498
+ // --- ÉCOUTEURS D'ÉVÉNEMENTS ---
1499
+ function initEventListeners() {
1500
+ console.log("initEventListeners: Attachement des écouteurs...");
1501
+
1502
+ // Auth Form
1503
+ if (loginForm) loginForm.addEventListener('submit', handleAuthAction);
1504
+ else console.error("!loginForm not found");
1505
+ if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode);
1506
+ else console.error("!authSwitchLink not found");
1507
+
1508
+ // Logout
1509
+ if (logoutBtn) logoutBtn.addEventListener('click', handleLogout);
1510
+ else console.error("!logoutBtn not found");
1511
+
1512
+ // Navigation
1513
+ navItems.forEach((item, index) => {
1514
+ if (item) {
1515
+ item.addEventListener('click', (e) => {
1516
+ e.preventDefault();
1517
+ const targetPageId = item.getAttribute('data-page');
1518
+ if (!currentFirebaseUser && targetPageId !== 'login-page') {
1519
+ showLoginPage(); // Redirect to login if not logged in
1520
+ return;
1521
+ }
1522
+ if (targetPageId) showPage(targetPageId);
1523
+ });
1524
+ } else console.error(`!navItem #${index} not found`);
1525
+ });
1526
+
1527
+ // Workout Actions
1528
+ if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('select-workout-type-page'); });
1529
+ else console.error("!newWorkoutBtn not found");
1530
+ if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('manage-types-page'); });
1531
+ else console.error("!manageTypesBtn not found");
1532
+
1533
+ // Type Management
1534
+ if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType);
1535
+ else console.error("!addTypeForm not found");
1536
+
1537
+ // Select Workout Type Page
1538
+ if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState);
1539
+ else console.error("!selectWorkoutTypeDropdown not found");
1540
+ 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.");} } });
1541
+ else console.error("!startStructuredWorkoutBtn not found");
1542
+ if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); });
1543
+ else console.error("!startFreeWorkoutBtn not found");
1544
+
1545
+ // General Navigation
1546
+ backToHomeBtns.forEach(btn => { if(btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("!backToHomeBtn missing"); });
1547
+
1548
+ // New Workout Page Actions
1549
+ if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => { if(confirm("Annuler cette séance ? Les données non enregistrées seront perdues.")) showPage('home-page'); });
1550
+ else console.error("!cancelNewWorkoutBtn not found");
1551
+ if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout);
1552
+ else console.error("!saveWorkoutBtn not found");
1553
+ if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise);
1554
+ else console.error("!addExerciseBtn not found");
1555
+
1556
+ // Exercise Navigation
1557
+ if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', () => navigateExerciseForm(-1));
1558
+ else console.error("!prevExerciseBtn not found");
1559
+ if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', () => navigateExerciseForm(1));
1560
+ else console.error("!nextExerciseBtn not found");
1561
+
1562
+ // Workout Details Actions
1563
+ if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout);
1564
+ else console.error("!deleteWorkoutBtn not found");
1565
+
1566
+ // Satisfaction Slider
1567
+ if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if(satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; });
1568
+ else console.error("!satisfactionRange not found");
1569
+
1570
+ console.log("initEventListeners: Fin attachement.");
1571
+ }
1572
+
1573
+
1574
+ // ==================================================
1575
+ // --- FIN DES DÉFINITIONS DE FONCTIONS ---
1576
+ // ==================================================
1577
+
1578
+ // --- INITIALISATION ---
1579
+ // Attend que le DOM soit entièrement chargé avant d'exécuter le JS principal
1580
+ document.addEventListener('DOMContentLoaded', () => {
1581
+ console.log("DOM chargé. Initialisation de l'application...");
1582
+
1583
+ // Vérifie que Firebase est chargé et initialisé (la variable 'auth' devrait être définie dans le try/catch initial)
1584
+ if (typeof firebase !== 'undefined' && auth && db) {
1585
+ console.log("Firebase prêt. Initialisation des listeners...");
1586
+ initAuthListener(); // Surveille les changements d'état de connexion
1587
+ initEventListeners(); // Attache les gestionnaires d'événements aux boutons, etc.
1588
+
1589
+ // Vérifie l'état de connexion initial au chargement de la page
1590
+ if (auth.currentUser) {
1591
+ console.log("Utilisateur déjà connecté au chargement:", auth.currentUser.uid);
1592
+ currentFirebaseUser = auth.currentUser;
1593
+ showApp(); // Affiche l'application principale
1594
+ } else {
1595
+ console.log("Utilisateur non connecté au chargement.");
1596
+ showLoginPage(); // Affiche la page de connexion
1597
+ }
1598
+ } else {
1599
+ // Si Firebase n'a pas pu s'initialiser (erreur dans le premier try/catch ou SDK non chargé)
1600
+ console.error("ERREUR CRITIQUE: Firebase n'a pas pu être initialisé ou n'est pas chargé.");
1601
+ // Affiche un message d'erreur clair à l'utilisateur car l'app ne peut pas fonctionner
1602
+ 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 services nécessaires pour l\'application. Veuillez vérifier votre connexion internet et les scripts Firebase. Si le problème persiste, contactez le support.</p></div>';
1603
+ }
1604
+ }); // FIN DU Listener DOMContentLoaded - Important !
1605
+
1606
+ // FIN DU CODE JAVASCRIPT PRINCIPAL
1607
+ </script> <!-- FIN de la balise SCRIPT - Important ! -->
1608
+
1609
+ </body> <!-- FIN de la balise BODY - Important ! -->
1610
+ </html> <!-- FIN de la balise HTML - Important ! -->