shapy-bg 0.0.0-stage → 1.0.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/README.md CHANGED
@@ -1,3 +1,712 @@
1
- # Temporary Holding Version
1
+ # shapy-bg
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Seedable generative shape backgrounds as a zero-dependency Web Component.
4
+
5
+ Shapy turns a short seed into a one-of-one composition of shapes, lines, organic
6
+ forms, emoji, images, or inline icons. It can also render an optional text
7
+ overlay, so a single element can be a complete hero background.
8
+
9
+ > **New to Shapy?** Read the
10
+ > [step-by-step guide](https://github.com/hemant-rao/shapy-bg/blob/main/docs/GETTING_STARTED.md).
11
+ > It covers installation, your first background, common recipes, and
12
+ > troubleshooting.
13
+
14
+ > **Sponsored by [Odio Book](https://odiobook.com)** — the all-in-one AI video &
15
+ > audio studio: AI video editing, auto subtitles, scene detection, voice
16
+ > generation and text-to-speech, all in the browser. Odio Book funds the time
17
+ > that goes into this package. **[Try it free →](https://odiobook.com)**
18
+
19
+ ---
20
+
21
+ ## Contents
22
+
23
+ | | |
24
+ | --- | --- |
25
+ | [Install](#install) · [Quick start](#quick-start) | Get running in 2 minutes |
26
+ | [What you can control](#what-you-can-control) · [Attributes](#attributes) | Every option and default |
27
+ | [Deterministic frames and video](#deterministic-frames-and-video) · [Text overlay](#text-overlay) · [Theming](#theming) | Capture, text, colors |
28
+ | [Custom shapes, images, and icons](#custom-shapes-images-and-icons) · [Optional stickers](#optional-stickers) | Your own media |
29
+ | [JavaScript API](#javascript-api) · [TypeScript](#typescript) | Programmatic control |
30
+ | [Troubleshooting](#troubleshooting) | Fixes |
31
+ | [Support this project](#support-this-project) · [Sponsor: Odio Book](#sponsor-odio-book) | Sponsor the maintenance |
32
+
33
+ ---
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ npm install shapy-bg
39
+ ```
40
+
41
+ Shapy is a browser-only Web Component. It needs Custom Elements and Shadow DOM,
42
+ which are available in current Chrome, Edge, Firefox, and Safari.
43
+
44
+ ## Quick start
45
+
46
+ ### 1. Import the package once
47
+
48
+ ```js
49
+ import 'shapy-bg';
50
+ ```
51
+
52
+ ### 2. Add the element
53
+
54
+ ```html
55
+ <shapy-background
56
+ seed="4D5E465BCA"
57
+ palette="midnight"
58
+ density="72"
59
+ scale="110"
60
+ style="position: fixed; inset: 0; display: block;"
61
+ ></shapy-background>
62
+ ```
63
+
64
+ ### 3. Give it a size
65
+
66
+ The element fills its parent. The inline style above makes it fill the viewport.
67
+ You can also use CSS:
68
+
69
+ ```css
70
+ shapy-background {
71
+ position: fixed;
72
+ inset: 0;
73
+ display: block;
74
+ }
75
+ ```
76
+
77
+ ## What you can control
78
+
79
+ - **The composition:** seed, shape pool, layout, alignment, size mode, density,
80
+ scale, motion, direction, speed, and movement.
81
+ - **The colors:** palette, background, text, muted text, accent, glow, and scrim,
82
+ with per-element attributes, host CSS variables, or both.
83
+ - **The text:** an eyebrow, headline, accent line, and body paragraph, positioned
84
+ in any of nine spots. Text is plain text, never HTML.
85
+ - **The media:** built-in shapes and emoji, the optional sticker registry, your
86
+ own image URLs, and safe inline SVG icons built from path data.
87
+ - **The API:** `setOptions()`, `getOptions()`, `seek()`, `refresh()`,
88
+ `shapy:ready`, and `shapy:change`.
89
+ - **Video and stills:** `live="off"`, `time`, seamless `loop`, 9:16 safe zones,
90
+ and exact frame capture with `seek()`.
91
+ - **The hooks:** `::part(text)`, `::part(eyebrow)`, `::part(headline)`,
92
+ `::part(body)`, and the `--shapy-*` host variables.
93
+
94
+ ## Attributes
95
+
96
+ ### Pattern and motion
97
+
98
+ | Attribute | Default | Description |
99
+ | --- | --- | --- |
100
+ | `seed` | fresh 128-bit seed | Deterministic seed. If omitted, each element instance gets a fresh 128-bit seed. |
101
+ | `palette` | `midnight` | `midnight`, `sandstone`, `bauhaus`, `forest`, `plum`, `ocean`, `clay`, `mono`. |
102
+ | `shape-set` | `all` | `all`, `geometry`, `lines`, `organic`, `emoji`, `stickers`, `custom`, or a registry group id. |
103
+ | `shapes` | empty | Comma list or JSON array of shape keys for `shape-set="custom"`. |
104
+ | `registry-data` | empty | JSON string with custom shape groups. `registry` is an alias. |
105
+ | `layout` | `auto` | `auto`, `scatter`, `grid`, `rows`, `columns`, `center`, `edges`. |
106
+ | `align` | `random` | `random`, `left`, `center`, `right`, `top`, `middle`, `bottom`. |
107
+ | `motion` | `mixed` | `mixed`, `chaos`, `flow`, `bounce`, `drift`, `spin`, `pulse`, `wobble`, `orbit`, `calm`. |
108
+ | `direction` | `random` | `random`, `lr`, `rl`, `tb`, `bt`, `dr`, `dl`, `ur`, `ul`, `alternate`. |
109
+ | `density` | `72` | 20–100. How many shapes are drawn. |
110
+ | `scale` | `110` | 60–140. Overall shape size multiplier, including responsive caps. |
111
+ | `size-mode` | `mixed` | `mixed`, `same`, `subtle`, `dramatic`, `small`, `medium`, `large`. |
112
+ | `speed` | `100` | 50–200. Animation speed multiplier. |
113
+ | `move` | `100` | 30–200. Movement distance multiplier. |
114
+ | `custom-colors` | empty | Comma-separated CSS colors. Unsafe or invalid tokens are dropped. |
115
+ | `safe-area` | off | Boolean attribute. Dims shapes behind the visible text overlay. |
116
+ | `safe-zones` | empty | Dims shapes inside platform UI bands. Presets: `tiktok`, `reels`, `shorts`, `story`; or a JSON array of `{top,right,bottom,left}` percentages. |
117
+ | `live` | on | `off` disables the periodic live mutations. Animations keep running unless `paused` or `time` is set. |
118
+ | `time` | unset | Freezes the timeline at an exact second. Implies deterministic mode. |
119
+ | `loop` | off | Seamless loop period in seconds. Bare `loop` uses 10 seconds. Loop mode disables live mutations. |
120
+ | `aspect` | `auto` | `auto`, `9:16`, `16:9`, `1:1`, or `4:5`. Sets the host aspect ratio when the parent allows automatic height. |
121
+ | `paused` | off | Boolean attribute. Pauses all animations. |
122
+
123
+ ### Colors
124
+
125
+ All color attributes accept a normal CSS color: hex, `rgb()`, `hsl()`, named
126
+ colors, `transparent`, and so on. Invalid values are ignored. See
127
+ [Theming](#theming) for precedence.
128
+
129
+ | Attribute | Default | Description |
130
+ | --- | --- | --- |
131
+ | `background` | palette background | Page/background base color. Aliases: `backgroundColor`, `bg`. |
132
+ | `text-color` | palette ink | Headline and primary text. Aliases: `textColor`, `foreground`. |
133
+ | `muted-color` | palette muted | Eyebrow and body text. |
134
+ | `accent-color` | palette accent | Accent text and highlighted words. |
135
+ | `glow-color` | palette glow | Radial glow behind the composition. |
136
+ | `scrim-color` | palette scrim | Readability scrim color. Visible only with `scrim`. |
137
+ | `scrim` | off | Boolean attribute. Shows the readability scrim. |
138
+
139
+ ### Text
140
+
141
+ Text is optional. Leave every text attribute empty to render only the
142
+ background.
143
+
144
+ | Attribute | Default | Description |
145
+ | --- | --- | --- |
146
+ | `text` | empty | Headline. Use a real newline (or `&#10;` in HTML) for line breaks. `headline` is an alias. |
147
+ | `text-accent` | empty | Accent line appended after the headline in the accent color. |
148
+ | `subtext` | empty | Supporting paragraph. `description` is an alias. |
149
+ | `eyebrow` | empty | Small uppercase label above the headline. |
150
+ | `text-position` | `middle-left` | `top-left`, `top-center`, `top-right`, `middle-left`, `middle-center`, `middle-right`, `bottom-left`, `bottom-center`, `bottom-right`. |
151
+ | `text-align` | inferred | `left`, `center`, or `right`. Defaults to the alignment implied by `text-position`. |
152
+ | `text-scale` | `100` | 50–200. Scales the headline and body text. |
153
+ | `font-family` | package display font | A safe CSS font stack, for example `Georgia, serif`. |
154
+ | `font-weight` | `400` | 100–900. Headline weight. |
155
+
156
+ Boolean attributes are enabled by presence:
157
+
158
+ ```html
159
+ <shapy-background safe-area paused scrim></shapy-background>
160
+ ```
161
+
162
+ ## Deterministic frames and video
163
+
164
+ Shapy can be the visual source for a video pipeline. Use `live="off"` to stop the
165
+ periodic composition mutations, and `seek()` to freeze an exact frame:
166
+
167
+ ```html
168
+ <shapy-background
169
+ seed="reel-01"
170
+ palette="ocean"
171
+ live="off"
172
+ loop="8"
173
+ safe-zones="tiktok"
174
+ aspect="9:16"
175
+ style="width: 100%; display: block;"
176
+ ></shapy-background>
177
+ ```
178
+
179
+ ```js
180
+ const element = document.querySelector('shapy-background');
181
+
182
+ await element.seek(2.5); // freeze at 2.5 seconds
183
+ // capture the element with your screenshot or recording tool
184
+
185
+ await element.seek(null); // resume the live timeline
186
+ ```
187
+
188
+ - `live="off"` keeps the seeded composition fixed. Only the CSS animations move.
189
+ - `time="4"` is the declarative equivalent of `seek(4)`.
190
+ - `loop="8"` makes every animation duration divide 8 seconds exactly, so the
191
+ frame at `8s` matches `0s`. `seek()` wraps around the loop.
192
+ - `safe-zones` accepts a comma list of presets or a JSON array of custom
193
+ rectangles. Custom values are percentages of the host box:
194
+
195
+ ```html
196
+ <shapy-background
197
+ safe-zones='[{"top":0,"right":100,"bottom":15,"left":0}]'
198
+ ></shapy-background>
199
+ ```
200
+
201
+ - `aspect="9:16"` sets an aspect ratio on the host. For a full-bleed video, size
202
+ a wrapper to 1080×1920 instead and let the element fill it.
203
+ - `paused` freezes animations but does not disable live mutations. Use
204
+ `live="off"` or `time` for reproducible output.
205
+
206
+ ### Seed stability
207
+
208
+ A seed is stable within an engine version. The same seed, options, viewport,
209
+ device pixel ratio, and `SHAPY_ENGINE_VERSION` produce the same static
210
+ composition and the same `seek()` frames. Package patches and minors keep the
211
+ engine version; any change that alters generated output bumps the engine version
212
+ and the package major version. Live mutations are intentionally non-deterministic
213
+ and are outside this contract. See
214
+ [Seed stability](https://github.com/hemant-rao/shapy-bg/blob/main/docs/SEED_STABILITY.md).
215
+
216
+ ## Video export
217
+
218
+ The core package has no encoder and stays zero-dependency. Use the separate
219
+ `shapy-render` package for MP4, WebM, or PNG sequences:
220
+
221
+ ```bash
222
+ npm install shapy-bg shapy-render
223
+ npx playwright install chromium
224
+ npx shapy-render --seed reel-01 --duration 8 --output reel.mp4
225
+ ```
226
+
227
+ - MP4 needs a system `ffmpeg` with `libx264`.
228
+ - Non-alpha WebM VP9 works with Playwright's bundled ffmpeg. Alpha WebM needs a
229
+ full ffmpeg with PNG decoding.
230
+ - Alpha is supported in WebM and PNG, not MP4.
231
+ - Keep `--duration` a multiple of `--loop` for a clean repeat.
232
+ - `shapy-render` captures with `live="off"` and `seek()` for deterministic frames.
233
+
234
+ See the [shapy-render README](https://github.com/hemant-rao/shapy-bg/tree/main/packages/shapy-render#readme)
235
+ for the CLI and library API.
236
+
237
+ ## Text overlay
238
+
239
+ ```html
240
+ <shapy-background
241
+ seed="hero"
242
+ palette="ocean"
243
+ background="#06121f"
244
+ text-color="#f8fafc"
245
+ accent-color="#fbbf24"
246
+ eyebrow="Shapy package"
247
+ text="Build something&#10;memorable"
248
+ text-accent="today."
249
+ subtext="A complete hero background from one custom element."
250
+ text-position="middle-center"
251
+ text-scale="110"
252
+ font-family="Georgia, serif"
253
+ font-weight="600"
254
+ scrim
255
+ safe-area
256
+ style="position: fixed; inset: 0; display: block;"
257
+ ></shapy-background>
258
+ ```
259
+
260
+ - `text` and `text-accent` are rendered as text nodes. HTML is not interpreted.
261
+ - `text-accent` is appended on its own line and uses the accent color.
262
+ - The overlay is `pointer-events: none`; it never blocks clicks on your page.
263
+ - When text is visible, the default host role becomes `group` and the default
264
+ label comes from the headline. When no text is set, the role returns to `img`
265
+ and the label returns to `Shapy generative background`.
266
+ - A host-supplied `role` or `aria-label` is never overwritten.
267
+ - `safe-area` uses the visible text overlay's bounds, so shapes behind the text
268
+ are dimmed.
269
+
270
+ ## Theming
271
+
272
+ Color precedence, from strongest to weakest:
273
+
274
+ 1. An explicit color attribute on the element.
275
+ 2. A host CSS custom property on the element or an ancestor.
276
+ 3. The selected palette.
277
+
278
+ | Host variable | Controls |
279
+ | --- | --- |
280
+ | `--shapy-bg` | Background base color |
281
+ | `--shapy-ink` | Primary text color |
282
+ | `--shapy-muted` | Muted text color |
283
+ | `--shapy-accent` | Accent text color |
284
+ | `--shapy-glow` | Radial glow color |
285
+ | `--shapy-scrim` | Scrim color |
286
+
287
+ ```css
288
+ .hero-background {
289
+ --shapy-bg: #08111f;
290
+ --shapy-ink: #f8fafc;
291
+ --shapy-muted: #a5b4c8;
292
+ --shapy-accent: #fbbf24;
293
+ --shapy-glow: rgb(251 191 36 / 0.18);
294
+ --shapy-scrim: rgb(8 17 31 / 0.72);
295
+ }
296
+ ```
297
+
298
+ ```html
299
+ <shapy-background class="hero-background" palette="ocean" scrim></shapy-background>
300
+ ```
301
+
302
+ `custom-colors` is separate: it overrides the colors used by the generated
303
+ shapes, while the color attributes and host variables theme the page, scrim, and
304
+ text.
305
+
306
+ ### CSS parts
307
+
308
+ The text overlay exposes stable parts:
309
+
310
+ ```css
311
+ shapy-background::part(text) {
312
+ max-width: 52rem;
313
+ }
314
+
315
+ shapy-background::part(headline) {
316
+ letter-spacing: -0.05em;
317
+ text-transform: uppercase;
318
+ }
319
+
320
+ shapy-background::part(eyebrow) {
321
+ letter-spacing: 0.24em;
322
+ }
323
+
324
+ shapy-background::part(body) {
325
+ max-width: 36ch;
326
+ }
327
+ ```
328
+
329
+ ## Custom shapes, images, and icons
330
+
331
+ Custom media lives in a registry. The registry is passed as JSON through
332
+ `registry-data`; it is never stored globally.
333
+
334
+ ```js
335
+ const registry = {
336
+ brand: {
337
+ label: 'Brand marks',
338
+ items: [
339
+ { key: 'logo', label: 'Logo', image: '/logo.svg', size: 1.1 },
340
+ { key: 'spark', label: 'Spark', emoji: '✨', color: '#fbbf24' },
341
+ {
342
+ key: 'heart',
343
+ label: 'Heart',
344
+ icon: {
345
+ path: 'M12 21s-7-4.35-9.33-8.02C.8 9.9 2.2 6 5.5 6c2 0 3.2 1.2 3.5 1.6C9.3 7.2 10.5 6 12.5 6c3.3 0 4.7 3.9 2.83 6.98C19 16.65 12 21 12 21z',
346
+ viewBox: '0 0 24 24',
347
+ fill: '#ff3366'
348
+ },
349
+ size: 1.2
350
+ }
351
+ ]
352
+ }
353
+ };
354
+
355
+ const element = document.querySelector('shapy-background');
356
+ element.setAttribute('registry-data', JSON.stringify(registry));
357
+ element.setAttribute('shape-set', 'brand');
358
+ ```
359
+
360
+ Or select individual registry items:
361
+
362
+ ```js
363
+ element.setAttribute('shape-set', 'custom');
364
+ element.setAttribute('shapes', 'logo,spark,heart');
365
+ ```
366
+
367
+ ### Registry item format
368
+
369
+ ```js
370
+ {
371
+ key: 'unique-key', // string; auto-generated if omitted
372
+ label: 'Readable name', // optional; used as image alt text
373
+ emoji: '✨', // optional media
374
+ image: '/logo.svg', // optional media
375
+ icon: { ... }, // optional media
376
+ color: '#fbbf24', // optional tint for emoji/icons
377
+ size: 1, // 0.05–5, default 1
378
+ weight: 1, // 0.01–100, default 1
379
+ ratio: [1, 1] // [width, height], each 0.05–10
380
+ }
381
+ ```
382
+
383
+ An item must provide at least one of `emoji`, `image`, or `icon`; items without
384
+ renderable media are dropped. If more than one is provided, `emoji` wins, then
385
+ `icon`, then `image`.
386
+
387
+ A group can be an object with `label` and `items`, or just an array of items:
388
+
389
+ ```js
390
+ const registry = {
391
+ simple: [
392
+ { key: 'star', emoji: '⭐' },
393
+ { key: 'dot', image: '/dot.png' }
394
+ ]
395
+ };
396
+ ```
397
+
398
+ ### Image formats
399
+
400
+ Allowed:
401
+
402
+ - `https://example.com/logo.png`
403
+ - `//cdn.example.com/logo.png`
404
+ - `/assets/logo.svg`, `./logo.svg`, `../logo.svg` (resolved by the browser
405
+ against your page)
406
+ - `data:image/png;base64,...`
407
+ - `data:image/jpeg;...`, `data:image/gif;...`, `data:image/webp;...`,
408
+ `data:image/avif;...`, `data:image/svg+xml;...`
409
+
410
+ Rejected:
411
+
412
+ - `javascript:...`
413
+ - `data:text/html,...`
414
+ - `blob:...`, `ftp:...`, and every other scheme
415
+ - URLs containing backslashes
416
+
417
+ Images render with `object-fit: contain`. Shapy validates the URL format but
418
+ does not fetch, proxy, or inspect remote image content.
419
+
420
+ ### Icon format
421
+
422
+ Icons are path data, not raw SVG markup. Raw `<svg>` strings are rejected.
423
+
424
+ ```js
425
+ {
426
+ path: 'M12 2 2 22h20L12 2z', // one path
427
+ paths: ['M...', 'M...'], // or several paths
428
+ viewBox: '0 0 24 24', // optional, four numbers
429
+ fill: 'currentColor', // optional CSS color
430
+ stroke: '#ffffff', // optional CSS color
431
+ strokeWidth: 2 // optional, 0–50
432
+ }
433
+ ```
434
+
435
+ Defaults:
436
+
437
+ - `viewBox` is `0 0 24 24` when omitted or invalid.
438
+ - With no `fill` and no `stroke`, paths use `currentColor`.
439
+ - With `stroke` and no `strokeWidth`, the width is `2`.
440
+ - Path data is limited to SVG path commands and numbers.
441
+
442
+ ### Limits and sanitization
443
+
444
+ - 20 registry groups per element.
445
+ - 200 items per group.
446
+ - 64 custom colors.
447
+ - 256 shape keys.
448
+ - Labels are truncated to 120 characters; emoji to 32 characters.
449
+ - Numeric values are clamped to the ranges above.
450
+ - Every value is treated as untrusted; unsafe colors, images, and icons are
451
+ dropped instead of being rendered.
452
+
453
+ ## Optional stickers
454
+
455
+ ```js
456
+ import 'shapy-bg';
457
+ import { STICKER_REGISTRY } from 'shapy-bg/stickers';
458
+
459
+ const element = document.querySelector('shapy-background');
460
+ element.setAttribute('registry-data', JSON.stringify(STICKER_REGISTRY));
461
+ element.setAttribute('shape-set', 'stickers');
462
+ ```
463
+
464
+ `shapy-bg/stickers` exports:
465
+
466
+ - `STICKER_CATALOG` — serializable sticker items.
467
+ - `STICKER_REGISTRY` — a ready-to-serialize registry group.
468
+ - `getRegistryData()` — returns the JSON string for `registry-data`.
469
+ - `registerStickers(element)` — sets `registry-data` on an element and returns
470
+ the registry. It never writes to `globalThis`.
471
+
472
+ ```js
473
+ import { registerStickers } from 'shapy-bg/stickers';
474
+
475
+ const element = document.querySelector('shapy-background');
476
+ registerStickers(element);
477
+ element.setAttribute('shape-set', 'stickers');
478
+ ```
479
+
480
+ Stickers are part of the `shapy-bg` package and load only when you import the
481
+ `shapy-bg/stickers` subpath.
482
+
483
+ ## JavaScript API
484
+
485
+ ```js
486
+ import { createShapy, createSeed } from 'shapy-bg';
487
+
488
+ const element = createShapy().mount(document.querySelector('#background'), {
489
+ seed: '4D5E465BCA',
490
+ palette: 'ocean',
491
+ shapeSet: 'custom',
492
+ shapes: ['circle', 'emoji-0'],
493
+ layout: 'rows',
494
+ align: 'center',
495
+ sizeMode: 'same',
496
+ background: '#08111f',
497
+ accentColor: '#fbbf24',
498
+ text: 'Shape your',
499
+ textAccent: 'story.',
500
+ textPosition: 'middle-center',
501
+ subtext: 'A complete hero from one element.'
502
+ });
503
+
504
+ element.setOptions({ palette: 'plum', text: 'Updated live' });
505
+ element.refresh();
506
+ console.log(element.getOptions());
507
+ ```
508
+
509
+ `mount()` accepts camelCase or kebab-case keys, takes an element as the
510
+ container, and returns the created element. Use `document.querySelector()` if
511
+ you only have a selector string. It replaces the container's existing content.
512
+
513
+ ### `setOptions(options)`
514
+
515
+ Updates any subset of options and returns the element. camelCase and kebab-case
516
+ keys both work. `null`, `undefined`, and `false` remove an option; `true` adds a
517
+ boolean attribute.
518
+
519
+ ```js
520
+ element.setOptions({
521
+ density: 48,
522
+ paused: true,
523
+ text: null,
524
+ background: null
525
+ });
526
+ ```
527
+
528
+ ### `getOptions()`
529
+
530
+ Returns the current normalized options, including the generated or explicit
531
+ seed, parsed registry, color values, and text settings. `customColors` is an
532
+ array of colors.
533
+
534
+ ```js
535
+ const options = element.getOptions();
536
+ console.log(options.seed, options.density, options.customColors);
537
+ ```
538
+
539
+ ### `refresh()`
540
+
541
+ Forces a re-render with the current attributes and returns the element.
542
+
543
+ ```js
544
+ element.refresh();
545
+ ```
546
+
547
+ ### `seek(seconds)`
548
+
549
+ Freezes the renderer at an exact timeline position. The promise resolves after
550
+ the frame has been painted. Pass `null` to resume the live timeline.
551
+
552
+ ```js
553
+ await element.seek(3.5);
554
+ await element.seek(null);
555
+ ```
556
+
557
+ `seek()` does not rewrite the `time` attribute, so repeated frame captures do not
558
+ trigger a full re-render. It does disable live mutations while frozen.
559
+
560
+ ### Events
561
+
562
+ ```js
563
+ element.addEventListener('shapy:ready', (event) => {
564
+ console.log('first render', event.detail.seed, event.detail.options);
565
+ });
566
+
567
+ element.addEventListener('shapy:change', (event) => {
568
+ console.log('updated options', event.detail.options);
569
+ });
570
+ ```
571
+
572
+ - `shapy:ready` fires once after the first render.
573
+ - `shapy:change` fires after `setOptions()`, `refresh()`, and debounced attribute
574
+ updates.
575
+ - Both events expose `detail.element`; `shapy:ready` also exposes
576
+ `detail.seed`. Both include `detail.options` from `getOptions()`.
577
+
578
+ ### New seed
579
+
580
+ ```js
581
+ element.setAttribute('seed', createSeed());
582
+ ```
583
+
584
+ ## TypeScript
585
+
586
+ Type definitions ship with the package. The element, options, registry items,
587
+ icons, events, and methods are all typed:
588
+
589
+ ```ts
590
+ import { createShapy, type ShapyOptions, type ShapyRegistry } from 'shapy-bg';
591
+
592
+ const registry: ShapyRegistry = {
593
+ brand: {
594
+ label: 'Brand',
595
+ items: [{ key: 'logo', image: '/logo.svg', size: 1.1 }]
596
+ }
597
+ };
598
+
599
+ const options: ShapyOptions = {
600
+ seed: '4D5E465BCA',
601
+ palette: 'ocean',
602
+ text: 'Hello',
603
+ textPosition: 'middle-center',
604
+ registryData: registry
605
+ };
606
+
607
+ const container = document.querySelector('#background');
608
+ if (container) createShapy().mount(container, options);
609
+ ```
610
+
611
+ ## Troubleshooting
612
+
613
+ ### Nothing appears
614
+
615
+ 1. Did you import `shapy-bg`?
616
+ 2. Does `shapy-background` have a size? Try
617
+ `shapy-background { position: fixed; inset: 0; display: block; }`.
618
+ 3. Check the browser console for errors.
619
+
620
+ ### The text does not appear
621
+
622
+ Check that at least one of `text`, `text-accent`, `subtext`, or `eyebrow` is set.
623
+ An empty `text` attribute alone is not enough.
624
+
625
+ ### A custom item is missing
626
+
627
+ An item needs `emoji`, `image`, or `icon`. Check that the key is selected through
628
+ `shape-set` or `shapes`, and that the image URL uses an allowed format.
629
+
630
+ ### Stickers do not appear
631
+
632
+ Import from `shapy-bg/stickers`, set `registry-data` to
633
+ `JSON.stringify(STICKER_REGISTRY)`, and set `shape-set="stickers"` or use
634
+ `shape-set="custom"` with sticker keys.
635
+
636
+ ### Vue says "Failed to resolve component: shapy-background"
637
+
638
+ Add the `isCustomElement` option shown in the
639
+ [step-by-step guide](https://github.com/hemant-rao/shapy-bg/blob/main/docs/GETTING_STARTED.md#use-with-vue-3).
640
+
641
+ ### The pattern changes every time I reload
642
+
643
+ Pass a fixed `seed`; without one, each element gets a fresh 128-bit seed.
644
+
645
+ ## Package exports
646
+
647
+ | Import | What you get |
648
+ | --- | --- |
649
+ | `shapy-bg` | Registers `<shapy-background>`, and exports `createShapy()`, `createSeed()`, `SHAPY_ENGINE_VERSION`, and the catalog helpers. |
650
+ | `shapy-bg/element` | Registers `<shapy-background>` only. |
651
+ | `shapy-bg/catalog` | Shape and emoji catalog data without registering the element. |
652
+ | `shapy-bg/stickers` | Sticker catalog, serializable registry, and helpers. |
653
+
654
+ ## Documentation
655
+
656
+ - [Step-by-step guide](https://github.com/hemant-rao/shapy-bg/blob/main/docs/GETTING_STARTED.md)
657
+ - [Attributes](#attributes)
658
+ - [Text overlay](#text-overlay)
659
+ - [Theming](#theming)
660
+ - [Custom shapes, images, and icons](#custom-shapes-images-and-icons)
661
+ - [JavaScript API](#javascript-api)
662
+ - [Full repository documentation](https://github.com/hemant-rao/shapy-bg#readme)
663
+ - [Contributing](https://github.com/hemant-rao/shapy-bg/blob/main/CONTRIBUTING.md)
664
+ - [Security policy](https://github.com/hemant-rao/shapy-bg/blob/main/SECURITY.md)
665
+ - [Code of conduct](https://github.com/hemant-rao/shapy-bg/blob/main/CODE_OF_CONDUCT.md)
666
+
667
+ ## Support this project
668
+
669
+ `shapy-bg` is free and MIT-licensed, built and maintained by
670
+ [Hemant Rao](https://github.com/hemant-rao) in his own time. If it saved you a
671
+ day of work — or your company ships it in production — you can back its
672
+ maintenance:
673
+
674
+ [![Sponsor on GitHub](https://img.shields.io/badge/Sponsor-%E2%9D%A4-db61a2?style=for-the-badge&logo=githubsponsors&logoColor=white)](https://github.com/sponsors/hemant-rao)
675
+ &nbsp;
676
+ [![Star on GitHub](https://img.shields.io/github/stars/hemant-rao/shapy-bg?style=for-the-badge&logo=github&color=444)](https://github.com/hemant-rao/shapy-bg)
677
+
678
+ **[Become a sponsor](https://github.com/sponsors/hemant-rao)** — one-time or
679
+ monthly, any amount.
680
+
681
+ Sponsorship funds bug fixes, browser testing, and keeping the docs honest.
682
+ Starring the repo and reporting issues helps just as much.
683
+
684
+ ## Sponsor: Odio Book
685
+
686
+ Maintenance of this package is sponsored by **[Odio Book](https://odiobook.com)**,
687
+ built by the same developer.
688
+
689
+ **Odio Book is an all-in-one AI video & audio studio that runs in your browser** —
690
+ describe the edit in plain English and it happens. No install, no watermarks,
691
+ and your files never leave the tab.
692
+
693
+ | | |
694
+ | --- | --- |
695
+ | **[AI Video Editor](https://odiobook.com)** | Plain-English editing instead of a timeline |
696
+ | **[AI Subtitles](https://odiobook.com)** | Auto-generate and burn in subtitles |
697
+ | **[AI Scene Detection](https://odiobook.com)** | Find cuts, export clips, chapters or a cut list |
698
+ | **[AI Voice Generator](https://odiobook.com)** | Natural speech in 7 languages, plus text-to-speech |
699
+ | **[Voice Studio](https://odiobook.com)** | Multi-character scripts into finished audio |
700
+ | **Manual toolkit** | Trim, merge, convert, resize, compress, stabilize, add music |
701
+
702
+ **[Start free at odiobook.com](https://odiobook.com)**
703
+
704
+ If this package saved you time, the best thank-you is giving Odio Book a spin —
705
+ it is what keeps this library maintained.
706
+
707
+ More packages by the same author:
708
+ [universal-toast-message](https://www.npmjs.com/package/universal-toast-message).
709
+
710
+ ## License
711
+
712
+ MIT © [Hemant Rao](https://github.com/hemant-rao). See [LICENSE](./LICENSE).