loading-state-zoo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/theme.css ADDED
@@ -0,0 +1,525 @@
1
+ /* src/theme.css */
2
+ @keyframes lz-spin {
3
+ to {
4
+ transform: rotate(360deg);
5
+ }
6
+ }
7
+ @keyframes lz-spin-reverse {
8
+ to {
9
+ transform: rotate(-360deg);
10
+ }
11
+ }
12
+ @keyframes lz-dot-bounce {
13
+ 0% {
14
+ transform: translateY(0);
15
+ }
16
+ 50% {
17
+ transform: translateY(var(--lz-bounce, -10px));
18
+ }
19
+ 100% {
20
+ transform: translateY(0);
21
+ }
22
+ }
23
+ @keyframes lz-eq-wave {
24
+ 0% {
25
+ transform: scaleY(var(--lz-eq-min, 0.3));
26
+ }
27
+ 50% {
28
+ transform: scaleY(1);
29
+ }
30
+ 100% {
31
+ transform: scaleY(var(--lz-eq-min, 0.3));
32
+ }
33
+ }
34
+ @keyframes lz-typing-pulse {
35
+ 0%, 100% {
36
+ transform: scale(var(--lz-typing-min, 0.35));
37
+ }
38
+ 50% {
39
+ transform: scale(var(--lz-typing-peak, 1.15));
40
+ }
41
+ }
42
+ @keyframes lz-dot-wave {
43
+ 0%, 100% {
44
+ transform: scale(var(--lz-wave-min, 0.35));
45
+ }
46
+ 40% {
47
+ transform: scale(var(--lz-wave-peak, 1.1));
48
+ }
49
+ }
50
+ @keyframes lz-pulse-breath {
51
+ 0%, 100% {
52
+ transform: scale(1);
53
+ }
54
+ 50% {
55
+ transform: scale(var(--lz-breath, 1.14));
56
+ }
57
+ }
58
+ @keyframes lz-shape-morph {
59
+ 0% {
60
+ transform: rotate(0deg) scale(var(--lz-morph, 0.72));
61
+ border-radius: 50%;
62
+ }
63
+ 50% {
64
+ transform: rotate(45deg) scale(1);
65
+ border-radius: var(--lz-morph-radius, 17%);
66
+ }
67
+ 100% {
68
+ transform: rotate(90deg) scale(var(--lz-morph, 0.72));
69
+ border-radius: 50%;
70
+ }
71
+ }
72
+ @keyframes lz-eye-gaze {
73
+ 0%, 100% {
74
+ transform: translateX(var(--lz-gaze-from, -3px));
75
+ }
76
+ 50% {
77
+ transform: translateX(var(--lz-gaze-to, 3px));
78
+ }
79
+ }
80
+ @keyframes lz-eye-blink {
81
+ 0%, 88%, 100% {
82
+ transform: scaleY(1);
83
+ }
84
+ 93% {
85
+ transform: scaleY(var(--lz-blink, 0.08));
86
+ }
87
+ }
88
+ @keyframes lz-skeleton-sheen {
89
+ 0% {
90
+ transform: translateX(var(--lz-sheen-from, -120%));
91
+ }
92
+ 100% {
93
+ transform: translateX(var(--lz-sheen-to, 220%));
94
+ }
95
+ }
96
+ @keyframes lz-orbit-ring-dot {
97
+ 0% {
98
+ opacity: var(--lz-orbit-fade, 0.12);
99
+ }
100
+ 50% {
101
+ opacity: 1;
102
+ }
103
+ 90% {
104
+ opacity: var(--lz-orbit-fade, 0.12);
105
+ }
106
+ 100% {
107
+ opacity: var(--lz-orbit-fade, 0.12);
108
+ }
109
+ }
110
+ @keyframes lz-indeterminate-slide {
111
+ 0% {
112
+ transform: translateX(var(--lz-slide-from, -100%));
113
+ }
114
+ 100% {
115
+ transform: translateX(var(--lz-slide-to, 300%));
116
+ }
117
+ }
118
+ @keyframes lz-helix-bob {
119
+ 0%, 100% {
120
+ transform: translateY(0);
121
+ }
122
+ 50% {
123
+ transform: translateY(var(--lz-helix, -12px));
124
+ }
125
+ }
126
+ @keyframes lz-grid-dot-wave {
127
+ 0% {
128
+ transform: scale(var(--lz-grid-min, 0.3));
129
+ opacity: var(--lz-grid-fade, 0.15);
130
+ }
131
+ 50% {
132
+ transform: scale(1);
133
+ opacity: 1;
134
+ }
135
+ 100% {
136
+ transform: scale(var(--lz-grid-min, 0.3));
137
+ opacity: var(--lz-grid-fade, 0.15);
138
+ }
139
+ }
140
+ .lz-bouncing-dots {
141
+ display: flex;
142
+ gap: 0.7em;
143
+ font-size: var(--lz-size, 8px);
144
+ }
145
+ .lz-bouncing-dots .lz-dot {
146
+ width: 1em;
147
+ height: 1em;
148
+ border-radius: 50%;
149
+ background: var(--lz-color, #f5f5f7);
150
+ animation: lz-dot-bounce calc(1.2s * var(--lz-speed, 1)) cubic-bezier(0.28, 0.1, 0.25, 1.15) infinite;
151
+ }
152
+ .lz-bouncing-dots .lz-dot:nth-child(2) {
153
+ animation-delay: 0.15s;
154
+ }
155
+ .lz-bouncing-dots .lz-dot:nth-child(3) {
156
+ animation-delay: 0.3s;
157
+ }
158
+ .lz-typing-dots {
159
+ display: flex;
160
+ gap: 0.8em;
161
+ font-size: var(--lz-size, 8px);
162
+ }
163
+ .lz-typing-dots .lz-dot {
164
+ width: 1em;
165
+ height: 1em;
166
+ border-radius: 50%;
167
+ background: var(--lz-color, #f5f5f7);
168
+ animation: lz-typing-pulse calc(0.9s * var(--lz-speed, 1)) cubic-bezier(0.28, 0.1, 0.25, 1.15) infinite;
169
+ }
170
+ .lz-typing-dots .lz-dot:nth-child(2) {
171
+ animation-delay: -0.18s;
172
+ }
173
+ .lz-typing-dots .lz-dot:nth-child(3) {
174
+ animation-delay: -0.36s;
175
+ }
176
+ .lz-wave-dots {
177
+ display: flex;
178
+ gap: 0.6em;
179
+ font-size: var(--lz-size, 8px);
180
+ }
181
+ .lz-wave-dots .lz-dot {
182
+ width: 1em;
183
+ height: 1em;
184
+ border-radius: 50%;
185
+ background: var(--lz-color, #f5f5f7);
186
+ animation: lz-dot-wave calc(1.2s * var(--lz-speed, 1)) cubic-bezier(0.28, 0.1, 0.25, 1.15) infinite;
187
+ }
188
+ .lz-wave-dots .lz-dot:nth-child(2) {
189
+ animation-delay: -0.1s;
190
+ }
191
+ .lz-wave-dots .lz-dot:nth-child(3) {
192
+ animation-delay: -0.2s;
193
+ }
194
+ .lz-wave-dots .lz-dot:nth-child(4) {
195
+ animation-delay: -0.3s;
196
+ }
197
+ .lz-helix-dots {
198
+ display: flex;
199
+ gap: 0.9em;
200
+ font-size: var(--lz-size, 6px);
201
+ }
202
+ .lz-helix-dots .lz-dot {
203
+ display: inline-block;
204
+ width: 1em;
205
+ height: 1em;
206
+ border-radius: 50%;
207
+ background: var(--lz-color, #f5f5f7);
208
+ animation: lz-helix-bob calc(1.4s * var(--lz-speed, 1)) ease-in-out infinite;
209
+ }
210
+ .lz-dot-grid-wave {
211
+ display: grid;
212
+ grid-template-columns: repeat(5, 1em);
213
+ gap: var(--lz-grid-gap, 12px);
214
+ font-size: var(--lz-size, 6px);
215
+ }
216
+ .lz-dot-grid-wave .lz-dot {
217
+ width: 1em;
218
+ height: 1em;
219
+ border-radius: 50%;
220
+ background: var(--lz-color, #f5f5f7);
221
+ animation: lz-grid-dot-wave calc(1.2s * var(--lz-speed, 1)) cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
222
+ }
223
+ .lz-arc-spinner {
224
+ width: var(--lz-size, 44px);
225
+ height: var(--lz-size, 44px);
226
+ border-radius: 50%;
227
+ background:
228
+ conic-gradient(
229
+ from 90deg,
230
+ var(--lz-color, #f5f5f7) 0deg,
231
+ var(--lz-color, #f5f5f7) 110deg,
232
+ transparent 111deg);
233
+ -webkit-mask:
234
+ radial-gradient(
235
+ farthest-side,
236
+ transparent calc(100% - var(--lz-stroke, 4px)),
237
+ #000 calc(100% - var(--lz-stroke, 4px)));
238
+ mask:
239
+ radial-gradient(
240
+ farthest-side,
241
+ transparent calc(100% - var(--lz-stroke, 4px)),
242
+ #000 calc(100% - var(--lz-stroke, 4px)));
243
+ animation: lz-spin calc(1s * var(--lz-speed, 1)) linear infinite;
244
+ }
245
+ .lz-material-double-arc {
246
+ position: relative;
247
+ width: var(--lz-size, 44px);
248
+ height: var(--lz-size, 44px);
249
+ }
250
+ .lz-material-double-arc .lz-arc {
251
+ position: absolute;
252
+ inset: 0;
253
+ border-radius: 50%;
254
+ -webkit-mask:
255
+ radial-gradient(
256
+ farthest-side,
257
+ transparent calc(100% - var(--lz-stroke, 4px)),
258
+ #000 calc(100% - var(--lz-stroke, 4px)));
259
+ mask:
260
+ radial-gradient(
261
+ farthest-side,
262
+ transparent calc(100% - var(--lz-stroke, 4px)),
263
+ #000 calc(100% - var(--lz-stroke, 4px)));
264
+ }
265
+ .lz-material-double-arc .lz-arc-a {
266
+ background:
267
+ conic-gradient(
268
+ from 0deg,
269
+ var(--lz-color, #f5f5f7) 0deg,
270
+ var(--lz-color, #f5f5f7) 135deg,
271
+ transparent 136deg);
272
+ animation: lz-spin calc(1s * var(--lz-speed, 1)) linear infinite;
273
+ }
274
+ .lz-material-double-arc .lz-arc-b {
275
+ opacity: 0.6;
276
+ background:
277
+ conic-gradient(
278
+ from 200deg,
279
+ var(--lz-color, #f5f5f7) 0deg,
280
+ var(--lz-color, #f5f5f7) 90deg,
281
+ transparent 91deg);
282
+ animation: lz-spin-reverse calc(2.4s * var(--lz-speed, 1)) linear infinite;
283
+ }
284
+ .lz-scan-sweep {
285
+ position: relative;
286
+ width: var(--lz-size, 44px);
287
+ height: var(--lz-size, 44px);
288
+ }
289
+ .lz-scan-sweep .lz-ring {
290
+ position: absolute;
291
+ inset: 0;
292
+ border-radius: 50%;
293
+ border: 1px solid var(--lz-track, rgba(255, 255, 255, 0.12));
294
+ }
295
+ .lz-scan-sweep .lz-ring-inner {
296
+ position: absolute;
297
+ inset: 0.28em;
298
+ border-radius: 50%;
299
+ border: 1px solid var(--lz-track-weak, rgba(255, 255, 255, 0.08));
300
+ }
301
+ .lz-scan-sweep .lz-core {
302
+ position: absolute;
303
+ width: 0.14em;
304
+ height: 0.14em;
305
+ left: 0.43em;
306
+ top: 0.43em;
307
+ border-radius: 50%;
308
+ background: var(--lz-color-weak, rgba(245, 245, 247, 0.8));
309
+ }
310
+ .lz-scan-sweep .lz-sweep {
311
+ position: absolute;
312
+ inset: 0;
313
+ border-radius: 50%;
314
+ background:
315
+ conic-gradient(
316
+ from 0deg,
317
+ var(--lz-color, #f5f5f7) 0deg,
318
+ var(--lz-color-sweep, rgba(245, 245, 247, 0.08)) 60deg,
319
+ transparent 90deg);
320
+ -webkit-mask: radial-gradient(closest-side, #000 100%);
321
+ mask: radial-gradient(closest-side, #000 100%);
322
+ animation: lz-spin calc(2.5s * var(--lz-speed, 1)) linear infinite;
323
+ }
324
+ .lz-orbit-dots {
325
+ position: relative;
326
+ width: var(--lz-size, 44px);
327
+ height: var(--lz-size, 44px);
328
+ }
329
+ .lz-orbit-dots .lz-orbit {
330
+ position: absolute;
331
+ inset: 0;
332
+ animation: lz-spin calc(1.8s * var(--lz-speed, 1)) linear infinite;
333
+ }
334
+ .lz-orbit-dots .lz-dot {
335
+ position: absolute;
336
+ top: 50%;
337
+ left: 50%;
338
+ width: 0.16em;
339
+ height: 0.16em;
340
+ margin: -0.08em 0 0 -0.08em;
341
+ border-radius: 50%;
342
+ background: var(--lz-color-weak, rgba(245, 245, 247, 0.9));
343
+ }
344
+ .lz-orbit-dots .lz-dot:nth-child(1) {
345
+ transform: rotate(0deg) translateY(-0.36em);
346
+ }
347
+ .lz-orbit-dots .lz-dot:nth-child(2) {
348
+ transform: rotate(120deg) translateY(-0.36em);
349
+ }
350
+ .lz-orbit-dots .lz-dot:nth-child(3) {
351
+ transform: rotate(240deg) translateY(-0.36em);
352
+ }
353
+ .lz-orbit-ring-dots {
354
+ position: relative;
355
+ width: var(--lz-size, 44px);
356
+ height: var(--lz-size, 44px);
357
+ }
358
+ .lz-orbit-ring-dots .lz-dot {
359
+ position: absolute;
360
+ top: 50%;
361
+ left: 50%;
362
+ width: 0.18em;
363
+ height: 0.18em;
364
+ margin: -0.09em 0 0 -0.09em;
365
+ border-radius: 50%;
366
+ background: var(--lz-color, #f5f5f7);
367
+ animation: lz-orbit-ring-dot calc(1.2s * var(--lz-speed, 1)) linear infinite;
368
+ }
369
+ .lz-orbit-ring-dots .lz-dot:nth-child(1) {
370
+ transform: rotate(0deg) translateY(-0.41em);
371
+ animation-delay: 0s;
372
+ }
373
+ .lz-orbit-ring-dots .lz-dot:nth-child(2) {
374
+ transform: rotate(45deg) translateY(-0.41em);
375
+ animation-delay: -0.15s;
376
+ }
377
+ .lz-orbit-ring-dots .lz-dot:nth-child(3) {
378
+ transform: rotate(90deg) translateY(-0.41em);
379
+ animation-delay: -0.3s;
380
+ }
381
+ .lz-orbit-ring-dots .lz-dot:nth-child(4) {
382
+ transform: rotate(135deg) translateY(-0.41em);
383
+ animation-delay: -0.45s;
384
+ }
385
+ .lz-orbit-ring-dots .lz-dot:nth-child(5) {
386
+ transform: rotate(180deg) translateY(-0.41em);
387
+ animation-delay: -0.6s;
388
+ }
389
+ .lz-orbit-ring-dots .lz-dot:nth-child(6) {
390
+ transform: rotate(225deg) translateY(-0.41em);
391
+ animation-delay: -0.75s;
392
+ }
393
+ .lz-orbit-ring-dots .lz-dot:nth-child(7) {
394
+ transform: rotate(270deg) translateY(-0.41em);
395
+ animation-delay: -0.9s;
396
+ }
397
+ .lz-orbit-ring-dots .lz-dot:nth-child(8) {
398
+ transform: rotate(315deg) translateY(-0.41em);
399
+ animation-delay: -1.05s;
400
+ }
401
+ .lz-eyes {
402
+ display: flex;
403
+ gap: 0.24em;
404
+ font-size: var(--lz-size, 44px);
405
+ }
406
+ .lz-eyes .lz-eye {
407
+ display: flex;
408
+ align-items: center;
409
+ justify-content: center;
410
+ width: 0.34em;
411
+ height: 0.2788em;
412
+ border-radius: 50%;
413
+ background: var(--lz-color, #f5f5f7);
414
+ animation: lz-eye-blink calc(4s * var(--lz-speed, 1)) ease-in-out infinite;
415
+ }
416
+ .lz-eyes .lz-pupil {
417
+ width: 0.17em;
418
+ height: 0.17em;
419
+ border-radius: 50%;
420
+ background: var(--lz-pupil, rgba(0, 0, 0, 0.85));
421
+ animation: lz-eye-gaze calc(1.6s * var(--lz-speed, 1)) ease-in-out infinite;
422
+ }
423
+ .lz-morphing-square {
424
+ width: var(--lz-size, 44px);
425
+ height: var(--lz-size, 44px);
426
+ background: var(--lz-accent, #0a84ff);
427
+ box-shadow: 0 0 calc(0.41 * var(--lz-size, 44px)) var(--lz-glow, rgba(10, 132, 255, 0.35));
428
+ animation: lz-shape-morph calc(2s * var(--lz-speed, 1)) cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
429
+ }
430
+ .lz-breathing-pulse {
431
+ width: var(--lz-size, 36px);
432
+ height: var(--lz-size, 36px);
433
+ border-radius: 50%;
434
+ background: var(--lz-color, #f5f5f7);
435
+ animation: lz-pulse-breath calc(2.4s * var(--lz-speed, 1)) ease-in-out infinite;
436
+ }
437
+ .lz-equalizer {
438
+ display: flex;
439
+ align-items: flex-end;
440
+ gap: var(--lz-gap, 3px);
441
+ }
442
+ .lz-equalizer .lz-bar {
443
+ width: var(--lz-bar-width, 3px);
444
+ height: var(--lz-size, 22px);
445
+ border-radius: 2px;
446
+ background: var(--lz-color, #f5f5f7);
447
+ transform-origin: bottom;
448
+ animation: lz-eq-wave calc(1s * var(--lz-speed, 1)) cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
449
+ }
450
+ .lz-equalizer .lz-bar:nth-child(2) {
451
+ animation-delay: 0.12s;
452
+ }
453
+ .lz-equalizer .lz-bar:nth-child(3) {
454
+ animation-delay: 0.24s;
455
+ }
456
+ .lz-equalizer .lz-bar:nth-child(4) {
457
+ animation-delay: 0.36s;
458
+ }
459
+ .lz-progress-ring {
460
+ position: relative;
461
+ width: var(--lz-size, 44px);
462
+ height: var(--lz-size, 44px);
463
+ }
464
+ .lz-progress-ring .lz-ring {
465
+ position: absolute;
466
+ inset: 0;
467
+ border-radius: 50%;
468
+ -webkit-mask:
469
+ radial-gradient(
470
+ farthest-side,
471
+ transparent calc(100% - var(--lz-stroke, 4px)),
472
+ #000 calc(100% - var(--lz-stroke, 4px)));
473
+ mask:
474
+ radial-gradient(
475
+ farthest-side,
476
+ transparent calc(100% - var(--lz-stroke, 4px)),
477
+ #000 calc(100% - var(--lz-stroke, 4px)));
478
+ }
479
+ .lz-progress-ring .lz-track {
480
+ background: conic-gradient(var(--lz-track, rgba(255, 255, 255, 0.15)) 0deg, var(--lz-track, rgba(255, 255, 255, 0.15)) 360deg);
481
+ }
482
+ .lz-progress-ring .lz-fill {
483
+ background: conic-gradient(var(--lz-color, #f5f5f7) calc(var(--lz-percent, 0) * 3.6deg), transparent 0deg);
484
+ transition: background 0.2s ease;
485
+ }
486
+ .lz-indeterminate-progress-bar {
487
+ position: relative;
488
+ overflow: hidden;
489
+ width: var(--lz-width, 160px);
490
+ height: var(--lz-height, 6px);
491
+ border-radius: 999px;
492
+ background: var(--lz-surface, #1c1c1e);
493
+ }
494
+ .lz-indeterminate-progress-bar .lz-bar {
495
+ position: absolute;
496
+ top: 0;
497
+ bottom: 0;
498
+ left: 0;
499
+ width: 36%;
500
+ border-radius: 999px;
501
+ background: var(--lz-accent, #0a84ff);
502
+ animation: lz-indeterminate-slide calc(1.6s * var(--lz-speed, 1)) cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
503
+ }
504
+ .lz-skeleton-shimmer {
505
+ position: relative;
506
+ overflow: hidden;
507
+ width: var(--lz-width, 120px);
508
+ height: var(--lz-height, 28px);
509
+ border-radius: var(--lz-radius, 10px);
510
+ background: var(--lz-surface, #1c1c1e);
511
+ }
512
+ .lz-skeleton-shimmer .lz-sheen {
513
+ position: absolute;
514
+ top: 0;
515
+ bottom: 0;
516
+ left: 0;
517
+ width: 50%;
518
+ background:
519
+ linear-gradient(
520
+ to right,
521
+ transparent,
522
+ var(--lz-sheen, rgba(255, 255, 255, 0.1)),
523
+ transparent);
524
+ animation: lz-skeleton-sheen calc(1.4s * var(--lz-speed, 1)) linear infinite;
525
+ }
package/docs/AI.md ADDED
@@ -0,0 +1,69 @@
1
+ # loading-state-zoo — AI & Agent Guide
2
+
3
+ **Audience: LLMs, coding assistants, and automation. This file is written to be parsed reliably.** The authoritative machine-readable spec is `docs/components.json` — when in doubt, read that file.
4
+
5
+ ## Package identity
6
+
7
+ - Package name: `loading-state-zoo`
8
+ - Version: `0.1.0`
9
+ - 17 loading-pattern UI components. All are *web components* (custom elements) with a thin React wrapper plus a standalone CSS theme.
10
+ - License: MIT. No data collection of any kind.
11
+
12
+ ## Entry points (exact import strings)
13
+
14
+ | Purpose | Import |
15
+ | --- | --- |
16
+ | Framework-agnostic web components | `import "loading-state-zoo";` |
17
+ | Web components, manual registration | `import { defineAll } from "loading-state-zoo";` |
18
+ | React components | `import { BouncingDots } from "loading-state-zoo/react";` |
19
+ | Plain CSS + hand-written markup | link `node_modules/loading-state-zoo/dist/theme.css` (export `loading-state-zoo/theme.css`) |
20
+
21
+ ## Critical rules for agents
22
+
23
+ 1. **Auto-registration.** Importing either entry point calls `defineAll()` once. Every `defineX()` and `customElements.define` is idempotent (guarded). Never call `defineAll()` twice, never register elements manually first.
24
+ 2. **SSR-safe.** On Node/servers (`HTMLElement` undefined) importing does nothing and never throws. React wrappers are plain function components; they render fine server-side as unknown tags.
25
+ 3. **React prop naming.** React wrapper props map to web attributes. Multi-word attributes become camelCase in React:
26
+ - `stroke-width` → `strokeWidth`
27
+ - `track-color` → `trackColor`
28
+ - `dot-size` → `dotSize`
29
+ - `bar-width` → `barWidth`
30
+ - `color-weak` → `colorWeak`
31
+ - `color-sweep` → `colorSweep`
32
+ Every React wrapper's props are declared in `dist/react.d.ts`.
33
+ 4. **Theming is CSS variables, not style overrides.** Because styles are in shadow DOM, do NOT try to reach inside. Theme by setting vars on the host or an ancestor:
34
+ - `--lz-color` (foreground), `--lz-accent` (accent), `--lz-speed` (1 = default), `--lz-stroke`, `--lz-surface`, `--lz-track`.
35
+ - In React, pass `style={{ ["--lz-color" as string]: "#0a84ff" }}` or wrap in a themed `<div>`.
36
+ 5. **Motion is also CSS variables.** Keyframe numbers are tunable without forking CSS: `--lz-bounce`, `--lz-typing-peak`, `--lz-helix`, `--lz-blink`, `--lz-sheen-from/to`, `--lz-slide-from/to`, etc. Full table in `docs/components.json` → `keyframeVars`.
37
+ 6. **Size semantics** — the `size` attribute means different things per family:
38
+ - Dots: dot diameter (default 8 → Helix/Grid 6).
39
+ - Spinners/shapes: the square bounding box (default 44 → Breathing 36).
40
+ - Bars/skeletons: use `width` + `height` instead (Indeterminate 160×6, Skeleton 120×28), Equalizer uses `size` = bar height (22).
41
+ 7. **Progress ring is static** (driven by `percent` 0–100, clamped; `role="progressbar"` with live `aria-valuenow`). It does not animate by itself.
42
+
43
+ ## Component registry (17)
44
+
45
+ Dots: `lz-bouncing-dots`, `lz-typing-dots`, `lz-wave-dots`, `lz-helix-dots`, `lz-dot-grid-wave`
46
+ Spinners: `lz-arc-spinner`, `lz-material-double-arc`, `lz-scan-sweep`, `lz-orbit-dots`, `lz-orbit-ring-dots`
47
+ Shapes: `lz-eyes`, `lz-morphing-square`, `lz-breathing-pulse`
48
+ Bars & rings: `lz-equalizer`, `lz-progress-ring`, `lz-indeterminate-progress-bar`
49
+ Skeletons: `lz-skeleton-shimmer`
50
+
51
+ Every element: shadow DOM (`mode:"open"`), inline `<style>` per element (shared keyframes string), geometry in `em` relative to host `font-size: var(--lz-size, 44px)`.
52
+
53
+ ## Minimal valid usage
54
+
55
+ Web:
56
+ ```html
57
+ <script type="module">
58
+ import "loading-state-zoo";
59
+ </script>
60
+ <lz-wave-dots></lz-wave-dots>
61
+ ```
62
+
63
+ React:
64
+ ```tsx
65
+ import { WaveDots, ArcSpinner } from "loading-state-zoo/react";
66
+ export default () => <WaveDots size={10} color="#0a84ff" />;
67
+ ```
68
+
69
+ Verify your work: `tsc --noEmit` passes, `tsup` build succeeds, Node `import('loading-state-zoo')` returns a module and registers nothing (no crash).