@zenithbuild/core 0.1.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.
Files changed (101) hide show
  1. package/.eslintignore +15 -0
  2. package/.gitattributes +2 -0
  3. package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +25 -0
  4. package/.github/ISSUE_TEMPLATE/new_ticket.yaml +34 -0
  5. package/.github/pull_request_template.md +15 -0
  6. package/.github/workflows/discord-changelog.yml +141 -0
  7. package/.github/workflows/discord-notify.yml +242 -0
  8. package/.github/workflows/discord-version.yml +195 -0
  9. package/.prettierignore +13 -0
  10. package/.prettierrc +21 -0
  11. package/.zen.d.ts +15 -0
  12. package/LICENSE +21 -0
  13. package/README.md +55 -0
  14. package/app/components/Button.zen +46 -0
  15. package/app/components/Link.zen +11 -0
  16. package/app/favicon.ico +0 -0
  17. package/app/layouts/Main.zen +59 -0
  18. package/app/pages/about.zen +23 -0
  19. package/app/pages/blog/[id].zen +53 -0
  20. package/app/pages/blog/index.zen +32 -0
  21. package/app/pages/dynamic-dx.zen +712 -0
  22. package/app/pages/dynamic-primitives.zen +453 -0
  23. package/app/pages/index.zen +154 -0
  24. package/app/pages/navigation-demo.zen +229 -0
  25. package/app/pages/posts/[...slug].zen +61 -0
  26. package/app/pages/primitives-demo.zen +273 -0
  27. package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
  28. package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
  29. package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
  30. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
  31. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +601 -0
  32. package/assets/logos/README.md +54 -0
  33. package/assets/logos/zen.icns +0 -0
  34. package/bun.lock +39 -0
  35. package/compiler/README.md +380 -0
  36. package/compiler/errors/compilerError.ts +24 -0
  37. package/compiler/finalize/finalizeOutput.ts +163 -0
  38. package/compiler/finalize/generateFinalBundle.ts +82 -0
  39. package/compiler/index.ts +44 -0
  40. package/compiler/ir/types.ts +83 -0
  41. package/compiler/legacy/binding.ts +254 -0
  42. package/compiler/legacy/bindings.ts +338 -0
  43. package/compiler/legacy/component-process.ts +1208 -0
  44. package/compiler/legacy/component.ts +301 -0
  45. package/compiler/legacy/event.ts +50 -0
  46. package/compiler/legacy/expression.ts +1149 -0
  47. package/compiler/legacy/mutation.ts +280 -0
  48. package/compiler/legacy/parse.ts +299 -0
  49. package/compiler/legacy/split.ts +608 -0
  50. package/compiler/legacy/types.ts +32 -0
  51. package/compiler/output/types.ts +34 -0
  52. package/compiler/parse/detectMapExpressions.ts +102 -0
  53. package/compiler/parse/parseScript.ts +22 -0
  54. package/compiler/parse/parseTemplate.ts +425 -0
  55. package/compiler/parse/parseZenFile.ts +66 -0
  56. package/compiler/parse/trackLoopContext.ts +82 -0
  57. package/compiler/runtime/dataExposure.ts +291 -0
  58. package/compiler/runtime/generateDOM.ts +144 -0
  59. package/compiler/runtime/generateHydrationBundle.ts +383 -0
  60. package/compiler/runtime/hydration.ts +309 -0
  61. package/compiler/runtime/navigation.ts +432 -0
  62. package/compiler/runtime/thinRuntime.ts +160 -0
  63. package/compiler/runtime/transformIR.ts +256 -0
  64. package/compiler/runtime/wrapExpression.ts +84 -0
  65. package/compiler/runtime/wrapExpressionWithLoop.ts +77 -0
  66. package/compiler/spa-build.ts +1000 -0
  67. package/compiler/test/validate-test.ts +104 -0
  68. package/compiler/transform/generateBindings.ts +47 -0
  69. package/compiler/transform/generateHTML.ts +28 -0
  70. package/compiler/transform/transformNode.ts +126 -0
  71. package/compiler/transform/transformTemplate.ts +38 -0
  72. package/compiler/validate/validateExpressions.ts +168 -0
  73. package/core/index.ts +135 -0
  74. package/core/lifecycle/index.ts +49 -0
  75. package/core/lifecycle/zen-mount.ts +182 -0
  76. package/core/lifecycle/zen-unmount.ts +88 -0
  77. package/core/reactivity/index.ts +54 -0
  78. package/core/reactivity/tracking.ts +167 -0
  79. package/core/reactivity/zen-batch.ts +57 -0
  80. package/core/reactivity/zen-effect.ts +139 -0
  81. package/core/reactivity/zen-memo.ts +146 -0
  82. package/core/reactivity/zen-ref.ts +52 -0
  83. package/core/reactivity/zen-signal.ts +121 -0
  84. package/core/reactivity/zen-state.ts +180 -0
  85. package/core/reactivity/zen-untrack.ts +44 -0
  86. package/docs/COMMENTS.md +111 -0
  87. package/docs/COMMITS.md +36 -0
  88. package/docs/CONTRIBUTING.md +116 -0
  89. package/docs/STYLEGUIDE.md +62 -0
  90. package/package.json +44 -0
  91. package/router/index.ts +76 -0
  92. package/router/manifest.ts +314 -0
  93. package/router/navigation/ZenLink.zen +231 -0
  94. package/router/navigation/index.ts +78 -0
  95. package/router/navigation/zen-link.ts +584 -0
  96. package/router/runtime.ts +458 -0
  97. package/router/types.ts +168 -0
  98. package/runtime/build.ts +17 -0
  99. package/runtime/serve.ts +93 -0
  100. package/scripts/webhook-proxy.ts +213 -0
  101. package/tsconfig.json +28 -0
