ansimax 1.3.1 → 1.3.2

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,676 @@
1
+ # šŸ“— ansimax — JavaScript ESM examples
2
+
3
+ All examples use **ES Modules** (`import`/`export`). Save as `.mjs` (or `.js` with `"type": "module"` in `package.json`) and run with:
4
+
5
+ ```bash
6
+ node example.mjs
7
+ ```
8
+
9
+ For TypeScript, see [`examples-ts.md`](./examples-ts.md). For CommonJS, see [`examples-cjs.md`](./examples-cjs.md).
10
+
11
+ ---
12
+
13
+ ## šŸŽØ 1. `color` — Terminal colors & styles
14
+
15
+ ### 1.1 — Basic colored output
16
+
17
+ ```js
18
+ import { color } from 'ansimax';
19
+
20
+ console.log(color.green('āœ“ Build successful'));
21
+ console.log(color.red('āœ— Build failed'));
22
+ console.log(color.yellow('⚠ 3 warnings'));
23
+ console.log(color.cyan('ℹ Check the logs above'));
24
+
25
+ // Composable styles
26
+ console.log(color.bold(color.magenta('Application started')));
27
+ ```
28
+
29
+ ### 1.2 — Status report with mixed colors
30
+
31
+ ```js
32
+ import { color } from 'ansimax';
33
+
34
+ const services = [
35
+ { name: 'api', status: 'up', latency: 45 },
36
+ { name: 'database', status: 'up', latency: 12 },
37
+ { name: 'cache', status: 'degraded', latency: 240 },
38
+ { name: 'auth', status: 'down', latency: 0 },
39
+ ];
40
+
41
+ console.log(color.bold('Service Health Report\n'));
42
+
43
+ for (const svc of services) {
44
+ const icon = svc.status === 'up' ? color.green('ā—')
45
+ : svc.status === 'degraded' ? color.yellow('ā—')
46
+ : color.red('ā—');
47
+ const lat = svc.latency > 100 ? color.red(`${svc.latency}ms`)
48
+ : svc.latency > 50 ? color.yellow(`${svc.latency}ms`)
49
+ : color.green(`${svc.latency}ms`);
50
+
51
+ console.log(`${icon} ${svc.name.padEnd(10)} ${lat}`);
52
+ }
53
+ ```
54
+
55
+ ### 1.3 — Truecolor & hex colors
56
+
57
+ ```js
58
+ import { color } from 'ansimax';
59
+
60
+ // Hex colors via background helpers
61
+ console.log(color.bgHex('#bd93f9')(' Dracula purple '));
62
+ console.log(color.bgHex('#ff79c6')(' Pink '));
63
+ console.log(color.bgHex('#50fa7b')(' Green '));
64
+
65
+ // Foreground hex
66
+ console.log(color.hex('#ff6b6b')('Bright red text'));
67
+ console.log(color.hex('#4ecdc4')('Turquoise text'));
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 🌈 2. `gradient` — Multi-stop gradients
73
+
74
+ ### 2.1 — Simple two-color gradient
75
+
76
+ ```js
77
+ import { gradient } from 'ansimax';
78
+
79
+ console.log(gradient('Hello, gradients!', ['#ff79c6', '#bd93f9']));
80
+ console.log(gradient('Pink to purple', ['#ff79c6', '#bd93f9']));
81
+ console.log(gradient('Sunset', ['#ff5e62', '#ff9966', '#ffd86f']));
82
+ ```
83
+
84
+ ### 2.2 — Reusable gradient with easing
85
+
86
+ ```js
87
+ import { createGradient } from 'ansimax';
88
+
89
+ const easings = ['linear', 'ease-in', 'ease-out', 'ease-in-out'];
90
+
91
+ for (const ease of easings) {
92
+ const grad = createGradient(['#8be9fd', '#bd93f9', '#ff79c6'], { easing: ease });
93
+ console.log(`${ease.padEnd(15)} ${grad('ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ')}`);
94
+ }
95
+
96
+ // Custom easing function
97
+ const cubic = createGradient(['#ff0000', '#0000ff'], {
98
+ easing: (t) => t * t * t,
99
+ });
100
+ console.log(cubic('Custom cubic easing across this whole line'));
101
+ ```
102
+
103
+ ### 2.3 — Animated gradient (color cycles over time)
104
+
105
+ ```js
106
+ import { animateGradient } from 'ansimax';
107
+
108
+ const ctrl = animateGradient(
109
+ '╔═══ Loading data... ═══╗',
110
+ ['#ff79c6', '#bd93f9', '#8be9fd', '#50fa7b'],
111
+ {
112
+ interval: 80,
113
+ cycles: 3,
114
+ duration: 800,
115
+ infinite: false,
116
+ },
117
+ );
118
+
119
+ await ctrl.promise;
120
+ console.log('\nāœ“ Done');
121
+ ```
122
+
123
+ ---
124
+
125
+ ## šŸ”¤ 3. `ascii` — Boxes, banners, image-to-ASCII
126
+
127
+ ### 3.1 — Boxes with multiple styles
128
+
129
+ ```js
130
+ import { ascii, gradient } from 'ansimax';
131
+
132
+ console.log(ascii.box('Hello world!', { borderStyle: 'rounded' }));
133
+ console.log(ascii.box('Important!', { borderStyle: 'double', padding: 2 }));
134
+
135
+ // Box around colored content — border stays uncolored
136
+ const colored = gradient('Rainbow inside', ['#ff79c6', '#bd93f9', '#8be9fd']);
137
+ console.log(ascii.box(colored, { borderStyle: 'heavy', padding: 1 }));
138
+
139
+ // Multi-line box
140
+ const multi = 'Line 1\nLine 2\nLine 3 longer\n\nWith blank line';
141
+ console.log(ascii.box(multi, { borderStyle: 'rounded', padding: 1 }));
142
+ ```
143
+
144
+ ### 3.2 — Banner with figlet font
145
+
146
+ ```js
147
+ import { ascii, gradient } from 'ansimax';
148
+
149
+ const banner = ascii.figletText('ANSIMAX', { font: 'standard' });
150
+ console.log(gradient(banner, ['#ff79c6', '#bd93f9', '#8be9fd']));
151
+
152
+ // Different font
153
+ const compact = ascii.figletText('v1.3.2', { font: 'small' });
154
+ console.log(ascii.box(compact, { borderStyle: 'rounded' }));
155
+ ```
156
+
157
+ ### 3.3 — Image-to-ASCII from a PixelGrid
158
+
159
+ ```js
160
+ import { ascii } from 'ansimax';
161
+
162
+ // Build a small "image" — a smiley face — programmatically
163
+ const N = null;
164
+ const Y = { r: 255, g: 220, b: 0 };
165
+ const K = { r: 0, g: 0, b: 0 };
166
+
167
+ const smiley = [
168
+ [N, N, Y, Y, Y, Y, N, N],
169
+ [N, Y, Y, Y, Y, Y, Y, N],
170
+ [Y, Y, K, Y, Y, K, Y, Y],
171
+ [Y, Y, Y, Y, Y, Y, Y, Y],
172
+ [Y, K, Y, Y, Y, Y, K, Y],
173
+ [Y, Y, K, K, K, K, Y, Y],
174
+ [N, Y, Y, Y, Y, Y, Y, N],
175
+ [N, N, Y, Y, Y, Y, N, N],
176
+ ];
177
+
178
+ console.log(ascii.fromImage(smiley, { width: 30, ramp: 'block' }));
179
+ ```
180
+
181
+ ---
182
+
183
+ ## ✨ 4. `animations` — Typewriter, fadeIn, fadeOut
184
+
185
+ ### 4.1 — Typewriter effect
186
+
187
+ ```js
188
+ import { animate } from 'ansimax';
189
+
190
+ await animate.typewriter('Loading project configuration...', { speed: 40 });
191
+ console.log();
192
+
193
+ // Abortable
194
+ const ctrl = new AbortController();
195
+ setTimeout(() => ctrl.abort(), 500);
196
+
197
+ try {
198
+ await animate.typewriter(
199
+ 'This is a very long sentence that will get cut off mid-print.',
200
+ { speed: 30, signal: ctrl.signal },
201
+ );
202
+ } catch {
203
+ console.log('\n(aborted)');
204
+ }
205
+ ```
206
+
207
+ ### 4.2 — Fade in with custom color
208
+
209
+ ```js
210
+ import { animate } from 'ansimax';
211
+
212
+ await animate.fadeIn('ā˜… Welcome to the demo ā˜…', {
213
+ duration: 1200,
214
+ color: '#bd93f9',
215
+ steps: 25,
216
+ });
217
+ console.log();
218
+ ```
219
+
220
+ ### 4.3 — Sequenced reveals for a fake intro
221
+
222
+ ```js
223
+ import { animate, color } from 'ansimax';
224
+
225
+ const lines = [
226
+ '> Initializing system...',
227
+ '> Loading modules: 12/12 āœ“',
228
+ '> Connecting to mainframe...',
229
+ '> Access granted',
230
+ '> Welcome, operator.',
231
+ ];
232
+
233
+ for (const line of lines) {
234
+ await animate.typewriter(color.green(line), { speed: 25 });
235
+ console.log();
236
+ await new Promise((resolve) => setTimeout(resolve, 200));
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## ā³ 5. `loaders` — Spinners, tasks, progress
243
+
244
+ ### 5.1 — Spinner with success/error states
245
+
246
+ ```js
247
+ import { loader } from 'ansimax';
248
+
249
+ const stop = loader.spin('Fetching data from API...', {
250
+ type: 'dots',
251
+ color: '#bd93f9',
252
+ });
253
+
254
+ try {
255
+ await new Promise((resolve) => setTimeout(resolve, 1500));
256
+ stop('Data loaded successfully!', true);
257
+ } catch {
258
+ stop('Request failed', false);
259
+ }
260
+ ```
261
+
262
+ ### 5.2 — Hierarchical task runner
263
+
264
+ ```js
265
+ import { loader } from 'ansimax';
266
+
267
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
268
+
269
+ await loader.tasks([
270
+ {
271
+ text: 'Build pipeline',
272
+ fn: async () => sleep(200),
273
+ subtasks: [
274
+ { text: 'Compile TypeScript', fn: async () => sleep(400) },
275
+ { text: 'Bundle for production', fn: async () => sleep(300) },
276
+ { text: 'Generate type defs', fn: async () => sleep(200) },
277
+ ],
278
+ },
279
+ {
280
+ text: 'Quality checks',
281
+ fn: async () => sleep(150),
282
+ subtasks: [
283
+ { text: 'Lint', fn: async () => sleep(250) },
284
+ { text: 'Test', fn: async () => sleep(500) },
285
+ ],
286
+ },
287
+ { text: 'Publish to npm', fn: async () => sleep(300) },
288
+ ]);
289
+ ```
290
+
291
+ ### 5.3 — Parallel task execution with results
292
+
293
+ ```js
294
+ import { loader } from 'ansimax';
295
+
296
+ const ping = async (name) => {
297
+ await new Promise((r) => setTimeout(r, 200 + Math.random() * 600));
298
+ return Math.random() > 0.2;
299
+ };
300
+
301
+ const results = await loader.tasks([
302
+ { text: 'Check api.example.com', fn: async () => { await ping('api'); } },
303
+ { text: 'Check db.example.com', fn: async () => { await ping('db'); } },
304
+ { text: 'Check auth.example.com', fn: async () => { await ping('auth'); } },
305
+ { text: 'Check cdn.example.com', fn: async () => { await ping('cdn'); } },
306
+ ], { parallel: true });
307
+
308
+ const failed = results.filter((r) => !r.success);
309
+ console.log(`\n${results.length - failed.length}/${results.length} services healthy`);
310
+ ```
311
+
312
+ ---
313
+
314
+ ## 🧱 6. `components` — Table, badge, status, timeline
315
+
316
+ ### 6.1 — Status report with badges
317
+
318
+ ```js
319
+ import { components } from 'ansimax';
320
+
321
+ console.log(
322
+ components.badge('VERSION', 'v1.3.2'),
323
+ components.badge('BUILD', 'passing'),
324
+ components.badge('TESTS', '2000+ passing'),
325
+ components.badge('LICENSE', 'Apache 2.0'),
326
+ );
327
+
328
+ console.log();
329
+ console.log(components.status('success', 'Build completed in 4.2s'));
330
+ console.log(components.status('warn', '3 deprecation warnings'));
331
+ console.log(components.status('error', 'Type error in src/main.js:42'));
332
+ console.log(components.status('info', 'Documentation generated at docs/'));
333
+ ```
334
+
335
+ ### 6.2 — Table with ANSI-aware cells
336
+
337
+ ```js
338
+ import { components, color } from 'ansimax';
339
+
340
+ const rows = [
341
+ ['Service', 'Status', 'Latency', 'Uptime'],
342
+ ['api', color.green('āœ“ healthy'), '45ms', '99.99%'],
343
+ ['db', color.green('āœ“ healthy'), '12ms', '99.95%'],
344
+ ['cache', color.yellow('⚠ degraded'), '240ms', '98.20%'],
345
+ ['auth', color.red('āœ— down'), '—', '95.10%'],
346
+ ];
347
+
348
+ console.log(components.table(rows, {
349
+ borderStyle: 'rounded',
350
+ padding: 1,
351
+ }));
352
+ ```
353
+
354
+ ### 6.3 — Project timeline with done/pending
355
+
356
+ ```js
357
+ import { components } from 'ansimax';
358
+
359
+ const events = [
360
+ { label: 'Project initialized', time: 'Mon', done: true },
361
+ { label: 'Dependencies set up', time: 'Tue', done: true },
362
+ { label: 'Core modules done', time: 'Wed', done: true },
363
+ { label: 'Tests passing', time: 'Thu', done: true },
364
+ { label: 'Documentation', time: 'Fri', done: false },
365
+ { label: 'Publish to npm', done: false },
366
+ ];
367
+
368
+ console.log(components.timeline(events, { node: 'ā—', connector: '│' }));
369
+ ```
370
+
371
+ ---
372
+
373
+ ## šŸŽØ 7. `themes` — Built-in and custom themes
374
+
375
+ ### 7.1 — Switching active theme
376
+
377
+ ```js
378
+ import { themes } from 'ansimax';
379
+
380
+ themes.use('dracula');
381
+ const active = themes.current();
382
+
383
+ console.log(`Active theme: ${active.name}`);
384
+ console.log(`Primary color: ${active.primary}`);
385
+ console.log(`Available themes: ${themes.list().join(', ')}`);
386
+ ```
387
+
388
+ ### 7.2 — Registering a custom theme
389
+
390
+ ```js
391
+ import { themes } from 'ansimax';
392
+
393
+ themes.register('synthwave', {
394
+ name: 'synthwave',
395
+ primary: '#ff6ec7',
396
+ secondary: '#36d6e7',
397
+ accent: '#ffd93d',
398
+ success: '#06d6a0',
399
+ warning: '#ffd93d',
400
+ error: '#ff5e5b',
401
+ info: '#36d6e7',
402
+ muted: '#6c757d',
403
+ bg: '#241734',
404
+ surface: '#34174f',
405
+ text: '#ffffff',
406
+ gradient: ['#ff6ec7', '#36d6e7'],
407
+ });
408
+
409
+ themes.use('synthwave');
410
+ console.log(`Now using: ${themes.current().name}`);
411
+ ```
412
+
413
+ ### 7.3 — Subscribing to theme changes (live UI updates)
414
+
415
+ ```js
416
+ import { themes } from 'ansimax';
417
+
418
+ const unsubscribe = themes.onChange((newT, oldT) => {
419
+ console.log(`Theme changed: ${oldT.name} → ${newT.name}`);
420
+ });
421
+
422
+ themes.use('dracula');
423
+ themes.use('nord');
424
+ themes.use('monokai');
425
+
426
+ unsubscribe();
427
+ themes.use('dracula'); // no log this time
428
+ ```
429
+
430
+ ---
431
+
432
+ ## 🌳 8. `trees` — Hierarchical rendering
433
+
434
+ ### 8.1 — Simple file tree
435
+
436
+ ```js
437
+ import { trees } from 'ansimax';
438
+
439
+ const projectTree = {
440
+ label: 'my-app/',
441
+ children: [
442
+ {
443
+ label: 'src/',
444
+ children: [
445
+ { label: 'index.js' },
446
+ { label: 'utils.js' },
447
+ { label: 'config.js' },
448
+ ],
449
+ },
450
+ {
451
+ label: 'tests/',
452
+ children: [
453
+ { label: 'unit/' },
454
+ { label: 'e2e/' },
455
+ ],
456
+ },
457
+ { label: 'package.json' },
458
+ { label: 'README.md' },
459
+ ],
460
+ };
461
+
462
+ console.log(trees.tree(projectTree, { style: 'unicode' }));
463
+ ```
464
+
465
+ ### 8.2 — Limit depth for large trees
466
+
467
+ ```js
468
+ import { trees } from 'ansimax';
469
+
470
+ const deep = {
471
+ label: 'root',
472
+ children: [
473
+ { label: 'level-1', children: [
474
+ { label: 'level-2', children: [
475
+ { label: 'level-3', children: [
476
+ { label: 'level-4 (hidden)' },
477
+ ]},
478
+ ]},
479
+ ]},
480
+ ],
481
+ };
482
+
483
+ console.log(trees.tree(deep, { maxDepth: 2 }));
484
+ ```
485
+
486
+ ### 8.3 — Cycle-safe rendering
487
+
488
+ ```js
489
+ import { trees } from 'ansimax';
490
+
491
+ const a = { label: 'Node A', children: [] };
492
+ const b = { label: 'Node B', children: [] };
493
+ a.children = [b];
494
+ b.children = [a]; // cycle!
495
+
496
+ console.log(trees.tree(a, { maxDepth: 5 }));
497
+ ```
498
+
499
+ ---
500
+
501
+ ## šŸŽ¬ 9. `frames` — Frame-by-frame animation
502
+
503
+ ### 9.1 — Loading spinner with custom frames
504
+
505
+ ```js
506
+ import { frames } from 'ansimax';
507
+
508
+ const spinner = ['ā ‹', 'ā ™', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ‡', 'ā '];
509
+
510
+ const ctrl = frames.play(
511
+ spinner.map((f) => `${f} Loading...`),
512
+ { interval: 80, loop: true },
513
+ );
514
+
515
+ setTimeout(() => ctrl.stop(), 3000);
516
+ await ctrl.promise;
517
+ console.log('Done!');
518
+ ```
519
+
520
+ ### 9.2 — Procedural frame generation
521
+
522
+ ```js
523
+ import { frames } from 'ansimax';
524
+
525
+ const pulse = frames.generate(20, (i, total) => {
526
+ const intensity = Math.sin((i / total) * Math.PI * 2);
527
+ const size = Math.round(Math.abs(intensity) * 5) + 1;
528
+ return 'ā—'.repeat(size).padEnd(7);
529
+ });
530
+
531
+ await frames.play(pulse, {
532
+ interval: 80,
533
+ loop: true,
534
+ signal: AbortSignal.timeout(2000),
535
+ }).promise;
536
+ ```
537
+
538
+ ### 9.3 — Morph one text into another
539
+
540
+ ```js
541
+ import { frames } from 'ansimax';
542
+
543
+ const morphed = frames.morph('HELLO', 'WORLD', 15, 'ā–‘ā–’ā–“ā–ˆā–“ā–’ā–‘');
544
+ await frames.play(morphed, { interval: 80, loop: false }).promise;
545
+
546
+ console.log('\nāœ“ Morph complete');
547
+ ```
548
+
549
+ ---
550
+
551
+ ## šŸ–¼ļø 10. `images` — Pixel art, canvas, gradients
552
+
553
+ ### 10.1 — Render a sprite from the built-in library
554
+
555
+ ```js
556
+ import { renderPixelArt, SPRITES } from 'ansimax';
557
+
558
+ console.log('All sprites:', Object.keys(SPRITES).join(', '));
559
+ console.log();
560
+ console.log(renderPixelArt(SPRITES.heart.pixels, { scale: 2 }));
561
+ ```
562
+
563
+ ### 10.2 — Gradient rectangle for visual demos
564
+
565
+ ```js
566
+ import { gradientRect } from 'ansimax';
567
+
568
+ // Sunset
569
+ console.log(gradientRect({
570
+ width: 50, height: 8,
571
+ colors: ['#ff5e62', '#ff9966', '#ffd86f'],
572
+ style: 'horizontal',
573
+ }));
574
+
575
+ console.log();
576
+
577
+ // Conic (rainbow wheel)
578
+ console.log(gradientRect({
579
+ width: 30, height: 15,
580
+ colors: ['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#ff00ff', '#ff0000'],
581
+ style: 'conic',
582
+ dither: 'bayer',
583
+ }));
584
+ ```
585
+
586
+ ### 10.3 — Canvas with drawing primitives
587
+
588
+ ```js
589
+ import { createCanvas } from 'ansimax';
590
+
591
+ const canvas = createCanvas(40, 20, { r: 30, g: 30, b: 40 });
592
+
593
+ const orange = { r: 255, g: 165, b: 0 };
594
+ const cyan = { r: 0, g: 200, b: 255 };
595
+ const pink = { r: 255, g: 105, b: 180 };
596
+
597
+ canvas.drawRect(2, 2, 12, 6, cyan, true);
598
+ canvas.drawCircle(25, 8, 4, orange, true);
599
+ canvas.drawLine(0, 19, 39, 0, pink);
600
+
601
+ canvas.print();
602
+ ```
603
+
604
+ ---
605
+
606
+ ## 🪟 11. `panels` + `json` — Layouts and pretty-printing (v1.3.0+)
607
+
608
+ ### 11.1 — Side-by-side columns with nesting
609
+
610
+ ```js
611
+ import { panels, ascii } from 'ansimax';
612
+
613
+ const sidebar = ascii.box('Sidebar\n• Item 1\n• Item 2\n• Item 3', { borderStyle: 'rounded' });
614
+ const main = ascii.box('Main content\n\nLorem ipsum dolor sit amet.', { borderStyle: 'rounded' });
615
+
616
+ console.log(panels.vsplit([sidebar, main], { gap: 2, align: 'start' }));
617
+
618
+ console.log();
619
+
620
+ console.log(panels.hsplit([
621
+ '── Header ──',
622
+ ascii.box('Body content'),
623
+ '── Footer ──',
624
+ ], { align: 'center', gap: 1 }));
625
+ ```
626
+
627
+ ### 11.2 — Centering and decorative frames
628
+
629
+ ```js
630
+ import { panels, ascii } from 'ansimax';
631
+
632
+ // Center a box within an 80-column terminal
633
+ const card = ascii.box('Welcome!', { borderStyle: 'rounded', padding: 2 });
634
+ console.log(panels.center(card, { width: 80 }));
635
+
636
+ console.log();
637
+
638
+ // Lighter alternative to full box — only top/bottom rules
639
+ console.log(panels.frame('Important section content here', {
640
+ title: 'Section Header',
641
+ padding: 1,
642
+ topChar: '─',
643
+ }));
644
+ ```
645
+
646
+ ### 11.3 — JSON pretty-print with all options
647
+
648
+ ```js
649
+ import { json } from 'ansimax';
650
+
651
+ const data = {
652
+ name: 'ansimax',
653
+ version: '1.3.2',
654
+ features: ['colors', 'gradients', 'panels', 'json'],
655
+ stats: { tests: 2104, coverage: 0.98 },
656
+ meta: { active: true },
657
+ };
658
+
659
+ // Default — colored, inline short arrays
660
+ console.log(json.pretty(data));
661
+
662
+ console.log('\n--- With sortKeys (alphabetical) ---');
663
+ console.log(json.pretty(data, { sortKeys: true }));
664
+
665
+ console.log('\n--- With maxDepth: 1 (collapsed) ---');
666
+ console.log(json.pretty(data, { maxDepth: 1 }));
667
+ ```
668
+
669
+ ---
670
+
671
+ ## šŸŽÆ Next steps
672
+
673
+ - **TypeScript?** → [`examples-ts.md`](./examples-ts.md)
674
+ - **JavaScript CommonJS?** → [`examples-cjs.md`](./examples-cjs.md)
675
+ - **Complete demo app combining everything?** → [`showcase.md`](./showcase.md)
676
+ - **Back to docs index?** → [`README.md`](./README.md)