Abmacode12 commited on
Commit
4c3d8b8
·
verified ·
1 Parent(s): d225191

Manual changes saved

Browse files
Files changed (1) hide show
  1. index.html +1141 -1
index.html CHANGED
@@ -146,4 +146,1144 @@
146
  feather.replace();
147
  </script>
148
  </body>
149
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  feather.replace();
147
  </script>
148
  </body>
149
+ </html><!doctype html>
150
+ <html lang="fr">
151
+ <head>
152
+ <meta charset="utf-8" />
153
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
154
+ <title>Espace Codage — Rosalinda</title>
155
+ <style>
156
+ :root{
157
+ --bg:#0b1220;
158
+ --panel:#0f1a2b;
159
+ --panel2:#101f35;
160
+ --border:rgba(255,255,255,.08);
161
+ --text:#e8eefc;
162
+ --muted:rgba(232,238,252,.65);
163
+ --muted2:rgba(232,238,252,.45);
164
+ --accent:#4ea1ff;
165
+ --accent2:#7dd3fc;
166
+ --danger:#ff6b6b;
167
+ --ok:#2ee59d;
168
+ --shadow: 0 10px 30px rgba(0,0,0,.35);
169
+ --radius:16px;
170
+ --radius2:22px;
171
+ --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace;
172
+ --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
173
+ }
174
+ *{box-sizing:border-box}
175
+ html,body{height:100%}
176
+ body{
177
+ margin:0;
178
+ font-family:var(--sans);
179
+ background: radial-gradient(1200px 600px at 40% 20%, rgba(78,161,255,.12), transparent 60%),
180
+ radial-gradient(900px 500px at 80% 10%, rgba(125,211,252,.08), transparent 55%),
181
+ var(--bg);
182
+ color:var(--text);
183
+ overflow:hidden;
184
+ }
185
+
186
+ /* Layout */
187
+ .app{
188
+ height:100vh;
189
+ padding:14px;
190
+ display:grid;
191
+ grid-template-columns: 320px 1fr 420px;
192
+ gap:14px;
193
+ }
194
+ .card{
195
+ background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.015));
196
+ border:1px solid var(--border);
197
+ border-radius: var(--radius2);
198
+ box-shadow: var(--shadow);
199
+ overflow:hidden;
200
+ display:flex;
201
+ flex-direction:column;
202
+ min-height:0;
203
+ }
204
+ .cardHeader{
205
+ padding:14px 14px 10px 14px;
206
+ display:flex;
207
+ align-items:center;
208
+ justify-content:space-between;
209
+ gap:10px;
210
+ border-bottom:1px solid var(--border);
211
+ background: rgba(255,255,255,.02);
212
+ }
213
+ .title{
214
+ display:flex;
215
+ align-items:center;
216
+ gap:10px;
217
+ font-weight:800;
218
+ letter-spacing:.2px;
219
+ }
220
+ .dot{
221
+ width:10px;height:10px;border-radius:999px;
222
+ background: rgba(78,161,255,.9);
223
+ box-shadow: 0 0 0 4px rgba(78,161,255,.15);
224
+ flex:0 0 auto;
225
+ }
226
+ .pill{
227
+ font-size:12px;
228
+ padding:6px 10px;
229
+ border-radius:999px;
230
+ border:1px solid var(--border);
231
+ background: rgba(255,255,255,.02);
232
+ color:var(--muted);
233
+ }
234
+ .pill.ok{ color: rgba(46,229,157,.95); border-color: rgba(46,229,157,.25); background: rgba(46,229,157,.06); }
235
+ .pill.bad{ color: rgba(255,107,107,.95); border-color: rgba(255,107,107,.25); background: rgba(255,107,107,.06); }
236
+
237
+ /* Sidebar */
238
+ .sidebarBody{ padding:14px; display:flex; flex-direction:column; gap:14px; min-height:0; }
239
+ .searchRow{ display:flex; gap:10px; align-items:center; }
240
+ .iconBtn{
241
+ width:40px;height:40px;border-radius:12px;
242
+ border:1px solid var(--border);
243
+ background: rgba(255,255,255,.02);
244
+ color:var(--text);
245
+ cursor:pointer;
246
+ display:grid;place-items:center;
247
+ transition:.15s transform, .15s background;
248
+ user-select:none;
249
+ }
250
+ .iconBtn:hover{ transform: translateY(-1px); background: rgba(255,255,255,.04); }
251
+ .iconBtn:active{ transform: translateY(0px) scale(.98); }
252
+ .input{
253
+ width:100%;
254
+ height:40px;
255
+ border-radius:12px;
256
+ border:1px solid var(--border);
257
+ background: rgba(0,0,0,.22);
258
+ color:var(--text);
259
+ padding:0 12px 0 38px;
260
+ outline:none;
261
+ font-size:14px;
262
+ }
263
+ .input::placeholder{ color: rgba(232,238,252,.45); }
264
+ .inputWrap{ position:relative; flex:1; }
265
+ .mag{
266
+ position:absolute; left:12px; top:50%; transform:translateY(-50%);
267
+ color: rgba(232,238,252,.45);
268
+ font-size:14px;
269
+ }
270
+ .sectionLabel{
271
+ font-size:12px;
272
+ color: var(--muted2);
273
+ letter-spacing:.12em;
274
+ text-transform:uppercase;
275
+ margin-top:2px;
276
+ }
277
+ .list{ display:flex; flex-direction:column; gap:10px; min-height:0; overflow:auto; padding-right:6px; }
278
+ .item{
279
+ padding:12px;
280
+ border:1px solid var(--border);
281
+ border-radius:14px;
282
+ background: rgba(255,255,255,.02);
283
+ display:flex;
284
+ gap:12px;
285
+ align-items:center;
286
+ cursor:pointer;
287
+ transition:.15s background, .15s transform;
288
+ user-select:none;
289
+ }
290
+ .item:hover{ background: rgba(255,255,255,.04); transform: translateY(-1px); }
291
+ .item.active{ border-color: rgba(78,161,255,.35); background: rgba(78,161,255,.08); }
292
+ .badge{
293
+ width:34px;height:34px;border-radius:12px;
294
+ display:grid;place-items:center;
295
+ border:1px solid var(--border);
296
+ background: rgba(0,0,0,.22);
297
+ color: rgba(232,238,252,.85);
298
+ font-family: var(--mono);
299
+ font-size:12px;
300
+ }
301
+ .itemText{ display:flex; flex-direction:column; gap:2px; }
302
+ .itemText .name{ font-weight:700; }
303
+ .itemText .sub{ font-size:12px; color: var(--muted); }
304
+ .footerRow{
305
+ margin-top:auto;
306
+ display:flex; align-items:center; justify-content:space-between; gap:10px;
307
+ padding-top:10px;
308
+ border-top:1px solid var(--border);
309
+ color: var(--muted);
310
+ font-size:12px;
311
+ }
312
+
313
+ /* Center (Chat) */
314
+ .centerBody{ padding:14px; display:flex; flex-direction:column; gap:12px; min-height:0; }
315
+ .crumbs{
316
+ display:flex; align-items:center; gap:8px;
317
+ color: var(--muted);
318
+ font-size:12px;
319
+ }
320
+ .crumb{ padding:6px 10px; border-radius:999px; border:1px solid var(--border); background: rgba(255,255,255,.02); }
321
+ .chatArea{
322
+ flex:1;
323
+ min-height:0;
324
+ border:1px dashed rgba(255,255,255,.12);
325
+ border-radius: var(--radius2);
326
+ background: rgba(0,0,0,.18);
327
+ display:flex;
328
+ align-items:center;
329
+ justify-content:center;
330
+ padding:18px;
331
+ position:relative;
332
+ overflow:hidden;
333
+ }
334
+ .chatInner{
335
+ width:100%;
336
+ height:100%;
337
+ display:flex;
338
+ flex-direction:column;
339
+ gap:12px;
340
+ overflow:auto;
341
+ padding-right:8px;
342
+ }
343
+ .emptyState{
344
+ text-align:center;
345
+ color: var(--muted);
346
+ max-width:520px;
347
+ padding:22px;
348
+ margin:auto;
349
+ }
350
+ .emptyIcon{
351
+ width:84px;height:84px;border-radius:18px;
352
+ margin:0 auto 14px auto;
353
+ border:1px solid var(--border);
354
+ background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.12), rgba(255,255,255,.02) 60%),
355
+ rgba(0,0,0,.22);
356
+ }
357
+ .emptyTitle{ font-weight:800; color: var(--text); font-size:20px; margin-bottom:6px; }
358
+ .emptySub{ font-size:13px; color: var(--muted); }
359
+
360
+ .msg{
361
+ display:flex;
362
+ gap:10px;
363
+ align-items:flex-start;
364
+ }
365
+ .avatar{
366
+ width:34px;height:34px;border-radius:12px;
367
+ border:1px solid var(--border);
368
+ background: rgba(0,0,0,.22);
369
+ display:grid;place-items:center;
370
+ font-weight:800;
371
+ color: rgba(232,238,252,.9);
372
+ flex:0 0 auto;
373
+ }
374
+ .bubble{
375
+ max-width: 880px;
376
+ border:1px solid var(--border);
377
+ border-radius:16px;
378
+ padding:10px 12px;
379
+ background: rgba(255,255,255,.02);
380
+ }
381
+ .bubble.user{ border-color: rgba(78,161,255,.25); background: rgba(78,161,255,.08); }
382
+ .meta{
383
+ font-size:12px;
384
+ color: var(--muted2);
385
+ margin-bottom:6px;
386
+ display:flex; gap:10px; align-items:center;
387
+ }
388
+ .text{ white-space:pre-wrap; line-height:1.4; font-size:14px; }
389
+ .codeBlock{
390
+ margin-top:8px;
391
+ padding:10px;
392
+ border-radius:14px;
393
+ border:1px solid rgba(255,255,255,.10);
394
+ background: rgba(0,0,0,.28);
395
+ font-family: var(--mono);
396
+ font-size:12.5px;
397
+ overflow:auto;
398
+ }
399
+
400
+ /* Composer */
401
+ .composer{
402
+ display:flex;
403
+ gap:10px;
404
+ align-items:center;
405
+ padding:10px;
406
+ border:1px solid var(--border);
407
+ border-radius: 18px;
408
+ background: rgba(255,255,255,.02);
409
+ }
410
+ .composeInput{
411
+ flex:1;
412
+ height:42px;
413
+ border-radius:14px;
414
+ border:1px solid transparent;
415
+ background: rgba(0,0,0,.20);
416
+ color: var(--text);
417
+ padding:0 12px;
418
+ outline:none;
419
+ font-size:14px;
420
+ }
421
+ .composeInput:focus{ border-color: rgba(78,161,255,.35); }
422
+ .sendBtn{
423
+ width:46px;height:46px;border-radius:16px;
424
+ border:1px solid rgba(78,161,255,.35);
425
+ background: rgba(78,161,255,.22);
426
+ cursor:pointer;
427
+ display:grid;place-items:center;
428
+ transition:.15s transform, .15s background;
429
+ }
430
+ .sendBtn:hover{ transform: translateY(-1px); background: rgba(78,161,255,.28); }
431
+ .sendBtn:active{ transform: translateY(0) scale(.98); }
432
+ .micOn{ border-color: rgba(46,229,157,.35) !important; background: rgba(46,229,157,.14) !important; }
433
+
434
+ /* Right panel */
435
+ .rightBody{ padding:12px; display:flex; flex-direction:column; gap:10px; min-height:0; }
436
+ .topTools{
437
+ display:flex; align-items:center; justify-content:space-between; gap:10px;
438
+ }
439
+ .tabs{
440
+ display:flex; gap:8px; align-items:center;
441
+ }
442
+ .tab{
443
+ padding:8px 10px;
444
+ border-radius:999px;
445
+ border:1px solid var(--border);
446
+ background: rgba(255,255,255,.02);
447
+ color: var(--muted);
448
+ cursor:pointer;
449
+ user-select:none;
450
+ font-size:12px;
451
+ }
452
+ .tab.active{
453
+ color: rgba(232,238,252,.95);
454
+ border-color: rgba(78,161,255,.35);
455
+ background: rgba(78,161,255,.10);
456
+ }
457
+ .toolRow{ display:flex; gap:8px; align-items:center; }
458
+ .miniBtn{
459
+ padding:8px 10px;
460
+ border-radius:12px;
461
+ border:1px solid var(--border);
462
+ background: rgba(255,255,255,.02);
463
+ cursor:pointer;
464
+ color: var(--text);
465
+ font-size:12px;
466
+ user-select:none;
467
+ transition:.15s transform, .15s background;
468
+ }
469
+ .miniBtn:hover{ transform: translateY(-1px); background: rgba(255,255,255,.04); }
470
+ .miniBtn:active{ transform: translateY(0) scale(.98); }
471
+ .miniBtn.primary{ border-color: rgba(78,161,255,.35); background: rgba(78,161,255,.18); }
472
+ .miniBtn.danger{ border-color: rgba(255,107,107,.35); background: rgba(255,107,107,.12); }
473
+
474
+ .editorWrap{
475
+ flex:1;
476
+ min-height:0;
477
+ border:1px solid var(--border);
478
+ border-radius: var(--radius2);
479
+ overflow:hidden;
480
+ background: rgba(0,0,0,.16);
481
+ display:flex;
482
+ flex-direction:column;
483
+ }
484
+ .editorHeader{
485
+ padding:10px 12px;
486
+ border-bottom:1px solid var(--border);
487
+ display:flex; align-items:center; justify-content:space-between;
488
+ color: var(--muted);
489
+ font-size:12px;
490
+ background: rgba(255,255,255,.02);
491
+ }
492
+ textarea.editor{
493
+ flex:1;
494
+ min-height:0;
495
+ width:100%;
496
+ resize:none;
497
+ border:none;
498
+ outline:none;
499
+ background: transparent;
500
+ color: rgba(232,238,252,.95);
501
+ padding:12px;
502
+ font-family: var(--mono);
503
+ font-size:12.8px;
504
+ line-height:1.45;
505
+ tab-size:2;
506
+ }
507
+ .previewWrap{
508
+ flex:1;
509
+ min-height:0;
510
+ border:1px solid var(--border);
511
+ border-radius: var(--radius2);
512
+ overflow:hidden;
513
+ background: rgba(0,0,0,.16);
514
+ display:flex;
515
+ flex-direction:column;
516
+ }
517
+ .previewBox{
518
+ flex:1;
519
+ min-height:0;
520
+ display:grid;
521
+ place-items:center;
522
+ padding:16px;
523
+ position:relative;
524
+ }
525
+ iframe{
526
+ width:100%;
527
+ height:100%;
528
+ border:none;
529
+ background:white;
530
+ border-radius: 14px;
531
+ }
532
+ .previewEmpty{
533
+ text-align:center;
534
+ color: var(--muted);
535
+ max-width:320px;
536
+ }
537
+ .previewEmpty .big{
538
+ font-weight:900;
539
+ color: var(--text);
540
+ margin-top:12px;
541
+ }
542
+ .statusBar{
543
+ display:flex; align-items:center; justify-content:space-between;
544
+ padding:10px 12px;
545
+ border-top:1px solid var(--border);
546
+ color: var(--muted);
547
+ font-size:12px;
548
+ background: rgba(255,255,255,.02);
549
+ }
550
+
551
+ /* Scrollbars */
552
+ .list::-webkit-scrollbar,
553
+ .chatInner::-webkit-scrollbar,
554
+ textarea.editor::-webkit-scrollbar{
555
+ width:10px;
556
+ }
557
+ .list::-webkit-scrollbar-thumb,
558
+ .chatInner::-webkit-scrollbar-thumb,
559
+ textarea.editor::-webkit-scrollbar-thumb{
560
+ background: rgba(255,255,255,.10);
561
+ border:3px solid transparent;
562
+ background-clip: padding-box;
563
+ border-radius:999px;
564
+ }
565
+
566
+ /* Responsive */
567
+ @media (max-width: 1100px){
568
+ body{ overflow:auto; }
569
+ .app{ grid-template-columns: 1fr; height:auto; overflow:auto; }
570
+ .card{ min-height: 320px; }
571
+ }
572
+ </style>
573
+ </head>
574
+ <body>
575
+ <div class="app">
576
+ <!-- LEFT -->
577
+ <section class="card" aria-label="Sidebar">
578
+ <div class="cardHeader">
579
+ <div class="title"><span class="dot"></span><span>Espace Codage</span></div>
580
+ <span class="pill" id="pillProject">Projet 1</span>
581
+ </div>
582
+
583
+ <div class="sidebarBody">
584
+ <div class="searchRow">
585
+ <button class="iconBtn" id="btnNewProject" title="Nouveau projet">+</button>
586
+ <div class="inputWrap">
587
+ <span class="mag">🔎</span>
588
+ <input class="input" id="projectSearch" placeholder="Rechercher..." autocomplete="off" />
589
+ </div>
590
+ </div>
591
+
592
+ <div class="sectionLabel">Projets</div>
593
+ <div class="list" id="projectList"></div>
594
+
595
+ <div class="sectionLabel">Raccourcis</div>
596
+ <div class="list" style="gap:10px">
597
+ <div class="item" data-shortcut="library">
598
+ <div class="badge">📚</div>
599
+ <div class="itemText">
600
+ <div class="name">Bibliothèque</div>
601
+ <div class="sub">Templates & snippets</div>
602
+ </div>
603
+ </div>
604
+ <div class="item" data-shortcut="rosalinda">
605
+ <div class="badge">🤖</div>
606
+ <div class="itemText">
607
+ <div class="name">Rosalinda</div>
608
+ <div class="sub">Assistant local</div>
609
+ </div>
610
+ </div>
611
+ <div class="item" data-shortcut="settings">
612
+ <div class="badge">⚙️</div>
613
+ <div class="itemText">
614
+ <div class="name">Paramètres</div>
615
+ <div class="sub">Préférences</div>
616
+ </div>
617
+ </div>
618
+ </div>
619
+
620
+ <div class="footerRow">
621
+ <span>Made with Espace Codage</span>
622
+ <span class="pill" style="padding:6px 10px;">Profil</span>
623
+ </div>
624
+ </div>
625
+ </section>
626
+
627
+ <!-- CENTER -->
628
+ <section class="card" aria-label="Centre">
629
+ <div class="cardHeader">
630
+ <div class="crumbs">
631
+ <span class="crumb">Aperçu</span>
632
+ <span class="crumb">Projet</span>
633
+ <span class="crumb" id="crumbName">Projet 1</span>
634
+ </div>
635
+ <span class="pill" id="pillMode">Mode: Preview</span>
636
+ </div>
637
+
638
+ <div class="centerBody">
639
+ <div class="chatArea">
640
+ <div class="chatInner" id="chatInner">
641
+ <!-- Empty state injected -->
642
+ </div>
643
+ </div>
644
+
645
+ <div class="composer">
646
+ <button class="iconBtn" id="btnPlus" title="Actions rapides">+</button>
647
+ <button class="iconBtn" id="btnAttach" title="Joindre (local)">📎</button>
648
+ <input class="composeInput" id="chatInput" placeholder="Écris ici… (ex: “crée une page”, ou colle du code HTML/CSS/JS)" />
649
+ <button class="iconBtn" id="btnMic" title="Micro (dictée)">🎤</button>
650
+ <button class="sendBtn" id="btnSend" title="Envoyer">➤</button>
651
+ </div>
652
+ </div>
653
+ </section>
654
+
655
+ <!-- RIGHT -->
656
+ <section class="card" aria-label="Droite">
657
+ <div class="cardHeader">
658
+ <div class="title" style="gap:8px;"><span style="font-weight:900">Aperçu</span></div>
659
+ <div class="toolRow">
660
+ <button class="iconBtn" id="btnBack" title="Retour">←</button>
661
+ <button class="iconBtn" id="btnForward" title="Avancer">→</button>
662
+ <button class="iconBtn" id="btnRefresh" title="Rafraîchir">⟳</button>
663
+ <button class="iconBtn" id="btnFull" title="Plein écran aperçu">⤢</button>
664
+ </div>
665
+ </div>
666
+
667
+ <div class="rightBody">
668
+ <div class="topTools">
669
+ <div class="tabs">
670
+ <div class="tab active" id="tabCode">Code</div>
671
+ <div class="tab" id="tabPreview">Aperçu</div>
672
+ </div>
673
+ <div class="toolRow">
674
+ <button class="miniBtn primary" id="btnRun">Run</button>
675
+ <button class="miniBtn" id="btnSave">Save</button>
676
+ <button class="miniBtn" id="btnDownload">Download</button>
677
+ <button class="miniBtn danger" id="btnReset">Reset</button>
678
+ </div>
679
+ </div>
680
+
681
+ <div class="editorWrap" id="panelCode">
682
+ <div class="editorHeader">
683
+ <span><b>Editor</b> — HTML/CSS/JS (local)</span>
684
+ <span class="pill" id="pillSaved">Auto-save</span>
685
+ </div>
686
+ <textarea class="editor" id="codeEditor" spellcheck="false"></textarea>
687
+ </div>
688
+
689
+ <div class="previewWrap" id="panelPreview" style="display:none">
690
+ <div class="editorHeader">
691
+ <span><b>Preview</b> — iframe sandbox</span>
692
+ <span class="pill bad" id="pillPreviewState">Hors ligne</span>
693
+ </div>
694
+ <div class="previewBox" id="previewBox">
695
+ <!-- Empty preview injected -->
696
+ </div>
697
+ <div class="statusBar">
698
+ <span id="statusLeft">Statut: Hors ligne</span>
699
+ <span id="statusRight">Mode: Preview</span>
700
+ </div>
701
+ </div>
702
+
703
+ </div>
704
+ </section>
705
+ </div>
706
+
707
+ <script>
708
+ /* =========================
709
+ Espace Codage — 100% local
710
+ - Projets + autosave (localStorage)
711
+ - Chat Rosalinda (assistant local)
712
+ - Micro dictée (Web Speech API)
713
+ - Éditeur code + aperçu iframe sandbox
714
+ ========================= */
715
+
716
+ (() => {
717
+ const $ = (q) => document.querySelector(q);
718
+
719
+ // --- UI refs
720
+ const projectList = $("#projectList");
721
+ const projectSearch = $("#projectSearch");
722
+ const btnNewProject = $("#btnNewProject");
723
+
724
+ const chatInner = $("#chatInner");
725
+ const chatInput = $("#chatInput");
726
+ const btnSend = $("#btnSend");
727
+ const btnAttach = $("#btnAttach");
728
+ const btnPlus = $("#btnPlus");
729
+ const btnMic = $("#btnMic");
730
+
731
+ const pillProject = $("#pillProject");
732
+ const crumbName = $("#crumbName");
733
+
734
+ const tabCode = $("#tabCode");
735
+ const tabPreview = $("#tabPreview");
736
+ const panelCode = $("#panelCode");
737
+ const panelPreview = $("#panelPreview");
738
+
739
+ const codeEditor = $("#codeEditor");
740
+ const btnRun = $("#btnRun");
741
+ const btnSave = $("#btnSave");
742
+ const btnDownload = $("#btnDownload");
743
+ const btnReset = $("#btnReset");
744
+
745
+ const previewBox = $("#previewBox");
746
+ const pillPreviewState = $("#pillPreviewState");
747
+ const statusLeft = $("#statusLeft");
748
+ const statusRight = $("#statusRight");
749
+
750
+ const btnBack = $("#btnBack");
751
+ const btnForward = $("#btnForward");
752
+ const btnRefresh = $("#btnRefresh");
753
+ const btnFull = $("#btnFull");
754
+
755
+ // --- Storage keys
756
+ const KEY = {
757
+ projects: "espaceCodage.projects.v1",
758
+ activeId: "espaceCodage.activeId.v1"
759
+ };
760
+
761
+ // --- Default template
762
+ const DEFAULT_CODE = `<!doctype html>
763
+ <html lang="fr">
764
+ <head>
765
+ <meta charset="utf-8" />
766
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
767
+ <title>Mon Aperçu</title>
768
+ <style>
769
+ body{font-family:system-ui,Segoe UI,Roboto,Arial;margin:0;padding:24px;background:#f6f7fb;}
770
+ .card{max-width:820px;margin:0 auto;background:#fff;border:1px solid #e7e7ef;border-radius:16px;padding:18px;box-shadow:0 10px 25px rgba(0,0,0,.06);}
771
+ h1{margin:0 0 8px 0;}
772
+ p{margin:0;color:#444;line-height:1.5;}
773
+ .btn{display:inline-block;margin-top:12px;padding:10px 14px;border-radius:12px;border:1px solid #d8d8e6;background:#f2f6ff;cursor:pointer}
774
+ </style>
775
+ </head>
776
+ <body>
777
+ <div class="card">
778
+ <h1>✅ Aperçu OK</h1>
779
+ <p>Ce projet tourne en local (iframe sandbox). Modifie le code, puis clique <b>Run</b>.</p>
780
+ <button class="btn" onclick="alert('Hello Rosalinda 👋')">Tester</button>
781
+ </div>
782
+ </body>
783
+ </html>`;
784
+
785
+ // --- Helpers
786
+ const nowTime = () => new Date().toLocaleTimeString([], {hour:"2-digit", minute:"2-digit"});
787
+
788
+ const escapeHTML = (s) =>
789
+ s.replace(/[&<>"']/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
790
+
791
+ function readProjects(){
792
+ try{
793
+ const raw = localStorage.getItem(KEY.projects);
794
+ if(!raw) return null;
795
+ return JSON.parse(raw);
796
+ }catch{ return null; }
797
+ }
798
+
799
+ function writeProjects(projects){
800
+ localStorage.setItem(KEY.projects, JSON.stringify(projects));
801
+ }
802
+
803
+ function getActiveId(){
804
+ return localStorage.getItem(KEY.activeId);
805
+ }
806
+ function setActiveId(id){
807
+ localStorage.setItem(KEY.activeId, id);
808
+ }
809
+
810
+ function uid(){
811
+ return "p_" + Math.random().toString(16).slice(2) + Date.now().toString(16);
812
+ }
813
+
814
+ function ensureData(){
815
+ let projects = readProjects();
816
+ if(!projects || !Array.isArray(projects) || projects.length === 0){
817
+ projects = [
818
+ { id: uid(), name:"Projet 1", code: DEFAULT_CODE, chat: [] },
819
+ { id: uid(), name:"Projet 2", code: DEFAULT_CODE, chat: [] },
820
+ ];
821
+ writeProjects(projects);
822
+ setActiveId(projects[0].id);
823
+ }
824
+ if(!getActiveId()){
825
+ setActiveId(projects[0].id);
826
+ }
827
+ return projects;
828
+ }
829
+
830
+ function getState(){
831
+ const projects = ensureData();
832
+ const activeId = getActiveId();
833
+ const active = projects.find(p => p.id === activeId) || projects[0];
834
+ return { projects, active };
835
+ }
836
+
837
+ function updateState(mutator){
838
+ const { projects, active } = getState();
839
+ mutator(projects, active);
840
+ writeProjects(projects);
841
+ }
842
+
843
+ // --- UI render
844
+ function renderProjects(){
845
+ const { projects, active } = getState();
846
+ const q = projectSearch.value.trim().toLowerCase();
847
+
848
+ projectList.innerHTML = "";
849
+ projects
850
+ .filter(p => p.name.toLowerCase().includes(q))
851
+ .forEach((p, idx) => {
852
+ const el = document.createElement("div");
853
+ el.className = "item" + (p.id === active.id ? " active" : "");
854
+ el.innerHTML = `
855
+ <div class="badge">&lt;/&gt;</div>
856
+ <div class="itemText">
857
+ <div class="name">${escapeHTML(p.name)}</div>
858
+ <div class="sub">${p.id === active.id ? "Actif" : "Clique pour ouvrir"}</div>
859
+ </div>
860
+ `;
861
+ el.addEventListener("click", () => {
862
+ setActiveId(p.id);
863
+ loadActiveProject();
864
+ });
865
+ projectList.appendChild(el);
866
+ });
867
+
868
+ pillProject.textContent = active.name;
869
+ crumbName.textContent = active.name;
870
+ }
871
+
872
+ function renderChat(){
873
+ const { active } = getState();
874
+ chatInner.innerHTML = "";
875
+
876
+ if(!active.chat || active.chat.length === 0){
877
+ chatInner.innerHTML = `
878
+ <div class="emptyState">
879
+ <div class="emptyIcon"></div>
880
+ <div class="emptyTitle">Zone centrale vide</div>
881
+ <div class="emptySub">La barre de saisie reste en bas comme demandé</div>
882
+ </div>
883
+ `;
884
+ return;
885
+ }
886
+
887
+ for(const m of active.chat){
888
+ const row = document.createElement("div");
889
+ row.className = "msg";
890
+ const isUser = m.role === "user";
891
+ row.innerHTML = `
892
+ <div class="avatar">${isUser ? "A" : "R"}</div>
893
+ <div class="bubble ${isUser ? "user" : ""}">
894
+ <div class="meta"><span>${isUser ? "Amine" : "Rosalinda"}</span><span>•</span><span>${escapeHTML(m.time)}</span></div>
895
+ <div class="text">${escapeHTML(m.text)}</div>
896
+ ${m.code ? `<div class="codeBlock">${escapeHTML(m.code)}</div>` : ""}
897
+ </div>
898
+ `;
899
+ chatInner.appendChild(row);
900
+ }
901
+
902
+ chatInner.scrollTop = chatInner.scrollHeight;
903
+ }
904
+
905
+ function renderPreviewEmpty(){
906
+ previewBox.innerHTML = `
907
+ <div class="previewEmpty">
908
+ <div class="emptyIcon" style="width:92px;height:92px;border-radius:20px;"></div>
909
+ <div class="big">Échec du chargement de l'aperçu</div>
910
+ <div style="margin-top:6px;color:rgba(232,238,252,.6);font-size:13px">
911
+ Vérifiez l'URL / le serveur local, puis rafraîchissez
912
+ </div>
913
+ </div>
914
+ `;
915
+ pillPreviewState.textContent = "Hors ligne";
916
+ pillPreviewState.classList.remove("ok");
917
+ pillPreviewState.classList.add("bad");
918
+ statusLeft.textContent = "Statut: Hors ligne";
919
+ }
920
+
921
+ function renderPreviewFromCode(html){
922
+ previewBox.innerHTML = "";
923
+ const frame = document.createElement("iframe");
924
+ // sandbox: allow scripts inside the preview but isolate from parent.
925
+ frame.setAttribute("sandbox", "allow-scripts allow-forms allow-modals allow-popups");
926
+ frame.srcdoc = html;
927
+
928
+ previewBox.appendChild(frame);
929
+ pillPreviewState.textContent = "En ligne (local)";
930
+ pillPreviewState.classList.remove("bad");
931
+ pillPreviewState.classList.add("ok");
932
+ statusLeft.textContent = "Statut: En ligne (local)";
933
+ }
934
+
935
+ function loadActiveProject(){
936
+ const { active } = getState();
937
+ codeEditor.value = active.code || DEFAULT_CODE;
938
+ renderProjects();
939
+ renderChat();
940
+ renderPreviewEmpty();
941
+ statusRight.textContent = "Mode: Preview";
942
+ }
943
+
944
+ // --- Tabs
945
+ function setTab(which){
946
+ const isCode = which === "code";
947
+ tabCode.classList.toggle("active", isCode);
948
+ tabPreview.classList.toggle("active", !isCode);
949
+ panelCode.style.display = isCode ? "flex" : "none";
950
+ panelPreview.style.display = isCode ? "none" : "flex";
951
+ }
952
+
953
+ tabCode.addEventListener("click", () => setTab("code"));
954
+ tabPreview.addEventListener("click", () => setTab("preview"));
955
+
956
+ // --- Save code
957
+ function saveCode(){
958
+ const code = codeEditor.value;
959
+ updateState((projects, active) => {
960
+ active.code = code;
961
+ });
962
+ }
963
+
964
+ let saveTimer = null;
965
+ codeEditor.addEventListener("input", () => {
966
+ if(saveTimer) clearTimeout(saveTimer);
967
+ saveTimer = setTimeout(() => saveCode(), 250);
968
+ });
969
+
970
+ btnSave.addEventListener("click", () => {
971
+ saveCode();
972
+ toast("✅ Code sauvegardé");
973
+ });
974
+
975
+ // --- Run preview
976
+ btnRun.addEventListener("click", () => {
977
+ saveCode();
978
+ setTab("preview");
979
+ renderPreviewFromCode(codeEditor.value);
980
+ });
981
+
982
+ // --- Download
983
+ btnDownload.addEventListener("click", () => {
984
+ saveCode();
985
+ const blob = new Blob([codeEditor.value], {type:"text/html;charset=utf-8"});
986
+ const a = document.createElement("a");
987
+ a.href = URL.createObjectURL(blob);
988
+ a.download = (getState().active.name || "projet") + ".html";
989
+ document.body.appendChild(a);
990
+ a.click();
991
+ a.remove();
992
+ URL.revokeObjectURL(a.href);
993
+ });
994
+
995
+ // --- Reset
996
+ btnReset.addEventListener("click", () => {
997
+ if(!confirm("Reset le code du projet actif ?")) return;
998
+ codeEditor.value = DEFAULT_CODE;
999
+ saveCode();
1000
+ renderPreviewEmpty();
1001
+ toast("♻️ Reset OK");
1002
+ });
1003
+
1004
+ // --- Quick actions
1005
+ btnPlus.addEventListener("click", () => {
1006
+ addAssistantMessage("Actions rapides :\n- Écris “template” pour un modèle\n- Colle du code HTML/CSS/JS\n- Écris “run” pour lancer l’aperçu\n- Écris “aide” pour les commandes");
1007
+ });
1008
+
1009
+ // Attach (local file import)
1010
+ btnAttach.addEventListener("click", async () => {
1011
+ const input = document.createElement("input");
1012
+ input.type = "file";
1013
+ input.accept = ".html,.txt";
1014
+ input.onchange = async () => {
1015
+ const file = input.files?.[0];
1016
+ if(!file) return;
1017
+ const text = await file.text();
1018
+ codeEditor.value = text;
1019
+ saveCode();
1020
+ addUserMessage(`J’ai importé un fichier: ${file.name}`);
1021
+ addAssistantMessage("✅ Fichier importé dans l’éditeur. Clique Run pour voir l’aperçu.");
1022
+ };
1023
+ input.click();
1024
+ });
1025
+
1026
+ // --- Projects
1027
+ btnNewProject.addEventListener("click", () => {
1028
+ const name = prompt("Nom du nouveau projet ?", "Nouveau Projet");
1029
+ if(!name) return;
1030
+ updateState((projects) => {
1031
+ const p = { id: uid(), name, code: DEFAULT_CODE, chat: [] };
1032
+ projects.unshift(p);
1033
+ setActiveId(p.id);
1034
+ });
1035
+ loadActiveProject();
1036
+ });
1037
+
1038
+ projectSearch.addEventListener("input", renderProjects);
1039
+
1040
+ // --- Chat helpers
1041
+ function pushChat(role, text, code=null){
1042
+ updateState((projects, active) => {
1043
+ active.chat = active.chat || [];
1044
+ active.chat.push({ role, text, time: nowTime(), code });
1045
+ });
1046
+ renderChat();
1047
+ }
1048
+
1049
+ function addUserMessage(text){
1050
+ pushChat("user", text);
1051
+ }
1052
+
1053
+ function addAssistantMessage(text, code=null){
1054
+ pushChat("assistant", text, code);
1055
+ }
1056
+
1057
+ // --- Rosalinda (assistant local simple)
1058
+ function extractCodeFromMessage(msg){
1059
+ // support ```...``` blocks
1060
+ const m = msg.match(/```[\s\S]*?```/);
1061
+ if(!m) return null;
1062
+ return m[0].replace(/^```[a-zA-Z]*\n?/, "").replace(/```$/, "");
1063
+ }
1064
+
1065
+ function rosalindaReply(userText){
1066
+ const t = userText.trim().toLowerCase();
1067
+
1068
+ if(t === "aide" || t === "help"){
1069
+ return {
1070
+ text:
1071
+ `Commandes rapides :
1072
+ - template → met un template propre dans l’éditeur
1073
+ - run → lance l’aperçu
1074
+ - reset → remet le template par défaut
1075
+ - code: ... → met ton code directement dans l’éditeur
1076
+ - status → affiche l’état
1077
+ Tu peux aussi coller du code entre \`\`\` \`\`\`.`
1078
+ };
1079
+ }
1080
+
1081
+ if(t === "template"){
1082
+ return { text:"✅ Template chargé dans l’éditeur. Tu peux modifier puis cliquer Run.", action:"template" };
1083
+ }
1084
+
1085
+ if(t === "run"){
1086
+ return { text:"▶️ Je lance l’aperçu.", action:"run" };
1087
+ }
1088
+
1089
+ if(t === "reset"){
1090
+ return { text:"♻️ Reset du code.", action:"reset" };
1091
+ }
1092
+
1093
+ if(t === "status"){
1094
+ return { text:`Statut local : ${navigator.onLine ? "Internet dispo (optionnel)" : "Hors ligne (OK)"}.\nAperçu : fonctionne via iframe sandbox.\nSauvegarde : localStorage par projet.` };
1095
+ }
1096
+
1097
+ // If user pasted code block
1098
+ const code = extractCodeFromMessage(userText);
1099
+ if(code){
1100
+ return { text:"✅ Code détecté. Je l’ai mis dans l’éditeur. Clique Run pour vérifier.", action:"setCode", code };
1101
+ }
1102
+
1103
+ // "code:" prefix
1104
+ if(t.startsWith("code:")){
1105
+ const c = userText.slice(5).trim();
1106
+ if(c) return { text:"✅ Code reçu. Je l’ai mis dans l’éditeur.", action:"setCode", code:c };
1107
+ }
1108
+
1109
+ // Simple smart hinting (local)
1110
+ if(t.includes("image") || t.includes("vidéo") || t.includes("video")){
1111
+ return {
1112
+ text:
1113
+ `Je peux préparer l’interface et les boutons localement ✅
1114
+ Mais pour générer VRAIMENT des images/vidéos, il faut un moteur local (modèle) installé sur ton PC.
1115
+ Si tu veux, on fera une version “plug-in” : bouton Image/Vidéo → envoie vers ton moteur local plus tard.
1116
+ Pour l’instant, l’éditeur + aperçu + micro + projets tournent parfaitement sans clé API.`
1117
+ };
1118
+ }
1119
+
1120
+ // Default response
1121
+ return {
1122
+ text:
1123
+ `OK. Dis-moi ce que tu veux construire :
1124
+ - une landing page
1125
+ - un dashboard
1126
+ - un formulaire
1127
+ - un thème e-commerce
1128
+ Ou colle ton code, je te le nettoie et je le rends propre.`,
1129
+ };
1130
+ }
1131
+
1132
+ function handleSend(){
1133
+ const text = chatInput.value.trim();
1134
+ if(!text) return;
1135
+ chatInput.value = "";
1136
+ addUserMessage(text);
1137
+
1138
+ const r = rosalindaReply(text);
1139
+ addAssistantMessage(r.text);
1140
+
1141
+ if(r.action === "template"){
1142
+ codeEditor.value = DEFAULT_CODE;
1143
+ saveCode();
1144
+ } else if(r.action === "setCode"){
1145
+ codeEditor.value = r.code || DEFAULT_CODE;
1146
+ saveCode();
1147
+ } else if(r.action === "reset"){
1148
+ codeEditor.value = DEFAULT_CODE;
1149
+ saveCode();
1150
+ renderPreviewEmpty();
1151
+ } else if(r.action === "run"){
1152
+ saveCode();
1153
+ setTab("preview");
1154
+ renderPreviewFromCode(codeEditor.value);
1155
+ }
1156
+ }
1157
+
1158
+ btnSend.addEventListener("click", handleSend);
1159
+ chatInput.addEventListener("keydown", (e) => {
1160
+ if(e.key === "Enter") handleSend();
1161
+ });
1162
+
1163
+ // --- Micro (Web Speech API)
1164
+ let recognition = null;
1165
+ let listening = false;
1166
+
1167
+ function initSpeech(){
1168
+ const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
1169
+ if(!SR) return null;
1170
+ const rec = new SR();
1171
+ rec.lang = "fr-FR";
1172
+ rec.interimResults = true;
1173
+ rec.continuous = false;
1174
+ return rec;
1175
+ }
1176
+
1177
+ function setMicUI(on){
1178
+ listening = on;
1179
+ btnMic.classList.toggle("micOn", on);
1180
+ btnMic.textContent = on ? "🟢" : "🎤";
1181
+ }
1182
+
1183
+ btnMic.addEventListener("click", () => {
1184
+ if(!recognition) recognition = initSpeech();
1185
+ if(!recognition){
1186
+ addAssistantMessage("⚠️ Dictée non supportée sur ce navigateur. Essaye Chrome Desktop.");
1187
+ return;
1188
+ }
1189
+
1190
+ if(listening){
1191
+ recognition.stop();
1192
+ setMicUI(false);
1193
+ return;
1194
+ }
1195
+
1196
+ let finalText = "";
1197
+ recognition.onresult = (event) => {
1198
+ let interim = "";
1199
+ for(let i=event.resultIndex; i<event.results.length; i++){
1200
+ const chunk = event.results[i][0].transcript;
1201
+ if(event.results[i].isFinal) finalText += chunk;
1202
+ else interim += chunk;
1203
+ }
1204
+ chatInput.value = (finalText + interim).trim();
1205
+ };
1206
+
1207
+ recognition.onerror = () => {
1208
+ setMicUI(false);
1209
+ addAssistantMessage("⚠️ Micro: erreur. Vérifie les autorisations micro du site.");
1210
+ };
1211
+
1212
+ recognition.onend = () => {
1213
+ setMicUI(false);
1214
+ if(chatInput.value.trim()){
1215
+ // auto-send after dictation ends
1216
+ handleSend();
1217
+ }
1218
+ };
1219
+
1220
+ setMicUI(true);
1221
+ recognition.start();
1222
+ });
1223
+
1224
+ // --- Preview nav buttons (simple)
1225
+ btnRefresh.addEventListener("click", () => {
1226
+ const iframe = previewBox.querySelector("iframe");
1227
+ if(iframe){
1228
+ // reload srcdoc by resetting it
1229
+ const html = codeEditor.value;
1230
+ iframe.srcdoc = html;
1231
+ toast("⟳ Aperçu rafraîchi");
1232
+ }else{
1233
+ renderPreviewEmpty();
1234
+ }
1235
+ });
1236
+
1237
+ btnBack.addEventListener("click", () => toast("↩︎ Historique non géré (local iframe)."));
1238
+ btnForward.addEventListener("click", () => toast("↪︎ Historique non géré (local iframe)."));
1239
+
1240
+ btnFull.addEventListener("click", () => {
1241
+ const iframe = previewBox.querySelector("iframe");
1242
+ if(!iframe){
1243
+ toast("Aucun aperçu à afficher.");
1244
+ return;
1245
+ }
1246
+ // Open preview in new tab (data URL)
1247
+ const blob = new Blob([codeEditor.value], {type:"text/html;charset=utf-8"});
1248
+ const url = URL.createObjectURL(blob);
1249
+ window.open(url, "_blank", "noopener,noreferrer");
1250
+ setTimeout(() => URL.revokeObjectURL(url), 30_000);
1251
+ });
1252
+
1253
+ // --- Toast
1254
+ function toast(msg){
1255
+ const t = document.createElement("div");
1256
+ t.textContent = msg;
1257
+ t.style.position="fixed";
1258
+ t.style.left="50%";
1259
+ t.style.bottom="18px";
1260
+ t.style.transform="translateX(-50%)";
1261
+ t.style.padding="10px 12px";
1262
+ t.style.borderRadius="14px";
1263
+ t.style.border="1px solid rgba(255,255,255,.12)";
1264
+ t.style.background="rgba(0,0,0,.55)";
1265
+ t.style.backdropFilter="blur(8px)";
1266
+ t.style.color="rgba(232,238,252,.95)";
1267
+ t.style.fontSize="13px";
1268
+ t.style.boxShadow="0 10px 24px rgba(0,0,0,.35)";
1269
+ t.style.zIndex=9999;
1270
+ document.body.appendChild(t);
1271
+ setTimeout(()=>{ t.style.opacity="0"; t.style.transition="opacity .2s"; }, 1100);
1272
+ setTimeout(()=> t.remove(), 1400);
1273
+ }
1274
+
1275
+ // --- Boot
1276
+ loadActiveProject();
1277
+ setTab("code");
1278
+
1279
+ // Seed a first assistant message (only once)
1280
+ const seeded = localStorage.getItem("espaceCodage.seeded.v1");
1281
+ if(!seeded){
1282
+ addAssistantMessage("👋 Bonjour Amine. Je suis Rosalinda (local). Tu peux écrire “aide”, “template”, ou coller du code.");
1283
+ localStorage.setItem("espaceCodage.seeded.v1", "1");
1284
+ }
1285
+
1286
+ })();
1287
+ </script>
1288
+ </body>
1289
+ </html>