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.
- package/bin/index.js +15 -0
- package/client/index.html +1615 -0
- package/db/habits.db +1 -0
- package/db/quiz.db +5 -0
- package/db/snippets.db +0 -0
- package/package.json +31 -0
- package/server/app.js +35 -0
- package/server/routes/docs.js +44 -0
- package/server/routes/habits.js +62 -0
- package/server/routes/quiz.js +46 -0
- package/server/routes/snippets.js +43 -0
|
@@ -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: "JetBrains Mono", 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: "JetBrains Mono", 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, "&")
|
|
1603
|
+
.replace(/</g, "<")
|
|
1604
|
+
.replace(/>/g, ">");
|
|
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>
|