Docfile commited on
Commit
3a25a69
·
verified ·
1 Parent(s): 3ff1b2b

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +356 -731
templates/index.html CHANGED
@@ -7,7 +7,7 @@
7
  <title>Mariam AI - Assistant Français Intelligent</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
11
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
12
  <style>
13
  @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');
@@ -20,7 +20,7 @@
20
  --accent: #6366f1;
21
  --success: #38a169;
22
  --danger: #e53e3e;
23
- --warning: #f97316; /* Ajout pour messages d'avertissement */
24
  --background: #f7fafc;
25
  --card-bg: rgba(255, 255, 255, 0.95);
26
  }
@@ -29,11 +29,13 @@
29
  font-family: 'Plus Jakarta Sans', sans-serif;
30
  background-color: #fafafa;
31
  scroll-behavior: smooth;
 
32
  }
33
 
34
  .glassmorphism {
35
  background: rgba(255, 255, 255, 0.95);
36
  backdrop-filter: blur(10px);
 
37
  border: 1px solid rgba(255, 255, 255, 0.3);
38
  box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
39
  }
@@ -50,29 +52,17 @@
50
  .gradient-text {
51
  background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
52
  -webkit-background-clip: text;
 
53
  -webkit-text-fill-color: transparent;
 
54
  }
55
 
56
  .loading-dot {
57
  animation: bounce 1.4s infinite;
58
  }
59
-
60
- .loading-dot:nth-child(2) {
61
- animation-delay: 0.2s;
62
- }
63
-
64
- .loading-dot:nth-child(3) {
65
- animation-delay: 0.4s;
66
- }
67
-
68
- @keyframes bounce {
69
- 0%, 80%, 100% {
70
- transform: translateY(0);
71
- }
72
- 40% {
73
- transform: translateY(-6px);
74
- }
75
- }
76
 
77
  .custom-radio:checked+span {
78
  background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
@@ -80,502 +70,187 @@
80
  border-color: var(--primary);
81
  }
82
 
83
- /* Styles pour les messages d'erreur/avertissement */
84
- .alert-message {
85
- display: flex;
86
- align-items: flex-start; /* Align items start for better text wrapping */
87
- /* space-x-3 equivalent using margin on icon */
88
- padding: 1rem; /* p-4 */
89
- border-radius: 0.5rem; /* rounded-lg */
90
- border-width: 1px;
91
- border-style: solid;
92
- }
93
- .alert-message i {
94
- margin-right: 0.75rem; /* space-x-3 equivalent */
95
- font-size: 1.25rem; /* text-xl */
96
- margin-top: 0.125rem; /* slight top margin for better alignment */
97
- }
98
- .alert-message div p:first-child {
99
- font-weight: 500; /* font-medium */
100
- }
101
- .alert-message div p:last-child {
102
- font-size: 0.875rem; /* text-sm */
103
- margin-top: 0.25rem; /* mt-1 */
104
- line-height: 1.4; /* Improved line height for wrap */
105
- }
106
-
107
- .alert-warning {
108
- color: #92400e; /* text-amber-700 */
109
- background-color: #fffbeb; /* bg-amber-50 */
110
- border-color: #fde68a /* border-amber-200 */
111
- }
112
- .alert-warning i {
113
- color: var(--warning); /* text-amber-500 */
114
- }
115
- .alert-warning div p:last-child {
116
- color: #b45309; /* text-amber-600 */
117
- }
118
-
119
- .alert-danger {
120
- color: #991b1b; /* text-red-700 */
121
- background-color: #fef2f2; /* bg-red-50 */
122
- border-color: #fecaca; /* border-red-200 */
123
- }
124
- .alert-danger i {
125
- color: var(--danger); /* text-red-500 */
126
- }
127
- .alert-danger div p:last-child {
128
- color: #b91c1c; /* text-red-600 */
129
- }
130
-
131
 
132
  /* Animations */
133
- @keyframes fadeIn {
134
- from { opacity: 0; transform: translateY(10px); }
135
- to { opacity: 1; transform: translateY(0); }
136
- }
137
-
138
- .fade-in {
139
- animation: fadeIn 0.3s ease-out forwards;
140
- }
141
-
142
- @keyframes scaleIn {
143
- from { transform: scale(0.95); opacity: 0; }
144
- to { transform: scale(1); opacity: 1; }
145
- }
146
-
147
- .scale-in {
148
- animation: scaleIn 0.3s ease-out forwards;
149
- }
150
-
151
- .backup-item {
152
- cursor: pointer;
153
- transition: all 0.25s ease;
154
- }
155
-
156
- .backup-item:hover {
157
- transform: translateY(-2px);
158
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05);
159
- }
160
-
161
- .backup-content {
162
- display: none;
163
- margin-top: 10px;
164
- max-height: 0;
165
- overflow: hidden;
166
- transition: max-height 0.3s ease-out, opacity 0.3s ease-out, margin-top 0.3s ease-out, padding-top 0.3s ease-out; /* Added padding-top */
167
- opacity: 0;
168
- padding-top: 0; /* Start with no padding */
169
- }
170
-
171
- .backup-content-expanded {
172
- display: block;
173
- max-height: 3000px; /* Augmenté pour les longs contenus */
174
- opacity: 1;
175
- margin-top: 1rem; /* mt-4 */
176
- padding-top: 0.75rem; /* pt-3 */
177
- }
178
-
179
-
180
- /* Styles for multiple image upload */
181
- .image-preview {
182
- display: flex;
183
- flex-wrap: wrap;
184
- gap: 12px;
185
- margin-top: 15px;
186
- }
187
-
188
- .image-preview-item {
189
- position: relative;
190
- width: 110px;
191
- height: 110px;
192
- border-radius: 8px;
193
- overflow: hidden;
194
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
195
- transition: transform 0.2s ease;
196
- }
197
-
198
- .image-preview-item:hover {
199
- transform: scale(1.05);
200
- }
201
-
202
- .image-preview-item img {
203
- width: 100%;
204
- height: 100%;
205
- object-fit: cover;
206
- }
207
-
208
- .remove-image {
209
- position: absolute;
210
- top: 5px;
211
- right: 5px;
212
- background-color: rgba(0, 0, 0, 0.6);
213
- color: white;
214
- border: none;
215
- border-radius: 50%;
216
- width: 22px;
217
- height: 22px;
218
- display: flex;
219
- align-items: center;
220
- justify-content: center;
221
- cursor: pointer;
222
- font-size: 12px;
223
- opacity: 0;
224
- transition: opacity 0.2s, transform 0.2s;
225
- }
226
-
227
- .image-preview-item:hover .remove-image {
228
- opacity: 1;
229
- }
230
-
231
- .remove-image:hover {
232
- background-color: rgba(0, 0, 0, 0.8);
233
- transform: scale(1.1);
234
- }
235
-
236
- /* Style pour préserver les espaces dans le markdown */
237
- .prose {
238
- white-space: pre-wrap;
239
- word-wrap: break-word;
240
- overflow-wrap: break-word;
241
- color: #2d3748;
242
- line-height: 1.7;
243
- }
244
-
245
- .prose h1, .prose h2, .prose h3, .prose h4 {
246
- color: var(--primary-dark);
247
- margin-top: 1.5em;
248
- margin-bottom: 0.5em;
249
- font-weight: 600;
250
- }
251
-
252
- .prose p {
253
- margin-bottom: 1em;
254
- }
255
-
256
- .prose ul, .prose ol {
257
- padding-left: 1.5em;
258
- margin-bottom: 1em;
259
- }
260
-
261
- .prose blockquote {
262
- border-left: 4px solid var(--primary);
263
- padding-left: 1em;
264
- font-style: italic;
265
- color: var(--secondary);
266
- margin: 1em 0;
267
- }
268
-
269
- /* Loading animation */
270
- .loader {
271
- display: inline-block;
272
- position: relative;
273
- width: 80px;
274
- height: 20px;
275
- }
276
-
277
- .loader div {
278
- position: absolute;
279
- top: 8px;
280
- width: 13px;
281
- height: 13px;
282
- border-radius: 50%;
283
- background: var(--primary);
284
- animation-timing-function: cubic-bezier(0, 1, 1, 0);
285
- }
286
-
287
- .loader div:nth-child(1) {
288
- left: 8px;
289
- animation: loader1 0.6s infinite;
290
- }
291
-
292
- .loader div:nth-child(2) {
293
- left: 8px;
294
- animation: loader2 0.6s infinite;
295
- }
296
-
297
- .loader div:nth-child(3) {
298
- left: 32px;
299
- animation: loader2 0.6s infinite;
300
- }
301
-
302
- .loader div:nth-child(4) {
303
- left: 56px;
304
- animation: loader3 0.6s infinite;
305
- }
306
-
307
- @keyframes loader1 {
308
- 0% {transform: scale(0);}
309
- 100% {transform: scale(1);}
310
- }
311
-
312
- @keyframes loader2 {
313
- 0% {transform: translate(0, 0);}
314
- 100% {transform: translate(24px, 0);}
315
- }
316
-
317
- @keyframes loader3 {
318
- 0% {transform: scale(1);}
319
- 100% {transform: scale(0);}
320
- }
321
-
322
- /* Focus styles */
323
- .focus-ring {
324
- position: relative;
325
- }
326
-
327
- .focus-ring:focus-within::after {
328
- content: '';
329
- position: absolute;
330
- top: -3px;
331
- left: -3px;
332
- right: -3px;
333
- bottom: -3px;
334
- border-radius: 14px;
335
- border: 2px solid rgba(59, 130, 246, 0.3);
336
- pointer-events: none;
337
- animation: focusIn 0.2s ease-out forwards;
338
- }
339
-
340
- @keyframes focusIn {
341
- from { opacity: 0; transform: scale(0.98); }
342
- to { opacity: 1; transform: scale(1); }
343
- }
344
-
345
- /* Style optionnel pour améliorer la lisibilité sur mobile */
346
- @media (max-width: 640px) {
347
- .prose {
348
- font-size: 0.95rem;
349
- line-height: 1.6;
350
- }
351
- .grid-cols-4 { /* Assure que les boutons radio s'affichent bien */
352
- grid-template-columns: repeat(2, minmax(0, 1fr));
353
- }
354
- }
355
-
356
- /* Style pour la checkbox DeepThink */
357
- .deepthink-label {
358
- display: flex;
359
- align-items: center;
360
- cursor: pointer;
361
- font-size: 0.875rem; /* text-sm */
362
- color: var(--secondary);
363
- }
364
- .deepthink-label input[type="checkbox"] {
365
- appearance: none;
366
- width: 1.25em;
367
- height: 1.25em;
368
- border: 2px solid #cbd5e0; /* border-gray-300 */
369
- border-radius: 0.375rem; /* rounded-md */
370
- margin-right: 0.5em;
371
- position: relative;
372
- top: 1px;
373
- transition: all 0.2s ease-in-out;
374
- cursor: pointer; /* Add cursor pointer */
375
- }
376
- .deepthink-label input[type="checkbox"]:checked {
377
- background-color: var(--primary);
378
- border-color: var(--primary);
379
- }
380
- .deepthink-label input[type="checkbox"]:checked::after {
381
- content: '\f00c'; /* FontAwesome check icon */
382
- font-family: 'Font Awesome 6 Free';
383
- font-weight: 900;
384
- color: white;
385
- font-size: 0.8em;
386
- position: absolute;
387
- top: 50%;
388
- left: 50%;
389
- transform: translate(-50%, -50%);
390
- }
391
- .deepthink-label input[type="checkbox"]:disabled {
392
- background-color: #e2e8f0; /* gray-200 */
393
- border-color: #cbd5e0; /* gray-300 */
394
- cursor: not-allowed;
395
- }
396
- .deepthink-label input[type="checkbox"]:disabled + span { /* Style text when disabled */
397
- color: #a0aec0; /* gray-400 */
398
- }
399
- .deepthink-label input[type="checkbox"]:disabled:checked::after {
400
- color: #a0aec0; /* gray-400 */
401
- }
402
-
403
- .deepthink-label:hover input[type="checkbox"]:not(:disabled) {
404
- border-color: var(--primary-dark);
405
- }
406
- .deepthink-tooltip {
407
- position: relative;
408
- display: inline-flex; /* Use inline-flex for alignment */
409
- align-items: center; /* Align icon vertically */
410
- margin-left: 6px;
411
- }
412
- .deepthink-tooltip .tooltip-text {
413
- visibility: hidden;
414
- width: 220px;
415
- background-color: #2d3748; /* gray-800 */
416
- color: #fff;
417
- text-align: center;
418
- border-radius: 6px;
419
- padding: 6px 10px;
420
- position: absolute;
421
- z-index: 10;
422
- bottom: 135%;
423
- left: 50%;
424
- margin-left: -110px;
425
- opacity: 0;
426
- transition: opacity 0.3s;
427
- font-size: 0.75rem; /* text-xs */
428
- line-height: 1.4;
429
- pointer-events: none; /* Prevent tooltip from interfering */
430
- }
431
- .deepthink-tooltip .tooltip-text::after {
432
- content: "";
433
- position: absolute;
434
- top: 100%;
435
- left: 50%;
436
- margin-left: -5px;
437
- border-width: 5px;
438
- border-style: solid;
439
- border-color: #2d3748 transparent transparent transparent;
440
- }
441
- .deepthink-tooltip:hover .tooltip-text {
442
- visibility: visible;
443
- opacity: 1;
444
- }
445
- /* MODIFICATION: Style pour le statut DeepThink */
446
- #deepthink-status {
447
- font-style: italic;
448
- margin-left: 0.5rem; /* ml-2 */
449
- }
450
 
