@soederpop/luca 0.2.2 → 0.2.3

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,974 @@
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>Luca: One Container to Rule Them All</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
10
+ <style>
11
+ /* ===========================================
12
+ THEME: Dark developer tool aesthetic
13
+ Amber/gold primary, cool blue secondary
14
+ =========================================== */
15
+ :root {
16
+ --bg-primary: #0a0a0f;
17
+ --bg-secondary: #12121a;
18
+ --bg-card: #1a1a25;
19
+ --text-primary: #f0ede6;
20
+ --text-secondary: #7a7a8a;
21
+ --text-muted: #4a4a5a;
22
+ --accent: #e8a634;
23
+ --accent-glow: rgba(232, 166, 52, 0.25);
24
+ --accent-dim: rgba(232, 166, 52, 0.08);
25
+ --blue: #5b8def;
26
+ --blue-glow: rgba(91, 141, 239, 0.2);
27
+ --green: #4ade80;
28
+ --orange: #f59e0b;
29
+ --red: #ef4444;
30
+ --code-bg: #0d0d14;
31
+ --border: rgba(255, 255, 255, 0.06);
32
+
33
+ --font-display: 'Inter', sans-serif;
34
+ --font-mono: 'JetBrains Mono', monospace;
35
+
36
+ --title-size: clamp(2rem, 5.5vw, 4.5rem);
37
+ --h2-size: clamp(1.5rem, 4vw, 3rem);
38
+ --h3-size: clamp(1rem, 2vw, 1.5rem);
39
+ --body-size: clamp(0.8rem, 1.3vw, 1.1rem);
40
+ --small-size: clamp(0.65rem, 1vw, 0.85rem);
41
+ --code-size: clamp(0.65rem, 1.1vw, 0.9rem);
42
+
43
+ --slide-padding: clamp(1.5rem, 5vw, 5rem);
44
+ --content-gap: clamp(0.75rem, 2vw, 2rem);
45
+ --element-gap: clamp(0.3rem, 1vw, 0.75rem);
46
+
47
+ --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
48
+ --ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
49
+ }
50
+
51
+ /* ===========================================
52
+ BASE + VIEWPORT FITTING
53
+ =========================================== */
54
+ *, *::before, *::after {
55
+ margin: 0;
56
+ padding: 0;
57
+ box-sizing: border-box;
58
+ }
59
+
60
+ html, body {
61
+ height: 100%;
62
+ overflow-x: hidden;
63
+ }
64
+
65
+ html {
66
+ scroll-snap-type: y mandatory;
67
+ scroll-behavior: smooth;
68
+ }
69
+
70
+ body {
71
+ font-family: var(--font-display);
72
+ background: var(--bg-primary);
73
+ color: var(--text-primary);
74
+ }
75
+
76
+ .slide {
77
+ width: 100vw;
78
+ height: 100vh;
79
+ height: 100dvh;
80
+ overflow: hidden;
81
+ scroll-snap-align: start;
82
+ display: flex;
83
+ flex-direction: column;
84
+ justify-content: center;
85
+ position: relative;
86
+ padding: var(--slide-padding);
87
+ }
88
+
89
+ .slide-content {
90
+ flex: 1;
91
+ display: flex;
92
+ flex-direction: column;
93
+ justify-content: center;
94
+ max-height: 100%;
95
+ overflow: hidden;
96
+ gap: var(--content-gap);
97
+ }
98
+
99
+ /* ===========================================
100
+ TYPOGRAPHY
101
+ =========================================== */
102
+ h1 {
103
+ font-size: var(--title-size);
104
+ font-weight: 900;
105
+ letter-spacing: -0.03em;
106
+ line-height: 1.05;
107
+ }
108
+
109
+ h2 {
110
+ font-size: var(--h2-size);
111
+ font-weight: 800;
112
+ letter-spacing: -0.02em;
113
+ line-height: 1.1;
114
+ }
115
+
116
+ h3 {
117
+ font-size: var(--h3-size);
118
+ font-weight: 700;
119
+ line-height: 1.2;
120
+ }
121
+
122
+ p, li {
123
+ font-size: var(--body-size);
124
+ line-height: 1.5;
125
+ color: var(--text-secondary);
126
+ }
127
+
128
+ .accent { color: var(--accent); }
129
+ .blue { color: var(--blue); }
130
+ .muted { color: var(--text-muted); }
131
+
132
+ code, .code {
133
+ font-family: var(--font-mono);
134
+ font-size: var(--code-size);
135
+ }
136
+
137
+ /* ===========================================
138
+ CODE BLOCKS
139
+ =========================================== */
140
+ .code-block {
141
+ background: var(--code-bg);
142
+ border: 1px solid var(--border);
143
+ border-radius: 12px;
144
+ padding: clamp(0.75rem, 2vw, 1.5rem);
145
+ overflow: hidden;
146
+ position: relative;
147
+ }
148
+
149
+ .code-block::before {
150
+ content: '';
151
+ position: absolute;
152
+ top: 0;
153
+ left: 0;
154
+ right: 0;
155
+ height: 1px;
156
+ background: linear-gradient(90deg, transparent, var(--accent-glow), transparent);
157
+ }
158
+
159
+ .code-block pre {
160
+ font-family: var(--font-mono);
161
+ font-size: var(--code-size);
162
+ line-height: 1.6;
163
+ color: var(--text-secondary);
164
+ white-space: pre;
165
+ overflow: hidden;
166
+ }
167
+
168
+ .code-block .comment { color: var(--text-muted); }
169
+ .code-block .keyword { color: var(--blue); }
170
+ .code-block .string { color: var(--green); }
171
+ .code-block .property { color: var(--accent); }
172
+ .code-block .method { color: #c4b5fd; }
173
+ .code-block .punctuation { color: var(--text-muted); }
174
+ .code-block .type { color: var(--accent); }
175
+ .code-block .number { color: var(--orange); }
176
+
177
+ /* ===========================================
178
+ TAGLINE BAR
179
+ =========================================== */
180
+ .tagline {
181
+ font-family: var(--font-mono);
182
+ font-size: var(--small-size);
183
+ color: var(--accent);
184
+ padding-top: clamp(0.5rem, 1.5vw, 1.25rem);
185
+ border-top: 1px solid var(--border);
186
+ letter-spacing: 0.02em;
187
+ margin-top: auto;
188
+ }
189
+
190
+ /* ===========================================
191
+ ANIMATIONS
192
+ =========================================== */
193
+ .reveal {
194
+ opacity: 0;
195
+ transform: translateY(24px);
196
+ transition: opacity 0.7s var(--ease-out-expo),
197
+ transform 0.7s var(--ease-out-expo);
198
+ }
199
+
200
+ .slide.visible .reveal {
201
+ opacity: 1;
202
+ transform: translateY(0);
203
+ }
204
+
205
+ .reveal-delay-1 { transition-delay: 0.08s; }
206
+ .reveal-delay-2 { transition-delay: 0.16s; }
207
+ .reveal-delay-3 { transition-delay: 0.24s; }
208
+ .reveal-delay-4 { transition-delay: 0.32s; }
209
+ .reveal-delay-5 { transition-delay: 0.40s; }
210
+ .reveal-delay-6 { transition-delay: 0.48s; }
211
+
212
+ .reveal-scale {
213
+ opacity: 0;
214
+ transform: scale(0.92);
215
+ transition: opacity 0.7s var(--ease-out-expo),
216
+ transform 0.7s var(--ease-out-expo);
217
+ }
218
+
219
+ .slide.visible .reveal-scale {
220
+ opacity: 1;
221
+ transform: scale(1);
222
+ }
223
+
224
+ /* ===========================================
225
+ PROGRESS BAR
226
+ =========================================== */
227
+ .progress-bar {
228
+ position: fixed;
229
+ top: 0;
230
+ left: 0;
231
+ height: 2px;
232
+ background: var(--accent);
233
+ z-index: 100;
234
+ transition: width 0.3s ease;
235
+ box-shadow: 0 0 10px var(--accent-glow);
236
+ }
237
+
238
+ /* ===========================================
239
+ NAV DOTS
240
+ =========================================== */
241
+ .nav-dots {
242
+ position: fixed;
243
+ right: clamp(0.75rem, 2vw, 1.5rem);
244
+ top: 50%;
245
+ transform: translateY(-50%);
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: 8px;
249
+ z-index: 100;
250
+ }
251
+
252
+ .nav-dot {
253
+ width: 6px;
254
+ height: 6px;
255
+ border-radius: 50%;
256
+ background: var(--text-muted);
257
+ cursor: pointer;
258
+ transition: all 0.3s ease;
259
+ border: none;
260
+ padding: 0;
261
+ }
262
+
263
+ .nav-dot.active {
264
+ background: var(--accent);
265
+ box-shadow: 0 0 8px var(--accent-glow);
266
+ transform: scale(1.4);
267
+ }
268
+
269
+ .nav-dot:hover {
270
+ background: var(--text-secondary);
271
+ transform: scale(1.3);
272
+ }
273
+
274
+ /* ===========================================
275
+ SLIDE-SPECIFIC STYLES
276
+ =========================================== */
277
+
278
+ /* Title slide */
279
+ .title-slide {
280
+ text-align: center;
281
+ align-items: center;
282
+ }
283
+
284
+ .title-slide .slide-content {
285
+ align-items: center;
286
+ max-width: 900px;
287
+ }
288
+
289
+ .title-sub {
290
+ font-family: var(--font-mono);
291
+ font-size: var(--small-size);
292
+ color: var(--text-muted);
293
+ letter-spacing: 0.15em;
294
+ text-transform: uppercase;
295
+ }
296
+
297
+ .title-bottom {
298
+ font-family: var(--font-mono);
299
+ font-size: var(--small-size);
300
+ color: var(--text-muted);
301
+ margin-top: auto;
302
+ padding-top: clamp(0.5rem, 1.5vw, 1rem);
303
+ }
304
+
305
+ /* Bullet list */
306
+ .bullet-list {
307
+ list-style: none;
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: clamp(0.6rem, 1.5vh, 1.25rem);
311
+ }
312
+
313
+ .bullet-list li {
314
+ display: flex;
315
+ align-items: flex-start;
316
+ gap: clamp(0.5rem, 1vw, 1rem);
317
+ font-size: var(--body-size);
318
+ line-height: 1.5;
319
+ }
320
+
321
+ .bullet-icon {
322
+ flex-shrink: 0;
323
+ width: clamp(1.5rem, 2.5vw, 2rem);
324
+ height: clamp(1.5rem, 2.5vw, 2rem);
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ font-size: clamp(0.8rem, 1.2vw, 1.1rem);
329
+ margin-top: 0.1em;
330
+ }
331
+
332
+ /* Layer diagram */
333
+ .layer-stack {
334
+ display: flex;
335
+ flex-direction: column;
336
+ gap: clamp(0.3rem, 0.8vh, 0.6rem);
337
+ max-width: 700px;
338
+ }
339
+
340
+ .layer {
341
+ display: flex;
342
+ align-items: center;
343
+ gap: clamp(0.75rem, 1.5vw, 1.25rem);
344
+ padding: clamp(0.6rem, 1.5vw, 1.25rem) clamp(0.75rem, 2vw, 1.5rem);
345
+ border-radius: 10px;
346
+ border: 1px solid var(--border);
347
+ position: relative;
348
+ overflow: hidden;
349
+ }
350
+
351
+ .layer::before {
352
+ content: '';
353
+ position: absolute;
354
+ inset: 0;
355
+ opacity: 0.06;
356
+ }
357
+
358
+ .layer-3 { border-color: rgba(245, 158, 11, 0.2); }
359
+ .layer-3::before { background: var(--orange); }
360
+ .layer-2 { border-color: rgba(91, 141, 239, 0.2); }
361
+ .layer-2::before { background: var(--blue); }
362
+ .layer-1 { border-color: rgba(74, 222, 128, 0.2); }
363
+ .layer-1::before { background: var(--green); }
364
+
365
+ .layer-label {
366
+ font-family: var(--font-mono);
367
+ font-size: var(--small-size);
368
+ font-weight: 700;
369
+ letter-spacing: 0.05em;
370
+ text-transform: uppercase;
371
+ white-space: nowrap;
372
+ }
373
+
374
+ .layer-3 .layer-label { color: var(--orange); }
375
+ .layer-2 .layer-label { color: var(--blue); }
376
+ .layer-1 .layer-label { color: var(--green); }
377
+
378
+ .layer-desc {
379
+ font-size: var(--small-size);
380
+ color: var(--text-secondary);
381
+ }
382
+
383
+ .layer-freq {
384
+ margin-left: auto;
385
+ font-family: var(--font-mono);
386
+ font-size: clamp(0.55rem, 0.8vw, 0.7rem);
387
+ color: var(--text-muted);
388
+ white-space: nowrap;
389
+ }
390
+
391
+ /* Helper diagram */
392
+ .helper-diagram {
393
+ display: flex;
394
+ flex-direction: column;
395
+ align-items: center;
396
+ gap: clamp(0.5rem, 1.5vh, 1.25rem);
397
+ }
398
+
399
+ .helper-base {
400
+ background: var(--bg-card);
401
+ border: 1px solid var(--accent);
402
+ border-radius: 10px;
403
+ padding: clamp(0.5rem, 1.2vw, 1rem) clamp(1rem, 3vw, 2.5rem);
404
+ text-align: center;
405
+ box-shadow: 0 0 20px var(--accent-dim);
406
+ }
407
+
408
+ .helper-base-title {
409
+ font-family: var(--font-mono);
410
+ font-size: var(--h3-size);
411
+ font-weight: 700;
412
+ color: var(--accent);
413
+ }
414
+
415
+ .helper-base-desc {
416
+ font-size: var(--small-size);
417
+ color: var(--text-muted);
418
+ margin-top: 0.25em;
419
+ }
420
+
421
+ .helper-connector {
422
+ width: 1px;
423
+ height: clamp(12px, 2vh, 24px);
424
+ background: var(--text-muted);
425
+ position: relative;
426
+ }
427
+
428
+ .helper-connector::after {
429
+ content: '';
430
+ position: absolute;
431
+ bottom: -3px;
432
+ left: -3px;
433
+ width: 7px;
434
+ height: 7px;
435
+ border-left: 1px solid var(--text-muted);
436
+ border-bottom: 1px solid var(--text-muted);
437
+ transform: rotate(-45deg);
438
+ }
439
+
440
+ .helper-children {
441
+ display: flex;
442
+ gap: clamp(0.3rem, 0.8vw, 0.6rem);
443
+ flex-wrap: wrap;
444
+ justify-content: center;
445
+ }
446
+
447
+ .helper-child {
448
+ background: var(--bg-card);
449
+ border: 1px solid var(--border);
450
+ border-radius: 8px;
451
+ padding: clamp(0.35rem, 0.8vw, 0.6rem) clamp(0.6rem, 1.2vw, 1rem);
452
+ font-family: var(--font-mono);
453
+ font-size: var(--small-size);
454
+ font-weight: 600;
455
+ color: var(--text-secondary);
456
+ transition: all 0.3s ease;
457
+ }
458
+
459
+ .helper-child:hover {
460
+ border-color: var(--accent);
461
+ color: var(--accent);
462
+ box-shadow: 0 0 12px var(--accent-dim);
463
+ }
464
+
465
+ .helper-traits {
466
+ display: flex;
467
+ gap: clamp(0.4rem, 1vw, 0.75rem);
468
+ flex-wrap: wrap;
469
+ justify-content: center;
470
+ }
471
+
472
+ .helper-trait {
473
+ font-family: var(--font-mono);
474
+ font-size: clamp(0.55rem, 0.9vw, 0.75rem);
475
+ color: var(--text-muted);
476
+ background: var(--accent-dim);
477
+ border: 1px solid rgba(232, 166, 52, 0.15);
478
+ border-radius: 20px;
479
+ padding: 0.2em 0.75em;
480
+ }
481
+
482
+ /* Directory tree */
483
+ .dir-tree {
484
+ font-family: var(--font-mono);
485
+ font-size: var(--code-size);
486
+ line-height: 1.8;
487
+ color: var(--text-secondary);
488
+ }
489
+
490
+ .dir-tree .folder { color: var(--blue); font-weight: 600; }
491
+ .dir-tree .arrow { color: var(--text-muted); }
492
+ .dir-tree .desc { color: var(--text-muted); font-size: clamp(0.55rem, 0.9vw, 0.75rem); }
493
+ .dir-tree .file { color: var(--accent); }
494
+
495
+ /* Three columns */
496
+ .three-col {
497
+ display: grid;
498
+ grid-template-columns: repeat(3, 1fr);
499
+ gap: clamp(0.5rem, 1.5vw, 1.25rem);
500
+ }
501
+
502
+ .col-card {
503
+ background: var(--bg-card);
504
+ border: 1px solid var(--border);
505
+ border-radius: 10px;
506
+ padding: clamp(0.75rem, 1.5vw, 1.5rem);
507
+ display: flex;
508
+ flex-direction: column;
509
+ gap: clamp(0.3rem, 0.75vh, 0.5rem);
510
+ }
511
+
512
+ .col-card h3 {
513
+ font-family: var(--font-mono);
514
+ color: var(--accent);
515
+ }
516
+
517
+ .col-card p {
518
+ font-size: var(--small-size);
519
+ }
520
+
521
+ .col-icon {
522
+ font-size: clamp(1.25rem, 2.5vw, 2rem);
523
+ margin-bottom: clamp(0.2rem, 0.5vh, 0.4rem);
524
+ }
525
+
526
+ /* Closing slide */
527
+ .closing-slide {
528
+ text-align: center;
529
+ align-items: center;
530
+ }
531
+
532
+ .closing-slide .slide-content {
533
+ align-items: center;
534
+ }
535
+
536
+ .closing-install {
537
+ font-family: var(--font-mono);
538
+ font-size: var(--h3-size);
539
+ color: var(--accent);
540
+ background: var(--accent-dim);
541
+ padding: 0.4em 1.2em;
542
+ border-radius: 8px;
543
+ border: 1px solid rgba(232, 166, 52, 0.15);
544
+ }
545
+
546
+ .closing-link {
547
+ font-family: var(--font-mono);
548
+ font-size: var(--body-size);
549
+ color: var(--blue);
550
+ }
551
+
552
+ /* Background grid pattern */
553
+ .grid-bg {
554
+ position: absolute;
555
+ inset: 0;
556
+ background-image:
557
+ linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px),
558
+ linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px);
559
+ background-size: 60px 60px;
560
+ pointer-events: none;
561
+ }
562
+
563
+ /* Slide number */
564
+ .slide-num {
565
+ position: absolute;
566
+ bottom: clamp(0.75rem, 2vw, 1.5rem);
567
+ right: clamp(0.75rem, 2vw, 1.5rem);
568
+ font-family: var(--font-mono);
569
+ font-size: clamp(0.55rem, 0.8vw, 0.7rem);
570
+ color: var(--text-muted);
571
+ }
572
+
573
+ /* ===========================================
574
+ RESPONSIVE BREAKPOINTS
575
+ =========================================== */
576
+ @media (max-height: 700px) {
577
+ :root {
578
+ --slide-padding: clamp(1rem, 3vw, 2.5rem);
579
+ --content-gap: clamp(0.5rem, 1.5vw, 1rem);
580
+ --title-size: clamp(1.5rem, 5vw, 3rem);
581
+ --h2-size: clamp(1.25rem, 3.5vw, 2rem);
582
+ }
583
+ }
584
+
585
+ @media (max-height: 600px) {
586
+ :root {
587
+ --slide-padding: clamp(0.75rem, 2.5vw, 1.5rem);
588
+ --content-gap: clamp(0.4rem, 1vw, 0.75rem);
589
+ --title-size: clamp(1.25rem, 4.5vw, 2.5rem);
590
+ --body-size: clamp(0.7rem, 1.2vw, 0.95rem);
591
+ }
592
+ .nav-dots { display: none; }
593
+ }
594
+
595
+ @media (max-height: 500px) {
596
+ :root {
597
+ --slide-padding: clamp(0.5rem, 2vw, 1rem);
598
+ --title-size: clamp(1rem, 3.5vw, 1.75rem);
599
+ --h2-size: clamp(0.9rem, 2.5vw, 1.25rem);
600
+ --body-size: clamp(0.65rem, 1vw, 0.85rem);
601
+ }
602
+ }
603
+
604
+ @media (max-width: 600px) {
605
+ .three-col {
606
+ grid-template-columns: 1fr;
607
+ }
608
+ .helper-children {
609
+ gap: 0.25rem;
610
+ }
611
+ }
612
+
613
+ @media (prefers-reduced-motion: reduce) {
614
+ *, *::before, *::after {
615
+ animation-duration: 0.01ms !important;
616
+ transition-duration: 0.2s !important;
617
+ }
618
+ html { scroll-behavior: auto; }
619
+ }
620
+ </style>
621
+ </head>
622
+ <body>
623
+
624
+ <div class="progress-bar" id="progress"></div>
625
+
626
+ <nav class="nav-dots" id="navDots"></nav>
627
+
628
+ <!-- ============================================
629
+ SLIDE 1: TITLE
630
+ ============================================ -->
631
+ <section class="slide title-slide" data-slide="0">
632
+ <div class="grid-bg"></div>
633
+ <div class="slide-content">
634
+ <p class="title-sub reveal">Lightweight Universal Conversational Architecture</p>
635
+ <h1 class="reveal reveal-delay-1">Luca<span class="muted">:</span> One Container<br>to Rule Them All</h1>
636
+ <p class="title-bottom reveal reveal-delay-2">stop rebuilding the same shit</p>
637
+ </div>
638
+ </section>
639
+
640
+ <!-- ============================================
641
+ SLIDE 2: THE PROBLEM
642
+ ============================================ -->
643
+ <section class="slide" data-slide="1">
644
+ <div class="grid-bg"></div>
645
+ <div class="slide-content">
646
+ <h2 class="reveal">You've Built This Before</h2>
647
+ <ul class="bullet-list">
648
+ <li class="reveal reveal-delay-1">
649
+ <span class="bullet-icon">&#x1f504;</span>
650
+ <span>Auth middleware &rarr; state management &rarr; event handling &rarr; caching &rarr; <em class="accent">repeat for every project</em></span>
651
+ </li>
652
+ <li class="reveal reveal-delay-2">
653
+ <span class="bullet-icon">&#x1f3d7;</span>
654
+ <span>New project? Cool, let's reinstall the OS to change an HTML file</span>
655
+ </li>
656
+ <li class="reveal reveal-delay-3">
657
+ <span class="bullet-icon">&#x1f4c2;</span>
658
+ <span>Your "reusable" code lives in 47 repos with 47 different patterns</span>
659
+ </li>
660
+ </ul>
661
+ <div class="tagline reveal reveal-delay-4">Every developer has built the same plumbing. Nobody caches it.</div>
662
+ </div>
663
+ <span class="slide-num">02</span>
664
+ </section>
665
+
666
+ <!-- ============================================
667
+ SLIDE 3: THE DOCKERFILE ANALOGY
668
+ ============================================ -->
669
+ <section class="slide" data-slide="2">
670
+ <div class="grid-bg"></div>
671
+ <div class="slide-content">
672
+ <h2 class="reveal">Think Docker Layers</h2>
673
+ <div class="layer-stack">
674
+ <div class="layer layer-3 reveal reveal-delay-3">
675
+ <span class="layer-label">Layer 3: Your App</span>
676
+ <span class="layer-desc">The actual thing you're building</span>
677
+ <span class="layer-freq">changes daily</span>
678
+ </div>
679
+ <div class="layer layer-2 reveal reveal-delay-2">
680
+ <span class="layer-label">Layer 2: Domain</span>
681
+ <span class="layer-desc">AI clients, GitHub, Stripe, your industry</span>
682
+ <span class="layer-freq">changes rarely</span>
683
+ </div>
684
+ <div class="layer layer-1 reveal reveal-delay-1">
685
+ <span class="layer-label">Layer 1: Platform</span>
686
+ <span class="layer-desc">fs, networking, processes, state, events</span>
687
+ <span class="layer-freq">solve once</span>
688
+ </div>
689
+ </div>
690
+ <div class="tagline reveal reveal-delay-4">Fix Layer 1 once. Every project benefits. Spend ALL your energy on Layer 3.</div>
691
+ </div>
692
+ <span class="slide-num">03</span>
693
+ </section>
694
+
695
+ <!-- ============================================
696
+ SLIDE 4: THE CONTAINER
697
+ ============================================ -->
698
+ <section class="slide" data-slide="3">
699
+ <div class="grid-bg"></div>
700
+ <div class="slide-content">
701
+ <h2 class="reveal">One Object. Everything You Need.</h2>
702
+ <div class="code-block reveal-scale reveal-delay-1">
703
+ <pre><span class="keyword">import</span> <span class="property">container</span> <span class="keyword">from</span> <span class="string">'@soederpop/luca/node'</span>
704
+
705
+ <span class="property">container</span>.<span class="method">fs</span> <span class="comment">// file system</span>
706
+ <span class="property">container</span>.<span class="method">git</span> <span class="comment">// git operations</span>
707
+ <span class="property">container</span>.<span class="method">ui</span> <span class="comment">// terminal UI</span>
708
+ <span class="property">container</span>.<span class="method">proc</span> <span class="comment">// process execution</span>
709
+ <span class="property">container</span>.<span class="method">networking</span> <span class="comment">// port utilities</span>
710
+
711
+ <span class="comment">// Need something specific? Ask for it</span>
712
+ <span class="keyword">const</span> cache = <span class="property">container</span>.<span class="method">feature</span>(<span class="string">'diskCache'</span>)
713
+ <span class="keyword">const</span> rest = <span class="property">container</span>.<span class="method">client</span>(<span class="string">'rest'</span>, { <span class="property">baseURL</span>: <span class="string">'https://api.stripe.com'</span> })
714
+ <span class="keyword">const</span> server = <span class="property">container</span>.<span class="method">server</span>(<span class="string">'express'</span>, { <span class="property">port</span>: <span class="number">3000</span> })</pre>
715
+ </div>
716
+ <div class="tagline reveal reveal-delay-2">Like window + document, but for your entire stack</div>
717
+ </div>
718
+ <span class="slide-num">04</span>
719
+ </section>
720
+
721
+ <!-- ============================================
722
+ SLIDE 5: HELPERS
723
+ ============================================ -->
724
+ <section class="slide" data-slide="4">
725
+ <div class="grid-bg"></div>
726
+ <div class="slide-content">
727
+ <h2 class="reveal">Everything Is a Helper</h2>
728
+ <div class="helper-diagram">
729
+ <div class="helper-base reveal reveal-delay-1">
730
+ <div class="helper-base-title">Helper</div>
731
+ <div class="helper-base-desc">The universal base class</div>
732
+ </div>
733
+ <div class="helper-connector reveal reveal-delay-2"></div>
734
+ <div class="helper-children reveal reveal-delay-2">
735
+ <div class="helper-child">Feature</div>
736
+ <div class="helper-child">Client</div>
737
+ <div class="helper-child">Server</div>
738
+ <div class="helper-child">Command</div>
739
+ <div class="helper-child">Endpoint</div>
740
+ </div>
741
+ <div class="helper-traits reveal reveal-delay-3">
742
+ <span class="helper-trait">Observable State</span>
743
+ <span class="helper-trait">Event Bus</span>
744
+ <span class="helper-trait">Introspection</span>
745
+ <span class="helper-trait">Tool Interface</span>
746
+ </div>
747
+ </div>
748
+ <div class="tagline reveal reveal-delay-4">Same API. Same patterns. Whether it's a file system or a Stripe client.</div>
749
+ </div>
750
+ <span class="slide-num">05</span>
751
+ </section>
752
+
753
+ <!-- ============================================
754
+ SLIDE 6: OBSERVABLE STATE
755
+ ============================================ -->
756
+ <section class="slide" data-slide="5">
757
+ <div class="grid-bg"></div>
758
+ <div class="slide-content">
759
+ <h2 class="reveal">Everything Is Watchable</h2>
760
+ <div class="code-block reveal-scale reveal-delay-1">
761
+ <pre><span class="keyword">const</span> cart = <span class="property">container</span>.<span class="method">feature</span>(<span class="string">'cart'</span>)
762
+
763
+ <span class="comment">// State is typed, versioned, observable</span>
764
+ cart.<span class="method">state</span>.<span class="method">set</span>(<span class="string">'items'</span>, [...items, newItem])
765
+ cart.<span class="method">state</span>.<span class="method">get</span>(<span class="string">'total'</span>) <span class="comment">// always current</span>
766
+ cart.<span class="method">state</span>.<span class="property">version</span> <span class="comment">// increments on every change</span>
767
+
768
+ <span class="comment">// React to changes</span>
769
+ cart.<span class="method">state</span>.<span class="method">observe</span>((<span class="property">type</span>, <span class="property">key</span>, <span class="property">value</span>) => {
770
+ <span class="keyword">if</span> (key === <span class="string">'items'</span>) <span class="method">renderBadge</span>(value.length)
771
+ })</pre>
772
+ </div>
773
+ <div class="tagline reveal reveal-delay-2">Debugging, analytics, reactive UI, AI monitoring &mdash; all fall out for free</div>
774
+ </div>
775
+ <span class="slide-num">06</span>
776
+ </section>
777
+
778
+ <!-- ============================================
779
+ SLIDE 7: EVENTS
780
+ ============================================ -->
781
+ <section class="slide" data-slide="6">
782
+ <div class="grid-bg"></div>
783
+ <div class="slide-content">
784
+ <h2 class="reveal">Components Talk.<br><span class="muted">They Don't Import Each Other.</span></h2>
785
+ <div class="code-block reveal-scale reveal-delay-1">
786
+ <pre><span class="comment">// Auth announces things</span>
787
+ auth.<span class="method">emit</span>(<span class="string">'userLoggedIn'</span>, user)
788
+
789
+ <span class="comment">// Analytics listens &mdash; doesn't need to import auth</span>
790
+ auth.<span class="method">on</span>(<span class="string">'userLoggedIn'</span>, (<span class="property">user</span>) => {
791
+ analytics.<span class="method">track</span>(<span class="string">'login'</span>, { <span class="property">userId</span>: user.id })
792
+ })
793
+
794
+ <span class="comment">// Wait for anything, anywhere</span>
795
+ <span class="keyword">await</span> server.<span class="method">waitFor</span>(<span class="string">'started'</span>)
796
+ <span class="keyword">await</span> db.<span class="method">waitFor</span>(<span class="string">'connected'</span>)
797
+ console.<span class="method">log</span>(<span class="string">'All systems go'</span>)</pre>
798
+ </div>
799
+ <div class="tagline reveal reveal-delay-2">Typed contracts. Build-time autocomplete. No magic strings.</div>
800
+ </div>
801
+ <span class="slide-num">07</span>
802
+ </section>
803
+
804
+ <!-- ============================================
805
+ SLIDE 8: REGISTRIES & FACTORIES
806
+ ============================================ -->
807
+ <section class="slide" data-slide="7">
808
+ <div class="grid-bg"></div>
809
+ <div class="slide-content">
810
+ <h2 class="reveal">Discover. Create. Cache.</h2>
811
+ <div class="code-block reveal-scale reveal-delay-1">
812
+ <pre><span class="comment">// What's available?</span>
813
+ <span class="property">container</span>.<span class="method">features</span>.<span class="property">available</span> <span class="comment">// ['fs', 'git', 'ui', 'proc', ...]</span>
814
+ <span class="property">container</span>.<span class="method">clients</span>.<span class="property">available</span> <span class="comment">// ['rest', 'websocket', 'openai', ...]</span>
815
+
816
+ <span class="comment">// Factory creates instances &mdash; same args = same instance</span>
817
+ <span class="keyword">const</span> api = <span class="property">container</span>.<span class="method">client</span>(<span class="string">'rest'</span>, { <span class="property">baseURL</span>: <span class="string">'https://api.github.com'</span> })
818
+ <span class="keyword">const</span> api2 = <span class="property">container</span>.<span class="method">client</span>(<span class="string">'rest'</span>, { <span class="property">baseURL</span>: <span class="string">'https://api.github.com'</span> })
819
+
820
+ api === api2 <span class="comment">// true &mdash; identity guarantee</span></pre>
821
+ </div>
822
+ <div class="tagline reveal reveal-delay-2">Self-registration at import time. No central manifest to maintain.</div>
823
+ </div>
824
+ <span class="slide-num">08</span>
825
+ </section>
826
+
827
+ <!-- ============================================
828
+ SLIDE 9: PROJECT STRUCTURE
829
+ ============================================ -->
830
+ <section class="slide" data-slide="8">
831
+ <div class="grid-bg"></div>
832
+ <div class="slide-content">
833
+ <h2 class="reveal">Convention Over Configuration</h2>
834
+ <div class="code-block reveal-scale reveal-delay-1">
835
+ <pre class="dir-tree"><span class="folder">my-app/</span>
836
+ ├── <span class="folder">commands/</span> <span class="arrow">&rarr;</span> <span class="desc">luca seed --count 10</span>
837
+ ├── <span class="folder">endpoints/</span> <span class="arrow">&rarr;</span> <span class="desc">luca serve (auto-discovered routes)</span>
838
+ ├── <span class="folder">features/</span> <span class="arrow">&rarr;</span> <span class="desc">container.feature('myThing')</span>
839
+ ├── <span class="folder">assistants/</span> <span class="arrow">&rarr;</span> <span class="desc">AI assistants with tool access</span>
840
+ ├── <span class="folder">scripts/</span> <span class="arrow">&rarr;</span> <span class="desc">luca run migrate.ts</span>
841
+ └── <span class="file">luca.cli.ts</span> <span class="arrow">&rarr;</span> <span class="desc">project-level hooks</span></pre>
842
+ </div>
843
+ <div class="tagline reveal reveal-delay-2">Drop a file in the right folder. It just works.</div>
844
+ </div>
845
+ <span class="slide-num">09</span>
846
+ </section>
847
+
848
+ <!-- ============================================
849
+ SLIDE 10: WHY LUCA
850
+ ============================================ -->
851
+ <section class="slide" data-slide="9">
852
+ <div class="grid-bg"></div>
853
+ <div class="slide-content">
854
+ <h2 class="reveal">Solve It Once. Use It Forever.</h2>
855
+ <div class="three-col">
856
+ <div class="col-card reveal reveal-delay-1">
857
+ <div class="col-icon">&#x1f4dc;</div>
858
+ <h3>Scripts</h3>
859
+ <p>One import. Full runtime. File system, git, processes, UI &mdash; all there.</p>
860
+ </div>
861
+ <div class="col-card reveal reveal-delay-2">
862
+ <div class="col-icon">&#x1f310;</div>
863
+ <h3>Servers</h3>
864
+ <p>Drop files in endpoints/. Get a REST API with OpenAPI docs. Done.</p>
865
+ </div>
866
+ <div class="col-card reveal reveal-delay-3">
867
+ <div class="col-icon">&#x269b;&#xfe0f;</div>
868
+ <h3>React</h3>
869
+ <p>Observable state = built-in data layer. No extra state library needed.</p>
870
+ </div>
871
+ </div>
872
+ <div class="tagline reveal reveal-delay-4">Same container. Same patterns. Every environment.</div>
873
+ </div>
874
+ <span class="slide-num">10</span>
875
+ </section>
876
+
877
+ <!-- ============================================
878
+ SLIDE 11: CLOSING
879
+ ============================================ -->
880
+ <section class="slide closing-slide" data-slide="10">
881
+ <div class="grid-bg"></div>
882
+ <div class="slide-content">
883
+ <h1 class="reveal">Stop Rebuilding.<br><span class="accent">Start Composing.</span></h1>
884
+ <div class="closing-install reveal reveal-delay-1">bun add @soederpop/luca</div>
885
+ <p class="closing-link reveal reveal-delay-2">github.com/soederpop/luca</p>
886
+ </div>
887
+ </section>
888
+
889
+ <script>
890
+ /* ===========================================
891
+ SLIDE PRESENTATION CONTROLLER
892
+ Handles navigation, scroll-triggered animations,
893
+ progress bar, and navigation dots.
894
+ =========================================== */
895
+ class SlidePresentation {
896
+ constructor() {
897
+ this.slides = document.querySelectorAll('.slide');
898
+ this.currentSlide = 0;
899
+ this.totalSlides = this.slides.length;
900
+ this.progressBar = document.getElementById('progress');
901
+ this.navContainer = document.getElementById('navDots');
902
+ this.isTransitioning = false;
903
+
904
+ this.createNavDots();
905
+ this.setupIntersectionObserver();
906
+ this.setupKeyboardNav();
907
+ this.updateProgress();
908
+ }
909
+
910
+ createNavDots() {
911
+ this.slides.forEach((_, i) => {
912
+ const dot = document.createElement('button');
913
+ dot.className = 'nav-dot' + (i === 0 ? ' active' : '');
914
+ dot.setAttribute('aria-label', `Go to slide ${i + 1}`);
915
+ dot.addEventListener('click', () => this.goToSlide(i));
916
+ this.navContainer.appendChild(dot);
917
+ });
918
+ this.dots = this.navContainer.querySelectorAll('.nav-dot');
919
+ }
920
+
921
+ setupIntersectionObserver() {
922
+ const observer = new IntersectionObserver((entries) => {
923
+ entries.forEach(entry => {
924
+ if (entry.isIntersecting) {
925
+ entry.target.classList.add('visible');
926
+ const index = parseInt(entry.target.dataset.slide);
927
+ if (!isNaN(index)) {
928
+ this.currentSlide = index;
929
+ this.updateProgress();
930
+ this.updateDots();
931
+ }
932
+ }
933
+ });
934
+ }, { threshold: 0.5 });
935
+
936
+ this.slides.forEach(slide => observer.observe(slide));
937
+ }
938
+
939
+ setupKeyboardNav() {
940
+ document.addEventListener('keydown', (e) => {
941
+ if (e.key === 'ArrowDown' || e.key === 'ArrowRight' || e.key === ' ') {
942
+ e.preventDefault();
943
+ this.goToSlide(Math.min(this.currentSlide + 1, this.totalSlides - 1));
944
+ } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
945
+ e.preventDefault();
946
+ this.goToSlide(Math.max(this.currentSlide - 1, 0));
947
+ }
948
+ });
949
+ }
950
+
951
+ goToSlide(index) {
952
+ this.slides[index].scrollIntoView({ behavior: 'smooth' });
953
+ }
954
+
955
+ updateProgress() {
956
+ const progress = ((this.currentSlide + 1) / this.totalSlides) * 100;
957
+ this.progressBar.style.width = progress + '%';
958
+ }
959
+
960
+ updateDots() {
961
+ this.dots.forEach((dot, i) => {
962
+ dot.classList.toggle('active', i === this.currentSlide);
963
+ });
964
+ }
965
+ }
966
+
967
+ /* Initialize on DOM ready */
968
+ document.addEventListener('DOMContentLoaded', () => {
969
+ new SlidePresentation();
970
+ });
971
+ </script>
972
+
973
+ </body>
974
+ </html>