@ponchia/ui 0.3.5 → 0.3.6

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,922 @@
1
+ /**
2
+ * @ponchia/ui — display glyphs.
3
+ *
4
+ * Small, frozen bitmap registry rendered on the existing `.ui-dotmatrix`
5
+ * primitive (see css/dots.css). CSS-first to the core: a glyph is just a
6
+ * grid of dot cells, three tones — `.` off (the dim panel dot), `#` hot,
7
+ * `*` accent — so it re-skins with the same `--field-dot*` tokens as every
8
+ * other dot surface.
9
+ *
10
+ * Framework-agnostic, dependency-free, side-effect-free on import, SSR-safe
11
+ * (`renderGlyph` returns a string; nothing touches the DOM here — the
12
+ * optional `initDotGlyph` behavior lives in behaviors/index.js).
13
+ *
14
+ * import { renderGlyph } from '@ponchia/ui/glyphs';
15
+ * el.innerHTML = renderGlyph('check', { label: 'Done' });
16
+ *
17
+ * The `GlyphName` union, helper signatures, and drift gate are generated +
18
+ * checked from this file — see scripts/gen-glyphs.mjs / check-glyphs.mjs.
19
+ */
20
+
21
+ /** The grid edge length: every glyph is GLYPH_SIZE rows of GLYPH_SIZE chars. */
22
+ export const GLYPH_SIZE = 16;
23
+
24
+ // Raw bitmaps. Each is GLYPH_SIZE rows of GLYPH_SIZE chars over [.#*]:
25
+ // `.` off · `#` hot · `*` accent. Only `spark` uses accent dots — it is the
26
+ // canonical two-tone demo; the gate in check-glyphs.mjs enforces the shape.
27
+ const RAW = {
28
+ 'arrow-down': [
29
+ '................',
30
+ '......####......',
31
+ '......####......',
32
+ '......####......',
33
+ '......####......',
34
+ '......####......',
35
+ '......####......',
36
+ '.##############.',
37
+ '..############..',
38
+ '...##########...',
39
+ '....########....',
40
+ '.....######.....',
41
+ '......####......',
42
+ '.......##.......',
43
+ '.......##.......',
44
+ '................',
45
+ ],
46
+ 'arrow-left': [
47
+ '................',
48
+ '........#.......',
49
+ '.......##.......',
50
+ '......###.......',
51
+ '.....####.......',
52
+ '....#####.......',
53
+ '...############.',
54
+ '.##############.',
55
+ '.##############.',
56
+ '...############.',
57
+ '....#####.......',
58
+ '.....####.......',
59
+ '......###.......',
60
+ '.......##.......',
61
+ '........#.......',
62
+ '................',
63
+ ],
64
+ 'arrow-right': [
65
+ '................',
66
+ '.......#........',
67
+ '.......##.......',
68
+ '.......###......',
69
+ '.......####.....',
70
+ '.......#####....',
71
+ '.############...',
72
+ '.##############.',
73
+ '.##############.',
74
+ '.############...',
75
+ '.......#####....',
76
+ '.......####.....',
77
+ '.......###......',
78
+ '.......##.......',
79
+ '.......#........',
80
+ '................',
81
+ ],
82
+ 'arrow-up': [
83
+ '................',
84
+ '.......##.......',
85
+ '.......##.......',
86
+ '......####......',
87
+ '.....######.....',
88
+ '....########....',
89
+ '...##########...',
90
+ '..############..',
91
+ '.##############.',
92
+ '......####......',
93
+ '......####......',
94
+ '......####......',
95
+ '......####......',
96
+ '......####......',
97
+ '......####......',
98
+ '................',
99
+ ],
100
+ bell: [
101
+ '................',
102
+ '.......##.......',
103
+ '......####......',
104
+ '.....######.....',
105
+ '....########....',
106
+ '....########....',
107
+ '....########....',
108
+ '...##########...',
109
+ '...##########...',
110
+ '..############..',
111
+ '.##############.',
112
+ '################',
113
+ '................',
114
+ '......####......',
115
+ '......####......',
116
+ '................',
117
+ ],
118
+ check: [
119
+ '................',
120
+ '................',
121
+ '.............###',
122
+ '............###.',
123
+ '...........###..',
124
+ '..........###...',
125
+ '.........###....',
126
+ '.##.....###.....',
127
+ '.##....###......',
128
+ '.###..###.......',
129
+ '..######........',
130
+ '...####.........',
131
+ '...###..........',
132
+ '................',
133
+ '................',
134
+ '................',
135
+ ],
136
+ 'chevron-down': [
137
+ '................',
138
+ '................',
139
+ '................',
140
+ '................',
141
+ '.##..........##.',
142
+ '.###........###.',
143
+ '..###......###..',
144
+ '...###....###...',
145
+ '....###..###....',
146
+ '.....######.....',
147
+ '......####......',
148
+ '.......##.......',
149
+ '................',
150
+ '................',
151
+ '................',
152
+ '................',
153
+ ],
154
+ 'chevron-left': [
155
+ '................',
156
+ '..........##....',
157
+ '.........###....',
158
+ '........###.....',
159
+ '.......###......',
160
+ '......###.......',
161
+ '.....###........',
162
+ '....###.........',
163
+ '....###.........',
164
+ '.....###........',
165
+ '......###.......',
166
+ '.......###......',
167
+ '........###.....',
168
+ '.........###....',
169
+ '..........##....',
170
+ '................',
171
+ ],
172
+ 'chevron-right': [
173
+ '................',
174
+ '....##..........',
175
+ '....###.........',
176
+ '.....###........',
177
+ '......###.......',
178
+ '.......###......',
179
+ '........###.....',
180
+ '.........###....',
181
+ '.........###....',
182
+ '........###.....',
183
+ '.......###......',
184
+ '......###.......',
185
+ '.....###........',
186
+ '....###.........',
187
+ '....##..........',
188
+ '................',
189
+ ],
190
+ 'chevron-up': [
191
+ '................',
192
+ '................',
193
+ '................',
194
+ '................',
195
+ '.......##.......',
196
+ '......####......',
197
+ '.....######.....',
198
+ '....###..###....',
199
+ '...###....###...',
200
+ '..###......###..',
201
+ '.###........###.',
202
+ '.##..........##.',
203
+ '................',
204
+ '................',
205
+ '................',
206
+ '................',
207
+ ],
208
+ clock: [
209
+ '................',
210
+ '.......##.......',
211
+ '....########....',
212
+ '...##......##...',
213
+ '..##...##...##..',
214
+ '..#....##....#..',
215
+ '..#....##....#..',
216
+ '.##....####..##.',
217
+ '.##....####..##.',
218
+ '..#..........#..',
219
+ '..#..........#..',
220
+ '..##........##..',
221
+ '...##......##...',
222
+ '....########....',
223
+ '.......##.......',
224
+ '................',
225
+ ],
226
+ close: [
227
+ '................',
228
+ '................',
229
+ '.###........###.',
230
+ '..###......###..',
231
+ '...###....###...',
232
+ '....###..###....',
233
+ '.....######.....',
234
+ '......####......',
235
+ '......####......',
236
+ '.....######.....',
237
+ '....###..###....',
238
+ '...###....###...',
239
+ '..###......###..',
240
+ '.###........###.',
241
+ '................',
242
+ '................',
243
+ ],
244
+ download: [
245
+ '................',
246
+ '.......##.......',
247
+ '.......##.......',
248
+ '.......##.......',
249
+ '.......##.......',
250
+ '.......##.......',
251
+ '...#########....',
252
+ '....#######.....',
253
+ '.....#####......',
254
+ '......###.......',
255
+ '.......#........',
256
+ '................',
257
+ '.##############.',
258
+ '.##############.',
259
+ '................',
260
+ '................',
261
+ ],
262
+ edit: [
263
+ '................',
264
+ '...........####.',
265
+ '..........#####.',
266
+ '.........#####..',
267
+ '........#####...',
268
+ '.......#####....',
269
+ '......#####.....',
270
+ '.....#####......',
271
+ '....#####.......',
272
+ '...#####........',
273
+ '..#####.........',
274
+ '.#####..........',
275
+ '.####...........',
276
+ '.###............',
277
+ '.#..............',
278
+ '................',
279
+ ],
280
+ eye: [
281
+ '................',
282
+ '................',
283
+ '................',
284
+ '....########....',
285
+ '..####....####..',
286
+ '.###..####..###.',
287
+ '.##..######..##.',
288
+ '##..########..##',
289
+ '.##..######..##.',
290
+ '.###..####..###.',
291
+ '..####....####..',
292
+ '....########....',
293
+ '................',
294
+ '................',
295
+ '................',
296
+ '................',
297
+ ],
298
+ 'eye-off': [
299
+ '................',
300
+ '................',
301
+ '#...............',
302
+ '.#..########....',
303
+ '..####....####..',
304
+ '.###.#####..###.',
305
+ '.##..###.##..##.',
306
+ '##..####.###..##',
307
+ '.##..######.#.#.',
308
+ '.###..####..###.',
309
+ '..####....####.#',
310
+ '....########.#..',
311
+ '............#...',
312
+ '...........#....',
313
+ '................',
314
+ '................',
315
+ ],
316
+ file: [
317
+ '................',
318
+ '..##########....',
319
+ '..##......###...',
320
+ '..##......####..',
321
+ '..##......#.##..',
322
+ '..##......####..',
323
+ '..##........##..',
324
+ '..##........##..',
325
+ '..##........##..',
326
+ '..##........##..',
327
+ '..##........##..',
328
+ '..##........##..',
329
+ '..############..',
330
+ '................',
331
+ '................',
332
+ '................',
333
+ ],
334
+ folder: [
335
+ '................',
336
+ '................',
337
+ '..#####.........',
338
+ '.#######........',
339
+ '.##############.',
340
+ '.##############.',
341
+ '.##..........##.',
342
+ '.##..........##.',
343
+ '.##..........##.',
344
+ '.##..........##.',
345
+ '.##..........##.',
346
+ '.##############.',
347
+ '................',
348
+ '................',
349
+ '................',
350
+ '................',
351
+ ],
352
+ gear: [
353
+ '.......##.......',
354
+ '.......##.......',
355
+ '......####......',
356
+ '....########....',
357
+ '...##########...',
358
+ '...###....###...',
359
+ '..###......###..',
360
+ '#####......#####',
361
+ '#####......#####',
362
+ '..###......###..',
363
+ '...###....###...',
364
+ '...##########...',
365
+ '....########....',
366
+ '......####......',
367
+ '.......##.......',
368
+ '.......##.......',
369
+ ],
370
+ grid: [
371
+ '................',
372
+ '..#####..#####..',
373
+ '..#####..#####..',
374
+ '..#####..#####..',
375
+ '..#####..#####..',
376
+ '..#####..#####..',
377
+ '................',
378
+ '................',
379
+ '..#####..#####..',
380
+ '..#####..#####..',
381
+ '..#####..#####..',
382
+ '..#####..#####..',
383
+ '..#####..#####..',
384
+ '................',
385
+ '................',
386
+ '................',
387
+ ],
388
+ heart: [
389
+ '................',
390
+ '...###....###...',
391
+ '..#####..#####..',
392
+ '.######..######.',
393
+ '.##############.',
394
+ '.##############.',
395
+ '..############..',
396
+ '..############..',
397
+ '...##########...',
398
+ '....########....',
399
+ '.....######.....',
400
+ '......####......',
401
+ '.......##.......',
402
+ '................',
403
+ '................',
404
+ '................',
405
+ ],
406
+ home: [
407
+ '................',
408
+ '.......##.......',
409
+ '......####......',
410
+ '.....######.....',
411
+ '....########....',
412
+ '...##########...',
413
+ '..############..',
414
+ '.##############.',
415
+ '..############..',
416
+ '..##........##..',
417
+ '..##..####..##..',
418
+ '..##..####..##..',
419
+ '..##..####..##..',
420
+ '..############..',
421
+ '................',
422
+ '................',
423
+ ],
424
+ info: [
425
+ '................',
426
+ '......####......',
427
+ '.....######.....',
428
+ '.....######.....',
429
+ '......####......',
430
+ '................',
431
+ '....######......',
432
+ '......####......',
433
+ '......####......',
434
+ '......####......',
435
+ '......####......',
436
+ '....##########..',
437
+ '....##########..',
438
+ '................',
439
+ '................',
440
+ '................',
441
+ ],
442
+ link: [
443
+ '................',
444
+ '................',
445
+ '.....######.....',
446
+ '....########....',
447
+ '...###....###...',
448
+ '..###......###..',
449
+ '..##........##..',
450
+ '....######......',
451
+ '......######....',
452
+ '..##........##..',
453
+ '..###......###..',
454
+ '...###....###...',
455
+ '....########....',
456
+ '.....######.....',
457
+ '................',
458
+ '................',
459
+ ],
460
+ lock: [
461
+ '................',
462
+ '.....######.....',
463
+ '....##....##....',
464
+ '....##....##....',
465
+ '...##......##...',
466
+ '...##......##...',
467
+ '..############..',
468
+ '..############..',
469
+ '..####....####..',
470
+ '..###......###..',
471
+ '..###......###..',
472
+ '..####....####..',
473
+ '..############..',
474
+ '..############..',
475
+ '................',
476
+ '................',
477
+ ],
478
+ mail: [
479
+ '................',
480
+ '................',
481
+ '.##############.',
482
+ '.##..........##.',
483
+ '.#.##......##.#.',
484
+ '.#..##....##..#.',
485
+ '.#...##..##...#.',
486
+ '.#....####....#.',
487
+ '.#.....##.....#.',
488
+ '.#............#.',
489
+ '.#............#.',
490
+ '.##############.',
491
+ '................',
492
+ '................',
493
+ '................',
494
+ '................',
495
+ ],
496
+ menu: [
497
+ '................',
498
+ '................',
499
+ '.##############.',
500
+ '.##############.',
501
+ '................',
502
+ '................',
503
+ '.##############.',
504
+ '.##############.',
505
+ '................',
506
+ '................',
507
+ '.##############.',
508
+ '.##############.',
509
+ '................',
510
+ '................',
511
+ '................',
512
+ '................',
513
+ ],
514
+ minus: [
515
+ '................',
516
+ '................',
517
+ '................',
518
+ '................',
519
+ '................',
520
+ '................',
521
+ '..############..',
522
+ '..############..',
523
+ '..############..',
524
+ '..############..',
525
+ '................',
526
+ '................',
527
+ '................',
528
+ '................',
529
+ '................',
530
+ '................',
531
+ ],
532
+ moon: [
533
+ '................',
534
+ '......##........',
535
+ '....###.........',
536
+ '...###..........',
537
+ '..####..........',
538
+ '..####..........',
539
+ '.#####..........',
540
+ '.#####..........',
541
+ '.#####..........',
542
+ '.######.........',
543
+ '..######........',
544
+ '..#######.......',
545
+ '...##########...',
546
+ '....########....',
547
+ '......####......',
548
+ '................',
549
+ ],
550
+ 'more-horizontal': [
551
+ '................',
552
+ '................',
553
+ '................',
554
+ '................',
555
+ '................',
556
+ '................',
557
+ '................',
558
+ '.##...##...##...',
559
+ '.##...##...##...',
560
+ '................',
561
+ '................',
562
+ '................',
563
+ '................',
564
+ '................',
565
+ '................',
566
+ '................',
567
+ ],
568
+ 'more-vertical': [
569
+ '................',
570
+ '.......##.......',
571
+ '.......##.......',
572
+ '................',
573
+ '................',
574
+ '.......##.......',
575
+ '.......##.......',
576
+ '................',
577
+ '................',
578
+ '.......##.......',
579
+ '.......##.......',
580
+ '................',
581
+ '................',
582
+ '................',
583
+ '................',
584
+ '................',
585
+ ],
586
+ pause: [
587
+ '................',
588
+ '...###....###...',
589
+ '...###....###...',
590
+ '...###....###...',
591
+ '...###....###...',
592
+ '...###....###...',
593
+ '...###....###...',
594
+ '...###....###...',
595
+ '...###....###...',
596
+ '...###....###...',
597
+ '...###....###...',
598
+ '...###....###...',
599
+ '...###....###...',
600
+ '...###....###...',
601
+ '................',
602
+ '................',
603
+ ],
604
+ play: [
605
+ '................',
606
+ '....#...........',
607
+ '....##..........',
608
+ '....###.........',
609
+ '....####........',
610
+ '....#####.......',
611
+ '....######......',
612
+ '....#######.....',
613
+ '....#######.....',
614
+ '....######......',
615
+ '....#####.......',
616
+ '....####........',
617
+ '....###.........',
618
+ '....##..........',
619
+ '....#...........',
620
+ '................',
621
+ ],
622
+ plus: [
623
+ '................',
624
+ '................',
625
+ '.......##.......',
626
+ '.......##.......',
627
+ '.......##.......',
628
+ '.......##.......',
629
+ '..############..',
630
+ '..############..',
631
+ '..############..',
632
+ '..############..',
633
+ '.......##.......',
634
+ '.......##.......',
635
+ '.......##.......',
636
+ '.......##.......',
637
+ '................',
638
+ '................',
639
+ ],
640
+ refresh: [
641
+ '.....##.........',
642
+ '.....####.......',
643
+ '.....###........',
644
+ '.....##.........',
645
+ '.....#....#.....',
646
+ '....##....##....',
647
+ '...##......##...',
648
+ '...#........#...',
649
+ '...#........#...',
650
+ '...#........#...',
651
+ '...##......##...',
652
+ '....##....##....',
653
+ '.....######.....',
654
+ '......####......',
655
+ '................',
656
+ '................',
657
+ ],
658
+ search: [
659
+ '................',
660
+ '...######.......',
661
+ '..########......',
662
+ '.###....###.....',
663
+ '.##......##.....',
664
+ '.##......##.....',
665
+ '.##......##.....',
666
+ '.###....###.....',
667
+ '..########......',
668
+ '...#######......',
669
+ '......#####.....',
670
+ '.......#####....',
671
+ '........#####...',
672
+ '.........###....',
673
+ '................',
674
+ '................',
675
+ ],
676
+ spark: [
677
+ '................',
678
+ '.......**.......',
679
+ '.......**.......',
680
+ '.......**.......',
681
+ '.......**.......',
682
+ '.......**.......',
683
+ '......****......',
684
+ '.******##******.',
685
+ '.******##******.',
686
+ '......****......',
687
+ '.......**.......',
688
+ '.......**.......',
689
+ '.......**.......',
690
+ '.......**.......',
691
+ '.......**.......',
692
+ '................',
693
+ ],
694
+ star: [
695
+ '................',
696
+ '.......##.......',
697
+ '.......##.......',
698
+ '......####......',
699
+ '......####......',
700
+ '.##############.',
701
+ '..############..',
702
+ '...##########...',
703
+ '....########....',
704
+ '....########....',
705
+ '...####..####...',
706
+ '..####....####..',
707
+ '..###......###..',
708
+ '................',
709
+ '................',
710
+ '................',
711
+ ],
712
+ sun: [
713
+ '.......##.......',
714
+ '.......##.......',
715
+ '..#..........#..',
716
+ '...#........#...',
717
+ '................',
718
+ '......####......',
719
+ '.....######.....',
720
+ '##...######...##',
721
+ '##...######...##',
722
+ '.....######.....',
723
+ '......####......',
724
+ '................',
725
+ '...#........#...',
726
+ '..#..........#..',
727
+ '.......##.......',
728
+ '.......##.......',
729
+ ],
730
+ trash: [
731
+ '................',
732
+ '......####......',
733
+ '....########....',
734
+ '.##############.',
735
+ '................',
736
+ '..############..',
737
+ '..###.##.##.##..',
738
+ '..###.##.##.##..',
739
+ '..###.##.##.##..',
740
+ '..###.##.##.##..',
741
+ '..###.##.##.##..',
742
+ '..###.##.##.##..',
743
+ '..###.##.##.##..',
744
+ '..############..',
745
+ '................',
746
+ '................',
747
+ ],
748
+ upload: [
749
+ '................',
750
+ '.......#........',
751
+ '......###.......',
752
+ '.....#####......',
753
+ '....#######.....',
754
+ '...#########....',
755
+ '.......##.......',
756
+ '.......##.......',
757
+ '.......##.......',
758
+ '.......##.......',
759
+ '.......##.......',
760
+ '................',
761
+ '.##############.',
762
+ '.##############.',
763
+ '................',
764
+ '................',
765
+ ],
766
+ user: [
767
+ '................',
768
+ '.....######.....',
769
+ '....########....',
770
+ '....########....',
771
+ '....########....',
772
+ '.....######.....',
773
+ '................',
774
+ '..############..',
775
+ '.##############.',
776
+ '###..........###',
777
+ '###..........###',
778
+ '###..........###',
779
+ '##............##',
780
+ '................',
781
+ '................',
782
+ '................',
783
+ ],
784
+ warning: [
785
+ '................',
786
+ '.......##.......',
787
+ '.......##.......',
788
+ '......####......',
789
+ '......####......',
790
+ '.....##..##.....',
791
+ '.....##..##.....',
792
+ '....###..###....',
793
+ '....##....##....',
794
+ '...###....###...',
795
+ '...##.####.##...',
796
+ '..###.####.###..',
797
+ '..##........##..',
798
+ '.###############',
799
+ '.###############',
800
+ '................',
801
+ ],
802
+ };
803
+
804
+ /** The frozen name→bitmap registry (keys sorted, every row frozen). */
805
+ export const GLYPHS = Object.freeze(
806
+ Object.fromEntries(
807
+ Object.keys(RAW)
808
+ .sort()
809
+ .map((name) => [name, Object.freeze(RAW[name])]),
810
+ ),
811
+ );
812
+
813
+ /** Every glyph name, frozen and sorted (mirrors GLYPHS keys). */
814
+ export const GLYPH_NAMES = Object.freeze(Object.keys(GLYPHS));
815
+
816
+ const TONE = { '#': 'hot', '*': 'accent' };
817
+
818
+ /** The raw bitmap rows for a glyph, or `undefined` if the name is unknown. */
819
+ export function glyph(name) {
820
+ return GLYPHS[name];
821
+ }
822
+
823
+ /** GLYPH_SIZE² cell descriptors (row-major), or `[]` if the name is unknown. */
824
+ export function glyphCells(name) {
825
+ const rows = GLYPHS[name];
826
+ if (!rows) return [];
827
+ const cells = [];
828
+ for (const row of rows) {
829
+ for (const ch of row) {
830
+ if (ch === '.') cells.push({ on: false });
831
+ else cells.push({ on: true, tone: TONE[ch] });
832
+ }
833
+ }
834
+ return cells;
835
+ }
836
+
837
+ const CELL = 'ui-dotmatrix__cell';
838
+
839
+ function cellClass(cell) {
840
+ if (!cell.on) return CELL;
841
+ if (cell.tone === 'hot') return `${CELL} ${CELL}--hot`;
842
+ if (cell.tone === 'accent') return `${CELL} ${CELL}--accent`;
843
+ return CELL;
844
+ }
845
+
846
+ function esc(s) {
847
+ return String(s)
848
+ .replace(/&/g, '&')
849
+ .replace(/</g, '&lt;')
850
+ .replace(/>/g, '&gt;')
851
+ .replace(/"/g, '&quot;');
852
+ }
853
+
854
+ // `dot`/`gap` land in an inline-CSS context (`style="--dotmatrix-dot:VALUE"`),
855
+ // where HTML-escaping a `"` stops attribute breakout but a `;` would still open
856
+ // a second CSS declaration (overlay/clickjacking, selector exfil). So restrict
857
+ // them to length/calc syntax — digits, units, %, whitespace and `()+-*/.,` for
858
+ // calc()/clamp()/var() — and drop anything else rather than emit it.
859
+ function cssLen(v) {
860
+ return /^[\w.%+\-*/()\s,]+$/.test(v) ? v : '';
861
+ }
862
+
863
+ /**
864
+ * A full `.ui-dotmatrix` HTML string for a glyph (`''` if the name is
865
+ * unknown). Decorative by default (`aria-hidden`); pass `label` to expose it
866
+ * as `role="img"`. Pins `--dotmatrix-cols` to GLYPH_SIZE so the square layout
867
+ * holds regardless of the 12-col default; `dot`/`gap` set `--dotmatrix-dot` /
868
+ * `--dotmatrix-gap` (sanitized to a CSS length/calc allowlist; a malformed
869
+ * value is dropped). `grid: false` drops the unlit panel dots (glyph-only).
870
+ *
871
+ * `solid: true` renders the lit cells as square, gapless pixels (a filled
872
+ * silhouette) instead of separated dots — the legible mode at small/inline
873
+ * sizes (~16–24px) where the dot look fragments. It implies glyph-only.
874
+ *
875
+ * `anim` opts into a decorative animation (disabled under reduced-motion;
876
+ * the static frame + `label` still carry the meaning): `'reveal'` powers the
877
+ * cells on in a scan, `'pulse'` makes the glyph breathe.
878
+ */
879
+ export function renderGlyph(name, options = {}) {
880
+ const cells = glyphCells(name);
881
+ if (!cells.length) return '';
882
+ const { grid = true, solid = false, anim, label, dot, gap } = options;
883
+
884
+ const style = [`--dotmatrix-cols:${GLYPH_SIZE}`];
885
+ const dotLen = dot && cssLen(dot);
886
+ const gapLen = gap && cssLen(gap);
887
+ if (dotLen) style.push(`--dotmatrix-dot:${dotLen}`);
888
+ // Solid mode fuses the dots into a crisp pixel glyph: square cells, no gap.
889
+ if (solid) style.push('--dotmatrix-dot-radius:0', '--dotmatrix-gap:0');
890
+ else if (gapLen) style.push(`--dotmatrix-gap:${gapLen}`);
891
+
892
+ const cls =
893
+ anim === 'reveal'
894
+ ? 'ui-dotmatrix ui-dotmatrix--reveal'
895
+ : anim === 'pulse'
896
+ ? 'ui-dotmatrix ui-dotmatrix--pulse'
897
+ : 'ui-dotmatrix';
898
+ // `reveal` staggers each cell by its row-major index via `--i`.
899
+ const stagger = anim === 'reveal';
900
+
901
+ const a11y = label ? `role="img" aria-label="${esc(label)}"` : 'aria-hidden="true"';
902
+
903
+ // Off cells keep the cell class (so they hold their grid track and 1:1
904
+ // aspect-ratio); `grid: false` (implied by `solid`) only drops their lit
905
+ // background, for the glyph-only look, without collapsing all-off rows.
906
+ const showPanel = grid && !solid;
907
+ const inner = cells
908
+ .map((c, i) => {
909
+ const cl = c.on ? cellClass(c) : 'ui-dotmatrix__cell';
910
+ const cellStyle = [];
911
+ if (!c.on && !showPanel) cellStyle.push('background:transparent');
912
+ if (stagger) cellStyle.push(`--i:${i}`);
913
+ const s = cellStyle.length ? ` style="${cellStyle.join(';')}"` : '';
914
+ return `<span class="${cl}"${s}></span>`;
915
+ })
916
+ .join('');
917
+
918
+ // A `<span>` (not `<div>`): it's phrasing content, so the glyph is valid
919
+ // inline and inside a `<button>` (its content model is phrasing-only) — the
920
+ // inline-icon use. `.ui-dotmatrix`'s `display: grid` works on a span.
921
+ return `<span class="${cls}" style="${style.join(';')}" ${a11y}>${inner}</span>`;
922
+ }