451
  </style>
452
  </head>
453
 
454
- <body class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50">
455
  <nav class="glassmorphism sticky top-0 z-50 border-b border-blue-100">
456
- <div class="container mx-auto px-6 py-4">
457
  <div class="flex items-center justify-between">
458
- <div class="flex items-center space-x-4">
459
  <div class="bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg p-2 shadow-lg transform hover:scale-110 transition-transform duration-300">
460
- <i class="fas fa-robot text-white text-xl"></i>
461
  </div>
462
- <h1 class="text-2xl font-bold gradient-text">Mariam AI</h1>
463
  </div>
464
- <div class="text-blue-900 font-medium bg-blue-50 py-1 px-4 rounded-full shadow-sm">Assistant Français Intelligent</div>
 
465
  </div>
466
  </div>
467
  </nav>
468
 
469
- <main class="container mx-auto px-6 py-12">
470
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
471
  <!-- Section Travail Argumentatif -->
472
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in">
473
- <div class="p-8">
474
- <div class="flex items-center space-x-4 mb-8">
475
- <div class="bg-blue-100 rounded-lg p-3 transform rotate-3">
476
- <i class="fas fa-pen-fancy text-blue-600 text-xl"></i>
477
  </div>
478
- <h2 class="text-2xl font-bold text-gray-800">Travail Argumentatif</h2>
479
  </div>
480
- <form id="francais-form" class="space-y-8">
481
- <div class="space-y-3">
482
  <label for="sujet-francais" class="block text-sm font-medium text-gray-700 flex items-center">
483
  <i class="fas fa-book-open mr-2 text-blue-500"></i>Sujet <span class="text-red-500 ml-1">*</span>
484
  </label>
485
  <div class="focus-ring">
486
  <textarea id="sujet-francais" name="sujet" rows="4"
487
- class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-blue-500 focus:outline-none transition-all duration-200 resize-none"
488
  placeholder="Entrez votre sujet ici..." required></textarea>
489
  </div>
490
  <div class="text-xs text-gray-400 text-right" id="character-count">0 caractères</div>
491
  </div>
492
 
493
- <div class="space-y-4">
494
  <label class="block text-sm font-medium text-gray-700 flex items-center">
495
  <i class="fas fa-tasks mr-2 text-blue-500"></i>Type d'argumentation
496
  </label>
497
- <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
 
498
  <label class="relative">
499
- <input type="radio" name="choix" value="Etaye"
500
- class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
501
- <span
502
- class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
503
- Étayer
504
- </span>
505
  </label>
506
  <label class="relative">
507
- <input type="radio" name="choix" value="refute"
508
- class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
509
- <span
510
- class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
511
- Réfuter
512
- </span>
513
  </label>
514
  <label class="relative">
515
- <input type="radio" name="choix" value="discuter"
516
- class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
517
- <span
518
- class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
519
- Discuter
520
- </span>
521
  </label>
522
  <label class="relative">
523
- <input type="radio" name="choix" value="dissertation"
524
- class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
525
- <span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
526
- Dissertation
527
- </span>
528
  </label>
529
  </div>
530
  </div>
531
 
532
- <div class="space-y-4">
533
  <label class="block text-sm font-medium text-gray-700 flex items-center">
534
  <i class="fas fa-feather-alt mr-2 text-blue-500"></i>Style d'écriture
535
  </label>
536
- <div class="grid grid-cols-2 gap-4">
537
  <label class="relative">
538
- <input type="radio" name="style" value="raffiné"
539
- class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
540
- <span
541
- class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
542
- Raffiné
543
- </span>
544
  </label>
545
  <label class="relative">
546
- <input type="radio" name="style" value="Normal"
547
- class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
548
- <span
549
- class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
550
- Normal
551
- </span>
552
  </label>
553
  </div>
554
  </div>
555
 
556
- <!-- MODIFICATION: Ajout span pour statut -->
557
- <div class="flex items-center justify-start pt-2">
558
  <label for="deepthink-checkbox" class="deepthink-label">
559
  <input type="checkbox" id="deepthink-checkbox" name="use_deepthink_visual">
560
- <span>Utiliser DeepThink</span> <!-- Mettre le texte dans un span pour le style :disabled -->
561
  <div class="deepthink-tooltip">
562
- <i class="fas fa-info-circle text-gray-400"></i>
563
- <span class="tooltip-text">Utilise un modèle plus avancé pour une meilleure qualité, mais peut être plus lent. Limité à 1 utilisation par jour.</span>
564
  </div>
565
  </label>
566
- <span id="deepthink-status" class="text-xs text-gray-500"></span> <!-- Indicateur de statut -->
567
  </div>
568
- <!-- Fin de la modification -->
569
 
570
  <button type="submit"
571
- class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-6 py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg">
572
- <div class="flex items-center justify-center space-x-3">
573
  <i class="fas fa-magic"></i>
574
  <span>Générer</span>
575
  </div>
576
  </button>
577
  </form>
578
- <div id="francais-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[100px]">
 
579
  <!-- Le contenu généré sera inséré ici -->
580
  </div>
581
  </div>
@@ -583,50 +258,52 @@
583
 
584
  <!-- Section Étude de texte -->
585
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in" style="animation-delay: 0.1s;">
586
- <div class="p-8">
587
- <div class="flex items-center space-x-4 mb-8">
588
- <div class="bg-blue-100 rounded-lg p-3 transform -rotate-3">
589
- <i class="fas fa-file-alt text-blue-600 text-xl"></i>
590
  </div>
591
- <h2 class="text-2xl font-bold text-gray-800">Étude de texte</h2>
592
  </div>
593
- <form id="etude-texte-form" class="space-y-8" enctype="multipart/form-data">
594
- <div class="space-y-4">
595
  <label class="block text-sm font-medium text-gray-700 flex items-center">
596
  <i class="fas fa-image mr-2 text-blue-500"></i>Image(s) du texte <span class="text-red-500 ml-1">*</span>
597
  </label>
598
- <div class="border-3 border-dashed border-gray-300 rounded-xl p-8 text-center cursor-pointer hover:border-blue-400 transition-all duration-200 group"
599
  id="drop-zone">
600
  <input type="file" id="image-upload" name="images" accept="image/*" class="hidden" multiple>
601
- <div class="space-y-4">
602
- <div class="bg-blue-50 rounded-full w-16 h-16 flex items-center justify-center mx-auto transform transition-transform group-hover:scale-110 group-hover:rotate-6">
603
- <i class="fas fa-cloud-upload-alt text-3xl text-blue-500"></i>
604
  </div>
605
- <p class="text-sm text-gray-600 font-medium">Glissez vos images ici ou cliquez
606
- pour sélectionner</p>
607
- <p class="text-xs text-gray-400">PNG, JPG, WEBP jusqu'à 10MB</p>
608
  </div>
609
  </div>
610
  <div id="image-preview" class="image-preview"></div>
611
  </div>
612
 
613
  <button type="submit"
614
- class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-6 py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg">
615
- <div class="flex items-center justify-center space-x-3">
616
  <i class="fas fa-search"></i>
617
  <span>Analyser</span>
618
  </div>
619
  </button>
620
  </form>
621
- <div id="etude-texte-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[100px]">
 
622
  <!-- Le contenu analysé sera inséré ici -->
623
  </div>
624
  </div>
625
  </div>
626
  </div>
627
- <div class="mt-12">
628
- <h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center">
629
- <i class="fas fa-save mr-3 text-blue-500"></i>
 
 
630
  Sauvegardes
631
  </h2>
632
  <div id="backups-list" class="space-y-4">
@@ -635,23 +312,21 @@
635
  </div>
636
  </main>
637
 
638
- <footer class="bg-gray-50 border-t border-gray-200 py-6 mt-16">
639
- <div class="container mx-auto px-6">
640
- <div class="flex flex-col md:flex-row justify-between items-center">
641
- <div class="flex items-center space-x-2 mb-4 md:mb-0">
642
  <div class="bg-blue-600 rounded-lg p-1 shadow">
643
  <i class="fas fa-robot text-white text-sm"></i>
644
  </div>
645
  <span class="text-gray-600 text-sm">Mariam AI</span>
646
  </div>
647
- <div class="flex space-x-6">
648
- <a href="#" class="text-gray-500 hover:text-blue-600 transition-colors">
649
- <i class="fas fa-question-circle"></i>
650
- <span class="ml-1 text-sm">Aide</span>
651
  </a>
