aionix 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1615 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>DEV-AI — Offline Developer Toolkit</title>
7
+ <link
8
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600;700&family=Syne:wght@400;600;700;800&display=swap"
9
+ rel="stylesheet"
10
+ />
11
+ <style>
12
+ *,
13
+ *::before,
14
+ *::after {
15
+ box-sizing: border-box;
16
+ margin: 0;
17
+ padding: 0;
18
+ }
19
+
20
+ :root {
21
+ --bg: #0a0a0f;
22
+ --bg2: #111118;
23
+ --bg3: #1a1a24;
24
+ --border: #2a2a3a;
25
+ --accent: #00f5c4;
26
+ --accent2: #7c3aed;
27
+ --accent3: #f59e0b;
28
+ --red: #ef4444;
29
+ --text: #e2e8f0;
30
+ --text2: #94a3b8;
31
+ --text3: #475569;
32
+ --card: #13131c;
33
+ --glow: 0 0 20px rgba(0, 245, 196, 0.15);
34
+ }
35
+
36
+ body {
37
+ font-family: "Syne", sans-serif;
38
+ background: var(--bg);
39
+ color: var(--text);
40
+ min-height: 100vh;
41
+ overflow-x: hidden;
42
+ }
43
+
44
+ /* Animated background grid */
45
+ body::before {
46
+ content: "";
47
+ position: fixed;
48
+ inset: 0;
49
+ background-image:
50
+ linear-gradient(rgba(0, 245, 196, 0.03) 1px, transparent 1px),
51
+ linear-gradient(90deg, rgba(0, 245, 196, 0.03) 1px, transparent 1px);
52
+ background-size: 40px 40px;
53
+ pointer-events: none;
54
+ z-index: 0;
55
+ }
56
+
57
+ /* SIDEBAR */
58
+ .sidebar {
59
+ position: fixed;
60
+ left: 0;
61
+ top: 0;
62
+ bottom: 0;
63
+ width: 220px;
64
+ background: var(--bg2);
65
+ border-right: 1px solid var(--border);
66
+ display: flex;
67
+ flex-direction: column;
68
+ z-index: 100;
69
+ padding: 0;
70
+ }
71
+
72
+ .logo {
73
+ padding: 24px 20px 20px;
74
+ border-bottom: 1px solid var(--border);
75
+ }
76
+
77
+ .logo-text {
78
+ font-size: 22px;
79
+ font-weight: 800;
80
+ letter-spacing: -0.5px;
81
+ color: var(--accent);
82
+ text-shadow: var(--glow);
83
+ }
84
+
85
+ .logo-sub {
86
+ font-size: 10px;
87
+ font-family: "JetBrains Mono", monospace;
88
+ color: var(--text3);
89
+ letter-spacing: 2px;
90
+ text-transform: uppercase;
91
+ margin-top: 2px;
92
+ }
93
+
94
+ .nav {
95
+ padding: 16px 10px;
96
+ flex: 1;
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: 4px;
100
+ }
101
+
102
+ .nav-item {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 10px;
106
+ padding: 10px 12px;
107
+ border-radius: 8px;
108
+ cursor: pointer;
109
+ font-size: 13px;
110
+ font-weight: 600;
111
+ color: var(--text2);
112
+ transition: all 0.2s;
113
+ border: 1px solid transparent;
114
+ letter-spacing: 0.3px;
115
+ }
116
+
117
+ .nav-item:hover {
118
+ background: var(--bg3);
119
+ color: var(--text);
120
+ }
121
+
122
+ .nav-item.active {
123
+ background: rgba(0, 245, 196, 0.08);
124
+ color: var(--accent);
125
+ border-color: rgba(0, 245, 196, 0.2);
126
+ }
127
+
128
+ .nav-icon {
129
+ font-size: 16px;
130
+ }
131
+
132
+ .sidebar-footer {
133
+ padding: 16px 20px;
134
+ border-top: 1px solid var(--border);
135
+ font-family: "JetBrains Mono", monospace;
136
+ font-size: 10px;
137
+ color: var(--text3);
138
+ }
139
+
140
+ .status-dot {
141
+ display: inline-block;
142
+ width: 6px;
143
+ height: 6px;
144
+ background: var(--accent);
145
+ border-radius: 50%;
146
+ margin-right: 6px;
147
+ animation: pulse 2s infinite;
148
+ }
149
+
150
+ @keyframes pulse {
151
+ 0%,
152
+ 100% {
153
+ opacity: 1;
154
+ }
155
+ 50% {
156
+ opacity: 0.3;
157
+ }
158
+ }
159
+
160
+ /* MAIN CONTENT */
161
+ .main {
162
+ margin-left: 220px;
163
+ min-height: 100vh;
164
+ position: relative;
165
+ z-index: 1;
166
+ }
167
+
168
+ .topbar {
169
+ padding: 20px 32px;
170
+ border-bottom: 1px solid var(--border);
171
+ background: rgba(10, 10, 15, 0.8);
172
+ backdrop-filter: blur(10px);
173
+ position: sticky;
174
+ top: 0;
175
+ z-index: 50;
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: space-between;
179
+ }
180
+
181
+ .page-title {
182
+ font-size: 20px;
183
+ font-weight: 800;
184
+ letter-spacing: -0.3px;
185
+ }
186
+
187
+ .page-title span {
188
+ color: var(--accent);
189
+ }
190
+
191
+ .content {
192
+ padding: 32px;
193
+ }
194
+
195
+ /* TABS */
196
+ .tab-content {
197
+ display: none;
198
+ }
199
+ .tab-content.active {
200
+ display: block;
201
+ animation: fadeIn 0.3s ease;
202
+ }
203
+
204
+ @keyframes fadeIn {
205
+ from {
206
+ opacity: 0;
207
+ transform: translateY(8px);
208
+ }
209
+ to {
210
+ opacity: 1;
211
+ transform: translateY(0);
212
+ }
213
+ }
214
+
215
+ /* CARDS */
216
+ .card {
217
+ background: var(--card);
218
+ border: 1px solid var(--border);
219
+ border-radius: 12px;
220
+ padding: 20px;
221
+ transition: border-color 0.2s;
222
+ }
223
+
224
+ .card:hover {
225
+ border-color: rgba(0, 245, 196, 0.3);
226
+ }
227
+
228
+ .card-header {
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: space-between;
232
+ margin-bottom: 16px;
233
+ }
234
+
235
+ .card-title {
236
+ font-size: 13px;
237
+ font-weight: 700;
238
+ text-transform: uppercase;
239
+ letter-spacing: 1.5px;
240
+ color: var(--text2);
241
+ }
242
+
243
+ /* GRID LAYOUTS */
244
+ .grid-2 {
245
+ display: grid;
246
+ grid-template-columns: 1fr 1fr;
247
+ gap: 20px;
248
+ }
249
+ .grid-3 {
250
+ display: grid;
251
+ grid-template-columns: 1fr 1fr 1fr;
252
+ gap: 16px;
253
+ }
254
+
255
+ /* INPUTS */
256
+ input,
257
+ textarea,
258
+ select {
259
+ background: var(--bg3);
260
+ border: 1px solid var(--border);
261
+ color: var(--text);
262
+ border-radius: 8px;
263
+ padding: 10px 14px;
264
+ font-family: "JetBrains Mono", monospace;
265
+ font-size: 13px;
266
+ width: 100%;
267
+ outline: none;
268
+ transition: border-color 0.2s;
269
+ }
270
+
271
+ input:focus,
272
+ textarea:focus,
273
+ select:focus {
274
+ border-color: var(--accent);
275
+ box-shadow: 0 0 0 3px rgba(0, 245, 196, 0.1);
276
+ }
277
+
278
+ textarea {
279
+ resize: vertical;
280
+ min-height: 120px;
281
+ }
282
+
283
+ .form-group {
284
+ margin-bottom: 14px;
285
+ }
286
+ .form-label {
287
+ display: block;
288
+ font-size: 11px;
289
+ font-weight: 700;
290
+ text-transform: uppercase;
291
+ letter-spacing: 1px;
292
+ color: var(--text3);
293
+ margin-bottom: 6px;
294
+ }
295
+
296
+ /* BUTTONS */
297
+ .btn {
298
+ padding: 9px 18px;
299
+ border-radius: 8px;
300
+ font-family: "Syne", sans-serif;
301
+ font-weight: 700;
302
+ font-size: 13px;
303
+ cursor: pointer;
304
+ border: none;
305
+ transition: all 0.2s;
306
+ letter-spacing: 0.3px;
307
+ }
308
+
309
+ .btn-primary {
310
+ background: var(--accent);
311
+ color: #000;
312
+ }
313
+
314
+ .btn-primary:hover {
315
+ background: #00ddb0;
316
+ transform: translateY(-1px);
317
+ box-shadow: 0 4px 15px rgba(0, 245, 196, 0.3);
318
+ }
319
+
320
+ .btn-ghost {
321
+ background: transparent;
322
+ color: var(--text2);
323
+ border: 1px solid var(--border);
324
+ }
325
+
326
+ .btn-ghost:hover {
327
+ background: var(--bg3);
328
+ color: var(--text);
329
+ }
330
+
331
+ .btn-danger {
332
+ background: transparent;
333
+ color: var(--red);
334
+ border: 1px solid rgba(239, 68, 68, 0.3);
335
+ }
336
+
337
+ .btn-danger:hover {
338
+ background: rgba(239, 68, 68, 0.1);
339
+ }
340
+
341
+ .btn-sm {
342
+ padding: 6px 12px;
343
+ font-size: 11px;
344
+ }
345
+
346
+ /* TAGS */
347
+ .tag {
348
+ display: inline-block;
349
+ padding: 3px 10px;
350
+ border-radius: 20px;
351
+ font-size: 11px;
352
+ font-weight: 600;
353
+ font-family: "JetBrains Mono", monospace;
354
+ }
355
+
356
+ .tag-js {
357
+ background: rgba(245, 158, 11, 0.15);
358
+ color: var(--accent3);
359
+ border: 1px solid rgba(245, 158, 11, 0.3);
360
+ }
361
+ .tag-react {
362
+ background: rgba(56, 189, 248, 0.15);
363
+ color: #38bdf8;
364
+ border: 1px solid rgba(56, 189, 248, 0.3);
365
+ }
366
+ .tag-css {
367
+ background: rgba(124, 58, 237, 0.15);
368
+ color: #a78bfa;
369
+ border: 1px solid rgba(124, 58, 237, 0.3);
370
+ }
371
+ .tag-node {
372
+ background: rgba(0, 245, 196, 0.1);
373
+ color: var(--accent);
374
+ border: 1px solid rgba(0, 245, 196, 0.25);
375
+ }
376
+ .tag-default {
377
+ background: var(--bg3);
378
+ color: var(--text2);
379
+ border: 1px solid var(--border);
380
+ }
381
+
382
+ /* ===== DASHBOARD ===== */
383
+ .stat-card {
384
+ background: var(--card);
385
+ border: 1px solid var(--border);
386
+ border-radius: 12px;
387
+ padding: 20px;
388
+ position: relative;
389
+ overflow: hidden;
390
+ }
391
+
392
+ .stat-card::before {
393
+ content: "";
394
+ position: absolute;
395
+ top: 0;
396
+ left: 0;
397
+ right: 0;
398
+ height: 2px;
399
+ }
400
+
401
+ .stat-card.green::before {
402
+ background: var(--accent);
403
+ }
404
+ .stat-card.purple::before {
405
+ background: var(--accent2);
406
+ }
407
+ .stat-card.yellow::before {
408
+ background: var(--accent3);
409
+ }
410
+ .stat-card.red::before {
411
+ background: var(--red);
412
+ }
413
+
414
+ .stat-num {
415
+ font-size: 36px;
416
+ font-weight: 800;
417
+ line-height: 1;
418
+ margin: 8px 0 4px;
419
+ }
420
+
421
+ .stat-label {
422
+ font-size: 12px;
423
+ color: var(--text3);
424
+ font-weight: 600;
425
+ text-transform: uppercase;
426
+ letter-spacing: 1px;
427
+ }
428
+ .stat-icon {
429
+ font-size: 24px;
430
+ margin-bottom: 4px;
431
+ }
432
+
433
+ /* ===== SNIPPETS ===== */
434
+ .snippet-card {
435
+ background: var(--card);
436
+ border: 1px solid var(--border);
437
+ border-radius: 12px;
438
+ overflow: hidden;
439
+ transition: all 0.2s;
440
+ }
441
+
442
+ .snippet-card:hover {
443
+ border-color: rgba(0, 245, 196, 0.3);
444
+ transform: translateY(-2px);
445
+ }
446
+
447
+ .snippet-header {
448
+ padding: 14px 16px;
449
+ border-bottom: 1px solid var(--border);
450
+ display: flex;
451
+ align-items: center;
452
+ justify-content: space-between;
453
+ background: var(--bg3);
454
+ }
455
+
456
+ .snippet-title {
457
+ font-size: 14px;
458
+ font-weight: 700;
459
+ }
460
+
461
+ .snippet-code {
462
+ padding: 16px;
463
+ font-family: "JetBrains Mono", monospace;
464
+ font-size: 12px;
465
+ line-height: 1.6;
466
+ color: var(--accent);
467
+ background: #0d0d14;
468
+ white-space: pre-wrap;
469
+ word-break: break-all;
470
+ max-height: 160px;
471
+ overflow-y: auto;
472
+ }
473
+
474
+ .snippet-footer {
475
+ padding: 10px 16px;
476
+ display: flex;
477
+ align-items: center;
478
+ justify-content: space-between;
479
+ }
480
+
481
+ /* ===== QUIZ ===== */
482
+ .quiz-topic-btn {
483
+ padding: 10px 20px;
484
+ border-radius: 8px;
485
+ border: 1px solid var(--border);
486
+ background: var(--card);
487
+ color: var(--text);
488
+ cursor: pointer;
489
+ font-family: "Syne", sans-serif;
490
+ font-weight: 600;
491
+ font-size: 13px;
492
+ transition: all 0.2s;
493
+ }
494
+
495
+ .quiz-topic-btn:hover,
496
+ .quiz-topic-btn.selected {
497
+ background: rgba(0, 245, 196, 0.08);
498
+ border-color: var(--accent);
499
+ color: var(--accent);
500
+ }
501
+
502
+ .question-card {
503
+ background: var(--card);
504
+ border: 1px solid var(--border);
505
+ border-radius: 12px;
506
+ padding: 28px;
507
+ }
508
+
509
+ .question-text {
510
+ font-size: 18px;
511
+ font-weight: 600;
512
+ line-height: 1.5;
513
+ margin-bottom: 24px;
514
+ }
515
+
516
+ .option-btn {
517
+ display: block;
518
+ width: 100%;
519
+ padding: 14px 18px;
520
+ margin-bottom: 10px;
521
+ border-radius: 8px;
522
+ border: 1px solid var(--border);
523
+ background: var(--bg3);
524
+ color: var(--text);
525
+ text-align: left;
526
+ cursor: pointer;
527
+ font-family: "Syne", sans-serif;
528
+ font-size: 14px;
529
+ font-weight: 500;
530
+ transition: all 0.2s;
531
+ }
532
+
533
+ .option-btn:hover:not(:disabled) {
534
+ border-color: var(--accent);
535
+ background: rgba(0, 245, 196, 0.06);
536
+ }
537
+ .option-btn.correct {
538
+ background: rgba(0, 245, 196, 0.12);
539
+ border-color: var(--accent);
540
+ color: var(--accent);
541
+ }
542
+ .option-btn.wrong {
543
+ background: rgba(239, 68, 68, 0.1);
544
+ border-color: var(--red);
545
+ color: var(--red);
546
+ }
547
+
548
+ .score-badge {
549
+ display: inline-flex;
550
+ align-items: center;
551
+ gap: 8px;
552
+ padding: 8px 16px;
553
+ background: rgba(0, 245, 196, 0.1);
554
+ border: 1px solid rgba(0, 245, 196, 0.3);
555
+ border-radius: 20px;
556
+ font-family: "JetBrains Mono", monospace;
557
+ font-size: 13px;
558
+ color: var(--accent);
559
+ }
560
+
561
+ /* ===== HABITS ===== */
562
+ .habit-row {
563
+ display: flex;
564
+ align-items: center;
565
+ justify-content: space-between;
566
+ padding: 16px;
567
+ background: var(--card);
568
+ border: 1px solid var(--border);
569
+ border-radius: 10px;
570
+ margin-bottom: 10px;
571
+ transition: all 0.2s;
572
+ }
573
+
574
+ .habit-row:hover {
575
+ border-color: rgba(0, 245, 196, 0.2);
576
+ }
577
+
578
+ .habit-name {
579
+ font-size: 15px;
580
+ font-weight: 600;
581
+ }
582
+ .habit-freq {
583
+ font-size: 11px;
584
+ color: var(--text3);
585
+ font-family: "JetBrains Mono", monospace;
586
+ margin-top: 2px;
587
+ }
588
+
589
+ .streak-badge {
590
+ display: flex;
591
+ align-items: center;
592
+ gap: 6px;
593
+ padding: 4px 12px;
594
+ background: rgba(245, 158, 11, 0.1);
595
+ border: 1px solid rgba(245, 158, 11, 0.3);
596
+ border-radius: 20px;
597
+ font-family: "JetBrains Mono", monospace;
598
+ font-size: 12px;
599
+ color: var(--accent3);
600
+ }
601
+
602
+ .done-badge {
603
+ background: rgba(0, 245, 196, 0.1);
604
+ border-color: rgba(0, 245, 196, 0.3);
605
+ color: var(--accent);
606
+ }
607
+
608
+ /* ===== DOCS ===== */
609
+ .docs-sidebar {
610
+ display: flex;
611
+ flex-direction: column;
612
+ gap: 6px;
613
+ }
614
+
615
+ .docs-lang-btn {
616
+ padding: 10px 14px;
617
+ border-radius: 8px;
618
+ border: 1px solid var(--border);
619
+ background: var(--card);
620
+ color: var(--text2);
621
+ cursor: pointer;
622
+ font-family: "Syne", sans-serif;
623
+ font-weight: 600;
624
+ font-size: 13px;
625
+ text-align: left;
626
+ transition: all 0.2s;
627
+ text-transform: capitalize;
628
+ }
629
+
630
+ .docs-lang-btn:hover,
631
+ .docs-lang-btn.active {
632
+ background: rgba(0, 245, 196, 0.08);
633
+ border-color: var(--accent);
634
+ color: var(--accent);
635
+ }
636
+
637
+ .doc-item {
638
+ background: var(--card);
639
+ border: 1px solid var(--border);
640
+ border-radius: 10px;
641
+ padding: 18px;
642
+ margin-bottom: 12px;
643
+ transition: border-color 0.2s;
644
+ }
645
+
646
+ .doc-item:hover {
647
+ border-color: rgba(0, 245, 196, 0.3);
648
+ }
649
+ .doc-item-title {
650
+ font-size: 15px;
651
+ font-weight: 700;
652
+ color: var(--accent);
653
+ margin-bottom: 8px;
654
+ }
655
+ .doc-item-content {
656
+ font-family: "JetBrains Mono", monospace;
657
+ font-size: 12px;
658
+ line-height: 1.7;
659
+ color: var(--text2);
660
+ }
661
+
662
+ /* ===== PLAYGROUND ===== */
663
+ .playground-grid {
664
+ display: grid;
665
+ grid-template-columns: 1fr 1fr;
666
+ gap: 16px;
667
+ height: calc(100vh - 180px);
668
+ }
669
+
670
+ .playground-panel {
671
+ background: var(--card);
672
+ border: 1px solid var(--border);
673
+ border-radius: 12px;
674
+ overflow: hidden;
675
+ display: flex;
676
+ flex-direction: column;
677
+ }
678
+
679
+ .panel-header {
680
+ padding: 12px 16px;
681
+ background: var(--bg3);
682
+ border-bottom: 1px solid var(--border);
683
+ display: flex;
684
+ align-items: center;
685
+ justify-content: space-between;
686
+ font-size: 12px;
687
+ font-weight: 700;
688
+ text-transform: uppercase;
689
+ letter-spacing: 1px;
690
+ color: var(--text3);
691
+ }
692
+
693
+ .code-editor {
694
+ flex: 1;
695
+ background: #0d0d14;
696
+ border: none;
697
+ color: #a8ff78;
698
+ font-family: "JetBrains Mono", monospace;
699
+ font-size: 13px;
700
+ line-height: 1.7;
701
+ padding: 20px;
702
+ resize: none;
703
+ outline: none;
704
+ }
705
+
706
+ .output-frame {
707
+ flex: 1;
708
+ border: none;
709
+ background: white;
710
+ }
711
+
712
+ /* EMPTY STATE */
713
+ .empty-state {
714
+ text-align: center;
715
+ padding: 60px 20px;
716
+ color: var(--text3);
717
+ }
718
+
719
+ .empty-icon {
720
+ font-size: 48px;
721
+ margin-bottom: 16px;
722
+ }
723
+ .empty-text {
724
+ font-size: 15px;
725
+ font-weight: 600;
726
+ color: var(--text2);
727
+ margin-bottom: 6px;
728
+ }
729
+ .empty-sub {
730
+ font-size: 13px;
731
+ }
732
+
733
+ /* MODAL */
734
+ .modal-overlay {
735
+ position: fixed;
736
+ inset: 0;
737
+ background: rgba(0, 0, 0, 0.7);
738
+ backdrop-filter: blur(4px);
739
+ z-index: 200;
740
+ display: none;
741
+ align-items: center;
742
+ justify-content: center;
743
+ }
744
+
745
+ .modal-overlay.open {
746
+ display: flex;
747
+ }
748
+
749
+ .modal {
750
+ background: var(--bg2);
751
+ border: 1px solid var(--border);
752
+ border-radius: 16px;
753
+ padding: 28px;
754
+ width: 500px;
755
+ max-width: 90vw;
756
+ animation: slideUp 0.3s ease;
757
+ }
758
+
759
+ @keyframes slideUp {
760
+ from {
761
+ opacity: 0;
762
+ transform: translateY(20px);
763
+ }
764
+ to {
765
+ opacity: 1;
766
+ transform: translateY(0);
767
+ }
768
+ }
769
+
770
+ .modal-title {
771
+ font-size: 18px;
772
+ font-weight: 800;
773
+ margin-bottom: 20px;
774
+ }
775
+
776
+ /* TOAST */
777
+ .toast {
778
+ position: fixed;
779
+ bottom: 24px;
780
+ right: 24px;
781
+ background: var(--bg2);
782
+ border: 1px solid var(--accent);
783
+ color: var(--accent);
784
+ padding: 12px 20px;
785
+ border-radius: 10px;
786
+ font-size: 13px;
787
+ font-weight: 600;
788
+ font-family: "JetBrains Mono", monospace;
789
+ z-index: 999;
790
+ opacity: 0;
791
+ transform: translateY(10px);
792
+ transition: all 0.3s;
793
+ pointer-events: none;
794
+ }
795
+
796
+ .toast.show {
797
+ opacity: 1;
798
+ transform: translateY(0);
799
+ }
800
+
801
+ /* SCROLLBAR */
802
+ ::-webkit-scrollbar {
803
+ width: 4px;
804
+ height: 4px;
805
+ }
806
+ ::-webkit-scrollbar-track {
807
+ background: transparent;
808
+ }
809
+ ::-webkit-scrollbar-thumb {
810
+ background: var(--border);
811
+ border-radius: 2px;
812
+ }
813
+ ::-webkit-scrollbar-thumb:hover {
814
+ background: var(--text3);
815
+ }
816
+
817
+ .flex {
818
+ display: flex;
819
+ }
820
+ .gap-2 {
821
+ gap: 8px;
822
+ }
823
+ .gap-3 {
824
+ gap: 12px;
825
+ }
826
+ .mb-4 {
827
+ margin-bottom: 16px;
828
+ }
829
+ .mb-6 {
830
+ margin-bottom: 24px;
831
+ }
832
+ .mt-4 {
833
+ margin-top: 16px;
834
+ }
835
+ .flex-wrap {
836
+ flex-wrap: wrap;
837
+ }
838
+ .items-center {
839
+ align-items: center;
840
+ }
841
+ .justify-between {
842
+ justify-content: space-between;
843
+ }
844
+ </style>
845
+ </head>
846
+ <body>
847
+ <!-- SIDEBAR -->
848
+ <aside class="sidebar">
849
+ <div class="logo">
850
+ <div class="logo-text">DEV-AI</div>
851
+ <div class="logo-sub">Offline Toolkit</div>
852
+ </div>
853
+ <nav class="nav">
854
+ <div class="nav-item active" onclick="switchTab('dashboard')">
855
+ <span class="nav-icon">⚡</span> Dashboard
856
+ </div>
857
+ <div class="nav-item" onclick="switchTab('snippets')">
858
+ <span class="nav-icon">📦</span> Snippets
859
+ </div>
860
+ <div class="nav-item" onclick="switchTab('quiz')">
861
+ <span class="nav-icon">🧠</span> Quiz
862
+ </div>
863
+ <div class="nav-item" onclick="switchTab('habits')">
864
+ <span class="nav-icon">🔥</span> Habits
865
+ </div>
866
+ <div class="nav-item" onclick="switchTab('docs')">
867
+ <span class="nav-icon">📖</span> Docs
868
+ </div>
869
+ <div class="nav-item" onclick="switchTab('playground')">
870
+ <span class="nav-icon">🎮</span> Playground
871
+ </div>
872
+ </nav>
873
+ <div class="sidebar-footer">
874
+ <span class="status-dot"></span> offline mode
875
+ </div>
876
+ </aside>
877
+
878
+ <!-- MAIN -->
879
+ <main class="main">
880
+ <div class="topbar">
881
+ <div class="page-title" id="pageTitle">⚡ <span>Dashboard</span></div>
882
+ <div
883
+ style="
884
+ font-family: &quot;JetBrains Mono&quot;, monospace;
885
+ font-size: 11px;
886
+ color: var(--text3);
887
+ "
888
+ id="currentTime"
889
+ ></div>
890
+ </div>
891
+
892
+ <div class="content">
893
+ <!-- DASHBOARD -->
894
+ <div class="tab-content active" id="tab-dashboard">
895
+ <div class="grid-3 mb-6" id="statsGrid">
896
+ <div class="stat-card green">
897
+ <div class="stat-icon">📦</div>
898
+ <div class="stat-num" id="stat-snippets">0</div>
899
+ <div class="stat-label">Snippets Saved</div>
900
+ </div>
901
+ <div class="stat-card yellow">
902
+ <div class="stat-icon">🔥</div>
903
+ <div class="stat-num" id="stat-habits">0</div>
904
+ <div class="stat-label">Active Habits</div>
905
+ </div>
906
+ <div class="stat-card purple">
907
+ <div class="stat-icon">🧠</div>
908
+ <div class="stat-num" id="stat-score">0%</div>
909
+ <div class="stat-label">Quiz Accuracy</div>
910
+ </div>
911
+ </div>
912
+
913
+ <div class="grid-2">
914
+ <div class="card">
915
+ <div class="card-header">
916
+ <div class="card-title">Quick Actions</div>
917
+ </div>
918
+ <div style="display: flex; flex-direction: column; gap: 10px">
919
+ <button
920
+ class="btn btn-ghost"
921
+ style="
922
+ justify-content: flex-start;
923
+ display: flex;
924
+ gap: 10px;
925
+ align-items: center;
926
+ "
927
+ onclick="
928
+ switchTab('snippets');
929
+ openSnippetModal();
930
+ "
931
+ >
932
+ <span>📦</span> Save a code snippet
933
+ </button>
934
+ <button
935
+ class="btn btn-ghost"
936
+ style="
937
+ justify-content: flex-start;
938
+ display: flex;
939
+ gap: 10px;
940
+ align-items: center;
941
+ "
942
+ onclick="switchTab('quiz')"
943
+ >
944
+ <span>🧠</span> Practice quiz
945
+ </button>
946
+ <button
947
+ class="btn btn-ghost"
948
+ style="
949
+ justify-content: flex-start;
950
+ display: flex;
951
+ gap: 10px;
952
+ align-items: center;
953
+ "
954
+ onclick="switchTab('playground')"
955
+ >
956
+ <span>🎮</span> Open playground
957
+ </button>
958
+ <button
959
+ class="btn btn-ghost"
960
+ style="
961
+ justify-content: flex-start;
962
+ display: flex;
963
+ gap: 10px;
964
+ align-items: center;
965
+ "
966
+ onclick="switchTab('habits')"
967
+ >
968
+ <span>🔥</span> Track a habit
969
+ </button>
970
+ </div>
971
+ </div>
972
+ <div class="card">
973
+ <div class="card-header">
974
+ <div class="card-title">Today's Habits</div>
975
+ </div>
976
+ <div id="dashHabits">
977
+ <div class="empty-state" style="padding: 20px">
978
+ <div class="empty-icon">🔥</div>
979
+ <div class="empty-text">No habits yet</div>
980
+ </div>
981
+ </div>
982
+ </div>
983
+ </div>
984
+ </div>
985
+
986
+ <!-- SNIPPETS -->
987
+ <div class="tab-content" id="tab-snippets">
988
+ <div class="flex items-center justify-between mb-6">
989
+ <div class="flex gap-2">
990
+ <input
991
+ type="text"
992
+ id="searchSnippets"
993
+ placeholder="Search snippets..."
994
+ style="width: 240px"
995
+ oninput="filterSnippets()"
996
+ />
997
+ <select
998
+ id="filterLang"
999
+ onchange="filterSnippets()"
1000
+ style="width: 140px"
1001
+ >
1002
+ <option value="">All Languages</option>
1003
+ <option>JavaScript</option>
1004
+ <option>Python</option>
1005
+ <option>React</option>
1006
+ <option>CSS</option>
1007
+ <option>Node.js</option>
1008
+ <option>Other</option>
1009
+ </select>
1010
+ </div>
1011
+ <button class="btn btn-primary" onclick="openSnippetModal()">
1012
+ + New Snippet
1013
+ </button>
1014
+ </div>
1015
+ <div id="snippetsList" class="grid-2"></div>
1016
+ </div>
1017
+
1018
+ <!-- QUIZ -->
1019
+ <div class="tab-content" id="tab-quiz">
1020
+ <div id="quizTopicSelect">
1021
+ <div class="card mb-6">
1022
+ <div class="card-header">
1023
+ <div class="card-title">Choose Topic</div>
1024
+ </div>
1025
+ <div id="topicButtons" class="flex flex-wrap gap-2"></div>
1026
+ </div>
1027
+ </div>
1028
+ <div id="quizArea" style="display: none">
1029
+ <div class="flex items-center justify-between mb-4">
1030
+ <div class="score-badge">
1031
+ Score: <strong id="quizScore">0</strong> /
1032
+ <span id="quizTotal">0</span>
1033
+ </div>
1034
+ <button class="btn btn-ghost btn-sm" onclick="resetQuiz()">
1035
+ ← Back to Topics
1036
+ </button>
1037
+ </div>
1038
+ <div class="question-card" id="questionCard"></div>
1039
+ <div style="text-align: center; margin-top: 20px">
1040
+ <button
1041
+ class="btn btn-primary"
1042
+ id="nextBtn"
1043
+ onclick="nextQuestion()"
1044
+ style="display: none"
1045
+ >
1046
+ Next Question →
1047
+ </button>
1048
+ </div>
1049
+ </div>
1050
+ </div>
1051
+
1052
+ <!-- HABITS -->
1053
+ <div class="tab-content" id="tab-habits">
1054
+ <div class="grid-2">
1055
+ <div>
1056
+ <div class="card mb-4">
1057
+ <div class="card-header">
1058
+ <div class="card-title">Add Habit</div>
1059
+ </div>
1060
+ <div class="form-group">
1061
+ <label class="form-label">Habit Name</label>
1062
+ <input
1063
+ type="text"
1064
+ id="habitName"
1065
+ placeholder="e.g. Code for 1 hour"
1066
+ />
1067
+ </div>
1068
+ <div class="form-group">
1069
+ <label class="form-label">Frequency</label>
1070
+ <select id="habitFreq">
1071
+ <option>Daily</option>
1072
+ <option>Weekly</option>
1073
+ <option>Weekdays only</option>
1074
+ </select>
1075
+ </div>
1076
+ <button class="btn btn-primary" onclick="addHabit()">
1077
+ + Add Habit
1078
+ </button>
1079
+ </div>
1080
+ </div>
1081
+ <div>
1082
+ <div id="habitsList"></div>
1083
+ </div>
1084
+ </div>
1085
+ </div>
1086
+
1087
+ <!-- DOCS -->
1088
+ <div class="tab-content" id="tab-docs">
1089
+ <div class="grid-2">
1090
+ <div>
1091
+ <div class="card">
1092
+ <div class="card-header">
1093
+ <div class="card-title">Languages</div>
1094
+ </div>
1095
+ <div class="docs-sidebar" id="docsLangList"></div>
1096
+ </div>
1097
+ </div>
1098
+ <div id="docsContent">
1099
+ <div class="empty-state">
1100
+ <div class="empty-icon">📖</div>
1101
+ <div class="empty-text">Select a language</div>
1102
+ <div class="empty-sub">
1103
+ Choose from the left to view offline docs
1104
+ </div>
1105
+ </div>
1106
+ </div>
1107
+ </div>
1108
+ </div>
1109
+
1110
+ <!-- PLAYGROUND -->
1111
+ <div class="tab-content" id="tab-playground">
1112
+ <div class="flex items-center justify-between mb-4">
1113
+ <div class="flex gap-2">
1114
+ <select
1115
+ id="playgroundLang"
1116
+ onchange="updatePlayground()"
1117
+ style="width: 140px"
1118
+ >
1119
+ <option value="html">HTML</option>
1120
+ <option value="js">JavaScript</option>
1121
+ </select>
1122
+ </div>
1123
+ <div class="flex gap-2">
1124
+ <button class="btn btn-ghost btn-sm" onclick="clearPlayground()">
1125
+ Clear
1126
+ </button>
1127
+ <button class="btn btn-primary btn-sm" onclick="runCode()">
1128
+ ▶ Run
1129
+ </button>
1130
+ </div>
1131
+ </div>
1132
+ <div class="playground-grid">
1133
+ <div class="playground-panel">
1134
+ <div class="panel-header">
1135
+ <span>CODE EDITOR</span>
1136
+ <span style="color: var(--accent)" id="langLabel">HTML</span>
1137
+ </div>
1138
+ <textarea
1139
+ class="code-editor"
1140
+ id="codeEditor"
1141
+ placeholder="Write your code here...
1142
+ ></textarea>
1143
+ </div>
1144
+ <div class="playground-panel">
1145
+ <div class="panel-header"><span>OUTPUT</span></div>
1146
+ <iframe
1147
+ class="output-frame"
1148
+ id="outputFrame"
1149
+ sandbox="allow-scripts"
1150
+ ></iframe>
1151
+ </div>
1152
+ </div>
1153
+ </div>
1154
+ </div>
1155
+ </main>
1156
+
1157
+ <!-- ADD SNIPPET MODAL -->
1158
+ <div class="modal-overlay" id="snippetModal">
1159
+ <div class="modal">
1160
+ <div class="modal-title">📦 New Snippet</div>
1161
+ <div class="form-group">
1162
+ <label class="form-label">Title</label>
1163
+ <input
1164
+ type="text"
1165
+ id="snippetTitle"
1166
+ placeholder="e.g. Debounce function"
1167
+ />
1168
+ </div>
1169
+ <div class="form-group">
1170
+ <label class="form-label">Language</label>
1171
+ <select id="snippetLang">
1172
+ <option>JavaScript</option>
1173
+ <option>Python</option>
1174
+ <option>React</option>
1175
+ <option>CSS</option>
1176
+ <option>Node.js</option>
1177
+ <option>Other</option>
1178
+ </select>
1179
+ </div>
1180
+ <div class="form-group">
1181
+ <label class="form-label">Code</label>
1182
+ <textarea
1183
+ id="snippetCode"
1184
+ placeholder="Paste your code here..."
1185
+ style="
1186
+ min-height: 160px;
1187
+ font-family: &quot;JetBrains Mono&quot;, monospace;
1188
+ color: var(--accent);
1189
+ "
1190
+ ></textarea>
1191
+ </div>
1192
+ <div class="form-group">
1193
+ <label class="form-label">Tags (comma separated)</label>
1194
+ <input
1195
+ type="text"
1196
+ id="snippetTags"
1197
+ placeholder="e.g. utility, async, array"
1198
+ />
1199
+ </div>
1200
+ <div class="flex gap-2 mt-4">
1201
+ <button class="btn btn-primary" onclick="saveSnippet()">
1202
+ Save Snippet
1203
+ </button>
1204
+ <button class="btn btn-ghost" onclick="closeSnippetModal()">
1205
+ Cancel
1206
+ </button>
1207
+ </div>
1208
+ </div>
1209
+ </div>
1210
+
1211
+ <!-- TOAST -->
1212
+ <div class="toast" id="toast"></div>
1213
+
1214
+ <script>
1215
+ const API = "";
1216
+ let quizQuestions = [];
1217
+ let currentQ = 0;
1218
+ let score = 0;
1219
+ let answered = false;
1220
+
1221
+ // ===== UTILS =====
1222
+ function showToast(msg) {
1223
+ const t = document.getElementById("toast");
1224
+ t.textContent = msg;
1225
+ t.classList.add("show");
1226
+ setTimeout(() => t.classList.remove("show"), 2500);
1227
+ }
1228
+
1229
+ function getLangTag(lang) {
1230
+ const map = {
1231
+ JavaScript: "tag-js",
1232
+ React: "tag-react",
1233
+ CSS: "tag-css",
1234
+ "Node.js": "tag-node",
1235
+ };
1236
+ return map[lang] || "tag-default";
1237
+ }
1238
+
1239
+ // ===== NAV =====
1240
+ function switchTab(tab) {
1241
+ document
1242
+ .querySelectorAll(".tab-content")
1243
+ .forEach((t) => t.classList.remove("active"));
1244
+ document
1245
+ .querySelectorAll(".nav-item")
1246
+ .forEach((n) => n.classList.remove("active"));
1247
+ document.getElementById("tab-" + tab).classList.add("active");
1248
+ document.querySelectorAll(".nav-item").forEach((n) => {
1249
+ if (n.textContent.toLowerCase().includes(tab))
1250
+ n.classList.add("active");
1251
+ });
1252
+ const titles = {
1253
+ dashboard: "⚡ <span>Dashboard</span>",
1254
+ snippets: "📦 <span>Snippets</span>",
1255
+ quiz: "🧠 <span>Quiz</span>",
1256
+ habits: "🔥 <span>Habits</span>",
1257
+ docs: "📖 <span>Docs</span>",
1258
+ playground: "🎮 <span>Playground</span>",
1259
+ };
1260
+ document.getElementById("pageTitle").innerHTML = titles[tab];
1261
+ if (tab === "snippets") loadSnippets();
1262
+ if (tab === "habits") loadHabits();
1263
+ if (tab === "quiz") loadQuizTopics();
1264
+ if (tab === "docs") loadDocsLangs();
1265
+ }
1266
+
1267
+ // ===== CLOCK =====
1268
+ function updateClock() {
1269
+ const now = new Date();
1270
+ document.getElementById("currentTime").textContent =
1271
+ now.toLocaleTimeString("en-US", { hour12: false }) +
1272
+ " — " +
1273
+ now.toLocaleDateString("en-US", {
1274
+ weekday: "short",
1275
+ month: "short",
1276
+ day: "numeric",
1277
+ });
1278
+ }
1279
+ setInterval(updateClock, 1000);
1280
+ updateClock();
1281
+
1282
+ // ===== SNIPPETS =====
1283
+ async function loadSnippets() {
1284
+ try {
1285
+ const res = await fetch("/api/snippets");
1286
+ const snippets = await res.json();
1287
+ renderSnippets(snippets);
1288
+ document.getElementById("stat-snippets").textContent =
1289
+ snippets.length;
1290
+ } catch (e) {
1291
+ console.error(e);
1292
+ }
1293
+ }
1294
+
1295
+ function renderSnippets(snippets) {
1296
+ const el = document.getElementById("snippetsList");
1297
+ if (!snippets.length) {
1298
+ el.innerHTML = `<div class="empty-state" style="grid-column:1/-1"><div class="empty-icon">📦</div><div class="empty-text">No snippets yet</div><div class="empty-sub">Save your first code snippet!</div></div>`;
1299
+ return;
1300
+ }
1301
+ el.innerHTML = snippets
1302
+ .map(
1303
+ (s) => `
1304
+ <div class="snippet-card">
1305
+ <div class="snippet-header">
1306
+ <span class="snippet-title">${s.title}</span>
1307
+ <span class="tag ${getLangTag(s.language)}">${s.language}</span>
1308
+ </div>
1309
+ <div class="snippet-code">${escHtml(s.code)}</div>
1310
+ <div class="snippet-footer">
1311
+ <div class="flex gap-2 flex-wrap">
1312
+ ${(s.tags || []).map((t) => `<span class="tag tag-default">${t}</span>`).join("")}
1313
+ </div>
1314
+ <div class="flex gap-2">
1315
+ <button class="btn btn-ghost btn-sm" onclick="copySnippet('${escAttr(s.code)}')">Copy</button>
1316
+ <button class="btn btn-danger btn-sm" onclick="deleteSnippet('${s._id}')">Del</button>
1317
+ </div>
1318
+ </div>
1319
+ </div>
1320
+ `,
1321
+ )
1322
+ .join("");
1323
+ }
1324
+
1325
+ function filterSnippets() {
1326
+ const q = document.getElementById("searchSnippets").value.toLowerCase();
1327
+ const lang = document.getElementById("filterLang").value;
1328
+ fetch("/api/snippets")
1329
+ .then((r) => r.json())
1330
+ .then((snippets) => {
1331
+ let filtered = snippets.filter((s) => {
1332
+ const matchQ =
1333
+ !q ||
1334
+ s.title.toLowerCase().includes(q) ||
1335
+ s.code.toLowerCase().includes(q);
1336
+ const matchL = !lang || s.language === lang;
1337
+ return matchQ && matchL;
1338
+ });
1339
+ renderSnippets(filtered);
1340
+ });
1341
+ }
1342
+
1343
+ function openSnippetModal() {
1344
+ document.getElementById("snippetModal").classList.add("open");
1345
+ }
1346
+
1347
+ function closeSnippetModal() {
1348
+ document.getElementById("snippetModal").classList.remove("open");
1349
+ ["snippetTitle", "snippetCode", "snippetTags"].forEach(
1350
+ (id) => (document.getElementById(id).value = ""),
1351
+ );
1352
+ }
1353
+
1354
+ async function saveSnippet() {
1355
+ const title = document.getElementById("snippetTitle").value.trim();
1356
+ const code = document.getElementById("snippetCode").value.trim();
1357
+ const language = document.getElementById("snippetLang").value;
1358
+ const tags = document
1359
+ .getElementById("snippetTags")
1360
+ .value.split(",")
1361
+ .map((t) => t.trim())
1362
+ .filter(Boolean);
1363
+ if (!title || !code) return showToast("⚠️ Title and code required");
1364
+ await fetch("/api/snippets", {
1365
+ method: "POST",
1366
+ headers: { "Content-Type": "application/json" },
1367
+ body: JSON.stringify({ title, code, language, tags }),
1368
+ });
1369
+ closeSnippetModal();
1370
+ loadSnippets();
1371
+ showToast("✅ Snippet saved!");
1372
+ }
1373
+
1374
+ async function deleteSnippet(id) {
1375
+ await fetch("/api/snippets/" + id, { method: "DELETE" });
1376
+ loadSnippets();
1377
+ showToast("🗑️ Deleted");
1378
+ }
1379
+
1380
+ function copySnippet(code) {
1381
+ navigator.clipboard.writeText(code);
1382
+ showToast("📋 Copied!");
1383
+ }
1384
+
1385
+ // ===== QUIZ =====
1386
+ async function loadQuizTopics() {
1387
+ const res = await fetch("/api/quiz");
1388
+ const topics = await res.json();
1389
+ document.getElementById("topicButtons").innerHTML = ["all", ...topics]
1390
+ .map(
1391
+ (t) => `
1392
+ <button class="quiz-topic-btn" onclick="startQuiz('${t}')">${t === "all" ? "🎯 All Topics" : t}</button>
1393
+ `,
1394
+ )
1395
+ .join("");
1396
+ }
1397
+
1398
+ async function startQuiz(topic) {
1399
+ const res = await fetch("/api/quiz/" + topic);
1400
+ quizQuestions = await res.json();
1401
+ quizQuestions = quizQuestions.sort(() => Math.random() - 0.5);
1402
+ currentQ = 0;
1403
+ score = 0;
1404
+ document.getElementById("quizTopicSelect").style.display = "none";
1405
+ document.getElementById("quizArea").style.display = "block";
1406
+ document.getElementById("quizTotal").textContent = quizQuestions.length;
1407
+ renderQuestion();
1408
+ }
1409
+
1410
+ function renderQuestion() {
1411
+ if (currentQ >= quizQuestions.length) return showResults();
1412
+ answered = false;
1413
+ const q = quizQuestions[currentQ];
1414
+ document.getElementById("quizScore").textContent = score;
1415
+ document.getElementById("nextBtn").style.display = "none";
1416
+ document.getElementById("questionCard").innerHTML = `
1417
+ <div style="font-size:11px;font-family:'JetBrains Mono',monospace;color:var(--text3);margin-bottom:12px;text-transform:uppercase;letter-spacing:1px">
1418
+ Question ${currentQ + 1} of ${quizQuestions.length} · ${q.topic}
1419
+ </div>
1420
+ <div class="question-text">${q.question}</div>
1421
+ ${q.options
1422
+ .map(
1423
+ (opt, i) => `
1424
+ <button class="option-btn" id="opt-${i}" onclick="selectAnswer(${i}, ${q.answer})">${opt}</button>
1425
+ `,
1426
+ )
1427
+ .join("")}
1428
+ `;
1429
+ }
1430
+
1431
+ function selectAnswer(selected, correct) {
1432
+ if (answered) return;
1433
+ answered = true;
1434
+ if (selected === correct) score++;
1435
+ document.querySelectorAll(".option-btn").forEach((btn, i) => {
1436
+ btn.disabled = true;
1437
+ if (i === correct) btn.classList.add("correct");
1438
+ if (i === selected && selected !== correct)
1439
+ btn.classList.add("wrong");
1440
+ });
1441
+ document.getElementById("quizScore").textContent = score;
1442
+ document.getElementById("nextBtn").style.display = "inline-block";
1443
+ }
1444
+
1445
+ function nextQuestion() {
1446
+ currentQ++;
1447
+ renderQuestion();
1448
+ }
1449
+
1450
+ function showResults() {
1451
+ const pct = Math.round((score / quizQuestions.length) * 100);
1452
+ document.getElementById("stat-score").textContent = pct + "%";
1453
+ document.getElementById("questionCard").innerHTML = `
1454
+ <div style="text-align:center;padding:40px">
1455
+ <div style="font-size:64px;margin-bottom:16px">${pct >= 70 ? "🏆" : pct >= 40 ? "👍" : "💪"}</div>
1456
+ <div style="font-size:32px;font-weight:800;color:var(--accent)">${pct}%</div>
1457
+ <div style="font-size:16px;color:var(--text2);margin-top:8px">You got ${score} out of ${quizQuestions.length} correct</div>
1458
+ <button class="btn btn-primary" style="margin-top:24px" onclick="resetQuiz()">Try Again</button>
1459
+ </div>
1460
+ `;
1461
+ document.getElementById("nextBtn").style.display = "none";
1462
+ }
1463
+
1464
+ function resetQuiz() {
1465
+ document.getElementById("quizTopicSelect").style.display = "block";
1466
+ document.getElementById("quizArea").style.display = "none";
1467
+ }
1468
+
1469
+ // ===== HABITS =====
1470
+ async function loadHabits() {
1471
+ const res = await fetch("/api/habits");
1472
+ const habits = await res.json();
1473
+ document.getElementById("stat-habits").textContent = habits.length;
1474
+ const today = new Date().toDateString();
1475
+
1476
+ document.getElementById("habitsList").innerHTML = habits.length
1477
+ ? habits
1478
+ .map((h) => {
1479
+ const doneToday =
1480
+ h.completedDates && h.completedDates.includes(today);
1481
+ return `
1482
+ <div class="habit-row">
1483
+ <div>
1484
+ <div class="habit-name">${h.name}</div>
1485
+ <div class="habit-freq">${h.frequency}</div>
1486
+ </div>
1487
+ <div class="flex gap-2 items-center">
1488
+ <span class="streak-badge ${doneToday ? "done-badge" : ""}">🔥 ${h.streak}</span>
1489
+ ${!doneToday ? `<button class="btn btn-primary btn-sm" onclick="completeHabit('${h._id}')">Done</button>` : '<span style="font-size:11px;color:var(--accent)">✓ Today</span>'}
1490
+ <button class="btn btn-danger btn-sm" onclick="deleteHabit('${h._id}')">×</button>
1491
+ </div>
1492
+ </div>`;
1493
+ })
1494
+ .join("")
1495
+ : `<div class="empty-state"><div class="empty-icon">🔥</div><div class="empty-text">No habits yet</div><div class="empty-sub">Add your first habit!</div></div>`;
1496
+
1497
+ // Dashboard habits
1498
+ document.getElementById("dashHabits").innerHTML = habits.length
1499
+ ? habits
1500
+ .slice(0, 4)
1501
+ .map((h) => {
1502
+ const done =
1503
+ h.completedDates && h.completedDates.includes(today);
1504
+ return `<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border)">
1505
+ <span style="font-size:13px">${h.name}</span>
1506
+ <span style="font-size:11px;color:${done ? "var(--accent)" : "var(--text3)"}">${done ? "✓ Done" : "Pending"}</span>
1507
+ </div>`;
1508
+ })
1509
+ .join("")
1510
+ : `<div class="empty-state" style="padding:20px"><div class="empty-icon">🔥</div><div class="empty-text">No habits</div></div>`;
1511
+ }
1512
+
1513
+ async function addHabit() {
1514
+ const name = document.getElementById("habitName").value.trim();
1515
+ const frequency = document.getElementById("habitFreq").value;
1516
+ if (!name) return showToast("⚠️ Enter habit name");
1517
+ await fetch("/api/habits", {
1518
+ method: "POST",
1519
+ headers: { "Content-Type": "application/json" },
1520
+ body: JSON.stringify({ name, frequency }),
1521
+ });
1522
+ document.getElementById("habitName").value = "";
1523
+ loadHabits();
1524
+ showToast("✅ Habit added!");
1525
+ }
1526
+
1527
+ async function completeHabit(id) {
1528
+ await fetch("/api/habits/" + id + "/complete", { method: "PATCH" });
1529
+ loadHabits();
1530
+ showToast("🔥 Streak updated!");
1531
+ }
1532
+
1533
+ async function deleteHabit(id) {
1534
+ await fetch("/api/habits/" + id, { method: "DELETE" });
1535
+ loadHabits();
1536
+ showToast("🗑️ Habit removed");
1537
+ }
1538
+
1539
+ // ===== DOCS =====
1540
+ async function loadDocsLangs() {
1541
+ const res = await fetch("/api/docs");
1542
+ const langs = await res.json();
1543
+ document.getElementById("docsLangList").innerHTML = langs
1544
+ .map(
1545
+ (l) => `
1546
+ <button class="docs-lang-btn" onclick="loadDocs('${l}')">${l}</button>
1547
+ `,
1548
+ )
1549
+ .join("");
1550
+ }
1551
+
1552
+ async function loadDocs(lang) {
1553
+ document
1554
+ .querySelectorAll(".docs-lang-btn")
1555
+ .forEach((b) => b.classList.remove("active"));
1556
+ event.target.classList.add("active");
1557
+ const res = await fetch("/api/docs/" + lang);
1558
+ const docs = await res.json();
1559
+ document.getElementById("docsContent").innerHTML = docs
1560
+ .map(
1561
+ (d) => `
1562
+ <div class="doc-item">
1563
+ <div class="doc-item-title">${d.title}</div>
1564
+ <div class="doc-item-content">${d.content}</div>
1565
+ </div>
1566
+ `,
1567
+ )
1568
+ .join("");
1569
+ }
1570
+
1571
+ // ===== PLAYGROUND =====
1572
+ function runCode() {
1573
+ const code = document.getElementById("codeEditor").value;
1574
+ const lang = document.getElementById("playgroundLang").value;
1575
+ const frame = document.getElementById("outputFrame");
1576
+ if (lang === "html") {
1577
+ frame.srcdoc = code;
1578
+ } else {
1579
+ frame.srcdoc = `<html><body style="background:#111;color:#0f0;font-family:monospace;padding:16px"><script>
1580
+ const log = console.log;
1581
+ const out = [];
1582
+ console.log = (...a) => { out.push(a.join(' ')); log(...a); };
1583
+ try { ${code} } catch(e) { out.push('Error: ' + e.message); }
1584
+ document.body.innerHTML = '<pre>' + out.join('\\n') + '</pre>';
1585
+ <\/script></body></html>`;
1586
+ }
1587
+ }
1588
+
1589
+ function clearPlayground() {
1590
+ document.getElementById("codeEditor").value = "";
1591
+ document.getElementById("outputFrame").srcdoc = "";
1592
+ }
1593
+
1594
+ function updatePlayground() {
1595
+ const lang = document.getElementById("playgroundLang").value;
1596
+ document.getElementById("langLabel").textContent = lang.toUpperCase();
1597
+ }
1598
+
1599
+ // ===== HELPERS =====
1600
+ function escHtml(str) {
1601
+ return String(str)
1602
+ .replace(/&/g, "&amp;")
1603
+ .replace(/</g, "&lt;")
1604
+ .replace(/>/g, "&gt;");
1605
+ }
1606
+ function escAttr(str) {
1607
+ return String(str).replace(/'/g, "\\'").replace(/\n/g, "\\n");
1608
+ }
1609
+
1610
+ // ===== INIT =====
1611
+ loadSnippets();
1612
+ loadHabits();
1613
+ </script>
1614
+ </body>
1615
+ </html>