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/LICENSE +21 -0
- package/README.md +711 -2
- package/catalog.d.ts +18 -0
- package/index.d.ts +310 -0
- package/package.json +91 -4
- package/src/catalog.js +57 -0
- package/src/index.js +23 -0
- package/src/seed.js +11 -0
- package/src/shapy-bg.js +783 -0
- package/src/shapy-template.js +2390 -0
- package/src/stickers.js +30 -0
- package/stickers.d.ts +22 -0
package/README.md
CHANGED
|
@@ -1,3 +1,712 @@
|
|
|
1
|
-
#
|
|
1
|
+
# shapy-bg
|
|
2
2
|
|
|
3
|
-
|
|
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 ` ` 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 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
|
+
[](https://github.com/sponsors/hemant-rao)
|
|
675
|
+
|
|
676
|
+
[](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).
|