652
- <a href="#" class="text-gray-500 hover:text-blue-600 transition-colors">
653
- <i class="fas fa-shield-alt"></i>
654
- <span class="ml-1 text-sm">Confidentialité</span>
655
  </a>
656
  </div>
657
  </div>
@@ -660,80 +335,50 @@
660
 
661
  <script>
662
  // --- Constantes ---
663
- const DEEPTHINK_STORAGE_KEY = 'deepThinkLastUsedDate'; // Clé localStorage
664
 
665
  // --- Gestionnaire de fichiers ---
666
  const uploadedFiles = new Map();
667
- // ... (initializeFileUpload, highlightDropZone, unhighlightDropZone, handleFiles, renderPreview, removeFile restent identiques) ...
668
  function initializeFileUpload() {
669
  const dropZone = document.getElementById('drop-zone');
670
  const fileInput = document.getElementById('image-upload');
671
  const imagePreview = document.getElementById('image-preview');
 
672
 
673
  dropZone.addEventListener('click', () => fileInput.click());
 
 
 
 
674
 
675
- ['dragenter', 'dragover'].forEach(eventName => {
676
- dropZone.addEventListener(eventName, highlightDropZone);
677
- });
678
-
679
- ['dragleave', 'drop'].forEach(eventName => {
680
- dropZone.addEventListener(eventName, unhighlightDropZone);
681
- });
682
-
683
- dropZone.addEventListener('drop', (e) => {
684
- e.preventDefault();
685
- const files = e.dataTransfer.files;
686
- handleFiles(files);
687
- });
688
-
689
- fileInput.addEventListener('change', () => {
690
- const files = fileInput.files;
691
- handleFiles(files);
692
- fileInput.value = '';
693
- });
694
-
695
- function highlightDropZone(e) {
696
- e.preventDefault();
697
- dropZone.classList.add('border-blue-500', 'bg-blue-50');
698
- }
699
-
700
- function unhighlightDropZone(e) {
701
- e.preventDefault();
702
- dropZone.classList.remove('border-blue-500', 'bg-blue-50');
703
- }
704
 
705
  function handleFiles(files) {
706
- const currentFiles = fileInput.files;
707
  const dataTransfer = new DataTransfer();
708
- uploadedFiles.forEach(file => dataTransfer.items.add(file));
709
- for (let i = 0; i < files.length; i++) {
710
- const file = files[i];
711
- if (!file.type.startsWith('image/')) continue;
712
  const fileId = `${file.name}-${file.size}`;
713
- if (uploadedFiles.has(fileId)) continue;
714
  uploadedFiles.set(fileId, file);
715
  dataTransfer.items.add(file);
716
  renderPreview(file, fileId);
717
- }
718
- fileInput.files = dataTransfer.files;
719
  }
720
 
721
  function renderPreview(file, fileId) {
722
  const reader = new FileReader();
723
- reader.onload = (e) => {
724
- const previewItem = document.createElement('div');
725
- previewItem.classList.add('image-preview-item', 'scale-in');
726
- previewItem.dataset.fileId = fileId;
727
- previewItem.innerHTML = `
728
- <img src="${e.target.result}" alt="${file.name}">
729
- <button class="remove-image" title="Supprimer"><i class="fas fa-times"></i></button>
730
- `;
731
- imagePreview.appendChild(previewItem);
732
- previewItem.querySelector('.remove-image').addEventListener('click', () => {
733
- removeFile(fileId, previewItem);
734
- });
735
- };
736
- reader.readAsDataURL(file);
737
  }
738
 
739
  function removeFile(fileId, previewElement) {
@@ -743,166 +388,151 @@
743
  fileInput.files = dataTransfer.files;
744
  previewElement.style.opacity = '0';
745
  previewElement.style.transform = 'scale(0.9)';
746
- setTimeout(() => {
747
- if (imagePreview.contains(previewElement)) {
748
- imagePreview.removeChild(previewElement);
749
- }
750
- }, 300);
751
  }
752
  }
753
 
754
-
755
  // --- Gestion des sauvegardes ---
756
- // ... (sauvegarderReponse, displayNotification, supprimerSauvegarde, afficherSauvegardes, closeModal restent identiques) ...
757
- function sauvegarderReponse(titre, contenu) {
758
  const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
759
  const dateSauvegarde = new Date().toISOString();
760
  const MAX_SAUVEGARDES = 20;
761
- if (sauvegardes.length >= MAX_SAUVEGARDES) {
762
- sauvegardes.shift();
763
- }
764
- sauvegardes.push({
765
- titre: titre || "Sauvegarde sans titre",
766
- contenu,
767
- date: dateSauvegarde
768
- });
769
  try {
770
  localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
 
 
771
  } catch (e) {
772
- console.error("Erreur lors de la sauvegarde dans localStorage (peut-être plein):", e);
773
- displayNotification("Erreur de sauvegarde: Espace de stockage local plein.", "error");
774
- return;
775
  }
776
- displayNotification("Sauvegardé avec succès", "success");
777
- afficherSauvegardes();
778
  }
779
 
780
  function displayNotification(message, type = 'success') {
781
  const notification = document.createElement('div');
782
- const bgColor = type === 'success' ? 'bg-green-50 border-green-200 text-green-700' : type === 'error' ? 'bg-red-50 border-red-200 text-red-700' : 'bg-yellow-50 border-yellow-200 text-yellow-700';
783
- const iconClass = type === 'success' ? 'fa-check-circle text-green-500' : type === 'error' ? 'fa-exclamation-circle text-red-500' : 'fa-exclamation-triangle text-yellow-500';
784
-
785
-
786
- notification.className = `fixed bottom-6 right-6 border ${bgColor} px-4 py-3 rounded-lg shadow-lg z-[100] flex items-center scale-in max-w-sm`; // z-index élevé, max-width
787
- notification.innerHTML = `
788
- <i class="fas ${iconClass} mr-3 text-lg flex-shrink-0"></i>
789
- <span class="text-sm font-medium">${message}</span>
790
- `;
791
- document.body.appendChild(notification);
792
-
793
  setTimeout(() => {
794
- notification.style.opacity = '0';
795
- notification.style.transform = 'translateY(10px)';
796
- setTimeout(() => {
797
- if (document.body.contains(notification)) {
798
- document.body.removeChild(notification);
799
- }
800
- }, 300);
801
- }, type === 'error' ? 6000 : 3000); // Longer display for errors
802
- }
803
-
804
-
805
- function supprimerSauvegarde(index) {
806
- let sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
807
- sauvegardes.splice(index, 1);
808
- localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
809
- afficherSauvegardes();
 
 
 
 
810
  }
811
 
812
-
813
  function afficherSauvegardes() {
814
- const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
815
- sauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
816
  const backupsList = document.getElementById('backups-list');
 
 
 
 
 
 
 
817
  backupsList.innerHTML = '';
818
 
819
  if (sauvegardes.length === 0) {
820
- backupsList.innerHTML = `
821
- <div class="bg-blue-50 rounded-lg p-8 text-center">
822
- <div class="bg-white rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4 shadow-sm">
823
- <i class="fas fa-folder-open text-blue-300 text-2xl"></i>
824
- </div>
825
- <p class="text-gray-600">Aucune sauvegarde pour le moment.</p>
826
- <p class="text-sm text-gray-500 mt-2">Vos réponses générées apparaîtront ici.</p>
827
- </div>
828
- `;
829
  return;
830
  }
831
 
832
  sauvegardes.forEach((sauvegarde, index) => {
833
  const date = new Date(sauvegarde.date);
834
  const formattedDate = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
835
- const displayTitle = sauvegarde.titre.length > 60 ? sauvegarde.titre.substring(0, 57) + '...' : sauvegarde.titre;
836
 
837
  const sauvegardeDiv = document.createElement('div');
838
- sauvegardeDiv.dataset.index = index;
839
  sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
840
  sauvegardeDiv.innerHTML = `
841
  <div class="flex items-start cursor-pointer item-header">
842
- <div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0"> <i class="fas fa-file-alt text-blue-600"></i> </div>
843
- <div class="flex-grow overflow-hidden">
844
- <h3 class="text-lg font-semibold text-gray-800 truncate" title="${sauvegarde.titre}">${displayTitle}</h3>
845
- <p class="text-sm text-gray-600 mb-2 flex items-center"> <i class="fas fa-clock text-xs mr-1 text-gray-400"></i> ${formattedDate} </p>
846
  </div>
847
- <div class="flex space-x-2 ml-2 flex-shrink-0">
848
- <button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1" title="Copier"> <i class="fas fa-copy"></i> </button>
849
- <button class="text-gray-400 hover:text-red-600 focus:outline-none delete-btn p-1" title="Supprimer"> <i class="fas fa-trash-alt"></i> </button>
850
  </div>
851
  </div>
852
- <div class="backup-content mt-4 text-sm text-gray-700 prose max-w-none border-t border-gray-100 pt-3"></div>`;
853
  backupsList.appendChild(sauvegardeDiv);
854
 
855
  const backupContentDiv = sauvegardeDiv.querySelector('.backup-content');
856
  const headerDiv = sauvegardeDiv.querySelector('.item-header');
857
 
858
- headerDiv.addEventListener('click', (e) => {
859
  const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
860
  document.querySelectorAll('.backup-content-expanded').forEach(content => {
861
- if (content !== backupContentDiv) {
862
- content.classList.remove('backup-content-expanded'); content.innerHTML = '';
863
- }
864
  });
865
  if (!isExpanded) {
866
- backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu);
867
  backupContentDiv.classList.add('backup-content-expanded');
868
- setTimeout(() => { MathJax.typesetPromise([backupContentDiv]).catch(err => console.error('MathJax error:', err)); }, 50);
869
  } else {
870
  backupContentDiv.classList.remove('backup-content-expanded'); backupContentDiv.innerHTML = '';
871
  }
872
  });
873
 
874
- const copyButton = sauvegardeDiv.querySelector('.copy-btn');
875
- copyButton.addEventListener('click', (e) => {
876
  e.stopPropagation();
877
- navigator.clipboard.writeText(sauvegarde.contenu).then(() => {
878
- const icon = copyButton.querySelector('i'); icon.className = 'fas fa-check text-green-500'; setTimeout(() => { icon.className = 'fas fa-copy'; }, 2000);
879
  }).catch(err => { console.error('Copy error:', err); displayNotification("Erreur copie", "error"); });
880
  });
881
 
882
- const deleteButton = sauvegardeDiv.querySelector('.delete-btn');
883
- deleteButton.addEventListener('click', (e) => {
884
  e.stopPropagation();
885
- const itemIndex = parseInt(sauvegardeDiv.dataset.index, 10);
886
- const storedSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
887
- storedSauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
888
- const itemToDelete = storedSauvegardes[itemIndex];
889
- const originalSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
890
- const originalIndex = originalSauvegardes.findIndex(s => s.date === itemToDelete?.date && s.titre === itemToDelete?.titre);
 
 
891
 
892
- if (originalIndex === -1) { console.error("Item not found for deletion."); displayNotification("Erreur suppression", "error"); return; }
 
893
 
894
  const confirmationModal = document.createElement('div');
895
- confirmationModal.className = 'fixed inset-0 flex items-center justify-center z-[100] bg-black bg-opacity-30 scale-in';
896
  confirmationModal.innerHTML = `
897
- <div class="bg-white rounded-lg p-6 max-w-sm mx-4 shadow-xl">
898
- <div class="flex items-center mb-4"> <div class="bg-red-100 rounded-full p-2 mr-3"><i class="fas fa-exclamation-triangle text-red-500"></i></div> <h3 class="text-lg font-semibold text-gray-800">Confirmation</h3> </div>
899
  <p class="text-gray-600 mb-6 text-sm">Supprimer cette sauvegarde ?</p>
900
  <div class="flex justify-end space-x-3"> <button class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 cancel-btn text-sm">Annuler</button> <button class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 confirm-btn text-sm">Supprimer</button> </div>
901
  </div>`;
902
  document.body.appendChild(confirmationModal);
903
  confirmationModal.querySelector('.cancel-btn').addEventListener('click', () => closeModal(confirmationModal));
904
  confirmationModal.querySelector('.confirm-btn').addEventListener('click', () => { supprimerSauvegarde(originalIndex); closeModal(confirmationModal); });
905
- confirmationModal.addEventListener('click', (event) => { if (event.target === confirmationModal) closeModal(confirmationModal); });
906
  });
907
  });
