@the-forge-flow/visual-explainer-pi 0.1.1

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,663 @@
1
+ /**
2
+ * Shared CSS variables, animations, and utilities for all templates
3
+ * Based on nicobailon/visual-explainer design system
4
+ */
5
+ // Font pairings - distinctive combinations (never Inter as primary)
6
+ export const FONT_PAIRINGS = {
7
+ blueprint: {
8
+ body: "'IBM Plex Sans', system-ui, sans-serif",
9
+ mono: "'IBM Plex Mono', 'SF Mono', Consolas, monospace",
10
+ },
11
+ editorial: {
12
+ body: "'Instrument Serif', Georgia, serif",
13
+ mono: "'JetBrains Mono', 'SF Mono', Consolas, monospace",
14
+ },
15
+ paper: {
16
+ body: "'Bricolage Grotesque', system-ui, sans-serif",
17
+ mono: "'Fragment Mono', 'SF Mono', Consolas, monospace",
18
+ },
19
+ terminal: {
20
+ body: "'JetBrains Mono', 'SF Mono', Consolas, monospace",
21
+ mono: "'JetBrains Mono', 'SF Mono', Consolas, monospace",
22
+ },
23
+ dracula: {
24
+ body: "'DM Sans', system-ui, sans-serif",
25
+ mono: "'Fira Code', 'SF Mono', Consolas, monospace",
26
+ },
27
+ nord: {
28
+ body: "'Plus Jakarta Sans', system-ui, sans-serif",
29
+ mono: "'Azeret Mono', 'SF Mono', Consolas, monospace",
30
+ },
31
+ solarized: {
32
+ body: "'IBM Plex Sans', system-ui, sans-serif",
33
+ mono: "'IBM Plex Mono', 'SF Mono', Consolas, monospace",
34
+ },
35
+ gruvbox: {
36
+ body: "'Bricolage Grotesque', system-ui, sans-serif",
37
+ mono: "'Fragment Mono', 'SF Mono', Consolas, monospace",
38
+ },
39
+ };
40
+ // Palettes - warm, distinctive colors (never indigo/violet/pink as primary)
41
+ export const PALETTES = {
42
+ blueprint: {
43
+ light: {
44
+ bg: "#faf7f5",
45
+ surface: "#ffffff",
46
+ surface2: "#f5f0ec",
47
+ surfaceElevated: "#fff9f5",
48
+ border: "rgba(0,0,0,0.07)",
49
+ borderBright: "rgba(0,0,0,0.14)",
50
+ text: "#292017",
51
+ textDim: "#8a7e72",
52
+ accent: "#c2410c",
53
+ accentDim: "rgba(194,65,12,0.07)", // terracotta
54
+ green: "#4d7c0f",
55
+ greenDim: "rgba(77,124,15,0.07)",
56
+ orange: "#b45309",
57
+ orangeDim: "rgba(180,83,9,0.07)",
58
+ teal: "#0f766e",
59
+ tealDim: "rgba(15,118,110,0.07)",
60
+ plum: "#9f1239",
61
+ plumDim: "rgba(159,18,57,0.07)",
62
+ },
63
+ dark: {
64
+ bg: "#1a1412",
65
+ surface: "#231d1a",
66
+ surface2: "#2e2622",
67
+ surfaceElevated: "#352d28",
68
+ border: "rgba(255,255,255,0.06)",
69
+ borderBright: "rgba(255,255,255,0.12)",
70
+ text: "#ede5dd",
71
+ textDim: "#a69889",
72
+ accent: "#fb923c",
73
+ accentDim: "rgba(251,146,60,0.12)",
74
+ green: "#a3e635",
75
+ greenDim: "rgba(163,230,53,0.1)",
76
+ orange: "#fbbf24",
77
+ orangeDim: "rgba(251,191,36,0.1)",
78
+ teal: "#5eead4",
79
+ tealDim: "rgba(94,234,212,0.1)",
80
+ plum: "#fda4af",
81
+ plumDim: "rgba(253,164,175,0.1)",
82
+ },
83
+ },
84
+ editorial: {
85
+ light: {
86
+ bg: "#faf9f7",
87
+ surface: "#ffffff",
88
+ surface2: "#f5f3f0",
89
+ surfaceElevated: "#fffcf7",
90
+ border: "rgba(0,0,0,0.06)",
91
+ borderBright: "rgba(0,0,0,0.12)",
92
+ text: "#1c1917",
93
+ textDim: "#78716c",
94
+ accent: "#1e3a5f",
95
+ accentDim: "rgba(30,58,95,0.08)", // deep blue
96
+ green: "#4d7c0f",
97
+ greenDim: "rgba(77,124,15,0.08)",
98
+ orange: "#d4a73a",
99
+ orangeDim: "rgba(212,167,58,0.08)", // gold
100
+ teal: "#0891b2",
101
+ tealDim: "rgba(8,145,178,0.08)",
102
+ plum: "#be123c",
103
+ plumDim: "rgba(190,18,60,0.08)", // rose
104
+ },
105
+ dark: {
106
+ bg: "#0c0a09",
107
+ surface: "#1c1917",
108
+ surface2: "#292524",
109
+ surfaceElevated: "#44403c",
110
+ border: "rgba(255,255,255,0.06)",
111
+ borderBright: "rgba(255,255,255,0.1)",
112
+ text: "#fafaf9",
113
+ textDim: "#a8a29e",
114
+ accent: "#60a5fa",
115
+ accentDim: "rgba(96,165,250,0.12)",
116
+ green: "#a3e635",
117
+ greenDim: "rgba(163,230,53,0.1)",
118
+ orange: "#fcd34d",
119
+ orangeDim: "rgba(252,211,77,0.1)",
120
+ teal: "#67e8f9",
121
+ tealDim: "rgba(103,232,249,0.1)",
122
+ plum: "#fda4af",
123
+ plumDim: "rgba(253,164,175,0.1)",
124
+ },
125
+ },
126
+ paper: {
127
+ light: {
128
+ bg: "#faf7f2",
129
+ surface: "#ffffff",
130
+ surface2: "#f5f0e8",
131
+ surfaceElevated: "#fffaf0",
132
+ border: "rgba(0,0,0,0.06)",
133
+ borderBright: "rgba(0,0,0,0.1)",
134
+ text: "#292524",
135
+ textDim: "#78716c",
136
+ accent: "#65a30d",
137
+ accentDim: "rgba(101,163,13,0.08)", // sage
138
+ green: "#059669",
139
+ greenDim: "rgba(5,150,105,0.08)",
140
+ orange: "#c2410c",
141
+ orangeDim: "rgba(194,65,12,0.08)", // terracotta
142
+ teal: "#0d9488",
143
+ tealDim: "rgba(13,148,136,0.08)",
144
+ plum: "#be123c",
145
+ plumDim: "rgba(190,18,60,0.08)",
146
+ },
147
+ dark: {
148
+ bg: "#1c1917",
149
+ surface: "#292524",
150
+ surface2: "#44403c",
151
+ surfaceElevated: "#57534e",
152
+ border: "rgba(255,255,255,0.05)",
153
+ borderBright: "rgba(255,255,255,0.1)",
154
+ text: "#fafaf9",
155
+ textDim: "#a8a29e",
156
+ accent: "#bef264",
157
+ accentDim: "rgba(190,242,100,0.1)",
158
+ green: "#34d399",
159
+ greenDim: "rgba(52,211,153,0.1)",
160
+ orange: "#fb923c",
161
+ orangeDim: "rgba(251,146,60,0.1)",
162
+ teal: "#5eead4",
163
+ tealDim: "rgba(94,234,212,0.1)",
164
+ plum: "#fda4af",
165
+ plumDim: "rgba(253,164,175,0.1)",
166
+ },
167
+ },
168
+ terminal: {
169
+ light: {
170
+ bg: "#fafaf9",
171
+ surface: "#ffffff",
172
+ surface2: "#f5f5f4",
173
+ surfaceElevated: "#e7e5e4",
174
+ border: "rgba(0,0,0,0.08)",
175
+ borderBright: "rgba(0,0,0,0.15)",
176
+ text: "#1c1917",
177
+ textDim: "#78716c",
178
+ accent: "#16a34a",
179
+ accentDim: "rgba(22,163,74,0.1)", // green terminal
180
+ green: "#15803d",
181
+ greenDim: "rgba(21,128,61,0.1)",
182
+ orange: "#ea580c",
183
+ orangeDim: "rgba(234,88,12,0.1)",
184
+ teal: "#0d9488",
185
+ tealDim: "rgba(13,148,136,0.1)",
186
+ plum: "#be123c",
187
+ plumDim: "rgba(190,18,60,0.1)",
188
+ },
189
+ dark: {
190
+ bg: "#0c0a09",
191
+ surface: "#1c1917",
192
+ surface2: "#292524",
193
+ surfaceElevated: "#44403c",
194
+ border: "rgba(34,197,94,0.2)",
195
+ borderBright: "rgba(34,197,94,0.3)",
196
+ text: "#4ade80",
197
+ textDim: "#22c55e", // green text for terminal
198
+ accent: "#22c55e",
199
+ accentDim: "rgba(34,197,94,0.15)",
200
+ green: "#86efac",
201
+ greenDim: "rgba(134,239,172,0.15)",
202
+ orange: "#fb923c",
203
+ orangeDim: "rgba(251,146,60,0.15)",
204
+ teal: "#5eead4",
205
+ tealDim: "rgba(94,234,212,0.15)",
206
+ plum: "#f87171",
207
+ plumDim: "rgba(248,113,113,0.15)",
208
+ },
209
+ },
210
+ dracula: {
211
+ light: {
212
+ bg: "#f8f8f2",
213
+ surface: "#ffffff",
214
+ surface2: "#f0f0ea",
215
+ surfaceElevated: "#e8e8e2",
216
+ border: "rgba(0,0,0,0.08)",
217
+ borderBright: "rgba(0,0,0,0.15)",
218
+ text: "#282a36",
219
+ textDim: "#6272a4",
220
+ accent: "#bd93f9",
221
+ accentDim: "rgba(189,147,249,0.12)", // purple (dracula's signature)
222
+ green: "#50fa7b",
223
+ greenDim: "rgba(80,250,123,0.12)",
224
+ orange: "#ffb86c",
225
+ orangeDim: "rgba(255,184,108,0.12)",
226
+ teal: "#8be9fd",
227
+ tealDim: "rgba(139,233,253,0.12)",
228
+ plum: "#ff79c6",
229
+ plumDim: "rgba(255,121,198,0.12)",
230
+ },
231
+ dark: {
232
+ bg: "#282a36",
233
+ surface: "#44475a",
234
+ surface2: "#6272a4",
235
+ surfaceElevated: "#717895",
236
+ border: "rgba(248,248,242,0.1)",
237
+ borderBright: "rgba(248,248,242,0.2)",
238
+ text: "#f8f8f2",
239
+ textDim: "#6272a4",
240
+ accent: "#bd93f9",
241
+ accentDim: "rgba(189,147,249,0.2)",
242
+ green: "#50fa7b",
243
+ greenDim: "rgba(80,250,123,0.2)",
244
+ orange: "#ffb86c",
245
+ orangeDim: "rgba(255,184,108,0.2)",
246
+ teal: "#8be9fd",
247
+ tealDim: "rgba(139,233,253,0.2)",
248
+ plum: "#ff79c6",
249
+ plumDim: "rgba(255,121,198,0.2)",
250
+ },
251
+ },
252
+ nord: {
253
+ light: {
254
+ bg: "#f9fafb",
255
+ surface: "#ffffff",
256
+ surface2: "#f3f4f6",
257
+ surfaceElevated: "#e5e7eb",
258
+ border: "rgba(0,0,0,0.06)",
259
+ borderBright: "rgba(0,0,0,0.12)",
260
+ text: "#1f2937",
261
+ textDim: "#6b7280",
262
+ accent: "#5e81ac",
263
+ accentDim: "rgba(94,129,172,0.1)", // nord blue
264
+ green: "#a3be8c",
265
+ greenDim: "rgba(163,190,140,0.1)",
266
+ orange: "#d08770",
267
+ orangeDim: "rgba(208,135,112,0.1)", // nord orange
268
+ teal: "#88c0d0",
269
+ tealDim: "rgba(136,192,208,0.1)",
270
+ plum: "#b48ead",
271
+ plumDim: "rgba(180,142,173,0.1)", // nord purple
272
+ },
273
+ dark: {
274
+ bg: "#2e3440",
275
+ surface: "#3b4252",
276
+ surface2: "#434c5e",
277
+ surfaceElevated: "#4c566a",
278
+ border: "rgba(216,222,233,0.1)",
279
+ borderBright: "rgba(216,222,233,0.2)",
280
+ text: "#d8dee9",
281
+ textDim: "#81a1c1",
282
+ accent: "#81a1c1",
283
+ accentDim: "rgba(129,161,193,0.2)",
284
+ green: "#a3be8c",
285
+ greenDim: "rgba(163,190,140,0.2)",
286
+ orange: "#d08770",
287
+ orangeDim: "rgba(208,135,112,0.2)",
288
+ teal: "#88c0d0",
289
+ tealDim: "rgba(136,192,208,0.2)",
290
+ plum: "#b48ead",
291
+ plumDim: "rgba(180,142,173,0.2)",
292
+ },
293
+ },
294
+ solarized: {
295
+ light: {
296
+ bg: "#fdf6e3",
297
+ surface: "#ffffff",
298
+ surface2: "#eee8d5",
299
+ surfaceElevated: "#e5dfcd",
300
+ border: "rgba(0,0,0,0.06)",
301
+ borderBright: "rgba(0,0,0,0.12)",
302
+ text: "#073642",
303
+ textDim: "#586e75",
304
+ accent: "#268bd2",
305
+ accentDim: "rgba(38,139,210,0.1)", // solarized blue
306
+ green: "#859900",
307
+ greenDim: "rgba(133,153,0,0.1)",
308
+ orange: "#cb4b16",
309
+ orangeDim: "rgba(203,75,22,0.1)",
310
+ teal: "#2aa198",
311
+ tealDim: "rgba(42,161,152,0.1)",
312
+ plum: "#d33682",
313
+ plumDim: "rgba(211,54,130,0.1)",
314
+ },
315
+ dark: {
316
+ bg: "#002b36",
317
+ surface: "#073642",
318
+ surface2: "#586e75",
319
+ surfaceElevated: "#657b83",
320
+ border: "rgba(253,246,227,0.1)",
321
+ borderBright: "rgba(253,246,227,0.2)",
322
+ text: "#fdf6e3",
323
+ textDim: "#93a1a1",
324
+ accent: "#268bd2",
325
+ accentDim: "rgba(38,139,210,0.2)",
326
+ green: "#859900",
327
+ greenDim: "rgba(133,153,0,0.2)",
328
+ orange: "#cb4b16",
329
+ orangeDim: "rgba(203,75,22,0.2)",
330
+ teal: "#2aa198",
331
+ tealDim: "rgba(42,161,152,0.2)",
332
+ plum: "#d33682",
333
+ plumDim: "rgba(211,54,130,0.2)",
334
+ },
335
+ },
336
+ gruvbox: {
337
+ light: {
338
+ bg: "#fbf1c7",
339
+ surface: "#ffffff",
340
+ surface2: "#f2e5bc",
341
+ surfaceElevated: "#ebdbb2",
342
+ border: "rgba(0,0,0,0.06)",
343
+ borderBright: "rgba(0,0,0,0.12)",
344
+ text: "#3c3836",
345
+ textDim: "#7c6f64",
346
+ accent: "#458588",
347
+ accentDim: "rgba(69,133,136,0.1)", // gruvbox blue
348
+ green: "#98971a",
349
+ greenDim: "rgba(152,151,26,0.1)",
350
+ orange: "#d65d0e",
351
+ orangeDim: "rgba(214,93,14,0.1)",
352
+ teal: "#689d6a",
353
+ tealDim: "rgba(104,157,106,0.1)",
354
+ plum: "#b16286",
355
+ plumDim: "rgba(177,98,134,0.1)",
356
+ },
357
+ dark: {
358
+ bg: "#282828",
359
+ surface: "#3c3836",
360
+ surface2: "#504945",
361
+ surfaceElevated: "#665c54",
362
+ border: "rgba(235,219,178,0.1)",
363
+ borderBright: "rgba(235,219,178,0.2)",
364
+ text: "#ebdbb2",
365
+ textDim: "#a89984",
366
+ accent: "#83a598",
367
+ accentDim: "rgba(131,165,152,0.2)",
368
+ green: "#b8bb26",
369
+ greenDim: "rgba(184,187,38,0.2)",
370
+ orange: "#fe8019",
371
+ orangeDim: "rgba(254,128,25,0.2)",
372
+ teal: "#8ec07c",
373
+ tealDim: "rgba(142,192,124,0.2)",
374
+ plum: "#d3869b",
375
+ plumDim: "rgba(211,134,155,0.2)",
376
+ },
377
+ },
378
+ };
379
+ // Shared CSS for all templates
380
+ export const SHARED_CSS = `
381
+ /* Reset */
382
+ * { margin: 0; padding: 0; box-sizing: border-box; }
383
+
384
+ /* Animation keyframes */
385
+ @keyframes fadeUp {
386
+ from { opacity: 0; transform: translateY(12px); }
387
+ to { opacity: 1; transform: translateY(0); }
388
+ }
389
+
390
+ @keyframes fadeScale {
391
+ from { opacity: 0; transform: scale(0.95); }
392
+ to { opacity: 1; transform: scale(1); }
393
+ }
394
+
395
+ /* Reduced motion */
396
+ @media (prefers-reduced-motion: reduce) {
397
+ *, *::before, *::after {
398
+ animation-duration: 0.01ms !important;
399
+ animation-delay: 0ms !important;
400
+ transition-duration: 0.01ms !important;
401
+ }
402
+ }
403
+
404
+ /* Base styles */
405
+ body {
406
+ min-height: 100vh;
407
+ line-height: 1.6;
408
+ }
409
+
410
+ /* Section card base */
411
+ .section {
412
+ background: var(--surface);
413
+ border: 1px solid var(--border);
414
+ border-radius: 12px;
415
+ padding: 20px 24px;
416
+ animation: fadeUp 0.4s ease-out both;
417
+ animation-delay: calc(var(--i, 0) * 0.06s);
418
+ }
419
+
420
+ .section--hero {
421
+ background: var(--surface-elevated);
422
+ border-color: color-mix(in srgb, var(--border) 50%, var(--accent) 50%);
423
+ box-shadow: 0 4px 20px rgba(0,0,0,0.06);
424
+ padding: 28px 32px;
425
+ }
426
+
427
+ .section--recessed {
428
+ background: var(--surface2);
429
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.04);
430
+ }
431
+
432
+ /* Section labels with dot indicators */
433
+ .section-label {
434
+ font-family: var(--font-mono);
435
+ font-size: 11px;
436
+ font-weight: 600;
437
+ text-transform: uppercase;
438
+ letter-spacing: 1.5px;
439
+ margin-bottom: 16px;
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 8px;
443
+ }
444
+
445
+ .section-label .dot {
446
+ width: 8px;
447
+ height: 8px;
448
+ border-radius: 50%;
449
+ display: inline-block;
450
+ }
451
+
452
+ /* Flow arrows */
453
+ .flow-arrow {
454
+ display: flex;
455
+ justify-content: center;
456
+ align-items: center;
457
+ gap: 8px;
458
+ color: var(--text-dim);
459
+ font-family: var(--font-mono);
460
+ font-size: 12px;
461
+ padding: 4px 0;
462
+ animation: fadeUp 0.4s ease-out both;
463
+ animation-delay: calc(var(--i, 0) * 0.06s);
464
+ }
465
+
466
+ .flow-arrow svg {
467
+ width: 20px;
468
+ height: 20px;
469
+ fill: none;
470
+ stroke: var(--border-bright);
471
+ stroke-width: 2;
472
+ stroke-linecap: round;
473
+ stroke-linejoin: round;
474
+ }
475
+
476
+ /* Code styling */
477
+ code {
478
+ font-family: var(--font-mono);
479
+ font-size: 0.9em;
480
+ background: var(--accent-dim);
481
+ color: var(--accent);
482
+ padding: 2px 6px;
483
+ border-radius: 4px;
484
+ }
485
+
486
+ /* Responsive */
487
+ @media (max-width: 768px) {
488
+ body { padding: 20px; }
489
+ .section { padding: 16px 20px; }
490
+ .section--hero { padding: 20px 24px; }
491
+ }
492
+ `;
493
+ // Mermaid zoom/pan CSS and JS
494
+ export const MERMAID_SHELL_CSS = `
495
+ .diagram-shell {
496
+ background: var(--surface);
497
+ border: 1px solid var(--border);
498
+ border-radius: 12px;
499
+ overflow: hidden;
500
+ }
501
+
502
+ .mermaid-wrap {
503
+ position: relative;
504
+ display: flex;
505
+ justify-content: center;
506
+ padding: 24px;
507
+ }
508
+
509
+ .mermaid-viewport {
510
+ overflow: hidden;
511
+ cursor: grab;
512
+ }
513
+
514
+ .mermaid-viewport:active {
515
+ cursor: grabbing;
516
+ }
517
+
518
+ .mermaid-canvas {
519
+ transform-origin: center center;
520
+ transition: transform 0.1s ease-out;
521
+ }
522
+
523
+ .zoom-controls {
524
+ position: absolute;
525
+ top: 12px;
526
+ right: 12px;
527
+ display: flex;
528
+ gap: 6px;
529
+ z-index: 10;
530
+ }
531
+
532
+ .zoom-btn {
533
+ width: 32px;
534
+ height: 32px;
535
+ border: 1px solid var(--border);
536
+ border-radius: 6px;
537
+ background: var(--surface);
538
+ color: var(--text);
539
+ font-size: 16px;
540
+ cursor: pointer;
541
+ display: flex;
542
+ align-items: center;
543
+ justify-content: center;
544
+ transition: all 0.2s;
545
+ }
546
+
547
+ .zoom-btn:hover {
548
+ background: var(--surface2);
549
+ border-color: var(--border-bright);
550
+ }
551
+ `;
552
+ export const MERMAID_SHELL_JS = `
553
+ function initMermaidControls(wrap) {
554
+ const viewport = wrap.querySelector('.mermaid-viewport');
555
+ const canvas = wrap.querySelector('.mermaid-canvas');
556
+ let scale = 1;
557
+ let isDragging = false;
558
+ let startX, startY, translateX = 0, translateY = 0;
559
+
560
+ function updateTransform() {
561
+ canvas.style.transform = \`translate(\${translateX}px, \${translateY}px) scale(\${scale})\`;
562
+ }
563
+
564
+ wrap.querySelector('[data-zoom="in"]').onclick = () => { scale *= 1.2; updateTransform(); };
565
+ wrap.querySelector('[data-zoom="out"]').onclick = () => { scale /= 1.2; updateTransform(); };
566
+ wrap.querySelector('[data-zoom="reset"]').onclick = () => { scale = 1; translateX = 0; translateY = 0; updateTransform(); };
567
+ wrap.querySelector('[data-zoom="expand"]').onclick = () => openMermaidInNewTab(wrap);
568
+
569
+ viewport.addEventListener('mousedown', (e) => {
570
+ isDragging = true;
571
+ startX = e.clientX - translateX;
572
+ startY = e.clientY - translateY;
573
+ viewport.style.cursor = 'grabbing';
574
+ });
575
+
576
+ window.addEventListener('mousemove', (e) => {
577
+ if (!isDragging) return;
578
+ translateX = e.clientX - startX;
579
+ translateY = e.clientY - startY;
580
+ updateTransform();
581
+ });
582
+
583
+ window.addEventListener('mouseup', () => {
584
+ isDragging = false;
585
+ viewport.style.cursor = 'grab';
586
+ });
587
+
588
+ viewport.addEventListener('wheel', (e) => {
589
+ if (e.ctrlKey || e.metaKey) {
590
+ e.preventDefault();
591
+ scale *= e.deltaY > 0 ? 0.9 : 1.1;
592
+ updateTransform();
593
+ }
594
+ }, { passive: false });
595
+ }
596
+
597
+ function openMermaidInNewTab(wrap) {
598
+ const svg = wrap.querySelector('svg');
599
+ if (!svg) return;
600
+ const blob = new Blob([svg.outerHTML], { type: 'image/svg+xml' });
601
+ const url = URL.createObjectURL(blob);
602
+ window.open(url, '_blank');
603
+ }
604
+ `;
605
+ // Helper to generate CSS variables from palette
606
+ export function generateCSSVariables(palette, fonts, isDark) {
607
+ const themeBlock = isDark ? "@media (prefers-color-scheme: dark) { :root {" : ":root {";
608
+ const closeBlock = isDark ? "} }" : "}";
609
+ return `${themeBlock}
610
+ --font-body: ${fonts.body};
611
+ --font-mono: ${fonts.mono};
612
+ --bg: ${palette.bg};
613
+ --surface: ${palette.surface};
614
+ --surface2: ${palette.surface2};
615
+ --surface-elevated: ${palette.surfaceElevated};
616
+ --border: ${palette.border};
617
+ --border-bright: ${palette.borderBright};
618
+ --text: ${palette.text};
619
+ --text-dim: ${palette.textDim};
620
+ --accent: ${palette.accent};
621
+ --accent-dim: ${palette.accentDim};
622
+ --green: ${palette.green};
623
+ --green-dim: ${palette.greenDim};
624
+ --orange: ${palette.orange};
625
+ --orange-dim: ${palette.orangeDim};
626
+ --teal: ${palette.teal};
627
+ --teal-dim: ${palette.tealDim};
628
+ --plum: ${palette.plum};
629
+ --plum-dim: ${palette.plumDim};
630
+ ${closeBlock}`;
631
+ }
632
+ // HTML shell generator
633
+ export function generateHtmlShell(title, bodyContent, aesthetic, cssVariables, extraHead = "", extraScripts = "") {
634
+ return `<!DOCTYPE html>
635
+ <html lang="en">
636
+ <head>
637
+ <meta charset="UTF-8">
638
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
639
+ <title>${escapeHtml(title)}</title>
640
+ <link rel="preconnect" href="https://fonts.googleapis.com">
641
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
642
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&family=Instrument+Serif&family=JetBrains+Mono:wght@400;500;600&family=DM+Sans:wght@400;500;600;700&family=Fira+Code:wght@400;500;600&family=Bricolage+Grotesque:wght@400;500;600;700&family=Fragment+Mono&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Azeret+Mono:wght@400;500;600&display=swap" rel="stylesheet">
643
+ <style>
644
+ ${cssVariables}
645
+ ${SHARED_CSS}
646
+ ${extraHead}
647
+ </style>
648
+ </head>
649
+ <body>
650
+ ${bodyContent}
651
+ ${extraScripts}
652
+ </body>
653
+ </html>`;
654
+ }
655
+ // Escape HTML entities
656
+ export function escapeHtml(str) {
657
+ return str
658
+ .replace(/&/g, "&amp;")
659
+ .replace(/</g, "&lt;")
660
+ .replace(/>/g, "&gt;")
661
+ .replace(/"/g, "&quot;")
662
+ .replace(/'/g, "&#039;");
663
+ }