@@ -0,0 +1,712 @@
1
+ <script>
2
+ // ============================================
3
+ // Dynamic HTML - Clean DX Demo
4
+ // ============================================
5
+
6
+ // User authentication state
7
+ state user = null
8
+ state isAuthenticated = false
9
+
10
+ // Data
11
+ state notifications = [
12
+ { id: 1, type: "info", message: "Welcome to Zenith!", read: false },
13
+ { id: 2, type: "success", message: "Profile updated", read: true },
14
+ { id: 3, type: "warning", message: "Storage almost full", read: false }
15
+ ]
16
+
17
+ state todoItems = [
18
+ { id: 1, text: "Learn Zenith basics", completed: true },
19
+ { id: 2, text: "Build a component", completed: true },
20
+ { id: 3, text: "Master dynamic HTML", completed: false },
21
+ { id: 4, text: "Deploy to production", completed: false }
22
+ ]
23
+
24
+ state todoCount = 4
25
+
26
+ // Auth toggle - used in onclick handler
27
+ function toggleAuth() {
28
+ if (isAuthenticated) {
29
+ user = null
30
+ isAuthenticated = false
31
+ } else {
32
+ user = { name: "Alex Developer", email: "alex@zenith.dev", avatar: "👨‍đŸ’ģ" }
33
+ isAuthenticated = true
34
+ }
35
+ }
36
+
37
+ // Mark all notifications as read - used in onclick handler
38
+ function markAllRead() {
39
+ notifications = notifications.map(n => ({ ...n, read: true }))
40
+ }
41
+
42
+ // Clear all notifications - used in onclick handler
43
+ function clearNotifications() {
44
+ notifications = []
45
+ }
46
+
47
+ // Toggle the first incomplete todo - used in onclick handler
48
+ function toggleNextTodo() {
49
+ const firstIncomplete = todoItems.find(t => !t.completed)
50
+ if (firstIncomplete) {
51
+ todoItems = todoItems.map(item =>
52
+ item.id === firstIncomplete.id ? { ...item, completed: true } : item
53
+ )
54
+ }
55
+ }
56
+
57
+ // Add a demo todo - used in onclick handler
58
+ function addTodo() {
59
+ todoCount = todoCount + 1
60
+ todoItems = [...todoItems, { id: todoCount, text: "New task #" + todoCount, completed: false }]
61
+ }
62
+ </script>
63
+
64
+ <style>
65
+ /* Reset and full-page container */
66
+ .dx-page {
67
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
68
+ background: #0f172a;
69
+ color: #f1f5f9;
70
+ min-height: 100vh;
71
+ margin: 0;
72
+ padding: 0;
73
+ }
74
+
75
+ /* Header */
76
+ .dx-header {
77
+ background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #ec4899 100%);
78
+ padding: 60px 32px;
79
+ text-align: center;
80
+ }
81
+
82
+ .dx-header h1 {
83
+ margin: 0 0 16px 0;
84
+ font-size: 3rem;
85
+ font-weight: 800;
86
+ color: white;
87
+ letter-spacing: -0.02em;
88
+ text-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
89
+ }
90
+
91
+ .dx-header .subtitle {
92
+ font-size: 1.25rem;
93
+ color: rgba(255, 255, 255, 0.9);
94
+ font-weight: 500;
95
+ letter-spacing: 0.1em;
96
+ }
97
+
98
+ /* Main content */
99
+ .dx-main {
100
+ max-width: 1000px;
101
+ margin: 0 auto;
102
+ padding: 48px 24px;
103
+ }
104
+
105
+ /* Feature Section */
106
+ .feature-section {
107
+ background: #1e293b;
108
+ border: 1px solid #334155;
109
+ border-radius: 20px;
110
+ padding: 32px;
111
+ margin-bottom: 32px;
112
+ }
113
+
114
+ .feature-header {
115
+ margin-bottom: 24px;
116
+ }
117
+
118
+ .feature-title {
119
+ font-size: 1.5rem;
120
+ margin: 0 0 8px 0;
121
+ color: #f1f5f9;
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 12px;
125
+ font-weight: 700;
126
+ }
127
+
128
+ .feature-description {
129
+ color: #94a3b8;
130
+ font-size: 1rem;
131
+ line-height: 1.6;
132
+ margin: 0;
133
+ }
134
+
135
+ .demo-card {
136
+ background: #0f172a;
137
+ border: 1px solid #334155;
138
+ border-radius: 16px;
139
+ padding: 24px;
140
+ margin-bottom: 24px;
141
+ }
142
+
143
+ .code-example {
144
+ background: #0a0e1a;
145
+ border: 1px solid #1e293b;
146
+ border-radius: 12px;
147
+ padding: 20px;
148
+ margin-top: 20px;
149
+ font-family: 'Fira Code', 'Consolas', monospace;
150
+ font-size: 13px;
151
+ line-height: 1.8;
152
+ overflow-x: auto;
153
+ }
154
+
155
+ .code-keyword {
156
+ color: #c084fc;
157
+ }
158
+
159
+ .code-string {
160
+ color: #34d399;
161
+ }
162
+
163
+ .code-comment {
164
+ color: #64748b;
165
+ }
166
+
167
+ /* Auth Card */
168
+ .auth-card {
169
+ background: #0f172a;
170
+ border: 1px solid #334155;
171
+ border-radius: 16px;
172
+ padding: 24px;
173
+ margin-bottom: 24px;
174
+ display: flex;
175
+ justify-content: space-between;
176
+ align-items: center;
177
+ flex-wrap: wrap;
178
+ gap: 20px;
179
+ }
180
+
181
+ .auth-info {
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 20px;
185
+ }
186
+
187
+ .user-card, .guest-card {
188
+ display: flex;
189
+ align-items: center;
190
+ padding: 16px 20px;
191
+ border-radius: 12px;
192
+ gap: 16px;
193
+ }
194
+
195
+ .user-card {
196
+ background: rgba(34, 197, 94, 0.1);
197
+ border: 1px solid rgba(34, 197, 94, 0.25);
198
+ }
199
+
200
+ .user-card .avatar {
201
+ background: linear-gradient(135deg, #22c55e, #16a34a);
202
+ }
203
+
204
+ .guest-card {
205
+ background: rgba(251, 191, 36, 0.1);
206
+ border: 1px solid rgba(251, 191, 36, 0.25);
207
+ }
208
+
209
+ .guest-card .avatar {
210
+ background: linear-gradient(135deg, #fbbf24, #f59e0b);
211
+ }
212
+
213
+ .avatar {
214
+ width: 48px;
215
+ height: 48px;
216
+ border-radius: 50%;
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ font-size: 24px;
221
+ flex-shrink: 0;
222
+ }
223
+
224
+ .auth-text {
225
+ font-size: 1rem;
226
+ font-weight: 600;
227
+ color: #e2e8f0;
228
+ }
229
+
230
+ .status-active-text {
231
+ color: #22c55e;
232
+ }
233
+
234
+ .status-inactive-text {
235
+ color: #fbbf24;
236
+ }
237
+
238
+ /* Buttons */
239
+ .btn {
240
+ background: #3b82f6;
241
+ color: white;
242
+ border: none;
243
+ border-radius: 12px;
244
+ padding: 14px 28px;
245
+ cursor: pointer;
246
+ font-size: 15px;
247
+ font-weight: 600;
248
+ font-family: inherit;
249
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
250
+ display: inline-flex;
251
+ align-items: center;
252
+ gap: 8px;
253
+ }
254
+
255
+ .btn:hover {
256
+ background: #2563eb;
257
+ transform: translateY(-2px);
258
+ box-shadow: 0 8px 24px rgba(59, 130, 246, 0.35);
259
+ }
260
+
261
+ .btn-outline {
262
+ background: transparent;
263
+ border: 2px solid #475569;
264
+ color: #e2e8f0;
265
+ }
266
+
267
+ .btn-outline:hover {
268
+ background: #334155;
269
+ border-color: #3b82f6;
270
+ box-shadow: none;
271
+ }
272
+
273
+ .btn-sm {
274
+ padding: 10px 20px;
275
+ font-size: 13px;
276
+ }
277
+
278
+ /* Notification Item */
279
+ .notification-list {
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 12px;
283
+ }
284
+
285
+ .notification-item {
286
+ background: #0f172a;
287
+ border: 1px solid #334155;
288
+ border-radius: 12px;
289
+ padding: 16px 20px;
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: space-between;
293
+ gap: 16px;
294
+ transition: all 0.2s ease;
295
+ }
296
+
297
+ .notification-item:hover {
298
+ border-color: #3b82f6;
299
+ }
300
+
301
+ .notification-content {
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 16px;
305
+ flex: 1;
306
+ }
307
+
308
+ .notification-icon {
309
+ width: 40px;
310
+ height: 40px;
311
+ border-radius: 10px;
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ font-size: 18px;
316
+ background: rgba(59, 130, 246, 0.15);
317
+ flex-shrink: 0;
318
+ }
319
+
320
+ .notification-text {
321
+ color: #e2e8f0;
322
+ font-size: 0.95rem;
323
+ }
324
+
325
+ .notification-unread {
326
+ width: 10px;
327
+ height: 10px;
328
+ border-radius: 50%;
329
+ background: #3b82f6;
330
+ flex-shrink: 0;
331
+ }
332
+
333
+ .empty-state {
334
+ text-align: center;
335
+ padding: 40px 20px;
336
+ color: #64748b;
337
+ font-size: 0.95rem;
338
+ }
339
+
340
+ .notification-header {
341
+ display: flex;
342
+ justify-content: space-between;
343
+ align-items: center;
344
+ margin-bottom: 20px;
345
+ flex-wrap: wrap;
346
+ gap: 12px;
347
+ }
348
+
349
+ .dismiss-btn {
350
+ background: rgba(239, 68, 68, 0.1);
351
+ border: none;
352
+ color: #f87171;
353
+ cursor: pointer;
354
+ padding: 10px 16px;
355
+ border-radius: 8px;
356
+ font-size: 13px;
357
+ font-weight: 600;
358
+ transition: all 0.2s ease;
359
+ }
360
+
361
+ .dismiss-btn:hover {
362
+ background: rgba(239, 68, 68, 0.2);
363
+ }
364
+
365
+ /* Todo section */
366
+ .todo-actions {
367
+ display: flex;
368
+ gap: 12px;
369
+ margin-bottom: 24px;
370
+ flex-wrap: wrap;
371
+ }
372
+
373
+ .todo-list {
374
+ display: flex;
375
+ flex-direction: column;
376
+ gap: 12px;
377
+ margin-bottom: 24px;
378
+ }
379
+
380
+ .todo-item {
381
+ display: flex;
382
+ align-items: center;
383
+ gap: 16px;
384
+ padding: 16px 20px;
385
+ background: #0f172a;
386
+ border-radius: 12px;
387
+ border: 1px solid #334155;
388
+ transition: all 0.2s ease;
389
+ }
390
+
391
+ .todo-item:hover {
392
+ border-color: #3b82f6;
393
+ }
394
+
395
+ .todo-completed {
396
+ opacity: 0.6;
397
+ }
398
+
399
+ .todo-checkbox {
400
+ width: 24px;
401
+ height: 24px;
402
+ border-radius: 6px;
403
+ border: 2px solid #475569;
404
+ display: flex;
405
+ align-items: center;
406
+ justify-content: center;
407
+ font-size: 14px;
408
+ color: #22c55e;
409
+ flex-shrink: 0;
410
+ }
411
+
412
+ .todo-text {
413
+ color: #e2e8f0;
414
+ font-size: 0.95rem;
415
+ font-weight: 500;
416
+ flex: 1;
417
+ }
418
+
419
+ .todo-stats {
420
+ display: flex;
421
+ gap: 32px;
422
+ padding-top: 20px;
423
+ border-top: 1px solid #334155;
424
+ color: #94a3b8;
425
+ font-size: 0.9rem;
426
+ }
427
+
428
+ .stat-value {
429
+ color: #3b82f6;
430
+ font-weight: 700;
431
+ font-size: 1.1rem;
432
+ }
433
+
434
+ /* Navigation */
435
+ .nav-section {
436
+ display: flex;
437
+ gap: 16px;
438
+ flex-wrap: wrap;
439
+ justify-content: center;
440
+ padding-top: 16px;
441
+ }
442
+
443
+ .nav-link {
444
+ display: inline-flex;
445
+ align-items: center;
446
+ gap: 10px;
447
+ padding: 16px 32px;
448
+ background: #334155;
449
+ color: white;
450
+ text-decoration: none;
451
+ border-radius: 14px;
452
+ font-weight: 600;
453
+ font-size: 15px;
454
+ transition: all 0.2s ease;
455
+ border: 1px solid #475569;
456
+ }
457
+
458
+ .nav-link:hover {
459
+ background: #475569;
460
+ border-color: #3b82f6;
461
+ transform: translateY(-2px);
462
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
463
+ }
464
+ </style>
465
+
466
+ <Main title="Dynamic HTML - DX Demo">
467
+ <div class="dx-page">
468
+ <header class="dx-header">
469
+ <h1>✨ Clean DX Dynamic HTML</h1>
470
+ <p class="subtitle">DECLARATIVE â€ĸ EXPRESSIVE â€ĸ INTUITIVE</p>
471
+ </header>
472
+
473
+ <main class="dx-main">
474
+ <!-- Conditional Rendering -->
475
+ <section class="feature-section">
476
+ <div class="feature-header">
477
+ <h2 class="feature-title">🔀 Conditional Rendering</h2>
478
+ <p class="feature-description">
479
+ Use <code style="background: rgba(59, 130, 246, 0.1); padding: 2px 6px; border-radius: 4px;">condition && element</code> to show or hide content based on state.
480
+ Perfect for authentication states, feature flags, and dynamic UI elements.
481
+ </p>
482
+ </div>
483
+
484
+ <div class="demo-card">
485
+ <div class="auth-card">
486
+ <div class="auth-info">
487
+ {isAuthenticated && user && (
488
+ <div class="user-card">
489
+ <div class="avatar">{user.avatar}</div>
490
+ <span class="auth-text">Welcome back, {user.name}!</span>
491
+ </div>
492
+ )}
493
+ {!isAuthenticated && (
494
+ <div class="guest-card">
495
+ <div class="avatar">👤</div>
496
+ <span class="auth-text">Welcome, Guest</span>
497
+ </div>
498
+ )}
499
+ </div>
500
+
501
+ <button class="btn" onclick="toggleAuth">
502
+ {isAuthenticated ? "đŸšĒ Logout" : "🔐 Login"}
503
+ </button>
504
+ </div>
505
+ </div>
506
+
507
+ <div class="code-example">
508
+ <span class="code-comment">// Conditional rendering with && operator</span>
509
+ <span class="code-keyword">state</span> isAuthenticated = <span class="code-keyword">false</span>
510
+ <span class="code-keyword">state</span> user = <span class="code-keyword">null</span>
511
+
512
+ <span class="code-comment">// Show element only when condition is true</span>
513
+ {isAuthenticated && user && (
514
+ &lt;div class="user-card"&gt;
515
+ Welcome back, {user.name}!
516
+ &lt;/div&gt;
517
+ )}
518
+
519
+ <span class="code-comment">// Negated condition for alternative content</span>
520
+ {!isAuthenticated && (
521
+ &lt;div class="guest-card"&gt;
522
+ Welcome, Guest
523
+ &lt;/div&gt;
524
+ )}
525
+ </div>
526
+ </section>
527
+
528
+ <!-- Ternary Expressions -->
529
+ <section class="feature-section">
530
+ <div class="feature-header">
531
+ <h2 class="feature-title">❓ Inline Ternary Expressions</h2>
532
+ <p class="feature-description">
533
+ Use <code style="background: rgba(59, 130, 246, 0.1); padding: 2px 6px; border-radius: 4px;">condition ? A : B</code> for concise inline conditional content.
534
+ Great for dynamic button text, status indicators, and toggleable values.
535
+ </p>
536
+ </div>
537
+
538
+ <div class="demo-card">
539
+ <div class="auth-card">
540
+ <div class="auth-info">
541
+ <span class="auth-text">Status:</span>
542
+ <span class="auth-text" :class="isAuthenticated ? 'status-active-text' : 'status-inactive-text'">
543
+ {isAuthenticated ? "✓ Authenticated" : "✗ Not Authenticated"}
544
+ </span>
545
+ </div>
546
+
547
+ <button class="btn" onclick="toggleAuth">
548
+ {isAuthenticated ? "đŸšĒ Logout" : "🔐 Login"}
549
+ </button>
550
+ </div>
551
+ </div>
552
+
553
+ <div class="code-example">
554
+ <span class="code-comment">// Ternary expression for dynamic content</span>
555
+ <span class="code-keyword">state</span> isAuthenticated = <span class="code-keyword">false</span>
556
+
557
+ &lt;button&gt;
558
+ {isAuthenticated ? <span class="code-string">"Logout"</span> : <span class="code-string">"Login"</span>}
559
+ &lt;/button&gt;
560
+
561
+ <span class="code-comment">// Ternary in attributes using :class binding</span>
562
+ &lt;span :class="isAuthenticated ? 'status-active' : 'status-inactive'"&gt;
563
+ {isAuthenticated ? <span class="code-string">"Authenticated"</span> : <span class="code-string">"Not Authenticated"</span>}
564
+ &lt;/span&gt;
565
+ </div>
566
+ </section>
567
+
568
+ <!-- Array Mapping -->
569
+ <section class="feature-section">
570
+ <div class="feature-header">
571
+ <h2 class="feature-title">📋 Array Iteration (map)</h2>
572
+ <p class="feature-description">
573
+ Use <code style="background: rgba(59, 130, 246, 0.1); padding: 2px 6px; border-radius: 4px;">array.map(item => &lt;element key={item.id}&gt;...&lt;/element&gt;)</code> to render lists.
574
+ The <code style="background: rgba(59, 130, 246, 0.1); padding: 2px 6px; border-radius: 4px;">key</code> prop is required for stable DOM updates.
575
+ </p>
576
+ </div>
577
+
578
+ <div class="demo-card">
579
+ <div class="notification-header">
580
+ <h3 style="margin: 0; color: #e2e8f0; font-size: 1.1rem;">🔔 Notifications ({notifications.length})</h3>
581
+ <div style="display: flex; gap: 12px;">
582
+ <button class="btn btn-sm btn-outline" onclick="markAllRead">
583
+ ✓ Mark all read
584
+ </button>
585
+ <button class="btn btn-sm dismiss-btn" onclick="clearNotifications">
586
+ ✕ Clear All
587
+ </button>
588
+ </div>
589
+ </div>
590
+
591
+ <div class="notification-list">
592
+ {notifications.length === 0 && (
593
+ <div class="empty-state">No notifications</div>
594
+ )}
595
+ {notifications.map(n => (
596
+ <div key={n.id} class="notification-item">
597
+ <div class="notification-content">
598
+ <div class="notification-icon">
599
+ {n.type === 'info' ? 'â„šī¸' : n.type === 'success' ? '✅' : 'âš ī¸'}
600
+ </div>
601
+ <span class="notification-text">{n.message}</span>
602
+ </div>
603
+ {!n.read && <div class="notification-unread"></div>}
604
+ </div>
605
+ ))}
606
+ </div>
607
+ </div>
608
+
609
+ <div class="code-example">
610
+ <span class="code-comment">// Array state</span>
611
+ <span class="code-keyword">state</span> notifications = [
612
+ { id: 1, type: <span class="code-string">"info"</span>, message: <span class="code-string">"Welcome!"</span>, read: <span class="code-keyword">false</span> },
613
+ { id: 2, type: <span class="code-string">"success"</span>, message: <span class="code-string">"Updated"</span>, read: <span class="code-keyword">true</span> }
614
+ ]
615
+
616
+ <span class="code-comment">// Map over array - key prop is required!</span>
617
+ &lt;div&gt;
618
+ {notifications.map(n =&gt; (
619
+ &lt;div key={n.id} class="notification-item"&gt;
620
+ &lt;span&gt;{n.message}&lt;/span&gt;
621
+ {!n.read && &lt;div class="unread"&gt;&lt;/div&gt;}
622
+ &lt;/div&gt;
623
+ ))}
624
+ &lt;/div&gt;
625
+
626
+ <span class="code-comment">// Empty state with conditional</span>
627
+ {notifications.length === 0 && (
628
+ &lt;div class="empty-state"&gt;No notifications&lt;/div&gt;
629
+ )}
630
+ </div>
631
+ </section>
632
+
633
+ <!-- Nested Composition -->
634
+ <section class="feature-section">
635
+ <div class="feature-header">
636
+ <h2 class="feature-title">đŸŽ¯ Nested Composition</h2>
637
+ <p class="feature-description">
638
+ Combine conditionals, ternaries, and maps for complex rendering logic.
639
+ Ternaries inside maps, conditionals inside ternaries, maps inside conditionals - it all works seamlessly!
640
+ </p>
641
+ </div>
642
+
643
+ <div class="demo-card">
644
+ <div class="todo-actions">
645
+ <button class="btn" onclick="addTodo">+ Add Task</button>
646
+ <button class="btn btn-outline" onclick="toggleNextTodo">✓ Complete Next</button>
647
+ </div>
648
+
649
+ <div class="todo-list">
650
+ {todoItems.length === 0 && (
651
+ <div class="empty-state">No tasks yet! Add one to get started.</div>
652
+ )}
653
+ {todoItems.map(todo => (
654
+ <div key={todo.id} class="todo-item" :class="todo.completed ? 'todo-completed' : ''">
655
+ <div class="todo-checkbox">{todo.completed && '✓'}</div>
656
+ <span class="todo-text">{todo.text}</span>
657
+ </div>
658
+ ))}
659
+ </div>
660
+
661
+ <div class="todo-stats">
662
+ <span>
663
+ <span class="stat-value">{todoItems.filter(t => t.completed).length}</span> completed
664
+ </span>
665
+ <span>
666
+ <span class="stat-value">{todoItems.filter(t => !t.completed).length}</span> remaining
667
+ </span>
668
+ <span>
669
+ <span class="stat-value">{todoItems.length}</span> total
670
+ </span>
671
+ </div>
672
+ </div>
673
+
674
+ <div class="code-example">
675
+ <span class="code-comment">// Complex nested composition</span>
676
+ <span class="code-keyword">state</span> todoItems = [
677
+ { id: 1, text: <span class="code-string">"Learn"</span>, completed: <span class="code-keyword">true</span> },
678
+ { id: 2, text: <span class="code-string">"Build"</span>, completed: <span class="code-keyword">false</span> }
679
+ ]
680
+
681
+ <span class="code-comment">// Map with nested conditional and ternary</span>
682
+ &lt;div&gt;
683
+ {todoItems.length === 0 && (
684
+ &lt;div class="empty-state"&gt;No tasks&lt;/div&gt;
685
+ )}
686
+ {todoItems.map(todo =&gt; (
687
+ &lt;div key={todo.id} :class="todo.completed ? 'todo-completed' : ''"&gt;
688
+ {todo.completed && &lt;span&gt;✓&lt;/span&gt;}
689
+ &lt;span&gt;{todo.text}&lt;/span&gt;
690
+ &lt;/div&gt;
691
+ ))}
692
+ &lt;/div&gt;
693
+
694
+ <span class="code-comment">// Ternary with array methods</span>
695
+ &lt;span&gt;
696
+ {todoItems.filter(t =&gt; t.completed).length} completed
697
+ &lt;/span&gt;
698
+ </div>
699
+ </section>
700
+
701
+ <!-- Navigation -->
702
+ <section class="feature-section">
703
+ <h2 class="feature-title">🔗 Navigation</h2>
704
+ <nav class="nav-section">
705
+ <ZenLink href="/" class="nav-link">← Home</ZenLink>
706
+ <ZenLink href="/dynamic-primitives" class="nav-link">Primitives Demo →</ZenLink>
707
+ <ZenLink href="/primitives-demo" class="nav-link">Zen* API Demo</ZenLink>
708
+ </nav>
709
+ </section>
710
+ </main>
711
+ </div>
712
+ </Main>