908
  }
@@ -915,26 +545,23 @@
915
 
916
 
917
  // --- Gestion DeepThink ---
918
- /**
919
- * Vérifie si DeepThink a déjà été utilisé aujourd'hui et met à jour l'UI.
920
- */
921
  function checkDeepThinkAvailability() {
922
  const checkbox = document.getElementById('deepthink-checkbox');
923
  const statusSpan = document.getElementById('deepthink-status');
924
- if (!checkbox || !statusSpan) return; // Safety check
925
-
926
- const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
927
- const todayStr = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
928
-
929
- if (lastUsedDateStr === todayStr) {
930
- checkbox.checked = false; // Ensure it's unchecked if disabled
931
- checkbox.disabled = true;
932
- statusSpan.textContent = "(déjà utilisé aujourd'hui)";
933
- statusSpan.classList.add('text-red-500'); // Make status noticeable
934
- } else {
935
- checkbox.disabled = false;
936
- statusSpan.textContent = ""; // Clear status
937
- statusSpan.classList.remove('text-red-500');
938
  }
939
  }
940
 
@@ -944,79 +571,73 @@
944
  const output = document.getElementById('francais-output');
945
  const sujetTextarea = document.getElementById('sujet-francais');
946
  const deepThinkCheckbox = document.getElementById('deepthink-checkbox');
 
 
 
947
 
948
  form.addEventListener('submit', async (e) => {
949
  e.preventDefault();
 
 
 
950
 
951
  const sujetValue = sujetTextarea.value.trim();
952
- const isDeepThinkChecked = deepThinkCheckbox.checked; // Store initial checked state
953
 
954
- // --- Validation avant envoi ---
955
  if (!sujetValue) {
956
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Champ obligatoire</p><p>Veuillez entrer un sujet.</p></div></div>`;
957
- sujetTextarea.focus();
958
- sujetTextarea.classList.add('border-red-500');
959
- setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
960
  return;
961
  }
962
-
963
- // MODIFICATION: Vérifier la limite DeepThink avant l'envoi si coché
964
  if (isDeepThinkChecked) {
965
- const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
966
- const todayStr = new Date().toISOString().split('T')[0];
967
- if (lastUsedDateStr === todayStr) {
968
- output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Limite atteinte</p><p>Vous avez déjà utilisé DeepThink aujourd'hui.</p></div></div>`;
969
- checkDeepThinkAvailability(); // Re-disable checkbox just in case
970
- return; // Stop submission
971
- }
 
 
 
972
  }
973
- // --- Fin Validation ---
974
 
 
 
975
 
976
- // --- Affichage chargement ---
977
- output.innerHTML = `
978
- <div class="flex flex-col items-center justify-center py-8">
979
- <div class="loader mb-4"><div></div><div></div><div></div><div></div></div>
980
- <p class="text-sm font-medium text-gray-600">Génération en cours...</p>
981
- ${isDeepThinkChecked ? '<p class="text-xs text-blue-500 mt-1">Mode DeepThink activé</p>' : ''}
982
- </div>`;
983
 
984
- // --- Préparation et Envoi ---
985
  const formData = new FormData(form);
986
- formData.append('use_deepthink', isDeepThinkChecked); // Use stored checked state
987
 
988
  try {
989
  const response = await fetch('/api/francais', { method: 'POST', body: formData });
990
  const result = await response.json();
991
-
992
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
993
 
994
- // --- Affichage Succès ---
995
- output.style.opacity = '0';
996
- output.style.transition = '';
997
- output.innerHTML = marked.parse(result.output);
998
- requestAnimationFrame(() => {
999
- output.style.transition = 'opacity 0.3s ease-in-out';
1000
- output.style.opacity = '1';
1001
- });
1002
 
1003
- // --- Sauvegarde & Mise à jour Limite DeepThink ---
1004
- const titreSauvegarde = sujetValue.length > 100 ? sujetValue.substring(0, 97) + '...' : sujetValue;
1005
  const deepThinkSuffix = isDeepThinkChecked ? ' [DeepThink]' : '';
1006
  sauvegarderReponse(titreSauvegarde + deepThinkSuffix, result.output);
1007
 
1008
- // MODIFICATION: Si DeepThink a été utilisé avec succès, enregistrer la date et mettre à jour l'UI
1009
  if (isDeepThinkChecked) {
1010
- const todayStr = new Date().toISOString().split('T')[0];
1011
- localStorage.setItem(DEEPTHINK_STORAGE_KEY, todayStr);
1012
- checkDeepThinkAvailability(); // Met à jour l'état de la checkbox et le message
 
 
1013
  }
 
1014
 
1015
- MathJax.typesetPromise([output]).catch(err => console.error('MathJax error:', err));
1016
-
1017
- } catch (error) { // --- Gestion Erreur ---
1018
  console.error("Erreur soumission (français):", error);
1019
- output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console pour détails."}</p></div></div>`;
 
 
 
1020
  }
1021
  });
1022
  }
@@ -1025,72 +646,75 @@
1025
  const form = document.getElementById('etude-texte-form');
1026
  const output = document.getElementById('etude-texte-output');
1027
  const fileInput = document.getElementById('image-upload');
 
 
 
1028
 
1029
  form.addEventListener('submit', async (e) => {
1030
  e.preventDefault();
 
 
 
 
 
1031
  if (uploadedFiles.size === 0) {
1032
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Aucune image</p><p>Ajoutez au moins une image.</p></div></div>`;
 
1033
  return;
1034
  }
1035
- output.innerHTML = `
1036
- <div class="flex flex-col items-center justify-center py-8">
1037
- <div class="loader mb-4"><div></div><div></div><div></div><div></div></div>
1038
- <p class="text-sm font-medium text-gray-600">Analyse en cours...</p>
1039
- <p class="text-xs text-gray-400 mt-2">Cela peut prendre un moment</p>
1040
- </div>`;
1041
  const formData = new FormData();
1042
  uploadedFiles.forEach((file) => formData.append('images', file, file.name));
1043
  try {
1044
  const response = await fetch('/api/etude-texte', { method: 'POST', body: formData });
1045
  const result = await response.json();
1046
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
1047
- output.style.opacity = '0';
1048
- output.style.transition = '';
1049
- output.innerHTML = marked.parse(result.output);
1050
- requestAnimationFrame(() => { output.style.transition = 'opacity 0.3s ease-in-out'; output.style.opacity = '1'; });
1051
  const titre = `Analyse image(s) - ${new Date().toLocaleDateString('fr-FR')}`;
1052
  sauvegarderReponse(titre, result.output);
1053
- MathJax.typesetPromise([output]).catch(err => console.error('MathJax error:', err));
1054
  } catch (error) {
1055
  console.error("Erreur soumission (étude texte):", error);
1056
- output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console pour détails."}</p></div></div>`;
 
 
 
1057
  }
1058
  });
1059
  }
1060
 
1061
  // --- Animations & Améliorations UI ---
1062
- // ... (animateOnScroll, enhanceTextareaFocus, enhanceRadioButtons restent identiques) ...
1063
  function animateOnScroll() {
1064
  const cards = document.querySelectorAll('.card-hover');
1065
  if (!('IntersectionObserver' in window)) { cards.forEach(card => card.style.opacity = '1'); return; }
1066
  const observer = new IntersectionObserver((entries) => {
1067
  entries.forEach((entry) => {
1068
- if (entry.isIntersecting) {
1069
- requestAnimationFrame(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; });
1070
- observer.unobserve(entry.target);
1071
- }
1072
  });
1073
- }, { threshold: 0.1 });
1074
- cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(30px)'; observer.observe(card); });
1075
  }
1076
  function enhanceTextareaFocus() {
1077
  const textarea = document.getElementById('sujet-francais');
1078
  const counter = document.getElementById('character-count');
1079
- textarea.addEventListener('input', () => {
1080
- const length = textarea.value.length; counter.textContent = `${length} caractère${length !== 1 ? 's' : ''}`; textarea.classList.remove('border-red-500');
1081
- });
1082
  }
