benyelles0mehdi0dris commited on
Commit
956b9f8
·
1 Parent(s): ee0b268

frontend ajouter

Browse files
Files changed (3) hide show
  1. frontend/index.html +312 -0
  2. frontend/script.js +177 -0
  3. frontend/styles.css +241 -0
frontend/index.html ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ <title>AI Tools Interface</title>
7
+ <!-- Tailwind CSS via CDN -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <!-- Custom CSS for additional styling -->
10
+ <style>
11
+ .custom-file-upload {
12
+ border: 2px dashed #cbd5e0;
13
+ transition: background-color 0.3s ease;
14
+ }
15
+ .custom-file-upload:hover {
16
+ background-color: #f7fafc;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body class="bg-gray-100 min-h-screen">
21
+ <div class="container mx-auto px-4 py-8">
22
+ <h1 class="text-3xl font-bold text-center mb-8 text-gray-800">AI Tools Interface</h1>
23
+
24
+ <!-- Conteneur principal avec des onglets -->
25
+ <div class="bg-white shadow-md rounded-lg overflow-hidden">
26
+ <!-- Navigation des onglets -->
27
+ <div class="flex border-b">
28
+ <button onclick="openTab('summarize')" class="w-1/5 py-4 text-center bg-gray-100 hover:bg-gray-200 focus:outline-none tab-btn active" data-tab="summarize">
29
+ Résumer
30
+ </button>
31
+ <button onclick="openTab('interpret-image')" class="w-1/5 py-4 text-center bg-gray-100 hover:bg-gray-200 focus:outline-none tab-btn" data-tab="interpret-image">
32
+ Légende Image
33
+ </button>
34
+ <button onclick="openTab('answer-question')" class="w-1/5 py-4 text-center bg-gray-100 hover:bg-gray-200 focus:outline-none tab-btn" data-tab="answer-question">
35
+ Réponse Question
36
+ </button>
37
+ <button onclick="openTab('generate-visualization')" class="w-1/5 py-4 text-center bg-gray-100 hover:bg-gray-200 focus:outline-none tab-btn" data-tab="generate-visualization">
38
+ Visualisation
39
+ </button>
40
+ <button onclick="openTab('translate-document')" class="w-1/5 py-4 text-center bg-gray-100 hover:bg-gray-200 focus:outline-none tab-btn" data-tab="translate-document">
41
+ Traduire
42
+ </button>
43
+ </div>
44
+
45
+ <!-- Contenu des onglets -->
46
+ <div class="p-6">
47
+ <!-- Onglet Résumé -->
48
+ <div id="summarize" class="tab-content">
49
+ <h2 class="text-xl font-semibold mb-4">Résumer un document</h2>
50
+ <form id="summarize-form" class="space-y-4">
51
+ <input type="file" id="summarize-file" accept=".txt,.pdf,.docx,.pptx" class="hidden" />
52
+ <label for="summarize-file" class="w-full p-4 border-2 border-dashed border-gray-300 rounded-lg text-center custom-file-upload cursor-pointer">
53
+ <span class="file-label">Sélectionner un fichier</span>
54
+ </label>
55
+ <button type="submit" class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 transition">
56
+ Générer le résumé
57
+ </button>
58
+ </form>
59
+ <div id="summarize-result" class="mt-4 p-4 bg-gray-50 rounded"></div>
60
+ </div>
61
+
62
+ <!-- Onglet Légende Image -->
63
+ <div id="interpret-image" class="tab-content hidden">
64
+ <h2 class="text-xl font-semibold mb-4">Générer une légende d'image</h2>
65
+ <form id="interpret-image-form" class="space-y-4">
66
+ <input type="file" id="interpret-image-file" accept="image/*" class="hidden" />
67
+ <label for="interpret-image-file" class="w-full p-4 border-2 border-dashed border-gray-300 rounded-lg text-center custom-file-upload cursor-pointer">
68
+ <span class="file-label">Sélectionner une image</span>
69
+ </label>
70
+ <button type="submit" class="w-full bg-green-500 text-white py-2 rounded hover:bg-green-600 transition">
71
+ Générer la légende
72
+ </button>
73
+ </form>
74
+ <div id="interpret-image-result" class="mt-4 p-4 bg-gray-50 rounded"></div>
75
+ </div>
76
+
77
+ <!-- Onglet Réponse Question -->
78
+ <div id="answer-question" class="tab-content hidden">
79
+ <h2 class="text-xl font-semibold mb-4">Poser une question à un document</h2>
80
+ <form id="answer-question-form" class="space-y-4">
81
+ <textarea id="question-input" placeholder="Votre question..." class="w-full p-2 border rounded" rows="3"></textarea>
82
+ <input type="file" id="answer-question-file" accept=".txt,.pdf,.docx" class="hidden" />
83
+ <label for="answer-question-file" class="w-full p-4 border-2 border-dashed border-gray-300 rounded-lg text-center custom-file-upload cursor-pointer">
84
+ <span class="file-label">Fichier optionnel</span>
85
+ </label>
86
+ <button type="submit" class="w-full bg-purple-500 text-white py-2 rounded hover:bg-purple-600 transition">
87
+ Obtenir la réponse
88
+ </button>
89
+ </form>
90
+ <div id="answer-question-result" class="mt-4 p-4 bg-gray-50 rounded"></div>
91
+ </div>
92
+
93
+ <!-- Onglet Visualisation -->
94
+ <div id="generate-visualization" class="tab-content hidden">
95
+ <h2 class="text-xl font-semibold mb-4">Générer une Visualisation</h2>
96
+ <form id="generate-visualization-form" class="space-y-4">
97
+ <input type="file" id="visualization-file" accept=".xlsx,.xls" class="hidden" />
98
+ <label for="visualization-file" class="w-full p-4 border-2 border-dashed border-gray-300 rounded-lg text-center custom-file-upload cursor-pointer">
99
+ <span class="file-label">Sélectionner un fichier Excel</span>
100
+ </label>
101
+ <input type="text" id="visualization-request" placeholder="Type de visualisation (ex: histogramme)" class="w-full p-2 border rounded" />
102
+ <button type="submit" class="w-full bg-indigo-500 text-white py-2 rounded hover:bg-indigo-600 transition">
103
+ Générer le graphique
104
+ </button>
105
+ </form>
106
+ <div id="generate-visualization-result" class="mt-4 p-4 bg-gray-50 rounded"></div>
107
+ </div>
108
+
109
+ <!-- Onglet Traduction -->
110
+ <div id="translate-document" class="tab-content hidden">
111
+ <h2 class="text-xl font-semibold mb-4">Traduire un Document</h2>
112
+ <form id="translate-document-form" class="space-y-4">
113
+ <input type="file" id="translate-file" accept=".txt,.docx,.pdf" class="hidden" />
114
+ <label for="translate-file" class="w-full p-4 border-2 border-dashed border-gray-300 rounded-lg text-center custom-file-upload cursor-pointer">
115
+ <span class="file-label">Sélectionner un document</span>
116
+ </label>
117
+ <select id="target-language" class="w-full p-2 border rounded">
118
+ <option value="en">Anglais</option>
119
+ <option value="fr">Français</option>
120
+ <option value="es">Espagnol</option>
121
+ <option value="de">Allemand</option>
122
+ </select>
123
+ <button type="submit" class="w-full bg-pink-500 text-white py-2 rounded hover:bg-pink-600 transition">
124
+ Traduire
125
+ </button>
126
+ </form>
127
+ <div id="translate-document-result" class="mt-4 p-4 bg-gray-50 rounded"></div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ <script>
133
+ // URL de base du backend
134
+ const BASE_URL = 'http://localhost:8000';
135
+
136
+ // Gestion des onglets
137
+ function openTab(tabName) {
138
+ document.querySelectorAll('.tab-content').forEach(tab => tab.classList.add('hidden'));
139
+ document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active', 'bg-gray-200'));
140
+ document.getElementById(tabName).classList.remove('hidden');
141
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active', 'bg-gray-200');
142
+ }
143
+
144
+ // Fonction générique pour gérer les uploads de fichiers
145
+ function setupFileUpload(inputId) {
146
+ const input = document.getElementById(inputId);
147
+ const label = input.labels[0];
148
+ input.addEventListener('change', function() {
149
+ label.querySelector('.file-label').textContent = this.files[0]?.name || 'Sélectionner un fichier';
150
+ });
151
+ }
152
+
153
+ // Configuration des uploads
154
+ ['summarize-file', 'interpret-image-file', 'answer-question-file',
155
+ 'visualization-file', 'translate-file'].forEach(setupFileUpload);
156
+
157
+ // Gestion des erreurs communes
158
+ function handleError(error, resultDiv) {
159
+ console.error(error);
160
+ resultDiv.innerHTML = `<p class="text-red-500">Erreur : ${error.message || 'Problème de communication avec le serveur'}</p>`;
161
+ }
162
+
163
+ // Résumé de document
164
+ document.getElementById('summarize-form').addEventListener('submit', async (e) => {
165
+ e.preventDefault();
166
+ const fileInput = document.getElementById('summarize-file');
167
+ const resultDiv = document.getElementById('summarize-result');
168
+ resultDiv.innerHTML = '<p class="text-blue-500">Traitement en cours...</p>';
169
+
170
+ try {
171
+ const formData = new FormData();
172
+ formData.append('file', fileInput.files[0]);
173
+
174
+ const response = await fetch(`http://localhost:8000/summarize`, {
175
+ method: 'POST',
176
+ body: formData
177
+ });
178
+
179
+ const data = await response.json();
180
+ if (data.error) throw new Error(data.error);
181
+
182
+ resultDiv.innerHTML = `
183
+ <h3 class="font-bold mb-2">Résumé de ${data.filename} :</h3>
184
+ <p class="whitespace-pre-wrap">${data.summary}</p>
185
+ `;
186
+ } catch (error) {
187
+ handleError(error, resultDiv);
188
+ }
189
+ });
190
+
191
+ // Légende d'image
192
+ document.getElementById('interpret-image-form').addEventListener('submit', async (e) => {
193
+ e.preventDefault();
194
+ const fileInput = document.getElementById('interpret-image-file');
195
+ const resultDiv = document.getElementById('interpret-image-result');
196
+ resultDiv.innerHTML = '<p class="text-blue-500">Traitement en cours...</p>';
197
+
198
+ try {
199
+ const formData = new FormData();
200
+ formData.append('file', fileInput.files[0]);
201
+
202
+ const response = await fetch(`${BASE_URL}/interpret_image`, {
203
+ method: 'POST',
204
+ body: formData
205
+ });
206
+
207
+ const data = await response.json();
208
+ if (data.error) throw new Error(data.error);
209
+
210
+ resultDiv.innerHTML = `
211
+ <h3 class="font-bold mb-2">${data.filename} :</h3>
212
+ <p>${data.caption}</p>
213
+ `;
214
+ } catch (error) {
215
+ handleError(error, resultDiv);
216
+ }
217
+ });
218
+
219
+ // Réponse aux questions
220
+ document.getElementById('answer-question-form').addEventListener('submit', async (e) => {
221
+ e.preventDefault();
222
+ const questionInput = document.getElementById('question-input');
223
+ const fileInput = document.getElementById('answer-question-file');
224
+ const resultDiv = document.getElementById('answer-question-result');
225
+ resultDiv.innerHTML = '<p class="text-blue-500">Recherche de réponse...</p>';
226
+
227
+ try {
228
+ const formData = new FormData();
229
+ formData.append('question', questionInput.value);
230
+ if (fileInput.files[0]) formData.append('file', fileInput.files[0]);
231
+
232
+ const response = await fetch(`${BASE_URL}/answer_question`, {
233
+ method: 'POST',
234
+ body: formData
235
+ });
236
+
237
+ const data = await response.json();
238
+ if (data.error) throw new Error(data.error);
239
+
240
+ resultDiv.innerHTML = `
241
+ <h3 class="font-bold mb-2">Réponse :</h3>
242
+ <p>${data.answer}</p>
243
+ ${data.score ? `<p class="text-sm text-gray-600">Confiance : ${(data.score * 100).toFixed(1)}%</p>` : ''}
244
+ `;
245
+ } catch (error) {
246
+ handleError(error, resultDiv);
247
+ }
248
+ });
249
+
250
+ // Génération de visualisation
251
+ document.getElementById('generate-visualization-form').addEventListener('submit', async (e) => {
252
+ e.preventDefault();
253
+ const fileInput = document.getElementById('visualization-file');
254
+ const requestInput = document.getElementById('visualization-request');
255
+ const resultDiv = document.getElementById('generate-visualization-result');
256
+ resultDiv.innerHTML = '<p class="text-blue-500">Génération du graphique...</p>';
257
+
258
+ try {
259
+ const formData = new FormData();
260
+ formData.append('file', fileInput.files[0]);
261
+ formData.append('request', requestInput.value);
262
+
263
+ const response = await fetch(`${BASE_URL}/generate_visualization`, {
264
+ method: 'POST',
265
+ body: formData
266
+ });
267
+
268
+ const data = await response.json();
269
+ if (data.error) throw new Error(data.error);
270
+
271
+ resultDiv.innerHTML = `
272
+ <h3 class="font-bold mb-2">${data.filename} :</h3>
273
+ <p>${data.message}</p>
274
+ <!-- Ajouter ici l'affichage de l'image si le backend est modifié -->
275
+ `;
276
+ } catch (error) {
277
+ handleError(error, resultDiv);
278
+ }
279
+ });
280
+
281
+ // Traduction de document
282
+ document.getElementById('translate-document-form').addEventListener('submit', async (e) => {
283
+ e.preventDefault();
284
+ const fileInput = document.getElementById('translate-file');
285
+ const languageSelect = document.getElementById('target-language');
286
+ const resultDiv = document.getElementById('translate-document-result');
287
+ resultDiv.innerHTML = '<p class="text-blue-500">Traduction en cours...</p>';
288
+
289
+ try {
290
+ const formData = new FormData();
291
+ formData.append('file', fileInput.files[0]);
292
+ formData.append('target_language', languageSelect.value);
293
+
294
+ const response = await fetch(`${BASE_URL}/translate_document`, {
295
+ method: 'POST',
296
+ body: formData
297
+ });
298
+
299
+ const data = await response.json();
300
+ if (data.error) throw new Error(data.error);
301
+
302
+ resultDiv.innerHTML = `
303
+ <h3 class="font-bold mb-2">Traduction (${languageSelect.options[languageSelect.selectedIndex].text}) :</h3>
304
+ <p class="whitespace-pre-wrap">${data.translated_text}</p>
305
+ `;
306
+ } catch (error) {
307
+ handleError(error, resultDiv);
308
+ }
309
+ });
310
+ </script>
311
+ </body>
312
+ </html>
frontend/script.js ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // URL de base du backend
2
+ const BASE_URL = 'http://localhost:8000';
3
+
4
+ // Gestion des onglets
5
+ function openTab(tabName) {
6
+ document.querySelectorAll('.tab-content').forEach(tab => tab.classList.add('hidden'));
7
+ document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
8
+ document.getElementById(tabName).classList.remove('hidden');
9
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
10
+ }
11
+
12
+ // Fonction générique pour gérer les uploads de fichiers
13
+ function setupFileUpload(inputId) {
14
+ const input = document.getElementById(inputId);
15
+ const label = input.labels[0];
16
+ input.addEventListener('change', function() {
17
+ label.querySelector('.file-label').textContent = this.files[0]?.name || 'Sélectionner un fichier';
18
+ });
19
+ }
20
+
21
+ // Configuration des uploads
22
+ ['summarize-file', 'interpret-image-file', 'answer-question-file',
23
+ 'visualization-file', 'translate-file'].forEach(setupFileUpload);
24
+
25
+ // Gestion des erreurs communes
26
+ function handleError(error, resultDiv) {
27
+ console.error(error);
28
+ resultDiv.innerHTML = `<p class="error">Erreur : ${error.message || 'Problème de communication avec le serveur'}</p>`;
29
+ }
30
+
31
+ // Résumé de document
32
+ document.getElementById('summarize-form').addEventListener('submit', async (e) => {
33
+ e.preventDefault();
34
+ const fileInput = document.getElementById('summarize-file');
35
+ const resultDiv = document.getElementById('summarize-result');
36
+ resultDiv.innerHTML = '<p class="info">Traitement en cours...</p>';
37
+
38
+ try {
39
+ const formData = new FormData();
40
+ formData.append('file', fileInput.files[0]);
41
+
42
+ const response = await fetch(`${BASE_URL}/summarize`, {
43
+ method: 'POST',
44
+ body: formData
45
+ });
46
+
47
+ const data = await response.json();
48
+ if (data.error) throw new Error(data.error);
49
+
50
+ resultDiv.innerHTML = `
51
+ <h3>${data.filename} :</h3>
52
+ <p>${data.summary}</p>
53
+ `;
54
+ } catch (error) {
55
+ handleError(error, resultDiv);
56
+ }
57
+ });
58
+
59
+ // Légende d'image
60
+ document.getElementById('interpret-image-form').addEventListener('submit', async (e) => {
61
+ e.preventDefault();
62
+ const fileInput = document.getElementById('interpret-image-file');
63
+ const resultDiv = document.getElementById('interpret-image-result');
64
+ resultDiv.innerHTML = '<p class="info">Traitement en cours...</p>';
65
+
66
+ try {
67
+ const formData = new FormData();
68
+ formData.append('file', fileInput.files[0]);
69
+
70
+ const response = await fetch(`${BASE_URL}/interpret_image`, {
71
+ method: 'POST',
72
+ body: formData
73
+ });
74
+
75
+ const data = await response.json();
76
+ if (data.error) throw new Error(data.error);
77
+
78
+ resultDiv.innerHTML = `
79
+ <h3>${data.filename} :</h3>
80
+ <p>${data.caption}</p>
81
+ `;
82
+ } catch (error) {
83
+ handleError(error, resultDiv);
84
+ }
85
+ });
86
+
87
+ // Réponse aux questions
88
+ document.getElementById('answer-question-form').addEventListener('submit', async (e) => {
89
+ e.preventDefault();
90
+ const questionInput = document.getElementById('question-input');
91
+ const fileInput = document.getElementById('answer-question-file');
92
+ const resultDiv = document.getElementById('answer-question-result');
93
+ resultDiv.innerHTML = '<p class="info">Recherche de réponse...</p>';
94
+
95
+ try {
96
+ const formData = new FormData();
97
+ formData.append('question', questionInput.value);
98
+ if (fileInput.files[0]) formData.append('file', fileInput.files[0]);
99
+
100
+ const response = await fetch(`${BASE_URL}/answer_question`, {
101
+ method: 'POST',
102
+ body: formData
103
+ });
104
+
105
+ const data = await response.json();
106
+ if (data.error) throw new Error(data.error);
107
+
108
+ resultDiv.innerHTML = `
109
+ <h3>Réponse :</h3>
110
+ <p>${data.answer}</p>
111
+ ${data.score ? `<p class="small">Confiance : ${(data.score * 100).toFixed(1)}%</p>` : ''}
112
+ `;
113
+ } catch (error) {
114
+ handleError(error, resultDiv);
115
+ }
116
+ });
117
+
118
+ // Génération de visualisation
119
+ document.getElementById('generate-visualization-form').addEventListener('submit', async (e) => {
120
+ e.preventDefault();
121
+ const fileInput = document.getElementById('visualization-file');
122
+ const requestInput = document.getElementById('visualization-request');
123
+ const resultDiv = document.getElementById('generate-visualization-result');
124
+ resultDiv.innerHTML = '<p class="info">Génération du graphique...</p>';
125
+
126
+ try {
127
+ const formData = new FormData();
128
+ formData.append('file', fileInput.files[0]);
129
+ formData.append('request', requestInput.value);
130
+
131
+ const response = await fetch(`${BASE_URL}/generate_visualization`, {
132
+ method: 'POST',
133
+ body: formData
134
+ });
135
+
136
+ const data = await response.json();
137
+ if (data.error) throw new Error(data.error);
138
+
139
+ resultDiv.innerHTML = `
140
+ <h3>${data.filename} :</h3>
141
+ <p>${data.message}</p>
142
+ <!-- Ici, ajouter l'affichage de l'image si le backend renvoie un visuel -->
143
+ `;
144
+ } catch (error) {
145
+ handleError(error, resultDiv);
146
+ }
147
+ });
148
+
149
+ // Traduction de document
150
+ document.getElementById('translate-document-form').addEventListener('submit', async (e) => {
151
+ e.preventDefault();
152
+ const fileInput = document.getElementById('translate-file');
153
+ const languageSelect = document.getElementById('target-language');
154
+ const resultDiv = document.getElementById('translate-document-result');
155
+ resultDiv.innerHTML = '<p class="info">Traduction en cours...</p>';
156
+
157
+ try {
158
+ const formData = new FormData();
159
+ formData.append('file', fileInput.files[0]);
160
+ formData.append('target_language', languageSelect.value);
161
+
162
+ const response = await fetch(`${BASE_URL}/translate_document`, {
163
+ method: 'POST',
164
+ body: formData
165
+ });
166
+
167
+ const data = await response.json();
168
+ if (data.error) throw new Error(data.error);
169
+
170
+ resultDiv.innerHTML = `
171
+ <h3>Traduction (${languageSelect.options[languageSelect.selectedIndex].text}) :</h3>
172
+ <p>${data.translated_text}</p>
173
+ `;
174
+ } catch (error) {
175
+ handleError(error, resultDiv);
176
+ }
177
+ });
frontend/styles.css ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Réinitialisation de base */
2
+ * {
3
+ margin: 0;
4
+ padding: 0;
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ body {
9
+ background-color: #f7fafc; /* Couleur de fond équivalente */
10
+ font-family: Arial, sans-serif;
11
+ min-height: 100vh;
12
+ }
13
+
14
+ /* Conteneur principal */
15
+ .container {
16
+ max-width: 1024px;
17
+ margin: 0 auto;
18
+ padding: 2rem 1rem;
19
+ }
20
+
21
+ /* Titre principal */
22
+ h1 {
23
+ font-size: 2rem; /* Approximation de text-3xl */
24
+ font-weight: bold;
25
+ text-align: center;
26
+ color: #2d3748; /* Couleur équivalente à text-gray-800 */
27
+ margin-bottom: 2rem;
28
+ }
29
+
30
+ /* Conteneur des onglets */
31
+ .tab-container {
32
+ background-color: #fff;
33
+ border-radius: 0.5rem;
34
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
35
+ overflow: hidden;
36
+ }
37
+
38
+ /* Navigation des onglets */
39
+ .tab-nav {
40
+ display: flex;
41
+ border-bottom: 1px solid #e2e8f0;
42
+ position: relative;
43
+ }
44
+
45
+ .tab-nav .tab-btn {
46
+ flex: 1;
47
+ padding: 1rem;
48
+ background-color: #f7fafc;
49
+ border: none;
50
+ cursor: pointer;
51
+ transition: background-color 0.3s ease, transform 0.2s ease;
52
+ text-align: center;
53
+ font-size: 1rem;
54
+ }
55
+
56
+ .tab-nav .tab-btn:hover {
57
+ background-color: #edf2f7;
58
+ transform: scale(1.05);
59
+ }
60
+
61
+ .tab-nav .tab-btn.active {
62
+ background-color: #edf2f7;
63
+ font-weight: bold;
64
+ }
65
+
66
+ /* Effet de soulignement dynamique sur les onglets */
67
+ .tab-nav .tab-btn {
68
+ position: relative;
69
+ }
70
+
71
+ .tab-nav .tab-btn::after {
72
+ content: "";
73
+ position: absolute;
74
+ left: 50%;
75
+ bottom: 0;
76
+ width: 0;
77
+ height: 3px;
78
+ background-color: #4299e1;
79
+ transition: width 0.3s ease, left 0.3s ease;
80
+ }
81
+
82
+ .tab-nav .tab-btn:hover::after,
83
+ .tab-nav .tab-btn.active::after {
84
+ width: 100%;
85
+ left: 0;
86
+ }
87
+
88
+ /* Contenu des onglets */
89
+ .tab-content-wrapper {
90
+ padding: 1.5rem;
91
+ }
92
+
93
+ .tab-content {
94
+ margin-bottom: 1rem;
95
+ }
96
+
97
+ /* Masquer les éléments */
98
+ .hidden {
99
+ display: none;
100
+ }
101
+
102
+ /* Styles des formulaires */
103
+ .form {
104
+ margin-bottom: 1rem;
105
+ }
106
+
107
+ .form input[type="text"],
108
+ .form textarea,
109
+ .form select {
110
+ width: 100%;
111
+ padding: 0.5rem;
112
+ border: 1px solid #cbd5e0;
113
+ border-radius: 0.25rem;
114
+ }
115
+
116
+ .textarea {
117
+ resize: vertical;
118
+ min-height: 80px;
119
+ }
120
+
121
+ /* Input type file caché */
122
+ .hidden-file {
123
+ display: none;
124
+ }
125
+
126
+ /* Label pour le chargement de fichier */
127
+ .file-upload {
128
+ display: block;
129
+ width: 100%;
130
+ padding: 1rem;
131
+ border: 2px dashed #cbd5e0;
132
+ border-radius: 0.5rem;
133
+ text-align: center;
134
+ cursor: pointer;
135
+ transition: background-color 0.3s ease;
136
+ margin-bottom: 1rem;
137
+ }
138
+
139
+ .file-upload:hover {
140
+ background-color: #f7fafc;
141
+ }
142
+
143
+ /* Boutons */
144
+ .btn {
145
+ width: 100%;
146
+ padding: 0.75rem;
147
+ border: none;
148
+ border-radius: 0.25rem;
149
+ color: #fff;
150
+ cursor: pointer;
151
+ transition: background-color 0.3s ease;
152
+ font-size: 1rem;
153
+ }
154
+
155
+ /* Boutons de couleur */
156
+ .btn-blue {
157
+ background-color: #4299e1;
158
+ }
159
+
160
+ .btn-blue:hover {
161
+ background-color: #3182ce;
162
+ }
163
+
164
+ .btn-green {
165
+ background-color: #48bb78;
166
+ }
167
+
168
+ .btn-green:hover {
169
+ background-color: #38a169;
170
+ }
171
+
172
+ .btn-purple {
173
+ background-color: #9f7aea;
174
+ }
175
+
176
+ .btn-purple:hover {
177
+ background-color: #805ad5;
178
+ }
179
+
180
+ .btn-indigo {
181
+ background-color: #667eea;
182
+ }
183
+
184
+ .btn-indigo:hover {
185
+ background-color: #5a67d8;
186
+ }
187
+
188
+ .btn-pink {
189
+ background-color: #ed64a6;
190
+ }
191
+
192
+ .btn-pink:hover {
193
+ background-color: #d53f8c;
194
+ }
195
+
196
+ /* Boîte de résultat */
197
+ .result-box {
198
+ margin-top: 1rem;
199
+ padding: 1rem;
200
+ background-color: #f7fafc;
201
+ border-radius: 0.25rem;
202
+ }
203
+
204
+ /* Sélecteur de formulaire */
205
+ .select {
206
+ width: 100%;
207
+ padding: 0.5rem;
208
+ border: 1px solid #cbd5e0;
209
+ border-radius: 0.25rem;
210
+ }
211
+
212
+ /* Navbar avec animation d'apparition */
213
+ nav {
214
+ filter: drop-shadow(0.25rem 0.25rem 0.25rem rgba(0, 0, 0, 0.3));
215
+ width: 100%;
216
+ max-width: 14rem;
217
+ opacity: 0;
218
+ transform: translateY(-20px);
219
+ animation: fadeInDown 0.5s ease-out forwards;
220
+ }
221
+
222
+ /* Animation keyframes pour la navbar */
223
+ @keyframes fadeInDown {
224
+ from {
225
+ opacity: 0;
226
+ transform: translateY(-20px);
227
+ }
228
+ to {
229
+ opacity: 1;
230
+ transform: translateY(0);
231
+ }
232
+ }
233
+
234
+ /* Préférence de réduction des animations */
235
+ @media (prefers-reduced-motion: reduce) {
236
+ nav * {
237
+ transition: 5s !important;
238
+ animation: 10ms !important;
239
+ }
240
+ }
241
+