├── README.md
├── asset
├── Frame_1_9.png
└── Frame_4_4.png
├── assets
├── Frame_1_9.png
├── Frame_4_4.png
├── logo.svg
├── pyde.jpg
├── python.png
└── python_ico.png
├── faq
├── faq.css
├── faq.html
└── faq.js
├── ide
├── css
│ └── style.css
├── ide.html
└── js
│ └── ide
│ ├── chat.js
│ ├── config.js
│ ├── editor.js
│ ├── fileManager.js
│ ├── main.js
│ ├── modal.js
│ ├── pyodide.js
│ ├── suggestions.js
│ └── ui.js
├── index.html
└── src
├── css
└── styles.css
└── js
└── script.js
/README.md:
--------------------------------------------------------------------------------
1 | # Editeur de Code en Ligne Python (IDE) 🐍
2 |
3 | ## Liens 🔗
4 | # https://pythonide.netlify.app
5 | # https://discord.gg/X7cZE28dAb
6 |
7 | ## Introduction 🚀
8 | L'Editeur de Code en Ligne est une application web permettant aux utilisateurs d'écrire, de compiler et d'exécuter du code Python *(pour le moment)* directement depuis leur navigateur. Cet IDE en ligne est conçu pour offrir une expérience de développement fluide et rapide, le design est pensé pour qu'il soit intuitif.
9 |
10 | ## Fonctionnalités ✨
11 | - **Exécution instantanée :** Exécutez votre code Python en temps réel sans besoin d'installation, directement depuis votre navigateur.
12 | - **Assistant IA :** Une Intelligence Artificielle accéssible directement dans l'IDE
13 | - **Interface simple et intuitive :** Conçu pour les développeurs débutants comme avancés, avec une interface claire et sans distraction.
14 |
15 | ## Installation 🛠️
16 | Si vous souhaitez installer l'application localement, voici les étapes à suivre :
17 |
18 | ### Avec GIT 🧑💻
19 | 1. Clonez ce dépôt :
20 | ```bash
21 | $ git clone https://github.com/F3kri/PythonIDE.git
22 | ```
23 | 2. Accédez au dossier du projet :
24 | ```bash
25 | $ cd PythonIDE
26 | ```
27 | 3. Lancez l'application :
28 | ```Ouvrez simplement le fichier index.html, ou bien utilisez un serveur web.```
29 |
30 | ### Sans GIT 📦
31 | 1. Téléchargez ce dépôt :
32 | Cliquez sur le bouton "Code" en haut à droite, puis sélectionnez "Télécharger en .ZIP".
33 | 2. Extraire l'archive :
34 | Utilisez WinRAR, 7zip, ou un autre outil pour extraire l'archive, puis accédez au dossier extrait.
35 | 3. Lancez l'application :
36 | ```Ouvez simplement l'index.html, ou bien utiliser un serveur web.```
37 |
38 | ## Contribuer 🤝
39 | Les contributions sont les bienvenues ! Si vous souhaitez ajouter de nouvelles fonctionnalités ou corriger des bugs, veuillez suivre les étapes suivantes :
40 | 1. Fork ce dépôt.
41 | 2. Créez une branche pour votre fonctionnalité (`git checkout -b feature/nom-de-la-fonctionnalité`, *ou utilisez l'interface github directemnet*).
42 | 3. Commitez vos changements (`git commit -m 'Ajout d'une fonctionnalité ou fix'`).
43 | 4. Push sur votre branche (`git push origin feature/nom-de-la-fonctionnalité`).
44 | 5. Ouvrez une pull request avec un détail clair sur les changements.
45 |
46 |
--------------------------------------------------------------------------------
/asset/Frame_1_9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F3kri/PythonIDE/2ce9f5eb030d59b74eb494dc3590d60dca33b091/asset/Frame_1_9.png
--------------------------------------------------------------------------------
/asset/Frame_4_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F3kri/PythonIDE/2ce9f5eb030d59b74eb494dc3590d60dca33b091/asset/Frame_4_4.png
--------------------------------------------------------------------------------
/assets/Frame_1_9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F3kri/PythonIDE/2ce9f5eb030d59b74eb494dc3590d60dca33b091/assets/Frame_1_9.png
--------------------------------------------------------------------------------
/assets/Frame_4_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F3kri/PythonIDE/2ce9f5eb030d59b74eb494dc3590d60dca33b091/assets/Frame_4_4.png
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/assets/pyde.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F3kri/PythonIDE/2ce9f5eb030d59b74eb494dc3590d60dca33b091/assets/pyde.jpg
--------------------------------------------------------------------------------
/assets/python.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F3kri/PythonIDE/2ce9f5eb030d59b74eb494dc3590d60dca33b091/assets/python.png
--------------------------------------------------------------------------------
/assets/python_ico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/F3kri/PythonIDE/2ce9f5eb030d59b74eb494dc3590d60dca33b091/assets/python_ico.png
--------------------------------------------------------------------------------
/faq/faq.css:
--------------------------------------------------------------------------------
1 | /* FAQ Styles */
2 | .faq-hero {
3 | padding: 8rem 0 4rem;
4 | background: linear-gradient(to bottom, var(--dark-bg), #1a1a1a);
5 | text-align: center;
6 | }
7 |
8 | /* Variables et styles de base */
9 | :root {
10 | --primary: #6366f1;
11 | --dark: #0f172a;
12 | --text: #f8fafc;
13 | }
14 |
15 | body {
16 | background-color: var(--dark);
17 | color: var(--text);
18 | font-family: 'Inter', sans-serif;
19 | }
20 |
21 | /* Styles du contenu FAQ */
22 | .faq-content {
23 | max-width: 1200px;
24 | margin: 100px auto 0;
25 | padding: 2rem 20px;
26 | position: relative;
27 | z-index: 1;
28 | background: transparent;
29 | backdrop-filter: blur(10px);
30 | }
31 |
32 | .faq-content::before,
33 | .faq-content::after,
34 | .decorative-circle-1,
35 | .decorative-circle-2 {
36 | content: '';
37 | position: fixed;
38 | border-radius: 50%;
39 | filter: blur(100px);
40 | opacity: 0.15;
41 | z-index: -1;
42 | }
43 |
44 | .faq-content::before {
45 | content: '';
46 | position: absolute;
47 | width: 100%;
48 | height: 1px;
49 | background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.3), transparent);
50 | top: -50px;
51 | left: 0;
52 | }
53 |
54 | .faq-content::after {
55 | background: #6366f1;
56 | bottom: 10%;
57 | right: 5%;
58 | animation: float 10s ease-in-out infinite reverse;
59 | }
60 |
61 | .decorative-circle-1 {
62 | width: 400px;
63 | height: 400px;
64 | background: #4f46e5;
65 | top: 40%;
66 | left: -10%;
67 | animation: float 12s ease-in-out infinite;
68 | }
69 |
70 | .decorative-circle-2 {
71 | width: 350px;
72 | height: 350px;
73 | background: #7c3aed;
74 | top: 60%;
75 | right: -5%;
76 | animation: float 15s ease-in-out infinite reverse;
77 | }
78 |
79 | @keyframes float {
80 | 0%, 100% {
81 | transform: translateY(0) scale(1);
82 | }
83 | 50% {
84 | transform: translateY(-20px) scale(1.05);
85 | }
86 | }
87 |
88 | @keyframes twinkle {
89 | 0%, 100% { opacity: 0; }
90 | 50% { opacity: 1; }
91 | }
92 |
93 | .stars {
94 | position: fixed;
95 | width: 100%;
96 | height: 100%;
97 | top: 0;
98 | left: 0;
99 | pointer-events: none;
100 | z-index: -1;
101 | }
102 |
103 | .star {
104 | position: absolute;
105 | width: 2px;
106 | height: 2px;
107 | background: rgba(255, 255, 255, 0.5);
108 | border-radius: 50%;
109 | }
110 |
111 | .star:nth-child(1) { top: 15%; left: 10%; animation: twinkle 3s ease-in-out infinite; }
112 | .star:nth-child(2) { top: 25%; left: 20%; animation: twinkle 4s ease-in-out infinite 0.3s; }
113 | .star:nth-child(3) { top: 35%; left: 35%; animation: twinkle 3.5s ease-in-out infinite 0.7s; }
114 | .star:nth-child(4) { top: 45%; left: 40%; animation: twinkle 4s ease-in-out infinite 1s; }
115 | .star:nth-child(5) { top: 55%; left: 55%; animation: twinkle 3s ease-in-out infinite 1.3s; }
116 | .star:nth-child(6) { top: 65%; left: 60%; animation: twinkle 3.5s ease-in-out infinite 1.6s; }
117 | .star:nth-child(7) { top: 75%; left: 75%; animation: twinkle 4s ease-in-out infinite 1.9s; }
118 | .star:nth-child(8) { top: 85%; left: 80%; animation: twinkle 3s ease-in-out infinite 2.2s; }
119 | .star:nth-child(9) { top: 20%; left: 85%; animation: twinkle 3.5s ease-in-out infinite 2.5s; }
120 | .star:nth-child(10) { top: 30%; left: 90%; animation: twinkle 4s ease-in-out infinite 2.8s; }
121 | .star:nth-child(11) { top: 40%; left: 15%; animation: twinkle 3s ease-in-out infinite 3.1s; }
122 | .star:nth-child(12) { top: 50%; left: 25%; animation: twinkle 3.5s ease-in-out infinite 3.4s; }
123 | .star:nth-child(13) { top: 60%; left: 45%; animation: twinkle 4s ease-in-out infinite 3.7s; }
124 | .star:nth-child(14) { top: 70%; left: 65%; animation: twinkle 3s ease-in-out infinite 4s; }
125 | .star:nth-child(15) { top: 80%; left: 70%; animation: twinkle 3.5s ease-in-out infinite 4.3s; }
126 | .star:nth-child(16) { top: 15%; left: 95%; animation: twinkle 4s ease-in-out infinite 4.6s; }
127 | .star:nth-child(17) { top: 25%; left: 5%; animation: twinkle 3s ease-in-out infinite 4.9s; }
128 | .star:nth-child(18) { top: 45%; left: 85%; animation: twinkle 3.5s ease-in-out infinite 5.2s; }
129 | .star:nth-child(19) { top: 75%; left: 25%; animation: twinkle 4s ease-in-out infinite 5.5s; }
130 | .star:nth-child(20) { top: 85%; left: 45%; animation: twinkle 3s ease-in-out infinite 5.8s; }
131 |
132 | body::before {
133 | content: '';
134 | position: fixed;
135 | top: 0;
136 | left: 0;
137 | width: 100%;
138 | height: 100%;
139 | background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAElBMVEUAAAD8/vz08vT09PT8+vz///+Tb6O0AAAABXRSTlMoKCh8/NfBiNUAAAA8SURBVDjLY2AYBaNg2AJGRkYGDECMxNTAwMgIYTMyEqUP2R5GRtLsYWQk0T5GRtLsYWAYDtE5CkbBwAIAulwEBnq4Z+QAAAAASUVORK5CYII=');
140 | opacity: 0.05;
141 | pointer-events: none;
142 | z-index: -1;
143 | }
144 |
145 | h1 {
146 | font-size: 3.5rem;
147 | font-family: 'Poppins', sans-serif;
148 | font-weight: 700;
149 | text-align: center;
150 | margin-bottom: 0.5rem;
151 | background: linear-gradient(45deg, #818cf8, #6366f1);
152 | -webkit-background-clip: text;
153 | background-clip: text;
154 | color: transparent;
155 | letter-spacing: -0.02em;
156 | }
157 |
158 | .subtitle {
159 | text-align: center;
160 | color: #94a3b8;
161 | margin-bottom: 3rem;
162 | font-size: 1.1rem;
163 | font-family: 'Inter', sans-serif;
164 | font-weight: 400;
165 | }
166 |
167 | /* Liste FAQ */
168 | .faq-list {
169 | max-width: 800px;
170 | margin: 0 auto;
171 | }
172 |
173 | .faq-item {
174 | background: rgba(15, 23, 42, 0.8);
175 | border: 1px solid rgba(255, 255, 255, 0.1);
176 | border-radius: 8px;
177 | margin-bottom: 0.75rem;
178 | overflow: hidden;
179 | backdrop-filter: blur(5px);
180 | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
181 | }
182 |
183 | .question {
184 | padding: 0.75rem 1.25rem;
185 | display: flex;
186 | justify-content: space-between;
187 | align-items: center;
188 | cursor: pointer;
189 | transition: background-color 0.3s ease;
190 | }
191 |
192 | .question:hover {
193 | background: rgba(255, 255, 255, 0.01);
194 | }
195 |
196 | .question h3 {
197 | margin: 0;
198 | font-size: 0.875rem;
199 | font-family: 'Inter', sans-serif;
200 | font-weight: 500;
201 | color: #f8fafc;
202 | }
203 |
204 | .question i {
205 | transition: transform 0.3s ease;
206 | color: #94a3b8;
207 | font-size: 1rem;
208 | }
209 |
210 | .answer {
211 | padding: 0 1.25rem;
212 | max-height: 0;
213 | overflow: hidden;
214 | transition: all 0.3s ease;
215 | }
216 |
217 | .answer p {
218 | color: #94a3b8;
219 | font-family: 'Inter', sans-serif;
220 | font-weight: 400;
221 | font-size: 1rem;
222 | line-height: 1.6;
223 | margin: 0;
224 | }
225 |
226 | .answer ul {
227 | list-style: none;
228 | padding: 0;
229 | margin: 0;
230 | }
231 |
232 | .answer ul li {
233 | color: #94a3b8;
234 | padding: 0.4rem 0;
235 | position: relative;
236 | padding-left: 1.5rem;
237 | }
238 |
239 | .answer ul li::before {
240 | content: '';
241 | position: absolute;
242 | left: 0;
243 | top: 50%;
244 | transform: translateY(-50%);
245 | width: 6px;
246 | height: 6px;
247 | border-radius: 50%;
248 | background: #6366f1;
249 | }
250 |
251 | .faq-item.active .answer {
252 | padding: 0.5rem 1.25rem 1rem 1.25rem;
253 | max-height: 500px;
254 | }
255 |
256 | .faq-item.active .question i {
257 | transform: rotate(180deg);
258 | }
259 |
260 | .faq-item.active {
261 | background: rgba(15, 23, 42, 0.8);
262 | border-color: rgba(99, 102, 241, 0.2);
263 | }
264 |
265 | /* Éléments décoratifs */
266 | .hero-shapes {
267 | position: fixed;
268 | top: 0;
269 | left: 0;
270 | width: 100%;
271 | height: 100%;
272 | overflow: hidden;
273 | pointer-events: none;
274 | z-index: -1;
275 | }
276 |
277 | .shape {
278 | position: absolute;
279 | filter: blur(100px);
280 | opacity: 0.5;
281 | animation: float 8s infinite;
282 | }
283 |
284 | .shape-1 {
285 | top: 15%;
286 | left: 10%;
287 | width: 300px;
288 | height: 300px;
289 | background: #818cf8;
290 | }
291 |
292 | .shape-2 {
293 | bottom: 20%;
294 | right: 15%;
295 | width: 400px;
296 | height: 400px;
297 | background: #6366f1;
298 | animation-delay: 2s;
299 | }
300 |
301 | .shape-3 {
302 | top: 50%;
303 | left: 50%;
304 | width: 350px;
305 | height: 350px;
306 | background: #4f46e5;
307 | animation-delay: 4s;
308 | }
309 |
310 | /* Snippets de code */
311 | .code-snippets {
312 | position: fixed;
313 | width: 100%;
314 | height: 100%;
315 | pointer-events: none;
316 | z-index: -1;
317 | }
318 |
319 | .snippet {
320 | position: absolute;
321 | padding: 0.5rem 1rem;
322 | background: rgba(99, 102, 241, 0.1);
323 | border: 1px solid rgba(99, 102, 241, 0.2);
324 | border-radius: 8px;
325 | font-family: 'JetBrains Mono', monospace;
326 | color: var(--text);
327 | opacity: 0.6;
328 | animation: floatSnippet 10s infinite;
329 | }
330 |
331 | .snippet:nth-child(1) { top: 20%; left: 15%; }
332 | .snippet:nth-child(2) { top: 60%; right: 10%; animation-delay: 3s; }
333 | .snippet:nth-child(3) { top: 80%; left: 30%; animation-delay: 6s; }
334 |
335 | /* Points de code */
336 | .code-dots {
337 | position: fixed;
338 | width: 100%;
339 | height: 100%;
340 | background-image: radial-gradient(circle at center, rgba(99, 102, 241, 0.1) 1px, transparent 1px);
341 | background-size: 30px 30px;
342 | pointer-events: none;
343 | z-index: -2;
344 | opacity: 0.5;
345 | }
346 |
347 | /* Animations */
348 | @keyframes floatSnippet {
349 | 0%, 100% { transform: translateY(0) rotate(0deg); }
350 | 50% { transform: translateY(-15px) rotate(2deg); }
351 | }
352 |
353 | /* Media Queries */
354 | @media (max-width: 768px) {
355 | h1 { font-size: 2.5rem; }
356 | .faq-content { padding: 0 1rem; }
357 | .question h3 { font-size: 0.8rem; }
358 | }
359 |
360 | /* ... le reste du CSS ... */
361 |
362 | /* ... rest of existing code ... */
--------------------------------------------------------------------------------
/faq/faq.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | FAQ - PyDE
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
print("FAQ")
20 |
help(PyDE)
21 |
info()
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
72 |
73 |
74 |
75 | FAQ
76 | Trouvez rapidement des réponses à vos questions
77 |
78 |
79 |
80 |
81 |
Comment puis-je commencer à utiliser PyDE ?
82 |
83 |
84 |
85 |
Cliquez simplement sur le bouton "App Python" dans la barre de navigation. Vous serez redirigé vers notre éditeur en ligne où vous pourrez commencer à coder immédiatement. Aucune installation n'est nécessaire !
86 |
87 |
88 |
89 |
90 |
91 |
Est-ce que PyDE est gratuit ?
92 |
93 |
94 |
95 |
Oui, PyDE est totalement gratuit et open source ! Vous pouvez l'utiliser sans limite et même contribuer à son développement via GitHub.
96 |
97 |
98 |
99 |
100 |
101 |
Mes codes sont-ils sauvegardés ?
102 |
103 |
104 |
105 |
Actuellement, les codes sont sauvegardés localement dans votre navigateur. Nous travaillons sur une fonction de sauvegarde en ligne pour une prochaine mise à jour.
106 |
107 |
108 |
109 |
110 |
111 |
Quelles fonctionnalités sont disponibles ?
112 |
113 |
114 |
115 |
116 | Assistant IA intégré pour l'aide à la programmation
117 | Coloration syntaxique
118 | Exécution du code Python en temps réel
119 | Console interactive
120 | Sauvegarde locale automatique
121 | Suggestions de code intelligentes
122 | Thèmes personnalisables
123 |
124 |
125 |
126 |
127 |
128 |
129 |
Comment fonctionne l'Assistant IA ?
130 |
131 |
132 |
133 |
134 | Assistant IA intégré pour l'aide à la programmation
135 | Coloration syntaxique
136 | Exécution du code Python en temps réel
137 | Console interactive
138 | Sauvegarde locale automatique
139 | Suggestions de code intelligentes
140 | Thèmes personnalisables
141 |
142 |
143 |
144 |
145 |
146 |
147 |
De qui est composé l'équipe de développement de PyDE ?
148 |
149 |
150 |
151 |
152 | F3kri ( Fondateur et Développeur de PyDE )
153 | Codealuxz ( Développeur de PyDE )
154 | FireBall ( Designer de PyDE )
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/faq/faq.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | // Gestion des questions/réponses
3 | const faqItems = document.querySelectorAll('.faq-item');
4 |
5 | faqItems.forEach(item => {
6 | const question = item.querySelector('.question');
7 |
8 | question.addEventListener('click', () => {
9 | // Fermer les autres questions
10 | faqItems.forEach(otherItem => {
11 | if (otherItem !== item && otherItem.classList.contains('active')) {
12 | otherItem.classList.remove('active');
13 | }
14 | });
15 |
16 | // Toggle l'état actif
17 | item.classList.toggle('active');
18 | });
19 | });
20 | });
--------------------------------------------------------------------------------
/ide/css/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary-color: #6366f1;
3 | --primary-hover: #4f46e5;
4 | --secondary-color: #22c55e;
5 | --error-color: #ef4444;
6 | --warning-color: #f59e0b;
7 | --bg-color: #ced3d9;
8 | --text-color: #1f2937;
9 | --sidebar-bg: #d9dada;
10 | --editor-bg: #d7d9df;
11 | --console-bg: #d8dee3;
12 | --border-radius: 1em;
13 | --box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
14 | --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
15 | --glass-bg: rgba(255, 255, 255, 0.7);
16 | --glass-border: 1px solid rgba(255, 255, 255, 0.2);
17 | --animate-duration: 0.3s;
18 | --animate-delay: 0s;
19 | --animate-repeat: 1;
20 | --gradient-primary: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
21 | --gradient-hover: linear-gradient(135deg, var(--primary-hover), var(--primary-color));
22 | --neon-glow: 0 0 10px rgba(99, 102, 241, 0.3), 0 0 20px rgba(99, 102, 241, 0.2), 0 0 30px rgba(99, 102, 241, 0.1);
23 | --glass-blur: blur(12px);
24 | }
25 |
26 | * {
27 | margin: 0;
28 | padding: 0;
29 | box-sizing: border-box;
30 | transition: background-color 0.3s, color 0.3s, transform 0.3s, opacity 0.3s;
31 | }
32 |
33 | [data-theme="dark"] {
34 | --primary-color: #818cf8;
35 | --primary-hover: #6366f1;
36 | --secondary-color: #34d399;
37 | --bg-color: #0f172a;
38 | --text-color: #e2e8f0;
39 | --sidebar-bg: rgba(30, 41, 59, 0.7);
40 | --editor-bg: #1e293b;
41 | --console-bg: rgba(30, 41, 59, 0.7);
42 | --glass-bg: rgba(30, 41, 59, 0.7);
43 | --glass-border: 1px solid rgba(255, 255, 255, 0.1);
44 | }
45 |
46 | body {
47 | font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
48 | background: var(--bg-color);
49 | color: var(--text-color);
50 | line-height: 1.6;
51 | background-image: radial-gradient(circle at top right,
52 | rgba(99, 102, 241, 0.15),
53 | transparent 40%),
54 | radial-gradient(circle at bottom left,
55 | rgba(34, 197, 94, 0.15),
56 | transparent 40%);
57 | background-attachment: fixed;
58 | transition: background-color 0.5s ease, color 0.5s ease;
59 | }
60 |
61 | .container {
62 | display: flex;
63 | height: 100vh;
64 | gap: 1.5rem;
65 | padding: 1.5rem;
66 | background: var(--bg-color);
67 | animation: fadeIn 0.5s ease-out;
68 | }
69 |
70 | .sidebar {
71 | width: 300px;
72 | background-color: var(--sidebar-bg);
73 | padding: 1.5rem;
74 | border-radius: var(--border-radius);
75 | box-shadow: var(--box-shadow);
76 | display: flex;
77 | flex-direction: column;
78 | gap: 1rem;
79 | background: var(--glass-bg);
80 | margin: 0.5rem;
81 | backdrop-filter: var(--glass-blur);
82 | border: var(--glass-border);
83 | position: relative;
84 | overflow: hidden;
85 | transition: width 0.3s ease, padding 0.3s ease;
86 | }
87 |
88 | .sidebar.collapsed {
89 | width: 48px;
90 | padding: 1rem 0.5rem;
91 | }
92 |
93 | .sidebar.collapsed .sidebar-header h3,
94 | .sidebar.collapsed #newCont,
95 | .sidebar.collapsed #jtcButton,
96 | .sidebar.collapsed #impFile,
97 | .sidebar.collapsed #fileExplorer {
98 | display: none !important;
99 | }
100 |
101 | .sidebar.collapsed #toggleSidebar {
102 | margin: 0 auto;
103 | }
104 |
105 | .sidebar.collapsed #toggleSidebar i {
106 | transform: rotate(180deg);
107 | }
108 |
109 | .sidebar-header {
110 | display: flex;
111 | flex-direction: column;
112 | gap: 1rem;
113 | }
114 |
115 | .sidebar-header h3 {
116 | font-size: 1.25rem;
117 | color: var(--primary-color);
118 | margin-bottom: 0;
119 | font-weight: 600;
120 | position: relative;
121 | overflow: hidden;
122 | }
123 |
124 | .sidebar-header h3::after {
125 | content: '';
126 | position: absolute;
127 | bottom: 0;
128 | left: 0;
129 | width: 100%;
130 | height: 2px;
131 | background: var(--primary-color);
132 | transform: translateX(-100%);
133 | animation: slideRight 0.3s ease-out forwards;
134 | }
135 |
136 | .sidebar button {
137 | width: 100%;
138 | padding: 0.875rem;
139 | border: none;
140 | border-radius: var(--border-radius);
141 | background-color: var(--primary-color);
142 | color: white;
143 | cursor: pointer;
144 | display: flex;
145 | align-items: center;
146 | justify-content: center;
147 | gap: 0.5rem;
148 | font-weight: 500;
149 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
150 | background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
151 | position: relative;
152 | overflow: hidden;
153 | }
154 |
155 | .sidebar button::after {
156 | content: '';
157 | position: absolute;
158 | top: 0;
159 | left: -100%;
160 | width: 100%;
161 | height: 100%;
162 | background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
163 | transition: 0.5s;
164 | }
165 |
166 | .sidebar button:hover::after {
167 | left: 100%;
168 | }
169 |
170 | .sidebar button:hover {
171 | transform: translateY(-2px);
172 | box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
173 | animation: glow 2s infinite;
174 | }
175 |
176 | .sidebar button:active {
177 | transform: scale(0.95);
178 | }
179 |
180 | input[type="file"] {
181 | display: none;
182 | }
183 |
184 | .sidebar .custom-file-upload {
185 | width: 100%;
186 | padding: 0.875rem;
187 | border: none;
188 | border-radius: var(--border-radius);
189 | background-color: var(--primary-color);
190 | color: white;
191 | cursor: pointer;
192 | display: flex;
193 | align-items: center;
194 | justify-content: center;
195 | gap: 0.5rem;
196 | font-weight: 500;
197 | height: 43.33px !important;
198 | font-size: 13.3px !important;
199 | font-family: Arial, Helvetica, sans-serif;
200 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
201 | background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
202 | position: relative;
203 | overflow: hidden;
204 | }
205 |
206 | .sidebar .custom-file-upload::after {
207 | content: '';
208 | position: absolute;
209 | top: 0;
210 | left: -100%;
211 | width: 100%;
212 | height: 100%;
213 | background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
214 | transition: 0.5s;
215 | }
216 |
217 | .sidebar .custom-file-upload:hover::after {
218 | left: 100%;
219 | }
220 |
221 | .sidebar .custom-file-upload:hover {
222 | transform: translateY(-2px);
223 | box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
224 | animation: glow 2s infinite;
225 | }
226 |
227 | .sidebar .custom-file-upload:active {
228 | transform: scale(0.95);
229 | }
230 |
231 | .main-content {
232 | flex: 1;
233 | padding: 0.5em;
234 | display: flex;
235 | flex-direction: column;
236 | gap: 1.5rem;
237 | background-color: var(--bg-color);
238 | border-radius: var(--border-radius);
239 | overflow: visible;
240 | }
241 |
242 | .toolbar {
243 | padding: 1rem 1.5rem;
244 | display: flex;
245 | align-items: center;
246 | border-radius: 1em;
247 | justify-content: flex-end;
248 | gap: 0.5rem;
249 | border-bottom: 1px solid rgba(99, 102, 241, 0.1);
250 | background: var(--glass-bg);
251 | backdrop-filter: var(--glass-blur);
252 | border: var(--glass-border);
253 | position: relative;
254 | overflow: hidden;
255 | }
256 |
257 | .toolbar button {
258 | padding: 0.75rem;
259 | width: auto;
260 | background: var(--gradient-primary);
261 | color: white;
262 | border: none;
263 | border-radius: var(--border-radius);
264 | cursor: pointer;
265 | display: flex;
266 | align-items: center;
267 | justify-content: center;
268 | position: relative;
269 | overflow: hidden;
270 | transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
271 | white-space: nowrap;
272 | box-shadow: var(--box-shadow);
273 | }
274 |
275 | .toolbar button span {
276 | position: static;
277 | transform: translateX(0);
278 | margin-left: 0.5rem;
279 | transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
280 | }
281 |
282 | .toolbar select {
283 | appearance: none;
284 | -webkit-appearance: none;
285 | -moz-appearance: none;
286 | width: 42px;
287 | padding: 0.75rem;
288 | background: var(--gradient-primary);
289 | color: white;
290 | border: none;
291 | border-radius: var(--border-radius);
292 | cursor: pointer;
293 | transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
294 | box-shadow: var(--box-shadow);
295 | text-align: center;
296 | font-family: inherit;
297 | position: relative;
298 | overflow: hidden;
299 | }
300 |
301 | .toolbar select:hover {
302 | width: 150px;
303 | padding-right: 2.5rem;
304 | }
305 |
306 | .toolbar select {
307 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='5'%3E%3C/circle%3E%3Cline x1='12' y1='1' x2='12' y2='3'%3E%3C/line%3E%3Cline x1='12' y1='21' x2='12' y2='23'%3E%3C/line%3E%3Cline x1='4.22' y1='4.22' x2='5.64' y2='5.64'%3E%3C/line%3E%3Cline x1='18.36' y1='18.36' x2='19.78' y2='19.78'%3E%3C/line%3E%3Cline x1='1' y1='12' x2='3' y2='12'%3E%3C/line%3E%3Cline x1='21' y1='12' x2='23' y2='12'%3E%3C/line%3E%3Cline x1='4.22' y1='19.78' x2='5.64' y2='18.36'%3E%3C/line%3E%3Cline x1='18.36' y1='5.64' x2='19.78' y2='4.22'%3E%3C/line%3E%3C/svg%3E");
308 | background-repeat: no-repeat;
309 | background-position: center;
310 | background-size: 20px;
311 | }
312 |
313 | .toolbar select:hover {
314 | background-position: calc(100% - 10px) center;
315 | }
316 |
317 | .toolbar button:hover,
318 | .toolbar select:hover {
319 | transform: translateY(-3px);
320 | box-shadow: var(--neon-glow);
321 | }
322 |
323 | .toolbar select option {
324 | background-color: var(--bg-color);
325 | color: var(--text-color);
326 | padding: 10px;
327 | }
328 |
329 | .line-gutter {
330 | width: 3rem;
331 | background-color: var(--sidebar-bg);
332 | border-right: 1px solid rgba(99, 102, 241, 0.1);
333 | }
334 |
335 | .editor-container {
336 | display: flex;
337 | flex: 1;
338 | background-color: var(--editor-bg);
339 | border-radius: var(--border-radius);
340 | overflow: hidden;
341 | background: var(--glass-bg);
342 | backdrop-filter: var(--glass-blur);
343 | border: var(--glass-border);
344 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
345 | position: relative;
346 | transition: all 0.3s ease;
347 | }
348 |
349 | .line-numbers-container {
350 | position: absolute;
351 | top: 0;
352 | left: 0;
353 | width: 48px;
354 | height: 100%;
355 | overflow: hidden;
356 | padding: 22px 5px;
357 | color: var(--text-color);
358 | line-height: 1.615;
359 | text-align: right;
360 | padding-bottom: 300px;
361 | }
362 |
363 | .line-numbers-container span {
364 | display: block;
365 | }
366 |
367 | .main-content.collapsedm .editor-container {
368 | flex: 0.5;
369 | }
370 |
371 | .main-content.collapsedm .console {
372 | flex: 1;
373 | }
374 |
375 | /* CSS de la fenêtre */
376 | .ai-chat-window {
377 | position: fixed;
378 | bottom: 20px;
379 | right: 20px;
380 | width: 450px;
381 | height: 500px;
382 | background: var(--glass-bg);
383 | border: var(--glass-border);
384 | border-radius: var(--border-radius);
385 | display: flex;
386 | flex-direction: column;
387 | backdrop-filter: var(--glass-blur);
388 | box-shadow: var(--box-shadow);
389 | z-index: 1000;
390 | display: none;
391 | user-select: none;
392 | }
393 |
394 | .chat-header {
395 | padding: 1rem;
396 | display: flex;
397 | justify-content: space-between;
398 | align-items: center;
399 | border-bottom: 1px solid rgba(99, 102, 241, 0.1);
400 | cursor: move;
401 | background: rgba(99, 102, 241, 0.1);
402 | }
403 |
404 | .chat-header h3 {
405 | margin: 0;
406 | display: flex;
407 | align-items: center;
408 | gap: 0.5rem;
409 | color: var(--primary-color);
410 | }
411 |
412 | .chat-messages {
413 | flex: 1;
414 | overflow-y: auto;
415 | padding: 1rem;
416 | user-select: text;
417 | }
418 |
419 | .spinner {
420 | width: 3em;
421 | height: 3em;
422 | cursor: not-allowed;
423 | border-radius: 50%;
424 | border: 2px solid #444;
425 | box-shadow: -10px -10px 10px #6359f8, 0px -10px 10px 0px #9c32e2, 10px -10px 10px #f36896, 10px 0 10px #ff0b0b, 10px 10px 10px 0px#ff5500, 0 10px 10px 0px #ff9500, -10px 10px 10px 0px #ffb700;
426 | animation: rot55 0.7s linear infinite;
427 | }
428 |
429 | .spinnerin {
430 | border: 2px solid #444;
431 | width: 1.5em;
432 | height: 1.5em;
433 | border-radius: 50%;
434 | position: absolute;
435 | top: 50%;
436 | left: 50%;
437 | transform: translate(-50%, -50%);
438 | }
439 |
440 | @keyframes rot55 {
441 | to {
442 | transform: rotate(360deg);
443 | }
444 | }
445 |
446 | #loding {
447 | display: none;
448 | width: 100%;
449 | height: 100%;
450 | position: absolute;
451 | top: 0;
452 | left: 0;
453 | z-index: 9999;
454 | background-color: #0000007e;
455 | justify-content: center;
456 | align-items: center;
457 | }
458 |
459 | .chat-input {
460 | padding: 1rem;
461 | display: flex;
462 | gap: 0.5rem;
463 | border-top: 1px solid rgba(99, 102, 241, 0.1);
464 | background: var(--glass-bg);
465 | }
466 |
467 | .chat-input textarea {
468 | flex: 1;
469 | padding: 0.75rem;
470 | border: 1px solid rgba(99, 102, 241, 0.2);
471 | border-radius: var(--border-radius);
472 | background: rgba(99, 102, 241, 0.05);
473 | color: var(--text-color);
474 | resize: none;
475 | height: 40px;
476 | font-family: inherit;
477 | transition: all 0.3s ease;
478 | }
479 |
480 | .chat-actions button {
481 | background: none;
482 | border: none;
483 | color: var(--text-color);
484 | cursor: pointer;
485 | padding: 0.5rem;
486 | transition: all 0.3s ease;
487 | opacity: 0.7;
488 | }
489 |
490 | .chat-actions button:hover {
491 | opacity: 1;
492 | transform: scale(1.1);
493 | color: var(--primary-color);
494 | }
495 |
496 | .chat-message-container {
497 | border-radius: 8px;
498 | padding: 12px;
499 | margin: 10px 0;
500 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
501 | border: #6365f16c solid;
502 | background-color: var(--bg-color)
503 | }
504 |
505 | .language-python {
506 | background-color: #0f172a !important;
507 | text-shadow: none !important;
508 | overflow: auto !important;
509 | }
510 |
511 | .chat-message {
512 | font-size: 14px;
513 | line-height: 1.6;
514 | margin-top: -45px !important;
515 | }
516 |
517 | /* Style du bouton "Copier" */
518 | .copy-button {
519 | position: absolute;
520 | top: 8px;
521 | right: 8px;
522 | padding: 6px 12px;
523 | background: var(--primary-color);
524 | color: white;
525 | border: none;
526 | border-radius: 6px;
527 | cursor: pointer;
528 | display: flex;
529 | align-items: center;
530 | gap: 6px;
531 | font-size: 0.85rem;
532 | opacity: 0;
533 | transition: all 0.3s ease;
534 | }
535 |
536 | .copy-button i {
537 | font-size: 0.9rem;
538 | }
539 |
540 | /* Afficher le bouton au survol du bloc de code */
541 | .chat-messages pre:hover .copy-button {
542 | opacity: 1;
543 | }
544 |
545 | .copy-button:hover {
546 | background: var(--primary-hover);
547 | transform: translateY(-1px);
548 | }
549 |
550 | /* Animation lors de la copie */
551 | .copy-button.copied {
552 | background: var(--secondary-color);
553 | }
554 |
555 | /* Position relative pour le conteneur de code */
556 | .chat-messages pre {
557 | position: relative;
558 | margin: 1em 0;
559 | }
560 |
561 | /* Ajustements pour le mode clair */
562 | [data-theme="light"] .copy-button {
563 | background: var(--primary-color);
564 | color: white;
565 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
566 | }
567 |
568 | [data-theme="light"] .copy-button:hover {
569 | background: var(--primary-hover);
570 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
571 | }
572 |
573 | [data-theme="light"] .copy-button.copied {
574 | background: var(--secondary-color);
575 | }
576 |
577 | .send-message {
578 | width: 40px;
579 | height: 40px;
580 | display: flex;
581 | align-items: center;
582 | justify-content: center;
583 | background: var(--primary-color);
584 | color: white;
585 | border: none;
586 | border-radius: 50%;
587 | cursor: pointer;
588 | transition: all 0.3s ease;
589 | box-shadow: var(--box-shadow);
590 | }
591 |
592 | .send-message:hover {
593 | transform: translateY(-2px);
594 | box-shadow: var(--neon-glow);
595 | background: var(--primary-hover);
596 | }
597 |
598 | .send-message i {
599 | font-size: 1rem;
600 | transition: transform 0.3s ease;
601 | }
602 |
603 | .send-message:hover i {
604 | transform: translateX(2px);
605 | }
606 |
607 | .new-cont {
608 | display: grid;
609 | grid-template-columns: 1fr 1fr;
610 | gap: 1rem;
611 | }
612 |
613 |
614 | #codeEditor {
615 | flex: 1;
616 | padding: 1.25rem;
617 | font-family: inherit;
618 | font-size: 0.95rem;
619 | line-height: 1.7;
620 | background: transparent;
621 | border: none;
622 | resize: none;
623 | outline: none;
624 | color: transparent;
625 | caret-color: var(--text-color);
626 | z-index: 2;
627 | white-space: pre;
628 | tab-size: 4;
629 | letter-spacing: normal;
630 | overflow: auto;
631 | }
632 |
633 | .highlight-layer {
634 | position: absolute;
635 | top: 0;
636 | left: 3rem;
637 | right: 0;
638 | bottom: 0;
639 | padding: 1.25rem;
640 | padding-bottom: calc(1.25rem + 100px);
641 | font-family: inherit;
642 | font-size: 0.95rem;
643 | line-height: 1.7;
644 | pointer-events: none;
645 | white-space: pre;
646 | tab-size: 4;
647 | z-index: 1;
648 | overflow: hidden;
649 | letter-spacing: normal;
650 | color: var(--text-color);
651 | }
652 |
653 | /* Styles pour Prism.js */
654 | .token.comment,
655 | .token.prolog,
656 | .token.doctype,
657 | .token.cdata {
658 | color: #6c7280;
659 | }
660 |
661 | .token.punctuation {
662 | color: inherit;
663 | }
664 |
665 | .token.namespace {
666 | opacity: .7;
667 | }
668 |
669 | .token.property,
670 | .token.tag,
671 | .token.boolean,
672 | .token.number,
673 | .token.constant,
674 | .token.symbol,
675 | .token.deleted {
676 | color: #f87171;
677 | }
678 |
679 | .token.selector,
680 | .token.attr-name,
681 | .token.string,
682 | .token.char,
683 | .token.builtin,
684 | .token.inserted {
685 | color: #34d399;
686 | }
687 |
688 | .token.operator,
689 | .token.entity,
690 | .token.url,
691 | .language-css .token.string,
692 | .style .token.string,
693 | .token.punctuation,
694 | .token.assign,
695 | .token.operator-equals {
696 | color: var(--text-color) !important;
697 | background: none !important;
698 | text-shadow: none !important;
699 | }
700 |
701 | .token.atrule,
702 | .token.attr-value,
703 | .token.keyword {
704 | color: #60a5fa;
705 | }
706 |
707 | .token.function,
708 | .token.class-name {
709 | color: #f59e0b;
710 | }
711 |
712 | .token.regex,
713 | .token.important,
714 | .token.variable {
715 | color: #ec4899;
716 | }
717 |
718 | [data-theme="light"] .token.comment {
719 | color: #6b7280;
720 | }
721 |
722 | [data-theme="light"] .token.punctuation {
723 | color: inherit;
724 | }
725 |
726 | [data-theme="light"] .token.property {
727 | color: #dc2626;
728 | }
729 |
730 | [data-theme="light"] .token.string {
731 | color: #059669;
732 | }
733 |
734 | [data-theme="light"] .token.operator {
735 | color: inherit;
736 | }
737 |
738 | [data-theme="light"] .token.keyword {
739 | color: #2563eb;
740 | }
741 |
742 | [data-theme="light"] .token.function {
743 | color: #d97706;
744 | }
745 |
746 | [data-theme="light"] .token.variable {
747 | color: #db2777;
748 | }
749 |
750 | .console {
751 | height: 200px;
752 | background-color: var(--console-bg);
753 | border-radius: var(--border-radius);
754 | overflow: hidden;
755 | box-shadow: var(--box-shadow);
756 | background: var(--glass-bg);
757 | backdrop-filter: var(--glass-blur);
758 | border: var(--glass-border);
759 | position: relative;
760 | overflow: hidden;
761 | transition: height 0.3s ease;
762 | }
763 |
764 | .console.collapsed {
765 | height: 48px;
766 | }
767 |
768 | .console::after {
769 | content: '';
770 | position: absolute;
771 | top: 0;
772 | left: 0;
773 | right: 0;
774 | height: 2px;
775 | background: linear-gradient(90deg,
776 | transparent,
777 | var(--primary-color),
778 | transparent);
779 | animation: loading 2s infinite;
780 | }
781 |
782 | .console-header {
783 | padding: 0.875rem 1rem;
784 | display: flex;
785 | justify-content: space-between;
786 | align-items: center;
787 | background-color: var(--sidebar-bg);
788 | border-bottom: 1px solid rgba(99, 102, 241, 0.1);
789 | }
790 |
791 | .console-header h3 {
792 | color: var(--primary-color);
793 | font-size: 0.875rem;
794 | font-weight: 600;
795 | position: relative;
796 | display: inline-block;
797 | }
798 |
799 | .console-header h3::after {
800 | content: '';
801 | position: absolute;
802 | bottom: -2px;
803 | left: 0;
804 | width: 100%;
805 | height: 2px;
806 | background: var(--gradient-primary);
807 | transform: scaleX(0);
808 | transition: transform 0.3s ease;
809 | }
810 |
811 | .console-header:hover h3::after {
812 | transform: scaleX(1);
813 | }
814 |
815 | .console-header button {
816 | transition: transform 0.3s ease;
817 | background: transparent;
818 | color: white;
819 | border: 0;
820 | }
821 |
822 | .console-header button:hover {
823 | transform: rotate(90deg);
824 | }
825 |
826 | .console-actions {
827 | display: flex;
828 | gap: 0.5rem;
829 | }
830 |
831 | .console-actions button {
832 | background: transparent;
833 | border: none;
834 | color: var(--text-color);
835 | cursor: pointer;
836 | padding: 0.25rem;
837 | transition: all 0.3s ease;
838 | }
839 |
840 | .console-actions button:hover {
841 | color: var(--primary-color);
842 | }
843 |
844 | #consoleOutput {
845 | padding: 0;
846 | height: calc(100% - 48px);
847 | overflow-y: auto;
848 | font-family: inherit;
849 | font-size: 0.875rem;
850 | }
851 |
852 | .error-message,
853 | .output-message,
854 | .input-message {
855 | font-size: 0.875rem;
856 | animation: slideUp 0.3s ease-out;
857 | margin: 0.75rem;
858 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
859 | position: relative;
860 | overflow: hidden;
861 | animation: slideUp 0.3s ease-out;
862 | transition: all 0.3s ease;
863 | transform-origin: left;
864 | animation: slideInMessage 0.3s ease-out;
865 | }
866 |
867 | .error-message::before,
868 | .output-message::before,
869 | .input-message::before {
870 | content: '';
871 | position: absolute;
872 | top: 0;
873 | left: 0;
874 | width: 4px;
875 | height: 100%;
876 | }
877 |
878 | .error-message::before {
879 | background: var(--error-color);
880 | }
881 |
882 | .output-message::before {
883 | background: var(--secondary-color);
884 | }
885 |
886 | .input-message::before {
887 | background: var(--primary-color);
888 | }
889 |
890 | @keyframes slideIn {
891 | from {
892 | opacity: 0;
893 | transform: translateY(-10px);
894 | }
895 |
896 | to {
897 | opacity: 1;
898 | transform: translateY(0);
899 | }
900 | }
901 |
902 | @keyframes slideRight {
903 | from {
904 | transform: translateX(-100%);
905 | opacity: 0;
906 | }
907 |
908 | to {
909 | transform: translateX(0);
910 | opacity: 1;
911 | }
912 | }
913 |
914 | @keyframes glow {
915 | 0% {
916 | box-shadow: 0 0 5px rgba(99, 102, 241, 0.2);
917 | }
918 |
919 | 50% {
920 | box-shadow: 0 0 20px rgba(99, 102, 241, 0.4);
921 | }
922 |
923 | 100% {
924 | box-shadow: 0 0 5px rgba(99, 102, 241, 0.2);
925 | }
926 | }
927 |
928 | @keyframes loading {
929 | 0% {
930 | transform: translateX(-100%);
931 | }
932 |
933 | 100% {
934 | transform: translateX(100%);
935 | }
936 | }
937 |
938 | @keyframes slideUp {
939 | from {
940 | opacity: 0;
941 | transform: translateY(20px);
942 | }
943 |
944 | to {
945 | opacity: 1;
946 | transform: translateY(0);
947 | }
948 | }
949 |
950 | ::-webkit-scrollbar {
951 | width: 8px;
952 | height: 8px;
953 | }
954 |
955 | ::-webkit-scrollbar-track {
956 | background: rgba(99, 102, 241, 0.1);
957 | border-radius: 4px;
958 | }
959 |
960 | ::-webkit-scrollbar-thumb {
961 | background: var(--gradient-primary);
962 | border-radius: 4px;
963 | transition: all 0.3s ease;
964 | }
965 |
966 | ::-webkit-scrollbar-thumb:hover {
967 | background: var(--gradient-hover);
968 | }
969 |
970 | .custom-modal {
971 | position: fixed;
972 | top: 0;
973 | left: 0;
974 | width: 100%;
975 | height: 100%;
976 | background: rgba(0, 0, 0, 0.5);
977 | display: flex;
978 | justify-content: center;
979 | align-items: center;
980 | z-index: 1000;
981 | }
982 |
983 | .modal-content {
984 | display: flex;
985 | justify-self: center;
986 | width: 300px;
987 | }
988 |
989 | .modal-title {
990 | font-size: 1.2em;
991 | font-weight: bold;
992 | margin-bottom: 10px;
993 | color: var(--primary-color);
994 | }
995 |
996 | .modal-message {
997 | margin-bottom: 15px;
998 | color: var(--text-color);
999 | }
1000 |
1001 | .modal-input {
1002 | width: 100%;
1003 | padding: 8px;
1004 | margin-bottom: 15px;
1005 | border: 1px solid var(--primary-color);
1006 | border-radius: 4px;
1007 | background: var(--editor-bg);
1008 | color: var(--text-color);
1009 | }
1010 |
1011 | .modal-buttons {
1012 | text-align: right;
1013 | }
1014 |
1015 | .modal-buttons button {
1016 | padding: 5px 15px;
1017 | background: var(--primary-color);
1018 | color: white;
1019 | border: none;
1020 | border-radius: 4px;
1021 | cursor: pointer;
1022 | }
1023 |
1024 | .modal-buttons button:hover {
1025 | opacity: 0.9;
1026 | }
1027 |
1028 | #fileExplorer {
1029 | flex: 1;
1030 | position: relative;
1031 | top: 20px;
1032 | overflow: scroll;
1033 | overflow-y: scroll;
1034 | max-height: calc(100vh - 310px);
1035 | margin-top: -30px;
1036 | margin-left: -20px;
1037 | width: calc(100% + 50px);
1038 | padding-top: 30px;
1039 | padding-right: 30px;
1040 | padding-left: 20px;
1041 | min-height: 100px;
1042 | }
1043 |
1044 | #fileExplorer::-webkit-scrollbar {
1045 | display: none;
1046 | }
1047 |
1048 | .file-item {
1049 | font-size: 1rem;
1050 | padding: 0.4rem 0;
1051 | margin-bottom: 0.2rem;
1052 | cursor: pointer;
1053 | display: flex;
1054 | align-items: center;
1055 | gap: 0.5rem;
1056 | transition: all 0.2s ease;
1057 | position: relative;
1058 | border-radius: 15px;
1059 | margin-bottom: 15px;
1060 | border: rgba(99, 102, 241, 0.15) solid;
1061 | }
1062 |
1063 | .file-item:hover {
1064 | background: rgba(99, 102, 241, 0.1);
1065 | width: calc(100% - 40px) !important;
1066 | }
1067 |
1068 | .drag-handle {
1069 | cursor: move;
1070 | padding: 0 5px;
1071 | color: #666;
1072 | display: inline-block;
1073 | font-size: 14px;
1074 | user-select: none;
1075 | }
1076 |
1077 | .folder-item,
1078 | .file-item {
1079 | cursor: pointer;
1080 | position: relative;
1081 | }
1082 |
1083 | .drag-over {
1084 | background-color: rgba(0, 120, 215, 0.1);
1085 | border: 2px dashed #0078D7;
1086 | }
1087 |
1088 | .file-content svg {
1089 | position: absolute;
1090 | left: 10px;
1091 | }
1092 |
1093 | .folder-header,
1094 | .file-content {
1095 | display: flex;
1096 | align-items: center;
1097 | gap: 5px;
1098 | }
1099 |
1100 | .folder-header {
1101 | display: flex;
1102 | align-items: center;
1103 | padding: 4px;
1104 | gap: 5px;
1105 | border-radius: 4px;
1106 | }
1107 |
1108 | .folder-header:hover {
1109 | background-color: rgba(0, 0, 0, 0.05);
1110 | }
1111 |
1112 | .file-content {
1113 | display: flex;
1114 | align-items: center;
1115 | gap: 0.5rem;
1116 | width: 100%;
1117 | padding-right: 0.75rem;
1118 | }
1119 |
1120 | .folder-arrow {
1121 | width: 12px;
1122 | transition: transform 0.2s ease;
1123 | color: var(--text-color);
1124 | opacity: 0.7;
1125 | }
1126 |
1127 | .folder-icon {
1128 | color: #dcb67a;
1129 | }
1130 |
1131 | .fa-file-code {
1132 | color: #519aba;
1133 | }
1134 |
1135 | .folder-content {
1136 | width: 100%;
1137 | }
1138 |
1139 | .file-item.active {
1140 | background: rgba(99, 102, 241, 0.15);
1141 | }
1142 |
1143 | .file-item span {
1144 | flex: 1;
1145 | white-space: nowrap;
1146 | overflow: hidden;
1147 | text-overflow: ellipsis;
1148 | }
1149 |
1150 | .file-actions {
1151 | opacity: 0;
1152 | margin-left: auto;
1153 | }
1154 |
1155 | .folder-header:hover .file-actions {
1156 | opacity: 1;
1157 | }
1158 |
1159 | .folder-actions {
1160 | opacity: 0;
1161 | margin-left: auto;
1162 | }
1163 |
1164 | .file-item:hover .folder-actions {
1165 | opacity: 1;
1166 | }
1167 |
1168 | .delete-file {
1169 | opacity: 0;
1170 | background: none;
1171 | border: none;
1172 | color: var(--error-color);
1173 | padding: 0.25rem;
1174 | border-radius: 4px;
1175 | cursor: pointer;
1176 | transition: var(--transition);
1177 | }
1178 |
1179 | .file-item:hover .delete-file {
1180 | opacity: 1;
1181 | }
1182 |
1183 | .delete-file:hover {
1184 | transform: scale(1.1);
1185 | background-color: var(--error-color) !important;
1186 | color: white;
1187 | }
1188 |
1189 | @keyframes fadeInUp {
1190 | from {
1191 | opacity: 0;
1192 | transform: translateY(10px);
1193 | }
1194 |
1195 | to {
1196 | opacity: 1;
1197 | transform: translateY(0);
1198 | }
1199 | }
1200 |
1201 | .console-message {
1202 | animation: fadeInUp 0.3s ease-out forwards;
1203 | }
1204 |
1205 | .file-item:nth-child(2) {
1206 | animation-delay: 0.1s;
1207 | }
1208 |
1209 | .file-item:nth-child(3) {
1210 | animation-delay: 0.2s;
1211 | }
1212 |
1213 | .file-item:nth-child(4) {
1214 | animation-delay: 0.3s;
1215 | }
1216 |
1217 | .file-item:nth-child(5) {
1218 | animation-delay: 0.4s;
1219 | }
1220 |
1221 | @keyframes fadeOut {
1222 | from {
1223 | opacity: 1;
1224 | transform: scale(1);
1225 | }
1226 |
1227 | to {
1228 | opacity: 0;
1229 | transform: scale(0.8);
1230 | }
1231 | }
1232 |
1233 | .file-item.deleting {
1234 | animation: fadeOut 0.3s ease-out forwards;
1235 | }
1236 |
1237 | @keyframes fadeIn {
1238 | from {
1239 | opacity: 0;
1240 | }
1241 |
1242 | to {
1243 | opacity: 1;
1244 | }
1245 | }
1246 |
1247 | input:focus,
1248 | textarea:focus {
1249 | animation: focusGlow 0.3s ease-out forwards;
1250 | }
1251 |
1252 | @keyframes focusGlow {
1253 | 0% {
1254 | box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.2);
1255 | }
1256 |
1257 | 100% {
1258 | box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.2);
1259 | }
1260 | }
1261 |
1262 | .error-message {
1263 | padding: 0.75rem 1rem;
1264 | margin: 0.75rem;
1265 | border-radius: var(--border-radius);
1266 | background-color: rgba(239, 68, 68, 0.1);
1267 | border-left: 4px solid var(--error-color);
1268 | }
1269 |
1270 | @keyframes shake {
1271 |
1272 | 0%,
1273 | 100% {
1274 | transform: translateX(0);
1275 | }
1276 |
1277 | 10%,
1278 | 30%,
1279 | 50%,
1280 | 70%,
1281 | 90% {
1282 | transform: translateX(-5px);
1283 | }
1284 |
1285 | 20%,
1286 | 40%,
1287 | 60%,
1288 | 80% {
1289 | transform: translateX(5px);
1290 | }
1291 | }
1292 |
1293 | .output-message {
1294 | padding: 0.5rem 1rem;
1295 | margin: 0;
1296 | font-size: 0.875rem;
1297 | background-color: rgba(34, 197, 94, 0.1);
1298 | border-left: 4px solid var(--secondary-color);
1299 | }
1300 |
1301 | .output-message+.output-message {
1302 | border-top: 1px solid rgba(34, 197, 94, 0.1);
1303 | }
1304 |
1305 | [data-theme="dark"],
1306 | [data-theme="light"] {
1307 | transition: all 0.5s ease;
1308 | }
1309 |
1310 | .sidebar::before,
1311 | .console::before,
1312 | .toolbar::before {
1313 | content: '';
1314 | position: absolute;
1315 | top: -50%;
1316 | left: -50%;
1317 | width: 200%;
1318 | height: 200%;
1319 | background: radial-gradient(circle at center,
1320 | rgba(255, 255, 255, 0.1) 0%,
1321 | transparent 50%);
1322 | transform: rotate(30deg);
1323 | pointer-events: none;
1324 | opacity: 0.5;
1325 | transition: transform 0.5s;
1326 | }
1327 |
1328 | .sidebar:hover::before,
1329 | .console:hover::before,
1330 | .toolbar:hover::before {
1331 | transform: rotate(30deg) translate(10%, 10%);
1332 | }
1333 |
1334 | #run {
1335 | animation: pulse 2s infinite;
1336 | }
1337 |
1338 | @keyframes pulse {
1339 | 0% {
1340 | box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.4);
1341 | }
1342 |
1343 | 70% {
1344 | box-shadow: 0 0 0 10px rgba(99, 102, 241, 0);
1345 | }
1346 |
1347 | 100% {
1348 | box-shadow: 0 0 0 0 rgba(99, 102, 241, 0);
1349 | }
1350 | }
1351 |
1352 | .line-numbers span {
1353 | opacity: 0.5;
1354 | transition: all 0.2s ease;
1355 | }
1356 |
1357 | .line-numbers span:hover {
1358 | opacity: 1;
1359 | color: var(--primary-color);
1360 | transform: scale(1.1);
1361 | }
1362 |
1363 | .editor-container:focus-within {
1364 | box-shadow: var(--neon-glow);
1365 | transform: scale(1.001);
1366 | }
1367 |
1368 | @keyframes slideInMessage {
1369 | from {
1370 | transform: translateX(-10px) scale(0.95);
1371 | opacity: 0;
1372 | }
1373 |
1374 | to {
1375 | transform: translateX(0) scale(1);
1376 | opacity: 1;
1377 | }
1378 | }
1379 |
1380 | .loading::after {
1381 | content: '';
1382 | position: absolute;
1383 | top: 0;
1384 | left: 0;
1385 | width: 100%;
1386 | height: 2px;
1387 | background: var(--gradient-primary);
1388 | animation: loading 1.5s infinite;
1389 | }
1390 |
1391 | @keyframes loading {
1392 | 0% {
1393 | transform: translateX(-100%);
1394 | }
1395 |
1396 | 100% {
1397 | transform: translateX(100%);
1398 | }
1399 | }
1400 |
1401 | input:focus,
1402 | textarea:focus {
1403 | outline: none;
1404 | box-shadow: var(--neon-glow);
1405 | }
1406 |
1407 | body {
1408 | transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
1409 | }
1410 |
1411 | .toolbar select {
1412 | background: var(--gradient-primary);
1413 | transition: all 0.3s ease;
1414 | }
1415 |
1416 | .toolbar select:hover {
1417 | transform: translateY(-2px);
1418 | box-shadow: var(--neon-glow);
1419 | }
1420 |
1421 | .current-file {
1422 | display: flex;
1423 | align-items: center;
1424 | gap: 0.5rem;
1425 | padding: 0.5rem 1rem;
1426 | background: rgba(99, 102, 241, 0.1);
1427 | border-radius: var(--border-radius);
1428 | margin-right: auto;
1429 | }
1430 |
1431 | .current-file i {
1432 | color: var(--primary-color);
1433 | }
1434 |
1435 | .current-file span {
1436 | color: var(--text-color);
1437 | font-size: 0.9rem;
1438 | }
1439 |
1440 | .toolbar-actions {
1441 | display: flex;
1442 | gap: 0.5rem;
1443 | }
1444 |
1445 | .file-actions {
1446 | position: absolute;
1447 | right: 0.5rem;
1448 | top: 50%;
1449 | transform: translateY(-50%);
1450 | opacity: 0;
1451 | transition: opacity 0.3s ease;
1452 | display: flex;
1453 | gap: 0.25rem;
1454 | background: var(--bg-color);
1455 | padding: 0.25rem;
1456 | border-radius: var(--border-radius);
1457 | }
1458 |
1459 | .file-item:hover .file-actions {
1460 | opacity: 1;
1461 | }
1462 |
1463 | .file-actions button {
1464 | background: none;
1465 | border: none;
1466 | color: var(--text-color);
1467 | padding: 0.25rem;
1468 | cursor: pointer;
1469 | transition: all 0.3s ease;
1470 | border-radius: 4px;
1471 | }
1472 |
1473 | .file-actions button:hover {
1474 | color: var(--primary-color);
1475 | background: var(--sidebar-bg);
1476 | }
1477 |
1478 | .folder-actions {
1479 | position: absolute;
1480 | right: 0.5rem;
1481 | top: 2px;
1482 | opacity: 0;
1483 | transition: opacity 0.3s ease;
1484 | display: flex;
1485 | gap: 0.25rem;
1486 | background: var(--bg-color);
1487 | padding: 0.25rem;
1488 | border-radius: var(--border-radius);
1489 | }
1490 |
1491 | .folder-header:hover .folder-actions {
1492 | opacity: 1;
1493 | }
1494 |
1495 | .folder-actions button {
1496 | background: none;
1497 | border: none;
1498 | color: var(--text-color);
1499 | padding: 0.25rem;
1500 | cursor: pointer;
1501 | transition: all 0.3s ease;
1502 | border-radius: 4px;
1503 | }
1504 |
1505 | .folder-actions button:hover {
1506 | color: var(--primary-color);
1507 | background: var(--sidebar-bg);
1508 | }
1509 |
1510 | #themeToggle {
1511 | position: relative;
1512 | }
1513 |
1514 | svg {
1515 | width: 17px;
1516 | }
1517 |
1518 | #themeToggle i {
1519 | transition: opacity 0.3s ease;
1520 | }
1521 |
1522 | #themeToggle:hover i {
1523 | opacity: 0.8;
1524 | }
1525 |
1526 | [data-theme="dark"] #themeToggle i {
1527 | opacity: 1;
1528 | }
1529 |
1530 | .file-icon {
1531 | width: 20px;
1532 | height: 20px;
1533 | }
1534 |
1535 | #loading-screen {
1536 | position: fixed;
1537 | top: 0;
1538 | left: 0;
1539 | width: 100%;
1540 | height: 100%;
1541 | background: var(--bg-color);
1542 | display: flex;
1543 | justify-content: center;
1544 | align-items: center;
1545 | z-index: 9999;
1546 | transition: all 0.5s ease;
1547 | background: linear-gradient(135deg,
1548 | var(--bg-color),
1549 | rgba(99, 102, 241, 0.1),
1550 | var(--bg-color));
1551 | }
1552 |
1553 | .loader {
1554 | background: var(--glass-bg);
1555 | border: var(--glass-border);
1556 | padding: 3rem;
1557 | border-radius: 20px;
1558 | box-shadow: var(--neon-glow);
1559 | width: 400px;
1560 | position: relative;
1561 | overflow: hidden;
1562 | }
1563 |
1564 | .loader::before {
1565 | content: '';
1566 | position: absolute;
1567 | top: 0;
1568 | left: -100%;
1569 | width: 200%;
1570 | height: 100%;
1571 | background: linear-gradient(90deg,
1572 | transparent,
1573 | rgba(99, 102, 241, 0.2),
1574 | transparent);
1575 | animation: shimmer 2s infinite;
1576 | }
1577 |
1578 | .loader-content {
1579 | display: flex;
1580 | flex-direction: column;
1581 | align-items: center;
1582 | gap: 1.5rem;
1583 | position: relative;
1584 | }
1585 |
1586 | .loader-logo i {
1587 | font-size: 4rem;
1588 | background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
1589 | -webkit-background-clip: text;
1590 | -webkit-text-fill-color: transparent;
1591 | animation: float 2s ease-in-out infinite;
1592 | margin-bottom: -1rem;
1593 | }
1594 |
1595 | .loader-text {
1596 | text-align: center;
1597 | }
1598 |
1599 | .loader-text h2 {
1600 | font-size: 2rem;
1601 | margin-bottom: 0.5rem;
1602 | background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
1603 | -webkit-background-clip: text;
1604 | -webkit-text-fill-color: transparent;
1605 | }
1606 |
1607 | .typing-text {
1608 | color: var(--text-color);
1609 | opacity: 0.8;
1610 | position: relative;
1611 | padding-right: 3px;
1612 | animation: typing 1.5s steps(13) infinite;
1613 | }
1614 |
1615 | .loader-progress-bar {
1616 | width: 100%;
1617 | height: 4px;
1618 | background: rgba(99, 102, 241, 0.1);
1619 | border-radius: 2px;
1620 | overflow: hidden;
1621 | position: relative;
1622 | }
1623 |
1624 | .progress-value {
1625 | position: absolute;
1626 | width: 100%;
1627 | height: 100%;
1628 | background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
1629 | animation: progress-animation 1s ease-in-out infinite;
1630 | }
1631 |
1632 | .loader-status {
1633 | width: 100%;
1634 | }
1635 |
1636 | .status-item {
1637 | display: flex;
1638 | align-items: center;
1639 | gap: 1rem;
1640 | color: var(--text-color);
1641 | opacity: 0.8;
1642 | }
1643 |
1644 | .status-item i {
1645 | color: var(--primary-color);
1646 | }
1647 |
1648 | @keyframes float {
1649 |
1650 | 0%,
1651 | 100% {
1652 | transform: translateY(0);
1653 | }
1654 |
1655 | 50% {
1656 | transform: translateY(-8px);
1657 | }
1658 | }
1659 |
1660 | @keyframes shimmer {
1661 | 100% {
1662 | left: 100%;
1663 | }
1664 | }
1665 |
1666 | @keyframes typing {
1667 | 0% {
1668 | content: 'Initialisation';
1669 | }
1670 |
1671 | 33% {
1672 | content: 'Initialisation.';
1673 | }
1674 |
1675 | 66% {
1676 | content: 'Initialisation..';
1677 | }
1678 |
1679 | 100% {
1680 | content: 'Initialisation...';
1681 | }
1682 | }
1683 |
1684 | @keyframes progress-animation {
1685 | from {
1686 | transform: translateX(-100%);
1687 | }
1688 |
1689 | to {
1690 | transform: translateX(100%);
1691 | }
1692 | }
1693 |
1694 | .modal-overlay {
1695 | position: fixed;
1696 | top: 0;
1697 | left: 0;
1698 | width: 100%;
1699 | height: 100%;
1700 | background: rgba(0, 0, 0, 0.7);
1701 | display: flex;
1702 | justify-content: center;
1703 | align-items: center;
1704 | z-index: 9999;
1705 | opacity: 0;
1706 | visibility: hidden;
1707 | transition: all 0.3s ease;
1708 | }
1709 |
1710 | .modal-overlay.active {
1711 | opacity: 1;
1712 | visibility: visible;
1713 | }
1714 |
1715 | .modal-container {
1716 | background: var(--glass-bg);
1717 | border: var(--glass-border);
1718 | padding: 2rem;
1719 | border-radius: var(--border-radius);
1720 | width: 400px;
1721 | transform: scale(0.9);
1722 | transition: all 0.3s ease;
1723 | box-shadow: var(--neon-glow);
1724 | }
1725 |
1726 | .modal-overlay.active .modal-container {
1727 | transform: scale(1);
1728 | }
1729 |
1730 | .modal-header {
1731 | margin-bottom: 1.5rem;
1732 | text-align: center;
1733 | }
1734 |
1735 | .modal-header h3 {
1736 | font-size: 1.5rem;
1737 | color: var(--primary-color);
1738 | margin-bottom: 0.5rem;
1739 | }
1740 |
1741 | .modal-content {
1742 | margin-bottom: 1.5rem;
1743 | }
1744 |
1745 | .modal-input {
1746 | width: 100%;
1747 | padding: 0.75rem;
1748 | border: 1px solid rgba(99, 102, 241, 0.2);
1749 | border-radius: var(--border-radius);
1750 | background: rgba(99, 102, 241, 0.05);
1751 | color: var(--text-color);
1752 | font-family: inherit;
1753 | margin-top: 0.5rem;
1754 | }
1755 |
1756 | .modal-actions {
1757 | display: flex;
1758 | gap: 1rem;
1759 | justify-content: center;
1760 | }
1761 |
1762 | .modal-button {
1763 | padding: 0.75rem 1.5rem;
1764 | border: none;
1765 | border-radius: var(--border-radius);
1766 | cursor: pointer;
1767 | font-family: inherit;
1768 | font-weight: 500;
1769 | transition: all 0.3s ease;
1770 | }
1771 |
1772 | .modal-button.primary {
1773 | background: var(--gradient-primary);
1774 | color: white;
1775 | }
1776 |
1777 | .modal-button.secondary {
1778 | background: rgba(99, 102, 241, 0.1);
1779 | color: var(--text-color);
1780 | }
1781 |
1782 | [data-theme="light"] .token.operator,
1783 | [data-theme="light"] .token.punctuation,
1784 | [data-theme="light"] .token.assign,
1785 | [data-theme="light"] .token.operator-equals {
1786 | color: var(--text-color) !important;
1787 | background: none !important;
1788 | text-shadow: none !important;
1789 | }
1790 |
1791 | /* Style de la barre de défilement */
1792 | #fileExplorer::-webkit-scrollbar {
1793 | width: 8px;
1794 | }
1795 |
1796 | #fileExplorer::-webkit-scrollbar-track {
1797 | background: rgba(99, 102, 241, 0.05);
1798 | border-radius: 4px;
1799 | }
1800 |
1801 | #fileExplorer::-webkit-scrollbar-thumb {
1802 | background: rgba(99, 102, 241, 0.2);
1803 | border-radius: 4px;
1804 | }
1805 |
1806 | #fileExplorer::-webkit-scrollbar-thumb:hover {
1807 | background: rgba(99, 102, 241, 0.3);
1808 | }
1809 |
1810 | /* Ajouter les différents thèmes de coloration */
1811 | [data-syntax-theme="default"] .token.string {
1812 | color: #34d399;
1813 | }
1814 |
1815 | [data-syntax-theme="default"] .token.keyword {
1816 | color: #60a5fa;
1817 | }
1818 |
1819 | [data-syntax-theme="default"] .token.function {
1820 | color: #f59e0b;
1821 | }
1822 |
1823 | [data-syntax-theme="monokai"] .token.string {
1824 | color: #e6db74;
1825 | }
1826 |
1827 | [data-syntax-theme="monokai"] .token.keyword {
1828 | color: #f92672;
1829 | }
1830 |
1831 | [data-syntax-theme="monokai"] .token.function {
1832 | color: #a6e22e;
1833 | }
1834 |
1835 | [data-syntax-theme="dracula"] .token.string {
1836 | color: #f1fa8c;
1837 | }
1838 |
1839 | [data-syntax-theme="dracula"] .token.keyword {
1840 | color: #ff79c6;
1841 | }
1842 |
1843 | [data-syntax-theme="dracula"] .token.function {
1844 | color: #50fa7b;
1845 | }
1846 |
1847 | .syntax-theme-options {
1848 | display: flex;
1849 | flex-direction: column;
1850 | gap: 1rem;
1851 | }
1852 |
1853 | .syntax-color-option {
1854 | display: flex;
1855 | align-items: center;
1856 | justify-content: space-between;
1857 | gap: 1rem;
1858 | }
1859 |
1860 | .syntax-color-option label {
1861 | flex: 1;
1862 | color: var(--text-color);
1863 | }
1864 |
1865 | .syntax-color-option input[type="color"] {
1866 | width: 50px;
1867 | height: 30px;
1868 | padding: 0;
1869 | border: none;
1870 | border-radius: var(--border-radius);
1871 | cursor: pointer;
1872 | }
1873 |
1874 | .syntax-color-option input[type="color"]::-webkit-color-swatch-wrapper {
1875 | padding: 0;
1876 | }
1877 |
1878 | .syntax-color-option input[type="color"]::-webkit-color-swatch {
1879 | border: none;
1880 | border-radius: var(--border-radius);
1881 | }
1882 |
1883 | .file-item:hover {
1884 | background: rgba(99, 102, 241, 0.1);
1885 | transform: translateX(10px);
1886 | width: calc(100% - 10px);
1887 | box-shadow: 0px 0px 25px var(--primary-color);
1888 | transition: all 0.3s ease;
1889 | }
1890 |
1891 | .toolbar-toggle,
1892 | .toolbar.collapsed,
1893 | .toolbar.collapsed.toolbar-toggle,
1894 | .toolbar.collapsed+.editor-container {
1895 | display: none;
1896 | }
1897 |
1898 | .sidebar-title {
1899 | display: flex;
1900 | justify-content: space-between;
1901 | align-items: center;
1902 | gap: 1rem;
1903 | }
1904 |
1905 | .sidebar-title h3 {
1906 | font-size: 1.25rem;
1907 | color: var(--primary-color);
1908 | font-weight: 600;
1909 | margin: 0;
1910 | }
1911 |
1912 | #toggleSidebar {
1913 | background: transparent;
1914 | border: none;
1915 | color: var(--text-color);
1916 | cursor: pointer;
1917 | padding: 0.25rem;
1918 | transition: all 0.3s ease;
1919 | font-size: 0.875rem;
1920 | width: 24px;
1921 | height: 24px;
1922 | display: flex;
1923 | align-items: center;
1924 | justify-content: center;
1925 | opacity: 0.7;
1926 | }
1927 |
1928 | #toggleSidebar:hover {
1929 | color: var(--primary-color);
1930 | opacity: 1;
1931 | }
1932 |
1933 | .sidebar.collapsed .sidebar-header h3,
1934 | .sidebar.collapsed .new-cont,
1935 | .sidebar.collapsed #fileExplorer {
1936 | display: none;
1937 | }
1938 |
1939 | .sidebar.collapsed #newFile {
1940 | width: 32px;
1941 | padding: 0.5rem;
1942 | }
1943 |
1944 | .sidebar.collapsed #toggleSidebar i {
1945 | transform: rotate(180deg);
1946 | }
1947 |
1948 | @media (max-width: 1280px) {
1949 | .toolbar {
1950 | flex: 0.25;
1951 | }
1952 |
1953 | .toolbar-actions {
1954 | position: absolute;
1955 | bottom: 15%;
1956 | left: 50%;
1957 | transform: translateX(-50%);
1958 | }
1959 |
1960 | .current-file {
1961 | position: absolute;
1962 | top: 20px;
1963 | left: 50%;
1964 | transform: translateX(-50%);
1965 | }
1966 | }
1967 |
1968 | @media (max-width: 1100px) {
1969 | .toolbar {
1970 | flex: 0.4;
1971 | }
1972 |
1973 | .toolbar-actions {
1974 | display: grid;
1975 | grid-template-columns: repeat(3, 150px);
1976 | bottom: 10px;
1977 | }
1978 |
1979 | .current-file {
1980 | top: 10px;
1981 | }
1982 | }
1983 |
1984 | /* (max-width: 1100px) (max-height: 775px)*/
1985 | @media (max-width: 1100px) and (max-height: 775px) {
1986 | .toolbar {
1987 | flex: 0.5;
1988 | }
1989 | }
1990 |
1991 | @media (max-width: 1100px) and (max-height: 700px) {
1992 | .toolbar {
1993 | flex: 0.6;
1994 | }
1995 | }
1996 |
1997 | @media (max-width: 1100px) and (max-height: 675px) {
1998 | .toolbar {
1999 | flex: 0.7;
2000 | }
2001 | }
2002 |
2003 | @media (max-width: 1100px) and (max-height: 650px) {
2004 | .toolbar {
2005 | flex: 0.8;
2006 | }
2007 | }
2008 |
2009 | @media (max-width: 1100px) and (max-height: 625px) {
2010 | .toolbar {
2011 | flex: 0.9;
2012 | }
2013 | }
2014 |
2015 | @media (max-width: 1100px) and (max-height: 600px) {
2016 | .toolbar {
2017 | flex: 1;
2018 | }
2019 | }
2020 |
2021 | .p {
2022 | position: fixed;
2023 | bottom: 0;
2024 | right: 5px;
2025 | font-size: 10px;
2026 | color: #0056b3;
2027 | }
2028 |
2029 | .current-file svg {
2030 | display: block !important;
2031 | opacity: 1 !important;
2032 | width: 20 !important;
2033 | height: 20 !important;
2034 | }
2035 |
2036 | .chat-message-user {
2037 | background-color: var(--primary-hover);
2038 | width: 60%;
2039 | border-radius: 15px;
2040 | padding: 5px 10px;
2041 | color: var(--text-color);
2042 | border: 2px solid #7777778a;
2043 | }
2044 |
2045 | [data-theme="light"] {
2046 | --primary-color: #6366f1;
2047 | --primary-hover: #4f46e5;
2048 | --secondary-color: #22c55e;
2049 | --bg-color: #f1f5f9;
2050 | --text-color: #1f2937;
2051 | --sidebar-bg: rgba(255, 255, 255, 0.7);
2052 | --editor-bg: #ffffff;
2053 | --console-bg: rgba(255, 255, 255, 0.7);
2054 | --glass-bg: rgba(255, 255, 255, 0.7);
2055 | --glass-border: 1px solid rgba(0, 0, 0, 0.1);
2056 | --keyword-color: #0000ff;
2057 | --string-color: #008000;
2058 | --function-color: #795e26;
2059 | --number-color: #098658;
2060 | --console-text: #000000;
2061 | }
2062 |
2063 | /* Styles pour l'éditeur en thème clair */
2064 | [data-theme="light"] .editor-container {
2065 | background-color: var(--editor-bg);
2066 | color: var(--text-color);
2067 | }
2068 |
2069 | [data-theme="light"] #codeEditor {
2070 | background-color: transparent;
2071 | color: var(--text-color);
2072 | }
2073 |
2074 | [data-theme="light"] .highlight-layer {
2075 | background-color: transparent;
2076 | color: var(--text-color);
2077 | }
2078 |
2079 | [data-theme="light"] .line-numbers-container {
2080 | background-color: var(--editor-bg);
2081 | color: var(--text-color);
2082 | }
2083 |
2084 | /* Couleurs de la syntaxe en mode clair */
2085 | [data-theme="light"] .token.keyword {
2086 | color: var(--keyword-color) !important;
2087 | }
2088 |
2089 | [data-theme="light"] .token.string {
2090 | color: var(--string-color) !important;
2091 | }
2092 |
2093 | [data-theme="light"] .token.function {
2094 | color: var(--function-color) !important;
2095 | }
2096 |
2097 | [data-theme="light"] .token.number {
2098 | color: var(--number-color) !important;
2099 | }
2100 |
2101 | /* Couleur du texte de la console en mode clair */
2102 | [data-theme="light"] .output-message {
2103 | color: var(--console-text);
2104 | }
2105 |
2106 | /* Couleur du nom de fichier en mode clair */
2107 | [data-theme="light"] .current-file span {
2108 | color: var(--text-color);
2109 | }
2110 |
2111 | /* Ajuster le contraste des messages de la console */
2112 | [data-theme="light"] .output-message {
2113 | background-color: rgba(34, 197, 94, 0.1);
2114 | border-left: 4px solid var(--secondary-color);
2115 | color: var(--text-color);
2116 | }
2117 |
2118 | [data-theme="light"] .error-message {
2119 | background-color: rgba(239, 68, 68, 0.1);
2120 | border-left: 4px solid var(--error-color);
2121 | color: var(--text-color);
2122 | }
2123 |
2124 | /* Couleur du nom de fichier en mode clair */
2125 | [data-theme="light"] .current-file span {
2126 | color: var(--text-color);
2127 | }
2128 |
2129 | /* Couleur des noms de fichiers dans l'explorateur en mode clair */
2130 | [data-theme="light"] .file-item .file-content span {
2131 | color: var(--text-color);
2132 | }
2133 |
2134 | [data-theme="light"] .file-item {
2135 | color: var(--text-color);
2136 | }
2137 |
2138 | [data-theme="light"] .file-item:hover {
2139 | background: rgba(0, 0, 0, 0.05);
2140 | }
2141 |
2142 | [data-theme="light"] .file-item.active {
2143 | background: rgba(0, 0, 0, 0.1);
2144 | }
2145 |
2146 | /* Couleur des icônes dans l'explorateur en mode clair */
2147 | [data-theme="light"] .file-actions i {
2148 | color: var(--text-color);
2149 | }
2150 |
2151 | /* Styles pour le code dans l'assistant IA en thème clair */
2152 | [data-theme="light"] .chat-messages pre {
2153 | background-color: #ffffff !important;
2154 | border: 1px solid rgba(0, 0, 0, 0.1);
2155 | }
2156 |
2157 | [data-theme="light"] .chat-messages code {
2158 | background-color: #ffffff !important;
2159 | color: var(--text-color) !important;
2160 | }
2161 |
2162 | [data-theme="light"] .chat-messages .token {
2163 | background: transparent !important;
2164 | }
2165 |
2166 | /* Ajuster les couleurs de la syntaxe dans le chat en mode clair */
2167 | [data-theme="light"] .chat-messages .token.keyword {
2168 | color: var(--keyword-color) !important;
2169 | }
2170 |
2171 | [data-theme="light"] .chat-messages .token.string {
2172 | color: var(--string-color) !important;
2173 | }
2174 |
2175 | [data-theme="light"] .chat-messages .token.function {
2176 | color: var(--function-color) !important;
2177 | }
2178 |
2179 | [data-theme="light"] .chat-messages .token.number {
2180 | color: var(--number-color) !important;
2181 | }
2182 |
2183 | /* Style des messages de l'IA en mode clair */
2184 | [data-theme="light"] .chat-message-container {
2185 | color: var(--text-color);
2186 | background-color: rgba(255, 255, 255, 0.9);
2187 | border: 1px solid rgba(0, 0, 0, 0.1);
2188 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
2189 | }
2190 |
2191 | [data-theme="light"] .chat-message {
2192 | color: var(--text-color);
2193 | }
2194 |
2195 | [data-theme="light"] .chat-message p {
2196 | color: var(--text-color);
2197 | }
2198 |
2199 | [data-theme="light"] .chat-message-container * {
2200 | color: var(--text-color);
2201 | }
2202 |
2203 | /* S'assurer que les liens restent visibles */
2204 | [data-theme="light"] .chat-message a {
2205 | color: var(--primary-color);
2206 | }
2207 |
2208 | .dragging {
2209 | opacity: 0.5;
2210 | }
2211 |
2212 | .folder-item.drag-over {
2213 | background-color: rgba(0, 120, 215, 0.1);
2214 | border: 2px dashed #0078D7;
2215 | }
2216 |
2217 | .folder-item.drag-over>.folder-header {
2218 | background-color: rgba(0, 120, 215, 0.1);
2219 | }
2220 |
2221 | .drag-handle:hover {
2222 | background-color: rgba(0, 0, 0, 0.1);
2223 | border-radius: 3px;
2224 | }
2225 |
2226 | /* Style pour les fichiers dans un dossier (premier niveau) */
2227 | .folder-item>.folder-content>.file-item {
2228 | font-size: 0.9rem;
2229 | padding: 0.3rem 0;
2230 | border-left: 2px solid #098658;
2231 | margin-left: 10px;
2232 | width: calc(100% - 45px);
2233 | }.folder-item>.folder-content>.file-item:hover {
2234 | width: calc(100% - 55px) !important;
2235 | }
2236 |
2237 | /* Style pour les fichiers dans un sous-dossier (deuxième niveau et plus) */
2238 | .folder-item>.folder-content>.folder-item>.folder-content>.file-item {
2239 | font-size: 0.85rem;
2240 | padding: 0.25rem 0;
2241 | border-left: 2px solid #008000;
2242 | margin-left: 15px;
2243 | }
2244 |
2245 | /* Ajuster la couleur du texte et de l'icône pour les fichiers dans les dossiers */
2246 | .folder-item>.folder-content>.file-item .file-content span,
2247 | .folder-item>.folder-content>.file-item .file-content i {
2248 | color: #098658;
2249 | }
2250 |
2251 | /* Ajuster la couleur du texte et de l'icône pour les fichiers dans les sous-dossiers */
2252 | .folder-item>.folder-content>.folder-item>.folder-content>.file-item .file-content span,
2253 | .folder-item>.folder-content>.folder-item>.folder-content>.file-item .file-content i {
2254 | color: #008000;
2255 | }
2256 |
2257 | /* Ajuster l'espacement et la bordure au survol */
2258 | .folder-item>.folder-content>.file-item:hover,
2259 | .folder-item>.folder-content>.folder-item>.folder-content>.file-item:hover {
2260 | background: rgba(0, 0, 0, 0.05);
2261 | border-left-width: 4px;
2262 | transition: all 0.2s ease;
2263 | }
2264 |
2265 | /* Ajuster la marge gauche pour compenser la bordure au survol */
2266 | .folder-item>.folder-content>.file-item:hover {
2267 | margin-left: 8px;
2268 | }
2269 |
2270 | .folder-item>.folder-content>.folder-item>.folder-content>.file-item:hover {
2271 | margin-left: 13px;
2272 | }
2273 |
2274 | /* Style pour les sous-dossiers */
2275 | .folder-item .folder-item {
2276 | margin-left: 0.5rem;
2277 | }
2278 |
2279 | /* Style pour les icônes de dossier selon le niveau */
2280 | .folder-item .folder-header i.fas {
2281 | color: #098658;
2282 | }
2283 |
2284 | .folder-item .folder-item .folder-header i.fas {
2285 | color: #008000;
2286 | }
2287 |
2288 | /* Style pour le bouton de création de sous-dossier */
2289 | .folder-actions .new-subfolder {
2290 | opacity: 0;
2291 | transition: opacity 0.2s;
2292 | }
2293 |
2294 | .folder-header:hover .new-subfolder {
2295 | opacity: 1;
2296 | }
2297 |
2298 | /* Amélioration de la visibilité de la hiérarchie */
2299 | .folder-content {
2300 | border-left: 1px solid rgba(0, 0, 0, 0.1);
2301 | margin-left: 1rem;
2302 | }
2303 |
2304 | /* Style de base pour les fichiers */
2305 | .file-item {
2306 | font-size: 1rem;
2307 | transition: all 0.2s ease;
2308 | }
2309 |
2310 | .file-item {
2311 | font-size: 0.9rem;
2312 | padding: 0.3rem 0;
2313 | border-left: 2px solid #098658;
2314 | margin-left: 10px;
2315 | width: calc(100% - 30px);
2316 | }
2317 |
2318 | /* Couleur du texte et des icônes pour les fichiers dans les dossiers */
2319 | .folder-content>.file-item .file-content span,
2320 | .folder-content>.file-item .file-content i,
2321 | .folder-content>.file-item .file-actions button {
2322 | color: #098658;
2323 | }
2324 |
2325 | /* Effet de survol pour les fichiers dans les dossiers */
2326 | .folder-content>.file-item:hover {
2327 | background: rgba(9, 134, 88, 0.1);
2328 | border-left-width: 4px;
2329 | margin-left: 8px;
2330 | }
2331 |
2332 | /* Style pour le bouton de suppression */
2333 | .folder-content>.file-item .file-actions .delete-file:hover {
2334 | color: #dc3545;
2335 | }
2336 |
2337 | /* Style pour la poignée de glissement dans les dossiers */
2338 | .folder-content>.file-item .drag-handle {
2339 | color: #098658;
2340 | opacity: 0.7;
2341 | }
2342 |
2343 | /* Style pour le fichier actif dans un dossier */
2344 | .folder-content>.file-item.active {
2345 | background: rgba(9, 134, 88, 0.15);
2346 | border-left-color: #098658;
2347 | }
2348 |
2349 | /* Style pour la zone de drop */
2350 | #fileExplorer.drag-over {
2351 | background-color: rgba(9, 134, 88, 0.1);
2352 | border: 2px dashed #098658;
2353 | }
2354 |
2355 | #fileExplorer {
2356 | min-height: 50px;
2357 | position: relative;
2358 | padding: 8px;
2359 | transition: all 0.2s ease;
2360 | }
--------------------------------------------------------------------------------
/ide/ide.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
47 |
48 |
49 | Python Web IDE
50 |
51 |
52 |
53 |
54 |
55 |
56 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
Python Web IDE
102 |
Initialisation...
103 |
104 |
107 |
108 |
109 |
110 |
170 | V4
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/ide/js/ide/chat.js:
--------------------------------------------------------------------------------
1 | // Création de la fenêtre de chat
2 | function createAIChatWindow() {
3 | const chatWindow = document.createElement('div');
4 | chatWindow.className = 'ai-chat-window';
5 | chatWindow.innerHTML = `
6 |
11 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | `;
30 | document.body.appendChild(chatWindow);
31 |
32 | // Ajout du gestionnaire d'événements pour le bouton d'envoi
33 | const sendButton = chatWindow.querySelector('.send-message');
34 | sendButton.addEventListener('click', sendToIa);
35 |
36 | // Ajout de la fonctionnalité de déplacement
37 | const header = chatWindow.querySelector('.chat-header');
38 | let isDragging = false;
39 | let currentX;
40 | let currentY;
41 | let initialX;
42 | let initialY;
43 | let xOffset = 0;
44 | let yOffset = 0;
45 |
46 | header.addEventListener('mousedown', startDragging);
47 | document.addEventListener('mousemove', drag);
48 | document.addEventListener('mouseup', stopDragging);
49 |
50 | function startDragging(e) {
51 | initialX = e.clientX - xOffset;
52 | initialY = e.clientY - yOffset;
53 | if (e.target.closest('.chat-header')) {
54 | isDragging = true;
55 | }
56 | }
57 |
58 | function drag(e) {
59 | if (isDragging) {
60 | e.preventDefault();
61 | currentX = e.clientX - initialX;
62 | currentY = e.clientY - initialY;
63 | xOffset = currentX;
64 | yOffset = currentY;
65 | chatWindow.style.transform = `translate(${currentX}px, ${currentY}px)`;
66 | }
67 | }
68 |
69 | function stopDragging() {
70 | isDragging = false;
71 | }
72 |
73 | return chatWindow;
74 | }
75 |
76 | // Envoi du message à l'IA
77 | async function sendToIa() {
78 | const message = document.getElementById('ia-input').value;
79 | const codeEditor = document.getElementById('codeEditor');
80 | const consoleContainer = document.getElementById('consoleOutput');
81 |
82 | document.getElementById('loding').style.display = 'flex';
83 |
84 | const chatMessages = document.querySelector('.chat-messages');
85 | chatMessages.innerHTML += `${message}
`;
86 |
87 | const code = codeEditor ? codeEditor.value : "aucun code dans l'IDE";
88 | const consoleHTML = consoleContainer ? consoleContainer.innerHTML : "";
89 |
90 | const payload = {
91 | code: code.trim(),
92 | message: message.trim(),
93 | chatMessages: "",
94 | console: consoleHTML.trim()
95 | };
96 |
97 | try {
98 | const response = await fetch("https://lee-valuable-italiano-financing.trycloudflare.com/api/generate", {
99 | method: "POST",
100 | headers: { "Content-Type": "application/json" },
101 | body: JSON.stringify(payload)
102 | });
103 |
104 | if (!response.ok) throw new Error(`Erreur: ${response.status}`);
105 | const data = await response.json();
106 | afficheResult(data);
107 | } catch (error) {
108 | console.error("Erreur lors de l'envoi du message:", error);
109 | afficheResult({ response: { parts: [{ text: "Une erreur est survenue lors de la communication avec l'IA." }] } });
110 | }
111 | }
112 |
113 | // Affichage du résultat
114 | function afficheResult(result) {
115 | document.getElementById('loding').style.display = 'none';
116 | const responseText = result.response.parts[0].text;
117 | const chatMessages = document.querySelector('.chat-messages');
118 |
119 | const messageContainer = document.createElement('div');
120 | messageContainer.classList.add('chat-message-container');
121 |
122 | const messageElement = document.createElement('div');
123 | messageElement.classList.add('chat-message');
124 | messageElement.innerHTML = ` ` + marked.parse(responseText);
125 |
126 | messageContainer.appendChild(messageElement);
127 |
128 | // Ajout des boutons "Copier" aux blocs de code
129 | messageElement.querySelectorAll('pre code').forEach((codeBlock) => {
130 | const copyButton = document.createElement('button');
131 | copyButton.classList.add('copy-button');
132 | copyButton.textContent = '📋 Copier';
133 |
134 | copyButton.onclick = function () {
135 | navigator.clipboard.writeText(codeBlock.innerText).then(() => {
136 | copyButton.textContent = '✅ Copié !';
137 | setTimeout(() => (copyButton.textContent = '📋 Copier'), 2000);
138 | });
139 | };
140 |
141 | const pre = codeBlock.closest('pre');
142 | pre.style.position = 'relative';
143 | pre.insertBefore(copyButton, codeBlock);
144 | });
145 |
146 | chatMessages.appendChild(messageContainer);
147 | document.getElementById('ia-input').value = '';
148 |
149 | if (window.Prism) {
150 | Prism.highlightAll();
151 | }
152 | }
153 |
154 | // Initialisation du chat
155 | function initializeChat() {
156 | const chatWindow = createAIChatWindow();
157 | let isChatVisible = false;
158 |
159 | const aiHelper = document.getElementById('aiHelper');
160 | aiHelper.addEventListener('click', () => {
161 | isChatVisible = !isChatVisible;
162 | chatWindow.style.display = isChatVisible ? 'flex' : 'none';
163 | });
164 |
165 | const closeChat = chatWindow.querySelector('.close-chat');
166 | closeChat.addEventListener('click', () => {
167 | chatWindow.style.display = 'none';
168 | isChatVisible = false;
169 | });
170 |
171 | const resetButton = chatWindow.querySelector('.reset-chat');
172 | resetButton.addEventListener('click', () => {
173 | const chatMessages = chatWindow.querySelector('.chat-messages');
174 | chatMessages.innerHTML = '';
175 | });
176 |
177 | const chatInput = document.querySelector('.chat-input textarea');
178 | if (!chatInput) return;
179 |
180 | chatInput.addEventListener('keydown', (e) => {
181 | if (e.key === 'Enter' && !e.shiftKey) { // Entrée sans Shift
182 | e.preventDefault(); // Empêcher le saut de ligne
183 | const sendButton = document.querySelector('.chat-input button');
184 | if (sendButton) {
185 | sendButton.click(); // Simuler un clic sur le bouton d'envoi
186 | }
187 | }
188 | // Pour faire un saut de ligne, utiliser Shift + Entrée
189 | });
190 | }
191 |
192 | export {
193 | createAIChatWindow,
194 | sendToIa,
195 | afficheResult,
196 | initializeChat
197 | };
--------------------------------------------------------------------------------
/ide/js/ide/config.js:
--------------------------------------------------------------------------------
1 | // Configuration globale
2 | export let currentLanguage = 'python';
3 |
4 | // Couleurs de la syntaxe - utiliser let au lieu de const
5 | export let syntaxColors = JSON.parse(localStorage.getItem('syntaxColors')) || {
6 | keywordColor: '#60a5fa',
7 | stringColor: '#34d399',
8 | functionColor: '#f59e0b',
9 | numberColor: '#f87171'
10 | };
11 |
12 | // Suggestions de code pour Python
13 | export const pythonSuggestions = {
14 | 'pr': 'print()',
15 | 'inp': 'input()',
16 | 'def': 'def function_name():',
17 | 'for': 'for i in range():',
18 | 'if': 'if condition:',
19 | 'wh': 'while condition:',
20 | 'try': 'try:\n \nexcept Exception as e:',
21 | 'imp': 'import ',
22 | 'cls': 'class ClassName:',
23 | 'ret': 'return ',
24 | 'len': 'len()',
25 | 'str': 'str()',
26 | 'int': 'int()',
27 | 'lis': 'list()',
28 | 'ran': 'range()',
29 | };
30 |
31 | // Suggestions de code pour JavaScript
32 | export const jsSuggestions = {
33 | 'con': 'console.log()',
34 | 'fun': 'function name() {\n \n}',
35 | 'for': 'for (let i = 0; i < n; i++) {\n \n}',
36 | 'if': 'if (condition) {\n \n}',
37 | 'wh': 'while (condition) {\n \n}',
38 | 'try': 'try {\n \n} catch (error) {\n \n}',
39 | 'let': 'let = ',
40 | 'con': 'const = ',
41 | 'imp': 'import from ""',
42 | 'cls': 'class {\n constructor() {\n \n }\n}',
43 | 'ret': 'return ',
44 | };
45 |
46 | // Mots-clés pour la coloration syntaxique
47 | export const pythonKeywords = [
48 | 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif',
49 | 'else', 'except', 'False', 'finally', 'for', 'from', 'global', 'if', 'import',
50 | 'in', 'is', 'lambda', 'None', 'nonlocal', 'not', 'or', 'pass', 'raise',
51 | 'return', 'True', 'try', 'while', 'with', 'yield'
52 | ];
53 |
54 | export const jsKeywords = [
55 | 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
56 | 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
57 | 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'return',
58 | 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
59 | 'while', 'with', 'yield', 'let', 'static', 'enum', 'await', 'async'
60 | ];
--------------------------------------------------------------------------------
/ide/js/ide/editor.js:
--------------------------------------------------------------------------------
1 | import { syntaxColors, currentLanguage } from './config.js';
2 | import { currentFile } from './fileManager.js';
3 |
4 | const lineNumbersContainer = document.getElementById('line-numbers');
5 |
6 | codeEditor.addEventListener('scroll', () => {
7 | lineNumbersContainer.scrollTop = codeEditor.scrollTop;
8 | });
9 |
10 | // Mise à jour de la coloration syntaxique
11 | function updateCodeHighlighting() {
12 | const codeEditor = document.getElementById('codeEditor');
13 | const code = codeEditor.value;
14 |
15 | const preElement = document.createElement('pre');
16 | preElement.className = currentLanguage === 'python' ? 'language-python' : 'language-javascript';
17 |
18 | const codeElement = document.createElement('code');
19 | codeElement.className = currentLanguage === 'python' ? 'language-python' : 'language-javascript';
20 | codeElement.textContent = code;
21 |
22 | preElement.appendChild(codeElement);
23 | Prism.highlightElement(codeElement);
24 |
25 | let highlightedContent = codeElement.innerHTML;
26 | if (code.endsWith('\n')) {
27 | highlightedContent += ' ';
28 | }
29 |
30 | const existingLayer = document.querySelector('.highlight-layer');
31 | if (existingLayer) {
32 | existingLayer.innerHTML = highlightedContent;
33 | } else {
34 | const highlightLayer = document.createElement('div');
35 | highlightLayer.className = 'highlight-layer';
36 | highlightLayer.innerHTML = highlightedContent;
37 | document.querySelector('.editor-container').appendChild(highlightLayer);
38 | }
39 | }
40 |
41 | // Mise à jour des couleurs de syntaxe
42 | function updateSyntaxColors(colors) {
43 | // Mettre à jour l'objet syntaxColors importé
44 | Object.assign(syntaxColors, colors);
45 | localStorage.setItem('syntaxColors', JSON.stringify(colors));
46 |
47 | const style = document.createElement('style');
48 | style.textContent = `
49 | .token.keyword { color: ${colors.keywordColor} !important; }
50 | .token.string { color: ${colors.stringColor} !important; }
51 | .token.function { color: ${colors.functionColor} !important; }
52 | .token.number { color: ${colors.numberColor} !important; }
53 | `;
54 |
55 | const oldStyle = document.getElementById('syntax-colors');
56 | if (oldStyle) {
57 | oldStyle.remove();
58 | }
59 |
60 | style.id = 'syntax-colors';
61 | document.head.appendChild(style);
62 | updateCodeHighlighting();
63 | }
64 |
65 | // Gestion des paires de caractères
66 | function handleCharacterPairs(e) {
67 | const pairs = {
68 | '"': '"',
69 | "'": "'",
70 | '(': ')',
71 | '[': ']',
72 | '{': '}'
73 | };
74 |
75 | if (pairs[e.key]) {
76 | e.preventDefault();
77 | const codeEditor = document.getElementById('codeEditor');
78 | const start = codeEditor.selectionStart;
79 | const end = codeEditor.selectionEnd;
80 |
81 | if (start !== end) {
82 | const selectedText = codeEditor.value.substring(start, end);
83 | codeEditor.value = codeEditor.value.substring(0, start) +
84 | e.key + selectedText + pairs[e.key] +
85 | codeEditor.value.substring(end);
86 | codeEditor.selectionStart = start;
87 | codeEditor.selectionEnd = end + 2;
88 | } else {
89 | codeEditor.value = codeEditor.value.substring(0, start) +
90 | e.key + pairs[e.key] +
91 | codeEditor.value.substring(end);
92 | codeEditor.selectionStart = codeEditor.selectionEnd = start + 1;
93 | }
94 | updateCodeHighlighting();
95 | }
96 | }
97 |
98 | // Gestion de la tabulation
99 | function handleTabKey(e) {
100 | if (e.key === 'Tab') {
101 | e.preventDefault();
102 | const codeEditor = document.getElementById('codeEditor');
103 | const start = codeEditor.selectionStart;
104 | const end = codeEditor.selectionEnd;
105 | codeEditor.value = codeEditor.value.substring(0, start) + " " + codeEditor.value.substring(end);
106 | codeEditor.selectionStart = codeEditor.selectionEnd = start + 4;
107 | updateCodeHighlighting();
108 | }
109 | }
110 |
111 | // Initialisation de l'éditeur
112 | async function initializeEditor() {
113 | const codeEditor = document.getElementById('codeEditor');
114 | if (!codeEditor) {
115 | console.error("L'éditeur de code n'a pas été trouvé");
116 | return;
117 | }
118 |
119 | // Initialiser avec le contenu du fichier courant
120 | if (currentFile && currentFile.content) {
121 | codeEditor.value = currentFile.content;
122 | }
123 |
124 | // Ajouter les écouteurs d'événements
125 | codeEditor.addEventListener('input', () => {
126 | updateCodeHighlighting();
127 | updateLineCounter();
128 | });
129 |
130 | codeEditor.addEventListener('keydown', handleTabKey);
131 | codeEditor.addEventListener('keypress', handleCharacterPairs);
132 | codeEditor.addEventListener('scroll', () => {
133 | const highlightLayer = document.querySelector('.highlight-layer');
134 | if (highlightLayer) {
135 | highlightLayer.scrollTop = codeEditor.scrollTop;
136 | highlightLayer.scrollLeft = codeEditor.scrollLeft;
137 | }
138 | });
139 |
140 | // Initialisation des couleurs de syntaxe
141 | if (syntaxColors) {
142 | updateSyntaxColors(syntaxColors);
143 | }
144 |
145 | // Mise à jour initiale
146 | updateCodeHighlighting();
147 | updateLineCounter();
148 |
149 | return Promise.resolve();
150 | }
151 |
152 | export {
153 | updateCodeHighlighting,
154 | updateSyntaxColors,
155 | initializeEditor
156 | };
--------------------------------------------------------------------------------
/ide/js/ide/fileManager.js:
--------------------------------------------------------------------------------
1 | import { currentLanguage as lang } from './config.js';
2 | import { createModal } from './modal.js';
3 | import { updateCodeHighlighting } from './editor.js';
4 |
5 | export let currentFile = {
6 | id: Date.now(),
7 | name: 'Untitled.py',
8 | content: 'print("Hello World !")'
9 | };
10 |
11 | export let files = [];
12 |
13 | // Chargement des fichiers depuis le localStorage
14 | async function loadFilesFromLocalStorage() {
15 | const storedFiles = localStorage.getItem('files');
16 | const lastActiveFileId = localStorage.getItem('lastActiveFileId');
17 |
18 | if (storedFiles) {
19 | Object.assign(files, JSON.parse(storedFiles));
20 | if (files.length > 0) {
21 | // Si on a un dernier fichier actif, on le charge
22 | if (lastActiveFileId) {
23 | const lastFile = files.find(f => f.id === parseInt(lastActiveFileId));
24 | if (lastFile) {
25 | Object.assign(currentFile, lastFile);
26 | return;
27 | }
28 | }
29 | // Sinon, on charge le premier fichier
30 | Object.assign(currentFile, files[0]);
31 | }
32 | }
33 | }
34 |
35 | // Sauvegarde des fichiers dans le localStorage
36 | function saveFilesToLocalStorage() {
37 | localStorage.setItem('files', JSON.stringify(files));
38 | }
39 |
40 | // Création d'un nouveau fichier
41 | async function createNewFile(defaultName = 'Untitled.py') {
42 | // Trouver le prochain numéro disponible pour Untitled
43 | let nextNumber = 1;
44 | const untitledFiles = files.filter(f => f.name.startsWith('Untitled'));
45 |
46 | if (untitledFiles.length > 0) {
47 | const numbers = untitledFiles
48 | .map(f => {
49 | const match = f.name.match(/Untitled(?:_(\d+))?\.py/);
50 | return match ? parseInt(match[1] || '1') : 1;
51 | })
52 | .sort((a, b) => b - a);
53 |
54 | nextNumber = (numbers[0] || 0) + 1;
55 | }
56 |
57 | const suggestedName = nextNumber === 1 ? 'Untitled.py' : `Untitled_${nextNumber}.py`;
58 | const fileName = await createModal('Nouveau fichier', suggestedName);
59 | if (!fileName) return;
60 |
61 | const baseName = fileName.replace('.py', '');
62 | if (baseName.length > 16) {
63 | alert("Le nom du fichier ne doit pas dépasser 16 caractères");
64 | return;
65 | }
66 |
67 | const newFile = {
68 | id: Date.now(),
69 | name: baseName.endsWith('.py') ? baseName : baseName + '.py',
70 | content: 'print("Hello World !")'
71 | };
72 |
73 | files.push(newFile);
74 | Object.assign(currentFile, newFile);
75 |
76 | // Mettre à jour l'éditeur et l'interface
77 | const codeEditor = document.getElementById('codeEditor');
78 | if (codeEditor) {
79 | codeEditor.value = newFile.content;
80 | updateCodeHighlighting();
81 | }
82 |
83 | updateFileExplorer();
84 | saveFilesToLocalStorage();
85 |
86 | return newFile;
87 | }
88 |
89 | // Suppression d'un fichier
90 | async function deleteFile(fileId) {
91 | if (files.length <= 1) {
92 | await createModal('Action impossible', 'Impossible de supprimer le dernier fichier.', null, 'alert');
93 | return false;
94 | }
95 |
96 | const confirmed = await createModal(
97 | 'Supprimer le fichier',
98 | 'Voulez-vous vraiment supprimer ce fichier ?',
99 | null,
100 | 'confirm'
101 | );
102 |
103 | if (!confirmed) return false;
104 |
105 | const index = files.findIndex(f => f.id === fileId);
106 | if (index !== -1) {
107 | files.splice(index, 1);
108 | if (currentFile.id === fileId) {
109 | Object.assign(currentFile, files[0]);
110 | const codeEditor = document.getElementById('codeEditor');
111 | if (codeEditor) {
112 | codeEditor.value = currentFile.content;
113 | updateCodeHighlighting();
114 | }
115 | }
116 | updateFileExplorer();
117 | return true;
118 | }
119 | return false;
120 | }
121 |
122 | // Renommage d'un fichier
123 | async function renameFile(fileId) {
124 | const currentFileName = document.getElementById('currentFileName');
125 | const file = files.find(f => f.id === fileId);
126 | if (!file) return false;
127 |
128 | const newName = await createModal('Renommer le fichier', file.name);
129 | if (!newName || newName === file.name) return false;
130 |
131 | const baseName = newName.replace('.py', '');
132 | if (baseName.length > 16) {
133 | alert("Le nom du fichier ne doit pas dépasser 16 caractères");
134 | return false;
135 | }
136 |
137 | file.name = baseName.endsWith('.py') ? baseName : baseName + '.py';
138 |
139 | // Mettre à jour currentFile si c'est le fichier actif
140 | if (currentFile.id === fileId) {
141 | currentFile.name = file.name;
142 | if (currentFileName) {
143 | currentFileName.textContent = file.name;
144 | }
145 | }
146 |
147 | updateFileExplorer();
148 | saveFilesToLocalStorage();
149 |
150 | return true;
151 | }
152 |
153 | // Changement de fichier courant
154 | function switchFile(file) {
155 | if (!file) return false;
156 |
157 | // Sauvegarder le contenu du fichier actuel
158 | const codeEditor = document.getElementById('codeEditor');
159 | if (codeEditor && currentFile) {
160 | const currentIndex = files.findIndex(f => f.id === currentFile.id);
161 | if (currentIndex !== -1) {
162 | files[currentIndex].content = codeEditor.value;
163 | saveFilesToLocalStorage();
164 | }
165 | }
166 |
167 | // Mettre à jour le fichier courant
168 | Object.assign(currentFile, file);
169 |
170 | // Mettre à jour l'interface
171 | const currentFileName = document.getElementById('currentFileName');
172 | const lineNumbersContainer = document.getElementById('line-numbers');
173 |
174 | if (codeEditor) {
175 | codeEditor.value = file.content || '';
176 | updateCodeHighlighting();
177 | updateLineCounter();
178 | }
179 |
180 | if (currentFileName) {
181 | currentFileName.textContent = file.name;
182 | }
183 |
184 | // Mettre à jour le langage
185 | const extension = file.name.endsWith('.js') ? '.js' : '.py';
186 | window.currentLanguage = extension === '.py' ? 'python' : 'javascript';
187 |
188 | const runButton = document.getElementById('run');
189 | if (runButton) {
190 | runButton.title = window.currentLanguage === 'python' ? 'Exécuter Python' : 'Exécuter JavaScript';
191 | }
192 |
193 | // Désélectionner tous les fichiers d'abord
194 | document.querySelectorAll('.file-item').forEach(element => {
195 | element.classList.remove('active');
196 | element.style.backgroundColor = '';
197 | });
198 |
199 | // Sélectionner le nouveau fichier
200 | const newActiveFile = document.querySelector(`.file-item[data-file-id="${file.id}"]`);
201 | if (newActiveFile) {
202 | newActiveFile.classList.add('active');
203 | }
204 |
205 | // Sauvegarder l'ID du fichier actif
206 | localStorage.setItem('lastActiveFileId', file.id);
207 |
208 | return true;
209 | }
210 |
211 | // Gestion de l'import de fichiers
212 | function handleFileUpload(event) {
213 | const file = event.target.files[0];
214 | if (!file) return;
215 |
216 | const reader = new FileReader();
217 | reader.onload = function (e) {
218 | const content = e.target.result;
219 | let fileName = file.name;
220 |
221 | if (fileName.length > 16) {
222 | const newName = prompt("Le nom du fichier ne doit pas dépasser 16 caractères. Le renommer :");
223 | if (!newName) return;
224 | if (newName.length > 16) {
225 | alert("Le nouveau nom de fichier ne doit pas dépasser 16 caractères.");
226 | return;
227 | }
228 | fileName = `${newName}.py`;
229 | }
230 |
231 | const newFile = {
232 | id: Date.now(),
233 | name: fileName,
234 | content: content
235 | };
236 |
237 | files.push(newFile);
238 | Object.assign(currentFile, newFile);
239 |
240 | // Mettre à jour l'éditeur
241 | const codeEditor = document.getElementById('codeEditor');
242 | if (codeEditor) {
243 | codeEditor.value = newFile.content;
244 | updateCodeHighlighting();
245 | }
246 |
247 | updateFileExplorer();
248 | };
249 | reader.readAsText(file);
250 | }
251 |
252 | // Mise à jour de l'explorateur de fichiers
253 | async function updateFileExplorer() {
254 | // Vérifier s'il n'y a aucun fichier
255 | if (files.length === 0) {
256 | const defaultFile = {
257 | id: Date.now(),
258 | name: 'Untitled.py',
259 | content: 'print("Hello World !")'
260 | };
261 | files.push(defaultFile);
262 | Object.assign(currentFile, defaultFile);
263 |
264 | const codeEditor = document.getElementById('codeEditor');
265 | if (codeEditor) {
266 | codeEditor.value = defaultFile.content;
267 | updateCodeHighlighting();
268 | }
269 | }
270 |
271 | updateLineCounter();
272 | const fragment = document.createDocumentFragment();
273 | document.getElementById('currentFileName').textContent = currentFile.name;
274 |
275 | // Séparer les fichiers et les dossiers
276 | const folders = files.filter(item => item.type === 'folder');
277 | const rootFiles = files.filter(item => !item.type && !item.path);
278 | const filesInFolders = files.filter(item => !item.type && item.path);
279 |
280 | // Trier par ordre
281 | const sortByOrder = (a, b) => (a.order || 0) - (b.order || 0);
282 | folders.sort(sortByOrder);
283 | rootFiles.sort(sortByOrder);
284 |
285 | // Créer les éléments de dossier
286 | folders.forEach(folder => {
287 | const folderElement = document.createElement('div');
288 | folderElement.className = 'folder-item';
289 | folderElement.style.paddingLeft = '1.2rem';
290 | folderElement.innerHTML = `
291 |
300 |
301 | `;
302 |
303 | // Gestion du drag & drop pour le dossier
304 | folderElement.addEventListener('dragover', (e) => {
305 | e.preventDefault();
306 | folderElement.classList.add('drag-over');
307 | });
308 |
309 | folderElement.addEventListener('dragleave', () => {
310 | folderElement.classList.remove('drag-over');
311 | });
312 |
313 | folderElement.addEventListener('drop', (e) => {
314 | e.preventDefault();
315 | folderElement.classList.remove('drag-over');
316 |
317 | const data = JSON.parse(e.dataTransfer.getData('text/plain'));
318 | if (data.type === 'file') {
319 | const file = files.find(f => f.id === parseInt(data.id));
320 | if (file) {
321 | file.path = folder.name;
322 | file.order = Date.now();
323 | updateFileExplorer();
324 | saveFilesToLocalStorage();
325 | }
326 | }
327 | });
328 |
329 | // Toggle folder
330 | const folderHeader = folderElement.querySelector('.folder-header');
331 | folderHeader.addEventListener('click', (e) => {
332 | if (!e.target.closest('.folder-actions')) {
333 | folder.isOpen = !folder.isOpen;
334 | const content = folderElement.querySelector('.folder-content');
335 | const icon = folderElement.querySelector('.fas');
336 | content.style.display = folder.isOpen ? 'block' : 'none';
337 | icon.classList.toggle('fa-folder-open');
338 | icon.classList.toggle('fa-folder');
339 | saveFilesToLocalStorage();
340 | }
341 | });
342 |
343 | // Renommer le dossier
344 | folderElement.querySelector('.rename-folder').addEventListener('click', async (e) => {
345 | e.stopPropagation();
346 | const newName = await createModal('Renommer le dossier', folder.name);
347 | if (newName && newName !== folder.name && newName.length <= 16) {
348 | const oldName = folder.name;
349 | folder.name = newName;
350 | // Mettre à jour les chemins des fichiers
351 | filesInFolders.forEach(file => {
352 | if (file.path === oldName) {
353 | file.path = newName;
354 | }
355 | });
356 | updateFileExplorer();
357 | saveFilesToLocalStorage();
358 | }
359 | });
360 |
361 | // Supprimer le dossier
362 | folderElement.querySelector('.delete-folder').addEventListener('click', async (e) => {
363 | e.stopPropagation();
364 | const hasFiles = filesInFolders.some(file => file.path === folder.name);
365 | if (hasFiles) {
366 | const confirmed = await createModal(
367 | 'Supprimer le dossier',
368 | 'Ce dossier contient des fichiers. Voulez-vous vraiment le supprimer ?',
369 | null,
370 | 'confirm'
371 | );
372 | if (!confirmed) return;
373 | }
374 |
375 | // Supprimer le dossier et tous les fichiers qu'il contient
376 | files = files.filter(f => f !== folder && f.path !== folder.name);
377 |
378 | updateFileExplorer();
379 | saveFilesToLocalStorage();
380 | });
381 |
382 | // Ajouter les fichiers du dossier
383 | const folderContent = folderElement.querySelector('.folder-content');
384 | const folderFiles = filesInFolders.filter(file => file.path === folder.name);
385 | folderFiles.sort(sortByOrder);
386 |
387 | folderFiles.forEach(file => {
388 | const fileElement = createFileElement(file);
389 | folderContent.appendChild(fileElement);
390 | });
391 |
392 | fragment.appendChild(folderElement);
393 | });
394 |
395 | // Ajouter les fichiers racine
396 | rootFiles.forEach(file => {
397 | const fileElement = createFileElement(file);
398 | fragment.appendChild(fileElement);
399 | });
400 |
401 | const fileExplorer = document.getElementById('fileExplorer');
402 | fileExplorer.innerHTML = '';
403 | fileExplorer.appendChild(fragment);
404 |
405 | // Ajouter les gestionnaires pour permettre le drop sur la racine
406 | fileExplorer.addEventListener('dragover', (e) => {
407 | e.preventDefault();
408 | // Ne montrer la zone de drop que si on n'est pas au-dessus d'un dossier
409 | if (!e.target.closest('.folder-item')) {
410 | fileExplorer.classList.add('drag-over');
411 | }
412 | });
413 |
414 | fileExplorer.addEventListener('dragleave', (e) => {
415 | e.preventDefault();
416 | if (!e.target.closest('.folder-item')) {
417 | fileExplorer.classList.remove('drag-over');
418 | }
419 | });
420 |
421 | fileExplorer.addEventListener('drop', (e) => {
422 | e.preventDefault();
423 | fileExplorer.classList.remove('drag-over');
424 |
425 | // Si on drop sur un dossier, laisser le gestionnaire du dossier s'en occuper
426 | if (e.target.closest('.folder-item')) {
427 | return;
428 | }
429 |
430 | try {
431 | const data = JSON.parse(e.dataTransfer.getData('text/plain'));
432 | if (data.type === 'file') {
433 | const file = files.find(f => f.id === parseInt(data.id));
434 | if (file) {
435 | // Supprimer le chemin pour mettre le fichier à la racine
436 | delete file.path;
437 | file.order = Date.now();
438 | updateFileExplorer();
439 | saveFilesToLocalStorage();
440 | }
441 | }
442 | } catch (error) {
443 | console.error('Erreur lors du drop sur fileExplorer:', error);
444 | }
445 | });
446 |
447 | saveFilesToLocalStorage();
448 | }
449 |
450 | // Fonction helper pour créer un élément de fichier
451 | function createFileElement(file) {
452 | const element = document.createElement('div');
453 | element.className = 'file-item';
454 | element.draggable = true;
455 | element.dataset.fileId = String(file.id);
456 | element.dataset.type = 'file';
457 | element.style.paddingLeft = file.path ? '2rem' : '1.2rem';
458 |
459 | if (currentFile && currentFile.id === file.id) {
460 | element.classList.add('active');
461 | }
462 |
463 | element.innerHTML = `
464 |
465 |
466 |
⋮⋮
467 |
${file.name}
468 |
469 |
470 |
471 |
472 |
473 | `;
474 |
475 | // Ajouter les gestionnaires d'événements pour les boutons
476 | element.querySelector('.delete-file').addEventListener('click', async (e) => {
477 | e.stopPropagation();
478 | await deleteFile(file.id);
479 | });
480 |
481 | element.querySelector('.rename-file').addEventListener('click', async (e) => {
482 | e.stopPropagation();
483 | await renameFile(file.id);
484 | });
485 |
486 | element.addEventListener('dragstart', (e) => {
487 | e.dataTransfer.setData('text/plain', JSON.stringify({
488 | type: 'file',
489 | id: file.id
490 | }));
491 | });
492 |
493 | element.addEventListener('click', (e) => {
494 | if (!e.target.closest('.file-actions')) {
495 | document.querySelectorAll('.file-item').forEach(el => {
496 | el.classList.remove('active');
497 | });
498 | element.classList.add('active');
499 | switchFile(file);
500 | }
501 | });
502 |
503 | return element;
504 | }
505 |
506 | // Création d'un nouveau dossier
507 | async function createNewFolder() {
508 | // Trouver le prochain numéro disponible pour les dossiers
509 | let nextNumber = 1;
510 | const existingFolders = files.filter(f => f.type === 'folder' && f.name.startsWith('Folder'));
511 |
512 | if (existingFolders.length > 0) {
513 | const numbers = existingFolders
514 | .map(f => {
515 | const match = f.name.match(/Folder(?:_(\d+))?/);
516 | return match ? parseInt(match[1] || '1') : 1;
517 | })
518 | .sort((a, b) => b - a);
519 |
520 | nextNumber = (numbers[0] || 0) + 1;
521 | }
522 |
523 | const suggestedName = nextNumber === 1 ? 'Folder' : `Folder_${nextNumber}`;
524 | const folderName = await createModal('Nouveau dossier', suggestedName);
525 | if (!folderName) return;
526 |
527 | if (folderName.length > 16) {
528 | alert("Le nom du dossier ne doit pas dépasser 16 caractères");
529 | return;
530 | }
531 |
532 | if (files.some(f => f.type === 'folder' && f.name === folderName)) {
533 | alert("Un dossier avec ce nom existe déjà");
534 | return;
535 | }
536 |
537 | const newFolder = {
538 | id: Date.now(),
539 | name: folderName,
540 | type: 'folder',
541 | isOpen: true,
542 | order: Date.now()
543 | };
544 |
545 | files.push(newFolder);
546 | updateFileExplorer();
547 | saveFilesToLocalStorage();
548 | }
549 |
550 | // Ajout d'une fonction pour sauvegarder le contenu actuel
551 | function saveCurrentFile() {
552 | const codeEditor = document.getElementById('codeEditor');
553 | if (codeEditor && currentFile) {
554 | const currentIndex = files.findIndex(f => f.id === currentFile.id);
555 | if (currentIndex !== -1) {
556 | files[currentIndex].content = codeEditor.value;
557 | currentFile.content = codeEditor.value;
558 | saveFilesToLocalStorage();
559 | }
560 | }
561 | }
562 |
563 | const codeEditor = document.getElementById('codeEditor');
564 | if (codeEditor) {
565 | codeEditor.addEventListener('input', () => {
566 | saveCurrentFile();
567 | });
568 | }
569 |
570 | // Pour la fermeture de la fenêtre
571 | window.addEventListener('beforeunload', () => {
572 | saveCurrentFile();
573 | });
574 |
575 | const newFolderButton = document.getElementById('newFolder');
576 | if (newFolderButton) {
577 | newFolderButton.addEventListener('click', createNewFolder);
578 | }
579 |
580 | // Fonction pour déplacer un fichier dans un dossier
581 | function moveFileToFolder(fileId, folderPath) {
582 | const file = files.find(f => f.id === fileId);
583 | if (!file) return;
584 |
585 | file.path = folderPath + '/' + file.name;
586 | updateFileExplorer();
587 | saveFilesToLocalStorage();
588 | }
589 |
590 | export {
591 | loadFilesFromLocalStorage,
592 | saveFilesToLocalStorage,
593 | createNewFile,
594 | createNewFolder,
595 | deleteFile,
596 | renameFile,
597 | switchFile,
598 | handleFileUpload,
599 | updateFileExplorer,
600 | saveCurrentFile
601 | };
--------------------------------------------------------------------------------
/ide/js/ide/main.js:
--------------------------------------------------------------------------------
1 | import { initPyodide, runPythonCode } from './pyodide.js';
2 | import { loadFilesFromLocalStorage, saveFilesToLocalStorage, currentFile, files, updateFileExplorer, createNewFile, handleFileUpload } from './fileManager.js';
3 | import { initializeEditor, updateCodeHighlighting } from './editor.js';
4 | import { initializeSuggestions } from './suggestions.js';
5 | import { initializeUI, checkMobileCompatibility } from './ui.js';
6 | import { initializeChat } from './chat.js';
7 |
8 | // Configuration des écouteurs d'événements
9 | function setupEventListeners() {
10 | const elements = {
11 | codeEditor: document.getElementById('codeEditor'),
12 | runButton: document.getElementById('run'),
13 | saveButton: document.getElementById('save'),
14 | clearButton: document.getElementById('clear'),
15 | newFileButton: document.getElementById('newFile'),
16 | fileUploadInput: document.getElementById('file-upload'),
17 | consoleOutput: document.getElementById('consoleOutput')
18 | };
19 |
20 | // Vérification de tous les éléments nécessaires
21 | for (const [name, element] of Object.entries(elements)) {
22 | if (!element) {
23 | console.error(`Élément ${name} non trouvé dans le DOM`);
24 | return;
25 | }
26 | }
27 |
28 | // Gestion des fichiers avec debounce pour éviter les appels multiples
29 | let fileOperationTimeout;
30 | const handleFileOperation = (operation) => {
31 | clearTimeout(fileOperationTimeout);
32 | fileOperationTimeout = setTimeout(() => {
33 | try {
34 | operation();
35 | updateFileExplorer();
36 | updateCodeHighlighting();
37 | saveFilesToLocalStorage();
38 | } catch (error) {
39 | console.error('Erreur lors de l\'opération sur le fichier:', error);
40 | }
41 | }, 100);
42 | };
43 |
44 | // Nouveau fichier
45 | elements.newFileButton.addEventListener('click', () => {
46 | handleFileOperation(() => createNewFile('nouveau_fichier.py'));
47 | });
48 |
49 | // Import de fichier
50 | elements.fileUploadInput.addEventListener('change', (event) => {
51 | handleFileOperation(() => handleFileUpload(event));
52 | });
53 |
54 | // Exécution du code avec gestion des erreurs améliorée
55 | elements.runButton.addEventListener('click', async () => {
56 | try {
57 | elements.consoleOutput.innerHTML = '';
58 | elements.runButton.disabled = true;
59 |
60 | const result = await runPythonCode(elements.codeEditor.value);
61 |
62 | if (result.success) {
63 | if (result.output?.trim()) {
64 | const lines = result.output.split('\n').filter(line => line.trim());
65 | elements.consoleOutput.innerHTML = lines
66 | .map(line => `${line}
`)
67 | .join('');
68 | }
69 | } else {
70 | elements.consoleOutput.innerHTML = `${result.error || 'Erreur inconnue'}
`;
71 | }
72 | } catch (error) {
73 | console.error('Erreur lors de l\'exécution:', error);
74 | elements.consoleOutput.innerHTML = `Erreur lors de l'exécution: ${error.message}
`;
75 | } finally {
76 | elements.runButton.disabled = false;
77 | }
78 | });
79 |
80 | // Sauvegarde du fichier avec gestion des erreurs
81 | elements.saveButton.addEventListener('click', () => {
82 | try {
83 | const code = elements.codeEditor.value;
84 | const blob = new Blob([code], { type: 'text/plain' });
85 | const url = URL.createObjectURL(blob);
86 | const a = document.createElement('a');
87 | a.href = url;
88 | a.download = currentFile.name;
89 | a.click();
90 | URL.revokeObjectURL(url);
91 | } catch (error) {
92 | console.error('Erreur lors de la sauvegarde:', error);
93 | }
94 | });
95 |
96 | // Effacement du code avec confirmation
97 | elements.clearButton.addEventListener('click', () => {
98 | if (confirm('Voulez-vous vraiment effacer tout le code ?')) {
99 | elements.codeEditor.value = '';
100 | updateCodeHighlighting();
101 | updateLineCounter();
102 | }
103 | });
104 |
105 | // Sauvegarde automatique avec debounce
106 | let saveTimeout;
107 | elements.codeEditor.addEventListener('input', () => {
108 | clearTimeout(saveTimeout);
109 | saveTimeout = setTimeout(() => {
110 | if (currentFile) {
111 | currentFile.content = elements.codeEditor.value;
112 | saveFilesToLocalStorage();
113 | }
114 | }, 500);
115 | });
116 |
117 | // Sauvegarde avant de quitter
118 | window.addEventListener('beforeunload', () => {
119 | if (currentFile) {
120 | currentFile.content = elements.codeEditor.value;
121 | saveFilesToLocalStorage();
122 | }
123 | });
124 | }
125 |
126 | // Initialisation de l'application avec gestion des erreurs améliorée
127 | async function initializeApp() {
128 | const loadingScreen = document.getElementById('loading-screen');
129 | const loadingText = loadingScreen?.querySelector('.typing-text');
130 | let messageInterval;
131 |
132 | try {
133 | // Enregistrer le temps de début
134 | const startTime = Date.now();
135 |
136 | // Animation du loader avec plus de messages
137 | if (loadingText) {
138 | const messages = [
139 | 'Chargement...',
140 | 'Configuration de l\'environnement...',
141 | 'Initialisation de Python...',
142 | 'Chargement des fichiers...',
143 | 'Configuration de l\'éditeur...',
144 | 'Presque prêt...'
145 | ];
146 | let messageIndex = 0;
147 | messageInterval = setInterval(() => {
148 | loadingText.textContent = messages[messageIndex];
149 | messageIndex = (messageIndex + 1) % messages.length;
150 | }, 600); // Messages plus rapides pour voir plus de messages
151 | }
152 |
153 | // Vérification de la compatibilité mobile
154 | if (!checkMobileCompatibility()) {
155 | throw new Error('Appareil non compatible');
156 | }
157 |
158 | // Initialisation de l'interface utilisateur
159 | initializeUI();
160 |
161 | // Initialisation des composants essentiels
162 | await Promise.all([
163 | loadFilesFromLocalStorage(),
164 | initializeEditor(),
165 | initializeSuggestions()
166 | ]);
167 |
168 | // Initialisation des composants non-bloquants
169 | Promise.all([
170 | initPyodide(),
171 | initializeChat()
172 | ]).catch(error => {
173 | console.error('Erreur non critique:', error);
174 | });
175 |
176 | // Configuration finale
177 | updateFileExplorer();
178 | setupEventListeners();
179 |
180 | // Calculer le temps écoulé
181 | const elapsedTime = Date.now() - startTime;
182 |
183 | // S'assurer que le loader reste visible au moins 3.5 secondes
184 | if (elapsedTime < 3500) {
185 | await new Promise(resolve => setTimeout(resolve, 3500 - elapsedTime));
186 | }
187 |
188 | } catch (error) {
189 | console.error('Erreur critique lors de l\'initialisation:', error);
190 | if (loadingText) {
191 | loadingText.textContent = 'Erreur de chargement. Veuillez rafraîchir la page.';
192 | }
193 | // Attendre un peu même en cas d'erreur
194 | await new Promise(resolve => setTimeout(resolve, 3500));
195 | } finally {
196 | // Nettoyage avec transition plus douce
197 | clearInterval(messageInterval);
198 | if (loadingScreen) {
199 | loadingScreen.style.transition = 'opacity 0.8s ease, visibility 0.8s';
200 | loadingScreen.style.opacity = '0';
201 | await new Promise(resolve => setTimeout(resolve, 800));
202 | loadingScreen.style.visibility = 'hidden';
203 | }
204 | // Afficher le contenu
205 | document.body.classList.add('loaded');
206 | }
207 | }
208 |
209 | // Démarrage sécurisé de l'application
210 | document.addEventListener('DOMContentLoaded', () => {
211 | initializeApp().catch(error => {
212 | console.error('Erreur fatale:', error);
213 | alert('Une erreur est survenue lors du démarrage de l\'application. Veuillez rafraîchir la page.');
214 | });
215 | });
216 |
217 | export {
218 | initializeApp,
219 | setupEventListeners
220 | };
--------------------------------------------------------------------------------
/ide/js/ide/modal.js:
--------------------------------------------------------------------------------
1 | // Gestion des modales
2 | export function createModal(title, content, onConfirm, type = 'input') {
3 | return new Promise((resolve) => {
4 | const overlay = document.createElement('div');
5 | overlay.className = 'modal-overlay';
6 |
7 | const modal = document.createElement('div');
8 | modal.className = 'modal-container';
9 |
10 | const header = document.createElement('div');
11 | header.className = 'modal-header';
12 | header.innerHTML = `${title} `;
13 |
14 | const modalContent = document.createElement('div');
15 | modalContent.className = 'modal-content';
16 |
17 | if (type === 'input') {
18 | const input = document.createElement('input');
19 | input.type = 'text';
20 | input.className = 'modal-input';
21 | input.value = content;
22 | modalContent.appendChild(input);
23 | } else {
24 | modalContent.innerHTML = content;
25 | }
26 |
27 | const actions = document.createElement('div');
28 | actions.className = 'modal-actions';
29 |
30 | if (type !== 'alert') {
31 | const cancelButton = document.createElement('button');
32 | cancelButton.className = 'modal-button secondary';
33 | cancelButton.textContent = 'Annuler';
34 | actions.appendChild(cancelButton);
35 |
36 | cancelButton.onclick = () => {
37 | overlay.classList.remove('active');
38 | setTimeout(() => document.body.removeChild(overlay), 300);
39 | resolve(null);
40 | };
41 | }
42 |
43 | const confirmButton = document.createElement('button');
44 | confirmButton.className = 'modal-button primary';
45 | confirmButton.textContent = type === 'input' && title === 'Renommer le fichier' ? 'Renommer' :
46 | type === 'input' && title === 'Nouveau fichier' ? 'Créer' :
47 | type === 'confirm' ? 'Supprimer' : 'OK';
48 | actions.appendChild(confirmButton);
49 |
50 | modal.appendChild(header);
51 | modal.appendChild(modalContent);
52 | modal.appendChild(actions);
53 | overlay.appendChild(modal);
54 |
55 | document.body.appendChild(overlay);
56 | setTimeout(() => overlay.classList.add('active'), 0);
57 |
58 | if (type === 'colors') {
59 | confirmButton.onclick = () => {
60 | const colors = {
61 | keywordColor: document.getElementById('keywordColor').value,
62 | stringColor: document.getElementById('stringColor').value,
63 | functionColor: document.getElementById('functionColor').value,
64 | numberColor: document.getElementById('numberColor').value
65 | };
66 | overlay.classList.remove('active');
67 | setTimeout(() => document.body.removeChild(overlay), 300);
68 | resolve(colors);
69 | };
70 | } else {
71 | confirmButton.onclick = () => {
72 | const value = type === 'input' ? modalContent.querySelector('input').value : true;
73 | overlay.classList.remove('active');
74 | setTimeout(() => document.body.removeChild(overlay), 300);
75 | resolve(value);
76 | };
77 | }
78 |
79 | if (type !== 'alert') {
80 | overlay.addEventListener('click', (e) => {
81 | if (e.target === overlay) {
82 | overlay.classList.remove('active');
83 | setTimeout(() => document.body.removeChild(overlay), 300);
84 | resolve(null);
85 | }
86 | });
87 | }
88 | });
89 | }
--------------------------------------------------------------------------------
/ide/js/ide/pyodide.js:
--------------------------------------------------------------------------------
1 | let pyodide = null;
2 | let isWaitingForInput = false;
3 |
4 | // Définir customInput globalement
5 | globalThis.customInput = (promptText) => {
6 | if (isWaitingForInput) return;
7 | isWaitingForInput = true;
8 |
9 | const value = window.prompt(promptText);
10 | isWaitingForInput = false;
11 |
12 | if (value === null) {
13 | return "";
14 | }
15 | return value;
16 | };
17 |
18 | // Initialisation de Pyodide
19 | async function initPyodide() {
20 | try {
21 | // Ajouter un timeout de 30 secondes
22 | const timeoutPromise = new Promise((_, reject) => {
23 | setTimeout(() => reject(new Error("Timeout lors du chargement de Pyodide")), 30000);
24 | });
25 |
26 | const loadingPromise = loadPyodide({
27 | indexURL: "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/"
28 | });
29 |
30 | // Utiliser Promise.race pour implémenter le timeout
31 | pyodide = await Promise.race([loadingPromise, timeoutPromise]);
32 |
33 | // Configuration de l'environnement Python
34 | await pyodide.runPythonAsync(`
35 | import sys
36 | from js import customInput
37 |
38 | def input(prompt_text=""):
39 | try:
40 | return str(customInput(prompt_text))
41 | except Exception as e:
42 | print(f"Erreur lors de l'input: {e}")
43 | return ""
44 |
45 | def int_input(prompt_text=""):
46 | try:
47 | val = customInput(prompt_text)
48 | return int(val) if val.strip() else 0
49 | except Exception as e:
50 | print(f"Erreur lors de l'int_input: {e}")
51 | return 0
52 |
53 | # Remplacer les fonctions d'input globales
54 | globals()['input'] = input
55 | globals()['int_input'] = int_input
56 | __builtins__.input = input
57 | __builtins__.int_input = int_input
58 | `);
59 |
60 | return true;
61 | } catch (error) {
62 | console.error('Erreur lors de l\'initialisation de Pyodide:', error);
63 | throw error; // Propager l'erreur pour la gestion dans main.js
64 | }
65 | }
66 |
67 | // Exécution du code Python
68 | async function runPythonCode(code) {
69 | if (!pyodide) {
70 | return {
71 | success: false,
72 | error: "Pyodide n'est pas encore chargé. Veuillez patienter..."
73 | };
74 | }
75 |
76 | try {
77 | // Remplacer tous les int(input(...)) par int_input(...)
78 | code = code.replace(/int\(input\((.*?)\)\)/g, 'int_input($1)');
79 |
80 | await pyodide.runPythonAsync(`
81 | import sys
82 | from io import StringIO
83 |
84 | class CustomStringIO(StringIO):
85 | def write(self, text):
86 | if text.endswith('\\n'):
87 | super().write(text)
88 | else:
89 | super().write(text + '\\n')
90 |
91 | sys.stdout = CustomStringIO()
92 | `);
93 |
94 | await pyodide.runPythonAsync(code);
95 | const output = await pyodide.runPythonAsync(`sys.stdout.getvalue()`);
96 |
97 | return {
98 | success: true,
99 | output: output
100 | };
101 | } catch (error) {
102 | return {
103 | success: false,
104 | error: error.message
105 | };
106 | }
107 | }
108 |
109 | // Fonction pour créer une zone d'entrée personnalisée
110 | function createCustomPrompt(message, title) {
111 | return new Promise((resolve) => {
112 | const modal = document.createElement('div');
113 | modal.className = 'custom-modal';
114 |
115 | const modalContent = document.createElement('div');
116 | modalContent.className = 'modal-content';
117 |
118 | const modalTitle = document.createElement('div');
119 | modalTitle.className = 'modal-title';
120 | modalTitle.textContent = title;
121 |
122 | const modalMessage = document.createElement('div');
123 | modalMessage.className = 'modal-message';
124 | modalMessage.textContent = message;
125 |
126 | const input = document.createElement('input');
127 | input.type = 'text';
128 | input.className = 'modal-input';
129 |
130 | const buttonContainer = document.createElement('div');
131 | buttonContainer.className = 'modal-buttons';
132 |
133 | const okButton = document.createElement('button');
134 | okButton.textContent = 'OK';
135 | okButton.onclick = () => {
136 | document.body.removeChild(modal);
137 | resolve(input.value);
138 | };
139 |
140 | buttonContainer.appendChild(okButton);
141 | modalContent.appendChild(modalTitle);
142 | modalContent.appendChild(modalMessage);
143 | modalContent.appendChild(input);
144 | modalContent.appendChild(buttonContainer);
145 | modal.appendChild(modalContent);
146 |
147 | document.body.appendChild(modal);
148 | input.focus();
149 |
150 | input.addEventListener('keypress', (e) => {
151 | if (e.key === 'Enter') {
152 | okButton.click();
153 | }
154 | });
155 | });
156 | }
157 |
158 | export {
159 | pyodide,
160 | isWaitingForInput,
161 | initPyodide,
162 | runPythonCode,
163 | createCustomPrompt
164 | };
--------------------------------------------------------------------------------
/ide/js/ide/suggestions.js:
--------------------------------------------------------------------------------
1 | import { pythonSuggestions, jsSuggestions, currentLanguage } from './config.js';
2 | import { updateCodeHighlighting } from './editor.js';
3 |
4 | let suggestionBox = null;
5 |
6 | // Création de la boîte de suggestions
7 | function createSuggestionBox() {
8 | const box = document.createElement('div');
9 | box.id = 'suggestionBox';
10 | box.style.cssText = `
11 | position: absolute;
12 | background: var(--editor-bg);
13 | border: 1px solid var(--primary-color);
14 | border-radius: 4px;
15 | max-height: 150px;
16 | overflow-y: auto;
17 | display: none;
18 | z-index: 1000;
19 | box-shadow: var(--box-shadow);
20 | `;
21 | document.querySelector('.editor-container').appendChild(box);
22 | return box;
23 | }
24 |
25 | // Affichage des suggestions
26 | function showSuggestions(suggestions, word) {
27 | if (!suggestionBox) {
28 | suggestionBox = createSuggestionBox();
29 | }
30 |
31 | const { left, top, height } = getCaretCoordinates();
32 |
33 | suggestionBox.innerHTML = suggestions
34 | .map(suggestion => `${suggestion}
`)
35 | .join('');
36 |
37 | suggestionBox.style.display = 'block';
38 | suggestionBox.style.left = `${left}px`;
39 | suggestionBox.style.top = `${top + height}px`;
40 |
41 | const items = suggestionBox.querySelectorAll('.suggestion-item');
42 | if (items.length > 0) {
43 | items[0].classList.add('active');
44 | }
45 |
46 | items.forEach(item => {
47 | item.addEventListener('click', () => {
48 | applySuggestion(item.textContent, word);
49 | });
50 | });
51 | }
52 |
53 | // Obtention des coordonnées du curseur
54 | function getCaretCoordinates() {
55 | const codeEditor = document.getElementById('codeEditor');
56 | const position = codeEditor.selectionStart;
57 | const text = codeEditor.value.substring(0, position);
58 | const lines = text.split('\n');
59 | const currentLine = lines.length;
60 | const currentColumn = lines[lines.length - 1].length;
61 |
62 | const lineHeight = parseInt(getComputedStyle(codeEditor).lineHeight);
63 | const padding = parseInt(getComputedStyle(codeEditor).padding);
64 |
65 | return {
66 | left: currentColumn * 8 + padding + 48, // 48px pour la gouttière
67 | top: (currentLine - 1) * lineHeight + padding,
68 | height: lineHeight
69 | };
70 | }
71 |
72 | // Application de la suggestion
73 | function applySuggestion(suggestion, word) {
74 | const codeEditor = document.getElementById('codeEditor');
75 | const cursorPos = codeEditor.selectionStart;
76 | const textBeforeCursor = codeEditor.value.substring(0, cursorPos - word.length);
77 | const textAfterCursor = codeEditor.value.substring(cursorPos);
78 |
79 | const cleanTextAfter = textAfterCursor.trimLeft();
80 | const isInsideParentheses = textBeforeCursor.trim().endsWith('(') && cleanTextAfter.startsWith(')');
81 |
82 | let finalText;
83 | if (isInsideParentheses) {
84 | finalText = textBeforeCursor.trimEnd() + suggestion.slice(0, -1) + cleanTextAfter;
85 | } else {
86 | finalText = textBeforeCursor + suggestion + cleanTextAfter;
87 | }
88 |
89 | codeEditor.value = finalText;
90 |
91 | const newCursorPos = textBeforeCursor.length + suggestion.length;
92 | if (suggestion.includes('()')) {
93 | codeEditor.selectionStart = codeEditor.selectionEnd = newCursorPos - 1;
94 | } else {
95 | codeEditor.selectionStart = codeEditor.selectionEnd = newCursorPos;
96 | }
97 |
98 | suggestionBox.style.display = 'none';
99 | updateCodeHighlighting();
100 | }
101 |
102 | // Mise à jour de l'élément actif
103 | function updateActiveItem(items, activeIndex) {
104 | items.forEach(item => item.classList.remove('active'));
105 | items[activeIndex].classList.add('active');
106 | items[activeIndex].scrollIntoView({ block: 'nearest' });
107 | }
108 |
109 | // Gestion des événements clavier pour les suggestions
110 | function handleSuggestionKeys(e) {
111 | if (suggestionBox && suggestionBox.style.display === 'block') {
112 | const items = suggestionBox.querySelectorAll('.suggestion-item');
113 | const activeItem = suggestionBox.querySelector('.suggestion-item.active') || items[0];
114 | let activeIndex = Array.from(items).indexOf(activeItem);
115 |
116 | switch (e.key) {
117 | case 'ArrowDown':
118 | e.preventDefault();
119 | activeIndex = (activeIndex + 1) % items.length;
120 | updateActiveItem(items, activeIndex);
121 | break;
122 | case 'ArrowUp':
123 | e.preventDefault();
124 | activeIndex = activeIndex <= 0 ? items.length - 1 : activeIndex - 1;
125 | updateActiveItem(items, activeIndex);
126 | break;
127 | case 'Tab':
128 | case 'Enter':
129 | e.preventDefault();
130 | if (items.length > 0) {
131 | const textBeforeCursor = document.getElementById('codeEditor').value
132 | .substring(0, document.getElementById('codeEditor').selectionStart);
133 | const lastWord = textBeforeCursor.split(/[\s\n]/).pop();
134 | applySuggestion(activeItem.textContent, lastWord);
135 | }
136 | break;
137 | case 'Escape':
138 | e.preventDefault();
139 | suggestionBox.style.display = 'none';
140 | break;
141 | }
142 | }
143 | }
144 |
145 | // Initialisation du système de suggestions
146 | function initializeSuggestions() {
147 | const codeEditor = document.getElementById('codeEditor');
148 |
149 | codeEditor.addEventListener('input', (e) => {
150 | const cursorPos = codeEditor.selectionStart;
151 | const textBeforeCursor = codeEditor.value.substring(0, cursorPos);
152 | const lastWord = textBeforeCursor.split(/[\s\n]/).pop();
153 |
154 | if (lastWord && lastWord.length >= 2) {
155 | const suggestions = currentLanguage === 'python' ? pythonSuggestions : jsSuggestions;
156 | const matches = Object.entries(suggestions)
157 | .filter(([key]) => key.startsWith(lastWord))
158 | .map(([key, value]) => value);
159 |
160 | if (matches.length > 0) {
161 | showSuggestions(matches, lastWord);
162 | } else {
163 | if (suggestionBox) {
164 | suggestionBox.style.display = 'none';
165 | }
166 | }
167 | } else {
168 | if (suggestionBox) {
169 | suggestionBox.style.display = 'none';
170 | }
171 | }
172 | });
173 |
174 | codeEditor.addEventListener('keydown', handleSuggestionKeys);
175 | }
176 |
177 | export {
178 | initializeSuggestions,
179 | createSuggestionBox,
180 | showSuggestions,
181 | applySuggestion,
182 | updateActiveItem
183 | };
--------------------------------------------------------------------------------
/ide/js/ide/ui.js:
--------------------------------------------------------------------------------
1 | import { syntaxColors } from './config.js';
2 | import { updateSyntaxColors } from './editor.js';
3 | import { createModal } from './modal.js';
4 |
5 | // Gestion du thème
6 | function initializeTheme() {
7 | // Forcer le thème sombre par défaut
8 | document.documentElement.style.setProperty('color-scheme', 'dark');
9 | document.body.setAttribute('data-theme', 'dark');
10 |
11 | // Marquer le body comme chargé pour activer la transition
12 | document.body.classList.add('loaded');
13 |
14 | const theme = localStorage.getItem('theme') || 'dark';
15 | const themeToggle = document.getElementById('themeToggle');
16 | if (!themeToggle) {
17 | console.error("Bouton de thème non trouvé");
18 | return;
19 | }
20 |
21 | const themeIcon = themeToggle.querySelector('i');
22 | themeIcon.className = theme === 'dark' ? 'fas fa-moon' : 'fas fa-sun';
23 |
24 | // Appliquer le thème sauvegardé
25 | document.body.setAttribute('data-theme', theme);
26 |
27 | themeToggle.addEventListener('click', () => {
28 | const currentTheme = document.body.getAttribute('data-theme');
29 | const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
30 | document.body.setAttribute('data-theme', newTheme);
31 | localStorage.setItem('theme', newTheme);
32 | themeIcon.className = newTheme === 'dark' ? 'fas fa-moon' : 'fas fa-sun';
33 | });
34 | }
35 |
36 | // Gestion de la barre latérale
37 | function initializeSidebar() {
38 | const toggleSidebarBtn = document.getElementById('toggleSidebar');
39 | const sidebar = document.querySelector('.sidebar');
40 |
41 | if (!toggleSidebarBtn || !sidebar) {
42 | console.error("Éléments de la barre latérale non trouvés");
43 | return;
44 | }
45 |
46 | toggleSidebarBtn.addEventListener('click', () => {
47 | const isCollapsed = sidebar.classList.toggle('collapsed');
48 | toggleSidebarBtn.querySelector('i').className = isCollapsed ?
49 | 'fas fa-chevron-right' : 'fas fa-chevron-left';
50 | });
51 | }
52 |
53 | // Gestion de la console
54 | function initializeConsole() {
55 | const toggleConsoleBtn = document.getElementById('toggleConsole');
56 | const consoleContainer = document.querySelector('.console');
57 | const editorContainer = document.querySelector('.editor-container');
58 | const clearConsoleBtn = document.getElementById('clearConsole');
59 | const maxConsoleBtn = document.getElementById('maxConsale');
60 |
61 | if (!toggleConsoleBtn || !consoleContainer || !editorContainer || !clearConsoleBtn || !maxConsoleBtn) {
62 | console.error("Éléments de la console non trouvés");
63 | return;
64 | }
65 |
66 | toggleConsoleBtn.addEventListener('click', () => {
67 | const isCollapsed = consoleContainer.classList.toggle('collapsed');
68 | toggleConsoleBtn.querySelector('i').className = isCollapsed ?
69 | 'fas fa-chevron-up' : 'fas fa-chevron-down';
70 | editorContainer.style.flex = '1';
71 | });
72 |
73 | clearConsoleBtn.addEventListener('click', () => {
74 | document.getElementById('consoleOutput').innerHTML = '';
75 | });
76 |
77 | maxConsoleBtn.addEventListener('click', () => {
78 | const mainContent = document.querySelector('.main-content');
79 | const isCollapsed = mainContent.classList.toggle('collapsedm');
80 | maxConsoleBtn.querySelector('i').className = isCollapsed ?
81 | 'fas fa-chevron-down' : 'fas fa-chevron-up';
82 | });
83 | }
84 |
85 | // Gestion de l'écran de chargement
86 | function initializeLoadingScreen() {
87 | return new Promise((resolve) => {
88 | const loadingScreen = document.getElementById('loading-screen');
89 | if (!loadingScreen) {
90 | console.error("L'écran de chargement n'a pas été trouvé");
91 | resolve();
92 | return;
93 | }
94 |
95 | const loadingText = loadingScreen.querySelector('.typing-text');
96 | if (!loadingText) {
97 | console.error("Le texte de chargement n'a pas été trouvé");
98 | loadingScreen.style.display = 'none';
99 | resolve();
100 | return;
101 | }
102 |
103 | const messages = [
104 | 'Chargement...',
105 | 'Configuration...',
106 | 'Presque prêt...'
107 | ];
108 | let messageIndex = 0;
109 |
110 | const updateMessage = () => {
111 | loadingText.textContent = messages[messageIndex];
112 | messageIndex = (messageIndex + 1) % messages.length;
113 | };
114 |
115 | const messageInterval = setInterval(updateMessage, 800);
116 |
117 | setTimeout(() => {
118 | clearInterval(messageInterval);
119 | loadingScreen.style.opacity = '0';
120 | loadingScreen.style.visibility = 'hidden';
121 | resolve();
122 | }, 2400);
123 | });
124 | }
125 |
126 | // Vérification de la compatibilité mobile
127 | function checkMobileCompatibility() {
128 | if (window.innerWidth <= 900) {
129 | alert('Le site est actuellement indisponible pour cette taille d\'écran');
130 | document.body.style.pointerEvents = 'none';
131 |
132 | const messageDiv = document.createElement('div');
133 | const messageDiv2 = document.createElement('div');
134 |
135 | messageDiv.style.cssText = `
136 | position: fixed;
137 | top: 0;
138 | left: 0;
139 | width: 100vw;
140 | height: 100vh;
141 | background: rgba(0, 0, 0, 0.9);
142 | z-index:999999;
143 | `;
144 |
145 | messageDiv2.style.cssText = `
146 | position: absolute;
147 | top: 50%;
148 | left: 50%;
149 | transform: translate(-50%, -50%);
150 | font-size: 1.5rem;
151 | color: white;
152 | text-align: center;
153 | padding: 2rem;
154 | border-radius: 10px;
155 | box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
156 | background: rgba(0, 0, 0, 0.7);
157 | z-index: 1000000;
158 | `;
159 |
160 | messageDiv2.textContent = 'Le site est actuellement indisponible pour cette taille d\'écran';
161 | document.body.appendChild(messageDiv);
162 | messageDiv.appendChild(messageDiv2);
163 | return false;
164 | }
165 | return true;
166 | }
167 |
168 | // Gestion du thème de syntaxe
169 | function initializeSyntaxTheme() {
170 | const syntaxThemeButton = document.getElementById('syntaxTheme');
171 | if (!syntaxThemeButton) {
172 | console.error("Bouton de thème de syntaxe non trouvé");
173 | return;
174 | }
175 |
176 | syntaxThemeButton.addEventListener('click', () => {
177 | createModal(
178 | 'Syntaxe',
179 | ``,
197 | null,
198 | 'colors'
199 | ).then(colors => {
200 | if (colors) {
201 | updateSyntaxColors(colors);
202 | }
203 | });
204 | });
205 | }
206 |
207 | // Initialisation de l'interface utilisateur
208 | function initializeUI() {
209 | initializeTheme();
210 | initializeSidebar();
211 | initializeConsole();
212 | initializeSyntaxTheme();
213 | }
214 |
215 | export {
216 | initializeUI,
217 | initializeTheme,
218 | initializeSidebar,
219 | initializeConsole,
220 | initializeLoadingScreen,
221 | checkMobileCompatibility
222 | };
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PyDE
7 |
8 |
9 |
10 |
11 |
12 |
13 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
print("Hello World")
45 |
def main():
46 |
class Python:
47 |
48 |
49 |
50 |
51 |
52 |
Python, c'est simple non ?
53 |
PyDE est l'environnement de développement nouvelle génération qui allie puissance et simplicité. Découvrez une nouvelle façon de coder en Python avec des outils intelligents et une interface intuitive.
54 |
55 |
56 |
57 |
62 |
def PyDE () :
63 | print ( "Le Meilleur Editeur de texte Python en ligne !" )
64 |
65 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
Fonctionnalités principales
83 |
84 |
85 |
86 |
87 |
88 |
Exécution de code en direct
89 |
Exécutez votre code Python directement dans l'éditeur et voyez les résultats instantanément.
90 |
91 |
92 |
93 |
94 |
95 |
Thèmes personnalisables
96 |
Personnalisez l'apparence de votre éditeur selon vos préférences.
97 |
98 |
99 |
100 |
101 |
102 |
Assistant IA
103 |
Bénéficiez d'une aide intelligente pour améliorer votre code et apprendre plus rapidement grâce à notre IA intégrée.
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
L'interface de PyDE
113 |
114 |
115 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
Ce qu'ils en pensent
132 |
133 |
134 |
135 |
136 |
En tant que formateur Python, PyDE est devenu mon outil principal. L'assistant IA aide mes étudiants à comprendre leurs erreurs et à progresser plus rapidement.
137 |
138 |
139 |
140 |
141 |
142 |
143 |
Thomas D.
144 |
Formateur Python
145 |
146 |
147 |
148 |
149 |
150 |
151 |
PyDE m'aide énormément dans mes études. L'interface est intuitive et l'assistant IA m'aide à comprendre mes erreurs. C'est parfait pour progresser en Python.
152 |
153 |
154 |
155 |
156 |
157 |
158 |
Marie L.
159 |
Étudiante en informatique
160 |
161 |
162 |
163 |
164 |
165 |
166 |
Je débute en programmation et PyDE est exactement ce dont j'avais besoin. Les explications de l'IA sont claires et adaptées à mon niveau.
167 |
168 |
169 |
170 |
171 |
172 |
173 |
Sophie R.
174 |
Étudiante en reconversion
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
208 |
209 |
225 |
226 |
227 |
228 |
229 |
--------------------------------------------------------------------------------
/src/css/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary-color: #6366f1;
3 | --secondary-color: #4f46e5;
4 | --dark-bg: #0f172a;
5 | --text-light: #f8fafc;
6 | --text-dark: #1e293b;
7 | --spacing-sm: 0.5rem;
8 | --spacing-md: 1rem;
9 | --spacing-lg: 2rem;
10 | }
11 |
12 | * {
13 | margin: 0;
14 | padding: 0;
15 | box-sizing: border-box;
16 | }
17 |
18 | body {
19 | font-family: 'Inter', sans-serif;
20 | line-height: 1.6;
21 | color: var(--text-dark);
22 | background-color: var(--dark-bg);
23 | }
24 |
25 | .container {
26 | max-width: 1200px;
27 | margin: 0 auto;
28 | padding: 0 var(--spacing-md);
29 | }
30 |
31 | /* Header Styles */
32 | .header {
33 | position: fixed;
34 | top: 0;
35 | left: 0;
36 | width: 100%;
37 | background-color: rgba(15, 23, 42, 0.9);
38 | backdrop-filter: blur(10px);
39 | z-index: 1000;
40 | padding: var(--spacing-md) 0;
41 | }
42 |
43 | .header .container {
44 | display: flex;
45 | justify-content: space-between;
46 | align-items: center;
47 | }
48 |
49 | .nav-list {
50 | display: flex;
51 | gap: var(--spacing-sm);
52 | list-style: none;
53 | align-items: center;
54 | }
55 |
56 | /* Ajustement de l'espacement entre les éléments de navigation */
57 | .nav-list li:nth-last-child(-n+2) {
58 | margin-left: var(--spacing-md);
59 | }
60 |
61 | .nav-link {
62 | color: var(--text-light);
63 | text-decoration: none;
64 | font-weight: 500;
65 | transition: all 0.2s ease;
66 | padding: 0.5rem;
67 | border-radius: 0.5rem;
68 | display: flex;
69 | align-items: center;
70 | position: relative;
71 | padding-bottom: 0.2rem;
72 | }
73 |
74 | .nav-link::after {
75 | content: '';
76 | position: absolute;
77 | width: 0;
78 | height: 2px;
79 | bottom: 0;
80 | left: 0;
81 | right: 0;
82 | margin: 0 auto;
83 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
84 | transition: width 0.2s ease;
85 | max-width: 80%;
86 | }
87 |
88 | .nav-link:hover {
89 | color: var(--primary-color);
90 | }
91 |
92 | .nav-link:hover::after {
93 | width: 100%;
94 | }
95 |
96 | .github-link {
97 | font-size: 1.5rem;
98 | display: flex;
99 | align-items: center;
100 | justify-content: center;
101 | width: 2rem;
102 | height: 2rem;
103 | position: relative;
104 | }
105 |
106 | .github-link:hover {
107 | color: var(--primary-color);
108 | }
109 |
110 | .github-link::after {
111 | content: '';
112 | position: absolute;
113 | width: 0;
114 | height: 2px;
115 | bottom: 0;
116 | left: 0;
117 | right: 0;
118 | margin: 0 auto;
119 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
120 | transition: width 0.2s ease;
121 | max-width: 80%;
122 | }
123 |
124 | .github-link:hover::after {
125 | width: 100%;
126 | }
127 |
128 | /* Nouvelles animations pour les boutons */
129 | @keyframes glow {
130 | 0% { box-shadow: 0 0 5px var(--primary-color); }
131 | 50% { box-shadow: 0 0 20px var(--primary-color); }
132 | 100% { box-shadow: 0 0 5px var(--primary-color); }
133 | }
134 |
135 | .btn:hover {
136 | animation: glow 1.5s infinite;
137 | }
138 |
139 | .btn:active {
140 | animation: none;
141 | transition: all 0.1s ease;
142 | }
143 |
144 | /* Hero Section */
145 | .hero {
146 | padding: 8rem 0 4rem;
147 | min-height: 100vh;
148 | display: flex;
149 | align-items: center;
150 | position: relative;
151 | overflow: hidden;
152 | }
153 |
154 | /* Éléments décoratifs du hero */
155 | .hero::before {
156 | content: '';
157 | position: absolute;
158 | width: 100%;
159 | height: 100%;
160 | top: 0;
161 | left: 0;
162 | background: radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.1) 0%, transparent 50%);
163 | z-index: -1;
164 | }
165 |
166 | .hero-shapes {
167 | position: absolute;
168 | top: 0;
169 | left: 0;
170 | width: 100%;
171 | height: 100%;
172 | z-index: -1;
173 | opacity: 0.15;
174 | }
175 |
176 | .code-snippets {
177 | position: absolute;
178 | bottom: 10%;
179 | right: 5%;
180 | transform: rotate(-10deg);
181 | font-family: 'Monaco', 'Consolas', monospace;
182 | z-index: 1;
183 | }
184 |
185 | .snippet {
186 | background: rgba(99, 102, 241, 0.15);
187 | padding: 0.5rem 1rem;
188 | margin: 0.5rem;
189 | border-radius: 4px;
190 | color: var(--primary-color);
191 | font-size: 1rem;
192 | opacity: 0.8;
193 | transform: translateX(0);
194 | animation: slideIn 8s linear infinite;
195 | text-shadow: 0 0 10px rgba(99, 102, 241, 0.3);
196 | box-shadow: 0 2px 10px rgba(99, 102, 241, 0.1);
197 | }
198 |
199 | .snippet:nth-child(2) {
200 | animation-delay: 2s;
201 | }
202 |
203 | .snippet:nth-child(3) {
204 | animation-delay: 4s;
205 | }
206 |
207 | .snippet:nth-child(4) {
208 | animation-delay: 6s;
209 | }
210 |
211 | @keyframes slideIn {
212 | 0% {
213 | transform: translateX(100%);
214 | opacity: 0;
215 | }
216 | 10% {
217 | transform: translateX(0);
218 | opacity: 0.8;
219 | }
220 | 90% {
221 | transform: translateX(0);
222 | opacity: 0.8;
223 | }
224 | 100% {
225 | transform: translateX(-100%);
226 | opacity: 0;
227 | }
228 | }
229 |
230 | .shape {
231 | position: absolute;
232 | border: 2px solid var(--primary-color);
233 | border-radius: 50%;
234 | backdrop-filter: blur(5px);
235 | background: rgba(99, 102, 241, 0.1);
236 | box-shadow:
237 | 0 0 20px rgba(99, 102, 241, 0.2),
238 | inset 0 0 20px rgba(99, 102, 241, 0.1);
239 | }
240 |
241 | .shape-1 {
242 | width: 300px;
243 | height: 300px;
244 | top: 10%;
245 | right: 5%;
246 | animation: float 6s ease-in-out infinite;
247 | }
248 |
249 | .shape-2 {
250 | width: 200px;
251 | height: 200px;
252 | bottom: 15%;
253 | left: 10%;
254 | animation: float 8s ease-in-out infinite;
255 | }
256 |
257 | .shape-3 {
258 | width: 150px;
259 | height: 150px;
260 | top: 30%;
261 | left: 20%;
262 | animation: float 7s ease-in-out infinite;
263 | }
264 |
265 | @keyframes float {
266 | 0% { transform: translateY(0) rotate(0deg); }
267 | 50% { transform: translateY(-20px) rotate(5deg); }
268 | 100% { transform: translateY(0) rotate(0deg); }
269 | }
270 |
271 | .code-dots {
272 | position: absolute;
273 | width: 100%;
274 | height: 100%;
275 | z-index: -1;
276 | opacity: 0.08;
277 | background-image: radial-gradient(var(--primary-color) 1px, transparent 1px);
278 | background-size: 40px 40px;
279 | }
280 |
281 | .hero .container {
282 | display: grid;
283 | grid-template-columns: 1fr 1fr;
284 | gap: 4rem;
285 | align-items: center;
286 | }
287 |
288 | .hero-left {
289 | display: flex;
290 | flex-direction: column;
291 | gap: 2rem;
292 | }
293 |
294 | .hero-right {
295 | max-width: 600px;
296 | }
297 |
298 | .hero h1 {
299 | font-size: 3.5rem;
300 | font-family: 'Poppins', sans-serif;
301 | color: var(--text-light);
302 | margin-bottom: var(--spacing-lg);
303 | }
304 |
305 | .hero p {
306 | font-size: 1.2rem;
307 | color: var(--text-light);
308 | opacity: 0.9;
309 | margin-bottom: var(--spacing-lg);
310 | }
311 |
312 | .hero-buttons {
313 | display: flex;
314 | gap: 1rem;
315 | }
316 |
317 | /* Responsive adjustments */
318 | @media (max-width: 992px) {
319 | .hero .container {
320 | grid-template-columns: 1fr;
321 | gap: 2rem;
322 | }
323 |
324 | .hero-left {
325 | order: 1;
326 | }
327 |
328 | .hero-right {
329 | order: 2;
330 | text-align: center;
331 | margin: 0 auto;
332 | }
333 | }
334 |
335 | @media (max-width: 480px) {
336 | .hero-buttons {
337 | flex-direction: column;
338 | width: 100%;
339 | }
340 |
341 | .hero-buttons .btn {
342 | width: 100%;
343 | justify-content: center;
344 | }
345 | }
346 |
347 | /* Responsive Design */
348 | @media (max-width: 768px) {
349 | .nav-list {
350 | justify-content: center;
351 | gap: var(--spacing-md);
352 | }
353 |
354 | .nav-list li:nth-last-child(-n+2) {
355 | margin-left: 0;
356 | }
357 |
358 | .hero {
359 | padding: 6rem 0 2rem;
360 | }
361 |
362 | .hero h1 {
363 | font-size: 2.5rem;
364 | }
365 | }
366 |
367 | /* Ajout des styles pour le logo */
368 | .logo {
369 | font-size: 1.5rem;
370 | font-weight: 700;
371 | color: var(--text-light);
372 | display: flex;
373 | align-items: center;
374 | gap: 0.5rem;
375 | }
376 |
377 | .logo i {
378 | color: var(--primary-color);
379 | font-size: 2rem;
380 | }
381 |
382 | /* Styles pour la preview de code */
383 | .code-preview, .code-window {
384 | background: #1a1a1a;
385 | border-radius: 8px;
386 | padding: 1.5rem;
387 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
388 | margin-top: 2rem;
389 | }
390 |
391 | .window-header {
392 | display: flex;
393 | gap: 0.5rem;
394 | margin-bottom: 1rem;
395 | }
396 |
397 | .dot {
398 | width: 12px;
399 | height: 12px;
400 | border-radius: 50%;
401 | }
402 |
403 | .dot.red { background-color: #ff5f56; }
404 | .dot.yellow { background-color: #ffbd2e; }
405 | .dot.green { background-color: #27c93f; }
406 |
407 | code {
408 | color: #f8f8f2;
409 | font-family: 'Monaco', 'Consolas', monospace;
410 | line-height: 1.5;
411 | }
412 |
413 | /* Coloration syntaxique */
414 | .keyword {
415 | color: #ff79c6;
416 | }
417 |
418 | .function {
419 | color: #50fa7b;
420 | }
421 |
422 | .string {
423 | color: #f1fa8c;
424 | }
425 |
426 | .parenthesis {
427 | color: #f8f8f2;
428 | }
429 |
430 | /* Feature cards */
431 | .features-grid {
432 | display: grid;
433 | grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
434 | gap: 1.5rem;
435 | margin: 3rem auto;
436 | }
437 |
438 | .feature-card {
439 | background: rgba(255, 255, 255, 0.05);
440 | border-radius: 12px;
441 | padding: 2rem;
442 | border: 1px solid rgba(255, 255, 255, 0.1);
443 | transition: border-color 0.3s ease;
444 | }
445 |
446 | .feature-card:hover {
447 | border-color: var(--primary-color);
448 | }
449 |
450 | .feature-icon {
451 | font-size: 2.5rem;
452 | color: var(--primary-color);
453 | margin-bottom: 1.5rem;
454 | }
455 |
456 | .feature-card h3 {
457 | color: var(--text-light);
458 | margin-bottom: 1rem;
459 | }
460 |
461 | .feature-card p {
462 | color: var(--text-light);
463 | opacity: 0.8;
464 | }
465 |
466 | /* Styles pour les titres de section */
467 | .section-title {
468 | color: var(--text-light);
469 | text-align: center;
470 | margin-bottom: 3rem;
471 | font-size: 2.5rem;
472 | font-family: 'Poppins', sans-serif;
473 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
474 | -webkit-background-clip: text;
475 | background-clip: text;
476 | color: transparent;
477 | }
478 |
479 | /* Demo section */
480 | .demo {
481 | padding: 6rem 0;
482 | background: linear-gradient(to bottom, var(--dark-bg), #1a1a1a);
483 | }
484 |
485 | /* Responsive adjustments */
486 | @media (max-width: 768px) {
487 | .code-window {
488 | font-size: 14px;
489 | }
490 | }
491 |
492 | /* Styles des boutons principaux */
493 | .btn {
494 | padding: 0.5rem 1.5rem;
495 | border-radius: 0.5rem;
496 | font-weight: 600;
497 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
498 | display: inline-flex;
499 | align-items: center;
500 | gap: 0.5rem;
501 | position: relative;
502 | overflow: hidden;
503 | z-index: 1;
504 | text-decoration: none;
505 | }
506 |
507 | .btn-primary {
508 | background-color: var(--primary-color);
509 | color: var(--text-light);
510 | border: none;
511 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
512 | background-size: 200% auto;
513 | }
514 |
515 | .btn-primary:hover {
516 | background-position: right center;
517 | box-shadow: 0 10px 20px -10px var(--primary-color);
518 | }
519 |
520 | .btn-secondary {
521 | background-color: transparent;
522 | border: 2px solid var(--primary-color);
523 | color: var(--text-light);
524 | }
525 |
526 | .btn-outline {
527 | background-color: transparent;
528 | border: 2px solid;
529 | border-image: linear-gradient(45deg, var(--primary-color), var(--secondary-color)) 1;
530 | color: var(--text-light);
531 | position: relative;
532 | z-index: 1;
533 | }
534 |
535 | .btn-outline::before {
536 | content: '';
537 | position: absolute;
538 | top: 0;
539 | left: 0;
540 | width: 100%;
541 | height: 100%;
542 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
543 | opacity: 0;
544 | transition: opacity 0.3s ease;
545 | z-index: -1;
546 | }
547 |
548 | .btn-outline:hover::before {
549 | opacity: 0.1;
550 | }
551 |
552 | .demo-image {
553 | margin-top: 1rem;
554 | width: 100%;
555 | height: auto;
556 | overflow: hidden;
557 | border-radius: 4px;
558 | }
559 |
560 | .demo-image img {
561 | width: 100%;
562 | height: auto;
563 | display: block;
564 | object-fit: cover;
565 | }
566 |
567 | /* Testimonials Section */
568 | .testimonials {
569 | padding: 6rem 0;
570 | background: linear-gradient(to bottom, #1a1a1a, var(--dark-bg));
571 | }
572 |
573 | .testimonials-grid {
574 | display: grid;
575 | grid-template-columns: repeat(3, 1fr);
576 | gap: 2rem;
577 | margin-top: 3rem;
578 | max-width: 1000px;
579 | margin-left: auto;
580 | margin-right: auto;
581 | }
582 |
583 | .testimonial-card {
584 | background: rgba(255, 255, 255, 0.05);
585 | border-radius: 12px;
586 | padding: 2rem;
587 | border: 1px solid rgba(255, 255, 255, 0.1);
588 | transition: all 0.3s ease;
589 | display: flex;
590 | flex-direction: column;
591 | justify-content: space-between;
592 | height: 100%;
593 | }
594 |
595 | .testimonial-card:hover {
596 | transform: translateY(-5px);
597 | border-color: var(--primary-color);
598 | box-shadow: 0 10px 20px rgba(99, 102, 241, 0.1);
599 | }
600 |
601 | .testimonial-content {
602 | margin-bottom: 1.5rem;
603 | flex-grow: 1;
604 | display: flex;
605 | flex-direction: column;
606 | }
607 |
608 | .testimonial-content i {
609 | color: var(--primary-color);
610 | font-size: 1.5rem;
611 | margin-bottom: 1rem;
612 | }
613 |
614 | .testimonial-content p {
615 | color: var(--text-light);
616 | font-style: italic;
617 | margin-top: 1rem;
618 | flex-grow: 1;
619 | }
620 |
621 | .testimonial-author {
622 | display: flex;
623 | align-items: center;
624 | gap: 1rem;
625 | margin-top: auto;
626 | }
627 |
628 | .author-avatar {
629 | width: 50px;
630 | height: 50px;
631 | border-radius: 50%;
632 | background: var(--primary-color);
633 | display: flex;
634 | align-items: center;
635 | justify-content: center;
636 | color: var(--text-light);
637 | }
638 |
639 | .author-info h4 {
640 | color: var(--text-light);
641 | margin-bottom: 0.25rem;
642 | }
643 |
644 | .author-info p {
645 | color: var(--text-light);
646 | opacity: 0.8;
647 | font-size: 0.9rem;
648 | }
649 |
650 | /* Contact Section */
651 | .contact {
652 | padding: 6rem 0;
653 | background: var(--dark-bg);
654 | }
655 |
656 | .contact-container {
657 | max-width: 600px;
658 | margin: 0 auto;
659 | }
660 |
661 | .contact-form {
662 | background: rgba(255, 255, 255, 0.05);
663 | padding: 2rem;
664 | border-radius: 12px;
665 | border: 1px solid rgba(255, 255, 255, 0.1);
666 | }
667 |
668 | .form-group {
669 | margin-bottom: 1.5rem;
670 | }
671 |
672 | .form-group label {
673 | display: block;
674 | color: var(--text-light);
675 | margin-bottom: 0.5rem;
676 | }
677 |
678 | .form-group input,
679 | .form-group textarea {
680 | width: 100%;
681 | padding: 0.75rem;
682 | background: rgba(255, 255, 255, 0.1);
683 | border: 1px solid rgba(255, 255, 255, 0.2);
684 | border-radius: 4px;
685 | color: var(--text-light);
686 | font-family: inherit;
687 | }
688 |
689 | .form-group input:focus,
690 | .form-group textarea:focus {
691 | outline: none;
692 | border-color: var(--primary-color);
693 | }
694 |
695 | /* Footer */
696 | .footer {
697 | background: linear-gradient(to bottom, var(--dark-bg), #080c14);
698 | padding: 4rem 0 2rem;
699 | color: var(--text-light);
700 | position: relative;
701 | overflow: hidden;
702 | text-align: center;
703 | }
704 |
705 | .footer::before {
706 | content: '';
707 | position: absolute;
708 | top: 0;
709 | left: 0;
710 | width: 100%;
711 | height: 1px;
712 | background: linear-gradient(90deg,
713 | transparent 0%,
714 | var(--primary-color) 50%,
715 | transparent 100%
716 | );
717 | opacity: 0.3;
718 | }
719 |
720 | .footer-brand {
721 | display: flex;
722 | flex-direction: column;
723 | gap: 1.5rem;
724 | text-align: center;
725 | margin: 0 auto;
726 | margin-bottom: 2rem;
727 | align-items: center;
728 | }
729 |
730 | .footer-brand .logo {
731 | font-size: 2rem;
732 | display: inline-flex;
733 | align-items: center;
734 | justify-content: center;
735 | gap: 0.5rem;
736 | background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
737 | -webkit-background-clip: text;
738 | background-clip: text;
739 | color: transparent;
740 | }
741 |
742 | .footer-brand p {
743 | opacity: 0.8;
744 | font-size: 1.1rem;
745 | line-height: 1.6;
746 | text-align: center;
747 | }
748 |
749 | .footer-bottom {
750 | display: flex;
751 | justify-content: center;
752 | align-items: center;
753 | position: relative;
754 | }
755 |
756 | .social-links {
757 | display: flex;
758 | gap: 1.5rem;
759 | justify-content: center;
760 | }
761 |
762 | .social-links a {
763 | color: var(--text-light);
764 | font-size: 1.8rem;
765 | opacity: 0.8;
766 | transition: all 0.3s ease;
767 | position: relative;
768 | }
769 |
770 | .social-links a:hover {
771 | color: var(--primary-color);
772 | transform: translateY(-3px);
773 | }
774 |
775 | .social-links a::after {
776 | content: '';
777 | position: absolute;
778 | bottom: -5px;
779 | left: 0;
780 | width: 100%;
781 | height: 2px;
782 | background: var(--primary-color);
783 | transform: scaleX(0);
784 | transition: transform 0.3s ease;
785 | }
786 |
787 | .social-links a:hover::after {
788 | transform: scaleX(1);
789 | }
790 |
791 | /* Responsive adjustments */
792 | @media (max-width: 992px) {
793 | .testimonials-grid {
794 | grid-template-columns: repeat(2, 1fr);
795 | }
796 | }
797 |
798 | @media (max-width: 768px) {
799 | .testimonials-grid {
800 | grid-template-columns: 1fr;
801 | }
802 | }
803 |
804 | /* Ajout d'un effet de brillance sur les formes */
805 | @keyframes glow-shape {
806 | 0%, 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.2), inset 0 0 20px rgba(99, 102, 241, 0.1); }
807 | 50% { box-shadow: 0 0 30px rgba(99, 102, 241, 0.3), inset 0 0 30px rgba(99, 102, 241, 0.2); }
808 | }
809 |
810 | .shape {
811 | animation: glow-shape 4s ease-in-out infinite alternate;
812 | }
--------------------------------------------------------------------------------
/src/js/script.js:
--------------------------------------------------------------------------------
1 | // Menu mobile toggle
2 | const mobileMenuBtn = document.querySelector('.mobile-menu-btn');
3 | const navList = document.querySelector('.nav-list');
4 |
5 | mobileMenuBtn.addEventListener('click', () => {
6 | navList.classList.toggle('active');
7 | });
8 |
9 | // Animation au scroll
10 | const observerOptions = {
11 | threshold: 0.1
12 | };
13 |
14 | const observer = new IntersectionObserver((entries) => {
15 | entries.forEach(entry => {
16 | if (entry.isIntersecting) {
17 | entry.target.classList.add('animate');
18 | }
19 | });
20 | }, observerOptions);
21 |
22 | // Sélectionner tous les éléments à animer
23 | document.querySelectorAll('.feature-card, .hero-content, .hero-image').forEach((el) => {
24 | observer.observe(el);
25 | });
26 |
27 | // Gestion du header sticky avec effet de transparence
28 | let lastScroll = 0;
29 | const header = document.querySelector('.header');
30 |
31 | window.addEventListener('scroll', () => {
32 | const currentScroll = window.pageYOffset;
33 |
34 | if (currentScroll <= 0) {
35 | header.classList.remove('scroll-up');
36 | return;
37 | }
38 |
39 | if (currentScroll > lastScroll && !header.classList.contains('scroll-down')) {
40 | header.classList.remove('scroll-up');
41 | header.classList.add('scroll-down');
42 | } else if (currentScroll < lastScroll && header.classList.contains('scroll-down')) {
43 | header.classList.remove('scroll-down');
44 | header.classList.add('scroll-up');
45 | }
46 | lastScroll = currentScroll;
47 | });
--------------------------------------------------------------------------------