1083
  function enhanceRadioButtons() {
1084
- const radioGroups = document.querySelectorAll('input[type="radio"] + span');
1085
- radioGroups.forEach(label => {
1086
  const input = label.previousElementSibling; if (input.disabled) return;
1087
- label.addEventListener('mouseenter', () => { if (!input.checked) label.classList.add('border-blue-400', 'shadow-md', 'transform' ,'-translate-y-0.5'); });
1088
- label.addEventListener('mouseleave', () => { if (!input.checked) label.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-0.5'); });
1089
  input.addEventListener('change', () => {
1090
  const groupName = input.name;
1091
  document.querySelectorAll(`input[name="${groupName}"] + span`).forEach(otherLabel => {
1092
- if (otherLabel !== label) otherLabel.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-0.5', 'border-primary');
1093
- else label.classList.remove('shadow-md', 'transform', '-translate-y-0.5');
1094
  });
1095
  });
1096
  });
@@ -1105,27 +729,28 @@
1105
  animateOnScroll();
1106
  enhanceTextareaFocus();
1107
  enhanceRadioButtons();
1108
- checkDeepThinkAvailability(); // MODIFICATION: Vérifier la limite au chargement
1109
  afficherSauvegardes();
1110
-
1111
- // MathJax Config
1112
- window.MathJax = { tex: { inlineMath: [['$', '$'], ['\\(', '\\)']], displayMath: [['$$', '$$'], ['\\[', '\\]']] }, svg: { fontCache: 'global' }, options: { skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'], ignoreHtmlClass: 'tex2jax_ignore', processHtmlClass: 'tex2jax_process' } };
1113
 
1114
  // Welcome Message (session based)
1115
- const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
1116
- if (showWelcome) {
1117
- const welcomeMessageContainer = document.createElement('div');
1118
- welcomeMessageContainer.innerHTML = `<div class="fixed bottom-6 right-6 bg-white rounded-xl shadow-lg p-5 transform transition-all duration-500 opacity-0 translate-y-4 max-w-xs z-[100]"><div class="flex items-start space-x-4"><div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-3 mt-1 flex-shrink-0"> <i class="fas fa-robot text-white text-xl"></i> </div><div> <p class="text-sm font-semibold text-gray-800">Bienvenue sur Mariam AI !</p> <p class="text-xs text-gray-500 mt-1">Assistant prêt. Posez un sujet ou analysez un texte.</p> </div></div><button class="absolute top-2 right-2 text-gray-400 hover:text-gray-600 focus:outline-none close-welcome p-1"> <i class="fas fa-times text-xs"></i> </button></div>`;
1119
- document.body.appendChild(welcomeMessageContainer);
1120
- const welcomeMessage = welcomeMessageContainer.firstElementChild;
1121
- setTimeout(() => { welcomeMessage.classList.remove('opacity-0', 'translate-y-4'); }, 500);
1122
- welcomeMessage.querySelector('.close-welcome').addEventListener('click', () => closeWelcomeMessage(welcomeMessage));
1123
- setTimeout(() => { if (document.body.contains(welcomeMessage)) closeWelcomeMessage(welcomeMessage); }, 7000);
1124
- sessionStorage.setItem('welcomeShown', 'true');
1125
- }
 
 
 
1126
  function closeWelcomeMessage(element) {
1127
- element.classList.add('opacity-0', 'translate-y-4');
1128
- setTimeout(() => { if (element.parentElement) element.parentElement.remove(); }, 500);
1129
  }
1130
  });
1131
  </script>
 
7
  <title>Mariam AI - Assistant Français Intelligent</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
10
+ <!-- MathJax retiré -->
11
  <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
12
  <style>
13
  @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');
 
20
  --accent: #6366f1;
21
  --success: #38a169;
22
  --danger: #e53e3e;
23
+ --warning: #f97316;
24
  --background: #f7fafc;
25
  --card-bg: rgba(255, 255, 255, 0.95);
26
  }
 
29
  font-family: 'Plus Jakarta Sans', sans-serif;
30
  background-color: #fafafa;
31
  scroll-behavior: smooth;
32
+ -webkit-tap-highlight-color: transparent; /* Remove blue tap highlight on mobile */
33
  }
34
 
35
  .glassmorphism {
36
  background: rgba(255, 255, 255, 0.95);
37
  backdrop-filter: blur(10px);
38
+ -webkit-backdrop-filter: blur(10px); /* Safari */
39
  border: 1px solid rgba(255, 255, 255, 0.3);
40
  box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
41
  }
 
52
  .gradient-text {
53
  background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
54
  -webkit-background-clip: text;
55
+ background-clip: text;
56
  -webkit-text-fill-color: transparent;
57
+ text-fill-color: transparent; /* Standard property */
58
  }
59
 
60
  .loading-dot {
61
  animation: bounce 1.4s infinite;
62
  }
63
+ .loading-dot:nth-child(2) { animation-delay: 0.2s; }
64
+ .loading-dot:nth-child(3) { animation-delay: 0.4s; }
65
+ @keyframes bounce { 0%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-6px); } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
  .custom-radio:checked+span {
68
  background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
 
70
  border-color: var(--primary);
71
  }
72
 
73
+ /* Styles pour les messages d'alerte */
74
+ .alert-message { display: flex; align-items: flex-start; padding: 1rem; border-radius: 0.5rem; border-width: 1px; border-style: solid; }
75
+ .alert-message i { margin-right: 0.75rem; font-size: 1.25rem; margin-top: 0.125rem; flex-shrink: 0; }
76
+ .alert-message div p:first-child { font-weight: 500; }
77
+ .alert-message div p:last-child { font-size: 0.875rem; margin-top: 0.25rem; line-height: 1.4; }
78
+ .alert-warning { color: #92400e; background-color: #fffbeb; border-color: #fde68a; }
79
+ .alert-warning i { color: var(--warning); }
80
+ .alert-warning div p:last-child { color: #b45309; }
81
+ .alert-danger { color: #991b1b; background-color: #fef2f2; border-color: #fecaca; }
82
+ .alert-danger i { color: var(--danger); }
83
+ .alert-danger div p:last-child { color: #b91c1c; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  /* Animations */
86
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
87
+ .fade-in { animation: fadeIn 0.3s ease-out forwards; }
88
+ @keyframes scaleIn { from { transform: scale(0.95); opacity: 0; } to { transform: scale(1); opacity: 1; } }
89
+ .scale-in { animation: scaleIn 0.3s ease-out forwards; }
90
+
91
+ /* Sauvegardes */
92
+ .backup-item { cursor: pointer; transition: all 0.25s ease; }
93
+ .backup-item:hover { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05); }
94
+ .backup-content { display: none; margin-top: 10px; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out, opacity 0.3s ease-out, margin-top 0.3s ease-out, padding-top 0.3s ease-out; opacity: 0; padding-top: 0; }
95
+ .backup-content-expanded { display: block; max-height: 3000px; opacity: 1; margin-top: 1rem; padding-top: 0.75rem; }
96
+
97
+ /* Upload Images */
98
+ .image-preview { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 15px; }
99
+ .image-preview-item { position: relative; width: 100px; height: 100px; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; }
100
+ .image-preview-item:hover { transform: scale(1.05); }
101
+ .image-preview-item img { width: 100%; height: 100%; object-fit: cover; }
102
+ .remove-image { position: absolute; top: 4px; right: 4px; background-color: rgba(0, 0, 0, 0.6); color: white; border: none; border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 11px; opacity: 0; transition: opacity 0.2s, transform 0.2s; }
103
+ .image-preview-item:hover .remove-image { opacity: 1; }
104
+ .remove-image:hover { background-color: rgba(0, 0, 0, 0.8); transform: scale(1.1); }
105
+
106
+ /* Style Prose (Zone de sortie) */
107
+ .prose { white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; color: #2d3748; line-height: 1.7; font-size: 0.95rem; /* légèrement plus petit par défaut */ }
108
+ @media (min-width: 640px) { .prose { font-size: 1rem; } } /* Taille normale sur sm+ */
109
+ .prose h1, .prose h2, .prose h3, .prose h4 { color: var(--primary-dark); margin-top: 1.5em; margin-bottom: 0.5em; font-weight: 600; }
110
+ .prose p { margin-bottom: 1em; }
111
+ .prose ul, .prose ol { padding-left: 1.5em; margin-bottom: 1em; }
112
+ .prose blockquote { border-left: 4px solid var(--primary); padding-left: 1em; font-style: italic; color: var(--secondary); margin: 1em 0; }
113
+ .prose code { background-color: #edf2f7; /* gray-200 */ padding: 0.2em 0.4em; border-radius: 0.25rem; font-size: 0.9em; }
114
+ .prose pre { background-color: #1a202c; /* gray-900 */ color: #e2e8f0; /* gray-200 */ padding: 1em; border-radius: 0.375rem; overflow-x: auto; }
115
+ .prose pre code { background-color: transparent; padding: 0; border-radius: 0; font-size: 0.875em; }
116
+
117
+ /* Loader */
118
+ .loader { display: inline-block; position: relative; width: 64px; height: 16px; /* Smaller */ }
119
+ .loader div { position: absolute; top: 6px; width: 10px; height: 10px; border-radius: 50%; background: var(--primary); animation-timing-function: cubic-bezier(0, 1, 1, 0); }
120
+ .loader div:nth-child(1) { left: 6px; animation: loader1 0.6s infinite; }
121
+ .loader div:nth-child(2) { left: 6px; animation: loader2 0.6s infinite; }
122
+ .loader div:nth-child(3) { left: 26px; animation: loader2 0.6s infinite; }
123
+ .loader div:nth-child(4) { left: 45px; animation: loader3 0.6s infinite; }
124
+ @keyframes loader1 { 0% { transform: scale(0); } 100% { transform: scale(1); } }
125
+ @keyframes loader2 { 0% { transform: translate(0, 0); } 100% { transform: translate(20px, 0); } }
126
+ @keyframes loader3 { 0% { transform: scale(1); } 100% { transform: scale(0); } }
127
+
128
+ /* Focus Ring */
129
+ .focus-ring { position: relative; }
130
+ .focus-ring:focus-within::after { content: ''; position: absolute; top: -3px; left: -3px; right: -3px; bottom: -3px; border-radius: 14px; border: 2px solid rgba(59, 130, 246, 0.3); pointer-events: none; animation: focusIn 0.2s ease-out forwards; }
131
+ @keyframes focusIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
132
+
133
+ /* DeepThink Checkbox */
134
+ .deepthink-label { display: flex; align-items: center; cursor: pointer; font-size: 0.875rem; color: var(--secondary); }
135
+ .deepthink-label input[type="checkbox"] { appearance: none; width: 1.25em; height: 1.25em; border: 2px solid #cbd5e0; border-radius: 0.375rem; margin-right: 0.5em; position: relative; top: 1px; transition: all 0.2s ease-in-out; cursor: pointer; flex-shrink: 0; }
136
+ .deepthink-label input[type="checkbox"]:checked { background-color: var(--primary); border-color: var(--primary); }
137
+ .deepthink-label input[type="checkbox"]:checked::after { content: '\f00c'; font-family: 'Font Awesome 6 Free'; font-weight: 900; color: white; font-size: 0.8em; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
138
+ .deepthink-label input[type="checkbox"]:disabled { background-color: #e2e8f0; border-color: #cbd5e0; cursor: not-allowed; }
139
+ .deepthink-label input[type="checkbox"]:disabled + span { color: #a0aec0; }
140
+ .deepthink-label input[type="checkbox"]:disabled:checked::after { color: #a0aec0; }
141
+ .deepthink-label:hover input[type="checkbox"]:not(:disabled) { border-color: var(--primary-dark); }
142
+ .deepthink-tooltip { position: relative; display: inline-flex; align-items: center; margin-left: 6px; }
143
+ .deepthink-tooltip .tooltip-text { visibility: hidden; width: 200px; background-color: #2d3748; color: #fff; text-align: center; border-radius: 6px; padding: 6px 10px; position: absolute; z-index: 10; bottom: 135%; left: 50%; margin-left: -100px; opacity: 0; transition: opacity 0.3s; font-size: 0.75rem; line-height: 1.4; pointer-events: none; }
144
+ .deepthink-tooltip .tooltip-text::after { content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: #2d3748 transparent transparent transparent; }
145
+ .deepthink-tooltip:hover .tooltip-text { visibility: visible; opacity: 1; }
146
+ #deepthink-status { font-style: italic; margin-left: 0.5rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  </style>
149
  </head>
150
 
151
+ <body class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 text-gray-800">
152
  <nav class="glassmorphism sticky top-0 z-50 border-b border-blue-100">
153
+ <div class="container mx-auto px-4 sm:px-6 py-3 sm:py-4"> {/* Ajustement padding nav */}
154
  <div class="flex items-center justify-between">
155
+ <div class="flex items-center space-x-3 sm:space-x-4">
156
  <div class="bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg p-2 shadow-lg transform hover:scale-110 transition-transform duration-300">
157
+ <i class="fas fa-robot text-white text-lg sm:text-xl"></i> {/* Taille icône */}
158
  </div>
159
+ <h1 class="text-xl sm:text-2xl font-bold gradient-text">Mariam AI</h1> {/* Taille titre */}
160
  </div>
161
+ <div class="text-xs sm:text-sm text-blue-900 font-medium bg-blue-50 py-1 px-3 sm:px-4 rounded-full shadow-sm hidden sm:block">Assistant Français</div> {/* Masqué sur xs */}
162
+ <div class="text-xs text-blue-900 font-medium bg-blue-50 py-1 px-3 rounded-full shadow-sm sm:hidden">Assistant</div> {/* Version courte pour xs */}
163
  </div>
164
  </div>
165
  </nav>
166
 
167
+ <main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12"> {/* Ajustement padding main */}
168
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 sm:gap-8">
169
  <!-- Section Travail Argumentatif -->
170
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in">
171
+ <div class="p-6 sm:p-8"> {/* Ajustement padding carte */}
172
+ <div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
173
+ <div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform rotate-3">
174
+ <i class="fas fa-pen-fancy text-blue-600 text-lg sm:text-xl"></i>
175
  </div>
176
+ <h2 class="text-xl sm:text-2xl font-bold text-gray-800">Travail Argumentatif</h2>
177
  </div>
178
+ <form id="francais-form" class="space-y-6 sm:space-y-8"> {/* Ajustement espacement */}
179
+ <div class="space-y-2">
180
  <label for="sujet-francais" class="block text-sm font-medium text-gray-700 flex items-center">
181
  <i class="fas fa-book-open mr-2 text-blue-500"></i>Sujet <span class="text-red-500 ml-1">*</span>
182
  </label>
183
  <div class="focus-ring">
184
  <textarea id="sujet-francais" name="sujet" rows="4"
185
+ class="w-full px-3 py-2 sm:px-4 sm:py-3 rounded-xl border-2 border-gray-200 focus:border-blue-500 focus:outline-none transition-all duration-200 resize-none text-base"
186
  placeholder="Entrez votre sujet ici..." required></textarea>
187
  </div>
188
  <div class="text-xs text-gray-400 text-right" id="character-count">0 caractères</div>
189
  </div>
190
 
191
+ <div class="space-y-3">
192
  <label class="block text-sm font-medium text-gray-700 flex items-center">
193
  <i class="fas fa-tasks mr-2 text-blue-500"></i>Type d'argumentation
194
  </label>
195
+ {/* Grid s'adapte déjà bien (2 cols par défaut, 4 sur sm+) */}
196
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4">
197
  <label class="relative">
198
+ <input type="radio" name="choix" value="Etaye" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
199
+ <span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Étayer</span>
 
 
 
 
200
  </label>
201
  <label class="relative">
202
+ <input type="radio" name="choix" value="refute" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
203
+ <span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Réfuter</span>
 
 
 
 
204
  </label>
205
  <label class="relative">
206
+ <input type="radio" name="choix" value="discuter" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
207
+ <span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Discuter</span>
 
 
 
 
208
  </label>
209
  <label class="relative">
210
+ <input type="radio" name="choix" value="dissertation" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
211
+ <span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Dissertation</span>
 
 
 
212
  </label>
213
  </div>
214
  </div>
215
 
216
+ <div class="space-y-3">
217
  <label class="block text-sm font-medium text-gray-700 flex items-center">
218
  <i class="fas fa-feather-alt mr-2 text-blue-500"></i>Style d'écriture
219
  </label>
220
+ <div class="grid grid-cols-2 gap-3 sm:gap-4">
221
  <label class="relative">
222
+ <input type="radio" name="style" value="raffiné" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
223
+ <span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Raffiné</span>
 
 
 
 
224
  </label>
225
  <label class="relative">
226
+ <input type="radio" name="style" value="Normal" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
227
+ <span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Normal</span>
 
 
 
 
228
  </label>
229
  </div>
230
  </div>
231
 
232
+ <div class="flex items-center justify-start pt-1 sm:pt-2">
 
233
  <label for="deepthink-checkbox" class="deepthink-label">
234
  <input type="checkbox" id="deepthink-checkbox" name="use_deepthink_visual">
235
+ <span class="text-sm">Utiliser DeepThink</span>
236
  <div class="deepthink-tooltip">
237
+ <i class="fas fa-info-circle text-gray-400 ml-1"></i>
238
+ <span class="tooltip-text">Modèle avancé (Gemini Pro). Plus lent, meilleure qualité. Limité: 1/jour.</span>
239
  </div>
240
  </label>
241
+ <span id="deepthink-status" class="text-xs text-gray-500 ml-2"></span>
242
  </div>
 
243
 
244
  <button type="submit"
245
+ class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-5 py-3 sm:px-6 sm:py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg text-base">
246
+ <div class="flex items-center justify-center space-x-2 sm:space-x-3">
247
  <i class="fas fa-magic"></i>
248
  <span>Générer</span>
249
  </div>
250
  </button>
251
  </form>
252
+ {/* Ajustement padding et min-height */}
253
+ <div id="francais-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]">
254
  <!-- Le contenu généré sera inséré ici -->
255
  </div>
256
  </div>
 
258
 
259
  <!-- Section Étude de texte -->
260
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in" style="animation-delay: 0.1s;">
261
+ <div class="p-6 sm:p-8"> {/* Ajustement padding carte */}
262
+ <div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
263
+ <div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform -rotate-3">
264
+ <i class="fas fa-file-alt text-blue-600 text-lg sm:text-xl"></i>
265
  </div>
266
+ <h2 class="text-xl sm:text-2xl font-bold text-gray-800">Étude de texte</h2>
267
  </div>
268
+ <form id="etude-texte-form" class="space-y-6 sm:space-y-8" enctype="multipart/form-data">
269
+ <div class="space-y-3">
270
  <label class="block text-sm font-medium text-gray-700 flex items-center">
271
  <i class="fas fa-image mr-2 text-blue-500"></i>Image(s) du texte <span class="text-red-500 ml-1">*</span>
272
  </label>
273
+ <div class="border-3 border-dashed border-gray-300 rounded-xl p-6 sm:p-8 text-center cursor-pointer hover:border-blue-400 transition-all duration-200 group"
274
  id="drop-zone">
275
  <input type="file" id="image-upload" name="images" accept="image/*" class="hidden" multiple>
276
+ <div class="space-y-3 sm:space-y-4">
277
+ <div class="bg-blue-50 rounded-full w-14 h-14 sm:w-16 sm:h-16 flex items-center justify-center mx-auto transform transition-transform group-hover:scale-110 group-hover:rotate-6">
278
+ <i class="fas fa-cloud-upload-alt text-2xl sm:text-3xl text-blue-500"></i>
279
  </div>
280
+ <p class="text-sm text-gray-600 font-medium">Glissez/cliquez pour ajouter</p>
281
+ <p class="text-xs text-gray-400">PNG, JPG, WEBP (max 10MB)</p>
 
282
  </div>
283
  </div>
284
  <div id="image-preview" class="image-preview"></div>
285
  </div>
286
 
287
  <button type="submit"
288
+ class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-5 py-3 sm:px-6 sm:py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg text-base">
289
+ <div class="flex items-center justify-center space-x-2 sm:space-x-3">
290
  <i class="fas fa-search"></i>
291
  <span>Analyser</span>
292
  </div>
293
  </button>
294
  </form>
295
+ {/* Ajustement padding et min-height */}
296
+ <div id="etude-texte-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]">
297
  <!-- Le contenu analysé sera inséré ici -->
298
  </div>
299
  </div>
300
  </div>
301
  </div>
302
+
303
+ <!-- Section Sauvegardes -->
304
+ <div class="mt-10 sm:mt-12">
305
+ <h2 class="text-xl sm:text-2xl font-bold text-gray-800 mb-4 sm:mb-6 flex items-center">
306
+ <i class="fas fa-save mr-2 sm:mr-3 text-blue-500"></i>
307
  Sauvegardes
308
  </h2>
309
  <div id="backups-list" class="space-y-4">
 
312
  </div>
313
  </main>
314
 
315
+ <footer class="bg-gray-50 border-t border-gray-200 py-5 sm:py-6 mt-12 sm:mt-16">
316
+ <div class="container mx-auto px-4 sm:px-6">
317
+ <div class="flex flex-col md:flex-row justify-between items-center space-y-3 md:space-y-0">
318
+ <div class="flex items-center space-x-2">
319
  <div class="bg-blue-600 rounded-lg p-1 shadow">
320
  <i class="fas fa-robot text-white text-sm"></i>
321
  </div>
322
  <span class="text-gray-600 text-sm">Mariam AI</span>
323
  </div>
324
+ <div class="flex space-x-4 sm:space-x-6">
325
+ <a href="#" class="text-gray-500 hover:text-blue-600 transition-colors text-xs sm:text-sm flex items-center">
326
+ <i class="fas fa-question-circle mr-1"></i> Aide
 
327
  </a>
328
+ <a href="#" class="text-gray-500 hover:text-blue-600 transition-colors text-xs sm:text-sm flex items-center">
329
+ <i class="fas fa-shield-alt mr-1"></i> Confidentialité
 
330
  </a>
331
  </div>
332
  </div>
 
335
 
336
  <script>
337
  // --- Constantes ---
338
+ const DEEPTHINK_STORAGE_KEY = 'deepThinkLastUsedDate';
339
 
340
  // --- Gestionnaire de fichiers ---
341
  const uploadedFiles = new Map();
 
342
  function initializeFileUpload() {
343
  const dropZone = document.getElementById('drop-zone');
344
  const fileInput = document.getElementById('image-upload');
345
  const imagePreview = document.getElementById('image-preview');
346
+ if(!dropZone || !fileInput || !imagePreview) return; // Safety check
347
 
348
  dropZone.addEventListener('click', () => fileInput.click());
349
+ ['dragenter', 'dragover'].forEach(ev => dropZone.addEventListener(ev, highlightDropZone));
350
+ ['dragleave', 'drop'].forEach(ev => dropZone.addEventListener(ev, unhighlightDropZone));
351
+ dropZone.addEventListener('drop', (e) => { e.preventDefault(); handleFiles(e.dataTransfer.files); });
352
+ fileInput.addEventListener('change', () => { handleFiles(fileInput.files); fileInput.value = ''; });
353
 
354
+ function highlightDropZone(e) { e.preventDefault(); dropZone.classList.add('border-blue-500', 'bg-blue-50'); }
355
+ function unhighlightDropZone(e) { e.preventDefault(); dropZone.classList.remove('border-blue-500', 'bg-blue-50'); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
 
357
  function handleFiles(files) {
 
358
  const dataTransfer = new DataTransfer();
359
+ uploadedFiles.forEach(file => dataTransfer.items.add(file));
360
+ Array.from(files).forEach(file => {
361
+ if (!file.type.startsWith('image/')) return;
 
362
  const fileId = `${file.name}-${file.size}`;
363
+ if (uploadedFiles.has(fileId)) return;
364
  uploadedFiles.set(fileId, file);
365
  dataTransfer.items.add(file);
366
  renderPreview(file, fileId);
367
+ });
368
+ fileInput.files = dataTransfer.files;
369
  }
370
 
371
  function renderPreview(file, fileId) {
372
  const reader = new FileReader();
373
+ reader.onload = (e) => {
374
+ const previewItem = document.createElement('div');
375
+ previewItem.classList.add('image-preview-item', 'scale-in');
376
+ previewItem.dataset.fileId = fileId;
377
+ previewItem.innerHTML = `<img src="${e.target.result}" alt="${file.name}"><button class="remove-image" title="Supprimer"><i class="fas fa-times"></i></button>`;
378
+ imagePreview.appendChild(previewItem);
379
+ previewItem.querySelector('.remove-image').addEventListener('click', (e) => { e.stopPropagation(); removeFile(fileId, previewItem); });
380
+ };
381
+ reader.readAsDataURL(file);
 
 
 
 
 
382
  }
383
 
384
  function removeFile(fileId, previewElement) {
 
388
  fileInput.files = dataTransfer.files;
389
  previewElement.style.opacity = '0';
390
  previewElement.style.transform = 'scale(0.9)';
391
+ setTimeout(() => { if (imagePreview.contains(previewElement)) imagePreview.removeChild(previewElement); }, 300);
 
 
 
 
392
  }
393
  }
394
 
 
395
  // --- Gestion des sauvegardes ---
396
+ function sauvegarderReponse(titre, contenu) {
 
397
  const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
398
  const dateSauvegarde = new Date().toISOString();
399
  const MAX_SAUVEGARDES = 20;
400
+ if (sauvegardes.length >= MAX_SAUVEGARDES) sauvegardes.shift();
401
+ sauvegardes.push({ titre: titre || "Sauvegarde", contenu, date: dateSauvegarde });
 
 
 
 
 
 
402
  try {
403
  localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
404
+ displayNotification("Sauvegardé", "success"); // Shorter message
405
+ afficherSauvegardes();
406
  } catch (e) {
407
+ console.error("Erreur sauvegarde localStorage:", e);
408
+ displayNotification("Erreur sauvegarde: Stockage plein?", "error");
 
409
  }
 
 
410
  }
411
 
412
  function displayNotification(message, type = 'success') {
413
  const notification = document.createElement('div');
414
+ const colors = {
415
+ success: 'bg-green-50 border-green-200 text-green-700',
416
+ error: 'bg-red-50 border-red-200 text-red-700',
417
+ warning: 'bg-yellow-50 border-yellow-200 text-yellow-700'
418
+ };
419
+ const icons = { success: 'fa-check-circle text-green-500', error: 'fa-exclamation-circle text-red-500', warning: 'fa-exclamation-triangle text-yellow-500' };
420
+ notification.className = `fixed bottom-4 right-4 sm:bottom-6 sm:right-6 border ${colors[type] || colors.success} px-4 py-3 rounded-lg shadow-lg z-[100] flex items-center scale-in max-w-[calc(100%-2rem)] sm:max-w-sm`; // Responsive max-width
421
+ notification.innerHTML = `<i class="fas ${icons[type] || icons.success} mr-3 text-lg flex-shrink-0"></i><span class="text-sm font-medium">${message}</span>`;
422
+ document.body.appendChild(notification);
423
+ const duration = type === 'error' ? 6000 : 3000;
 
424
  setTimeout(() => {
425
+ notification.style.opacity = '0'; notification.style.transform = 'scale(0.9)'; notification.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
426
+ setTimeout(() => { if (document.body.contains(notification)) document.body.removeChild(notification); }, 300);
427
+ }, duration);
428
+ }
429
+
430
+ function supprimerSauvegarde(originalIndex) {
431
+ try {
432
+ let sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
433
+ if (originalIndex >= 0 && originalIndex < sauvegardes.length) {
434
+ sauvegardes.splice(originalIndex, 1);
435
+ localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
436
+ afficherSauvegardes();
437
+ } else {
438
+ console.error("Index de suppression invalide:", originalIndex);
439
+ displayNotification("Erreur suppression: Index invalide", "error");
440
+ }
441
+ } catch (e) {
442
+ console.error("Erreur suppression sauvegarde:", e);
443
+ displayNotification("Erreur suppression", "error");
444
+ }
445
  }
446
 
 
447
  function afficherSauvegardes() {
 
 
448
  const backupsList = document.getElementById('backups-list');
449
+ if(!backupsList) return;
450
+ let sauvegardes = [];
451
+ try {
452
+ sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
453
+ } catch(e) { console.error("Erreur lecture sauvegardes:", e); }
454
+
455
+ sauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
456
  backupsList.innerHTML = '';
457
 
458
  if (sauvegardes.length === 0) {
459
+ backupsList.innerHTML = `<div class="bg-blue-50 rounded-lg p-6 sm:p-8 text-center"><div class="bg-white rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4 shadow-sm"><i class="fas fa-folder-open text-blue-300 text-2xl"></i></div><p class="text-gray-600 text-sm sm:text-base">Aucune sauvegarde.</p><p class="text-xs sm:text-sm text-gray-500 mt-2">Vos réponses apparaîtront ici.</p></div>`;
 
 
 
 
 
 
 
 
460
  return;
461
  }
462
 
463
  sauvegardes.forEach((sauvegarde, index) => {
464
  const date = new Date(sauvegarde.date);
465
  const formattedDate = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
466
+ const displayTitle = (sauvegarde.titre || "Sans titre").length > 50 ? sauvegarde.titre.substring(0, 47) + '...' : (sauvegarde.titre || "Sans titre");
467
 
468
  const sauvegardeDiv = document.createElement('div');
469
+ sauvegardeDiv.dataset.originalDate = sauvegarde.date; // Use date for more reliable deletion lookup
470
  sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
471
  sauvegardeDiv.innerHTML = `
472
  <div class="flex items-start cursor-pointer item-header">
473
+ <div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0"> <i class="fas fa-file-alt text-blue-600 text-sm"></i> </div>
474
+ <div class="flex-grow overflow-hidden mr-2">
475
+ <h3 class="text-base sm:text-lg font-semibold text-gray-800 truncate" title="${sauvegarde.titre || 'Sans titre'}">${displayTitle}</h3>
476
+ <p class="text-xs sm:text-sm text-gray-600 mb-1 flex items-center"> <i class="fas fa-clock text-xs mr-1 text-gray-400"></i> ${formattedDate} </p>
477
  </div>
478
+ <div class="flex space-x-1 sm:space-x-2 ml-auto flex-shrink-0">
479
+ <button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1 rounded-full hover:bg-gray-100" title="Copier"> <i class="fas fa-copy text-xs sm:text-sm"></i> </button>
480
+ <button class="text-gray-400 hover:text-red-600 focus:outline-none delete-btn p-1 rounded-full hover:bg-gray-100" title="Supprimer"> <i class="fas fa-trash-alt text-xs sm:text-sm"></i> </button>
481
  </div>
482
  </div>
483
+ <div class="backup-content text-sm text-gray-700 prose max-w-none border-t border-gray-100"></div>`;
484
  backupsList.appendChild(sauvegardeDiv);
485
 
486
  const backupContentDiv = sauvegardeDiv.querySelector('.backup-content');
487
  const headerDiv = sauvegardeDiv.querySelector('.item-header');
488
 
489
+ headerDiv.addEventListener('click', () => {
490
  const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
491
  document.querySelectorAll('.backup-content-expanded').forEach(content => {
492
+ if (content !== backupContentDiv) { content.classList.remove('backup-content-expanded'); content.innerHTML = ''; }
 
 
493
  });
494
  if (!isExpanded) {
495
+ try { backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu || ''); } catch(e) { backupContentDiv.innerHTML = "Erreur d'affichage du contenu."; console.error(e); }
496
  backupContentDiv.classList.add('backup-content-expanded');
497
+ // MathJax retiré
498
  } else {
499
  backupContentDiv.classList.remove('backup-content-expanded'); backupContentDiv.innerHTML = '';
500
  }
501
  });
502
 
503
+ sauvegardeDiv.querySelector('.copy-btn').addEventListener('click', (e) => {
 
504
  e.stopPropagation();
505
+ navigator.clipboard.writeText(sauvegarde.contenu || '').then(() => {
506
+ const icon = e.currentTarget.querySelector('i'); icon.className = 'fas fa-check text-green-500 text-xs sm:text-sm'; setTimeout(() => { icon.className = 'fas fa-copy text-xs sm:text-sm'; }, 2000);
507
  }).catch(err => { console.error('Copy error:', err); displayNotification("Erreur copie", "error"); });
508
  });
509
 
510
+ sauvegardeDiv.querySelector('.delete-btn').addEventListener('click', (e) => {
 
511
  e.stopPropagation();
512
+ const itemDate = sauvegardeDiv.dataset.originalDate;
513
+
514
+ // Trouver l'index original basé sur la date (plus fiable que l'index d'affichage trié)
515
+ let originalIndex = -1;
516
+ try {
517
+ const originalSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
518
+ originalIndex = originalSauvegardes.findIndex(s => s.date === itemDate);
519
+ } catch (err) { console.error("Erreur recherche sauvegarde:", err); }
520
 
521
+
522
+ if (originalIndex === -1) { console.error("Item not found for deletion by date:", itemDate); displayNotification("Erreur suppression", "error"); return; }
523
 
524
  const confirmationModal = document.createElement('div');
525
+ confirmationModal.className = 'fixed inset-0 flex items-center justify-center z-[100] bg-black bg-opacity-50 scale-in p-4'; // Ajout p-4 pour mobile
526
  confirmationModal.innerHTML = `
527
+ <div class="bg-white rounded-lg p-5 sm:p-6 max-w-sm w-full shadow-xl">
528
+ <div class="flex items-center mb-4"> <div class="bg-red-100 rounded-full p-2 mr-3"><i class="fas fa-exclamation-triangle text-red-500"></i></div> <h3 class="text-base sm:text-lg font-semibold text-gray-800">Confirmation</h3> </div>
529
  <p class="text-gray-600 mb-6 text-sm">Supprimer cette sauvegarde ?</p>
530
  <div class="flex justify-end space-x-3"> <button class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 cancel-btn text-sm">Annuler</button> <button class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 confirm-btn text-sm">Supprimer</button> </div>
531
  </div>`;
532
  document.body.appendChild(confirmationModal);
533
  confirmationModal.querySelector('.cancel-btn').addEventListener('click', () => closeModal(confirmationModal));
534
  confirmationModal.querySelector('.confirm-btn').addEventListener('click', () => { supprimerSauvegarde(originalIndex); closeModal(confirmationModal); });
535
+ confirmationModal.addEventListener('click', (event) => { if (event.target === confirmationModal) closeModal(confirmationModal); });
536
  });
537
  });
538
  }
 
545
 
546
 
547
  // --- Gestion DeepThink ---
 
 
 
548
  function checkDeepThinkAvailability() {
549
  const checkbox = document.getElementById('deepthink-checkbox');
550
  const statusSpan = document.getElementById('deepthink-status');
551
+ if (!checkbox || !statusSpan) return;
552
+ try {
553
+ const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
554
+ const todayStr = new Date().toISOString().split('T')[0];
555
+ if (lastUsedDateStr === todayStr) {
556
+ checkbox.checked = false; checkbox.disabled = true;
557
+ statusSpan.textContent = "(utilisé)"; statusSpan.classList.add('text-red-500');
558
+ } else {
559
+ checkbox.disabled = false; statusSpan.textContent = ""; statusSpan.classList.remove('text-red-500');
560
+ }
561
+ } catch (e) {
562
+ console.error("Erreur accès localStorage (DeepThink):", e);
563
+ checkbox.disabled = true; // Disable if error
564
+ statusSpan.textContent = "(erreur)";
565
  }
566
  }
567
 
 
571
  const output = document.getElementById('francais-output');
572
  const sujetTextarea = document.getElementById('sujet-francais');
573
  const deepThinkCheckbox = document.getElementById('deepthink-checkbox');
574
+ const submitButton = form.querySelector('button[type="submit"]'); // Get button for disabling
575
+
576
+ if(!form || !output || !sujetTextarea || !deepThinkCheckbox || !submitButton) return; // Safety check
577
 
578
  form.addEventListener('submit', async (e) => {
579
  e.preventDefault();
580
+ const originalButtonContent = submitButton.innerHTML; // Store original content
581
+ submitButton.disabled = true; // Disable button
582
+ submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2">Génération...</span></div>`; // Show loader in button
583
 
584
  const sujetValue = sujetTextarea.value.trim();
585
+ const isDeepThinkChecked = deepThinkCheckbox.checked;
586
 
587
+ // Validation
588
  if (!sujetValue) {
589
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Champ obligatoire</p><p>Veuillez entrer un sujet.</p></div></div>`;
590
+ sujetTextarea.focus(); sujetTextarea.classList.add('border-red-500'); setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
591
+ submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; // Re-enable button
 
592
  return;
593
  }
 
 
594
  if (isDeepThinkChecked) {
595
+ try {
596
+ const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
597
+ const todayStr = new Date().toISOString().split('T')[0];
598
+ if (lastUsedDateStr === todayStr) {
599
+ output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Limite atteinte</p><p>DeepThink déjà utilisé aujourd'hui.</p></div></div>`;
600
+ checkDeepThinkAvailability();
601
+ submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; // Re-enable button
602
+ return;
603
+ }
604
+ } catch(e) { /* Ignore localStorage error here, proceed without check */ }
605
  }
 
606
 
607
+ // Clear previous output and show minimal loader
608
+ output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
609
 
 
 
 
 
 
 
 
610
 
 
611
  const formData = new FormData(form);
612
+ formData.append('use_deepthink', isDeepThinkChecked);
613
 
614
  try {
615
  const response = await fetch('/api/francais', { method: 'POST', body: formData });
616
  const result = await response.json();
 
617
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
618
 
619
+ // Render output
620
+ try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage de la réponse."; console.error(e); }
 
 
 
 
 
 
621
 
622
+ const titreSauvegarde = sujetValue.substring(0, 50) + (sujetValue.length > 50 ? '...' : ''); // Shorter title
 
623
  const deepThinkSuffix = isDeepThinkChecked ? ' [DeepThink]' : '';
624
  sauvegarderReponse(titreSauvegarde + deepThinkSuffix, result.output);
625
 
 
626
  if (isDeepThinkChecked) {
627
+ try {
628
+ const todayStr = new Date().toISOString().split('T')[0];
629
+ localStorage.setItem(DEEPTHINK_STORAGE_KEY, todayStr);
630
+ checkDeepThinkAvailability();
631
+ } catch(e) { console.error("Erreur sauvegarde date DeepThink:", e); }
632
  }
633
+ // MathJax retiré
634
 
635
+ } catch (error) {
 
 
636
  console.error("Erreur soumission (français):", error);
637
+ output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
638
+ } finally {
639
+ submitButton.disabled = false; // Always re-enable button
640
+ submitButton.innerHTML = originalButtonContent; // Restore original content
641
  }
642
  });
643
  }
 
646
  const form = document.getElementById('etude-texte-form');
647
  const output = document.getElementById('etude-texte-output');
648
  const fileInput = document.getElementById('image-upload');
649
+ const submitButton = form.querySelector('button[type="submit"]'); // Get button for disabling
650
+
651
+ if(!form || !output || !fileInput || !submitButton) return; // Safety check
652
 
653
  form.addEventListener('submit', async (e) => {
654
  e.preventDefault();
655
+ const originalButtonContent = submitButton.innerHTML; // Store original content
656
+ submitButton.disabled = true; // Disable button
657
+ submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2">Analyse...</span></div>`; // Show loader in button
658
+
659
+
660
  if (uploadedFiles.size === 0) {
661
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Aucune image</p><p>Ajoutez au moins une image.</p></div></div>`;
662
+ submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; // Re-enable button
663
  return;
664
  }
665
+
666
+ // Clear previous output and show minimal loader
667
+ output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
668
+
 
 
669
  const formData = new FormData();
670
  uploadedFiles.forEach((file) => formData.append('images', file, file.name));
671
  try {
672
  const response = await fetch('/api/etude-texte', { method: 'POST', body: formData });
673
  const result = await response.json();
674
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
675
+
676
+ try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage de la réponse."; console.error(e); }
677
+
 
678
  const titre = `Analyse image(s) - ${new Date().toLocaleDateString('fr-FR')}`;
679
  sauvegarderReponse(titre, result.output);
680
+ // MathJax retiré
681
  } catch (error) {
682
  console.error("Erreur soumission (étude texte):", error);
683
+ output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
684
+ } finally {
685
+ submitButton.disabled = false; // Always re-enable button
686
+ submitButton.innerHTML = originalButtonContent; // Restore original content
687
  }
688
  });
689
  }
690
 
691
  // --- Animations & Améliorations UI ---
 
692
  function animateOnScroll() {
693
  const cards = document.querySelectorAll('.card-hover');
694
  if (!('IntersectionObserver' in window)) { cards.forEach(card => card.style.opacity = '1'); return; }
695
  const observer = new IntersectionObserver((entries) => {
696
  entries.forEach((entry) => {
697
+ if (entry.isIntersecting) { requestAnimationFrame(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; }); observer.unobserve(entry.target); }
 
 
 
698
  });
699
+ }, { threshold: 0.05 }); // Lower threshold for earlier trigger
700
+ cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; observer.observe(card); }); // Smaller initial translate
701
  }
702
  function enhanceTextareaFocus() {
703
  const textarea = document.getElementById('sujet-francais');
704
  const counter = document.getElementById('character-count');
705
+ if(!textarea || !counter) return;
706
+ textarea.addEventListener('input', () => { const length = textarea.value.length; counter.textContent = `${length} caractère${length !== 1 ? 's' : ''}`; textarea.classList.remove('border-red-500'); });
 
707
  }
708
  function enhanceRadioButtons() {
709
+ document.querySelectorAll('input[type="radio"] + span').forEach(label => {
 
710
  const input = label.previousElementSibling; if (input.disabled) return;
711
+ label.addEventListener('mouseenter', () => { if (!input.checked) label.classList.add('border-blue-400', 'shadow-md', 'transform' ,'-translate-y-px'); }); // Smaller translate
712
+ label.addEventListener('mouseleave', () => { if (!input.checked) label.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-px'); });
713
  input.addEventListener('change', () => {
714
  const groupName = input.name;
715
  document.querySelectorAll(`input[name="${groupName}"] + span`).forEach(otherLabel => {
716
+ if (otherLabel !== label) otherLabel.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-px', 'border-primary');
717
+ else label.classList.remove('shadow-md', 'transform', '-translate-y-px');
718
  });
719
  });
720
  });
 
729
  animateOnScroll();
730
  enhanceTextareaFocus();
731
  enhanceRadioButtons();
732
+ checkDeepThinkAvailability();
733
  afficherSauvegardes();
734
+ // MathJax retiré
 
 
735
 
736
  // Welcome Message (session based)
737
+ try {
738
+ const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
739
+ if (showWelcome) {
740
+ const welcomeMessageContainer = document.createElement('div');
741
+ welcomeMessageContainer.innerHTML = `<div class="fixed bottom-4 right-4 sm:bottom-6 sm:right-6 bg-white rounded-xl shadow-lg p-4 sm:p-5 transform transition-all duration-500 opacity-0 translate-y-4 max-w-[calc(100%-2rem)] sm:max-w-xs z-[100]"><div class="flex items-start space-x-3 sm:space-x-4"><div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-2 sm:p-3 mt-1 flex-shrink-0"> <i class="fas fa-robot text-white text-lg sm:text-xl"></i> </div><div> <p class="text-sm font-semibold text-gray-800">Bienvenue sur Mariam AI !</p> <p class="text-xs text-gray-500 mt-1">Assistant prêt.</p> </div></div><button class="absolute top-1 right-1 text-gray-400 hover:text-gray-600 focus:outline-none close-welcome p-1"> <i class="fas fa-times text-xs"></i> </button></div>`;
742
+ document.body.appendChild(welcomeMessageContainer);
743
+ const welcomeMessage = welcomeMessageContainer.firstElementChild;
744
+ setTimeout(() => { welcomeMessage.classList.remove('opacity-0', 'translate-y-4'); }, 500);
745
+ welcomeMessage.querySelector('.close-welcome').addEventListener('click', () => closeWelcomeMessage(welcomeMessage));
746
+ setTimeout(() => { if (document.body.contains(welcomeMessage)) closeWelcomeMessage(welcomeMessage); }, 7000);
747
+ sessionStorage.setItem('welcomeShown', 'true');
748
+ }
749
+ } catch(e) { console.warn("Erreur accès sessionStorage (Welcome Msg):", e); }
750
+
751
  function closeWelcomeMessage(element) {
752
+ element.classList.add('opacity-0', 'translate-y-4'); element.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
753
+ setTimeout(() => { if (element?.parentElement) element.parentElement.remove(); }, 300);
754
  }
755
  });
756
  </script>