interactive-bg 0.0.0-stage → 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.
Files changed (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -2
  3. package/dist/core/canvas2d-renderer.cjs +119 -0
  4. package/dist/core/canvas2d-renderer.cjs.map +1 -0
  5. package/dist/core/canvas2d-renderer.d.cts +7 -0
  6. package/dist/core/canvas2d-renderer.d.ts +7 -0
  7. package/dist/core/canvas2d-renderer.js +119 -0
  8. package/dist/core/canvas2d-renderer.js.map +1 -0
  9. package/dist/core/color.cjs +46 -0
  10. package/dist/core/color.cjs.map +1 -0
  11. package/dist/core/color.d.cts +6 -0
  12. package/dist/core/color.d.ts +6 -0
  13. package/dist/core/color.js +46 -0
  14. package/dist/core/color.js.map +1 -0
  15. package/dist/core/engine.cjs +367 -0
  16. package/dist/core/engine.cjs.map +1 -0
  17. package/dist/core/engine.d.cts +69 -0
  18. package/dist/core/engine.d.ts +69 -0
  19. package/dist/core/engine.js +366 -0
  20. package/dist/core/engine.js.map +1 -0
  21. package/dist/core/style.cjs +21 -0
  22. package/dist/core/style.cjs.map +1 -0
  23. package/dist/core/style.d.cts +85 -0
  24. package/dist/core/style.d.ts +85 -0
  25. package/dist/core/style.js +21 -0
  26. package/dist/core/style.js.map +1 -0
  27. package/dist/core/surface.cjs +25 -0
  28. package/dist/core/surface.cjs.map +1 -0
  29. package/dist/core/surface.d.cts +42 -0
  30. package/dist/core/surface.d.ts +42 -0
  31. package/dist/core/surface.js +24 -0
  32. package/dist/core/surface.js.map +1 -0
  33. package/dist/core/webgl-renderer.cjs +137 -0
  34. package/dist/core/webgl-renderer.cjs.map +1 -0
  35. package/dist/core/webgl-renderer.d.cts +8 -0
  36. package/dist/core/webgl-renderer.d.ts +8 -0
  37. package/dist/core/webgl-renderer.js +137 -0
  38. package/dist/core/webgl-renderer.js.map +1 -0
  39. package/dist/core/webgl.cjs +62 -0
  40. package/dist/core/webgl.cjs.map +1 -0
  41. package/dist/core/webgl.d.cts +15 -0
  42. package/dist/core/webgl.d.ts +15 -0
  43. package/dist/core/webgl.js +60 -0
  44. package/dist/core/webgl.js.map +1 -0
  45. package/dist/dots/canvas2d.cjs +14 -0
  46. package/dist/dots/canvas2d.cjs.map +1 -0
  47. package/dist/dots/canvas2d.d.cts +5 -0
  48. package/dist/dots/canvas2d.d.ts +5 -0
  49. package/dist/dots/canvas2d.js +13 -0
  50. package/dist/dots/canvas2d.js.map +1 -0
  51. package/dist/dots/index.cjs +16 -0
  52. package/dist/dots/index.cjs.map +1 -0
  53. package/dist/dots/index.d.cts +5 -0
  54. package/dist/dots/index.d.ts +5 -0
  55. package/dist/dots/index.js +15 -0
  56. package/dist/dots/index.js.map +1 -0
  57. package/dist/dots/webgl.cjs +14 -0
  58. package/dist/dots/webgl.cjs.map +1 -0
  59. package/dist/dots/webgl.d.cts +5 -0
  60. package/dist/dots/webgl.d.ts +5 -0
  61. package/dist/dots/webgl.js +13 -0
  62. package/dist/dots/webgl.js.map +1 -0
  63. package/dist/index.cjs +24 -0
  64. package/dist/index.d.cts +18 -0
  65. package/dist/index.d.ts +18 -0
  66. package/dist/index.js +11 -0
  67. package/dist/index.umd.js +211 -0
  68. package/dist/index.umd.js.map +1 -0
  69. package/dist/plus/canvas2d.cjs +14 -0
  70. package/dist/plus/canvas2d.cjs.map +1 -0
  71. package/dist/plus/canvas2d.d.cts +5 -0
  72. package/dist/plus/canvas2d.d.ts +5 -0
  73. package/dist/plus/canvas2d.js +13 -0
  74. package/dist/plus/canvas2d.js.map +1 -0
  75. package/dist/plus/index.cjs +16 -0
  76. package/dist/plus/index.cjs.map +1 -0
  77. package/dist/plus/index.d.cts +5 -0
  78. package/dist/plus/index.d.ts +5 -0
  79. package/dist/plus/index.js +15 -0
  80. package/dist/plus/index.js.map +1 -0
  81. package/dist/plus/webgl.cjs +14 -0
  82. package/dist/plus/webgl.cjs.map +1 -0
  83. package/dist/plus/webgl.d.cts +5 -0
  84. package/dist/plus/webgl.d.ts +5 -0
  85. package/dist/plus/webgl.js +13 -0
  86. package/dist/plus/webgl.js.map +1 -0
  87. package/dist/shared/buffer.cjs +20 -0
  88. package/dist/shared/buffer.cjs.map +1 -0
  89. package/dist/shared/buffer.d.cts +11 -0
  90. package/dist/shared/buffer.d.ts +11 -0
  91. package/dist/shared/buffer.js +19 -0
  92. package/dist/shared/buffer.js.map +1 -0
  93. package/dist/shared/grid-webgl.cjs +63 -0
  94. package/dist/shared/grid-webgl.cjs.map +1 -0
  95. package/dist/shared/grid-webgl.d.cts +10 -0
  96. package/dist/shared/grid-webgl.d.ts +10 -0
  97. package/dist/shared/grid-webgl.js +61 -0
  98. package/dist/shared/grid-webgl.js.map +1 -0
  99. package/dist/shared/grid.cjs +133 -0
  100. package/dist/shared/grid.cjs.map +1 -0
  101. package/dist/shared/grid.d.cts +72 -0
  102. package/dist/shared/grid.d.ts +72 -0
  103. package/dist/shared/grid.js +123 -0
  104. package/dist/shared/grid.js.map +1 -0
  105. package/dist/shared/noise.cjs +166 -0
  106. package/dist/shared/noise.cjs.map +1 -0
  107. package/dist/shared/noise.d.cts +4 -0
  108. package/dist/shared/noise.d.ts +4 -0
  109. package/dist/shared/noise.js +166 -0
  110. package/dist/shared/noise.js.map +1 -0
  111. package/dist/styles/dots/canvas2d.cjs +99 -0
  112. package/dist/styles/dots/canvas2d.cjs.map +1 -0
  113. package/dist/styles/dots/canvas2d.d.cts +7 -0
  114. package/dist/styles/dots/canvas2d.d.ts +7 -0
  115. package/dist/styles/dots/canvas2d.js +99 -0
  116. package/dist/styles/dots/canvas2d.js.map +1 -0
  117. package/dist/styles/dots/definition.cjs +15 -0
  118. package/dist/styles/dots/definition.cjs.map +1 -0
  119. package/dist/styles/dots/definition.d.cts +7 -0
  120. package/dist/styles/dots/definition.d.ts +7 -0
  121. package/dist/styles/dots/definition.js +16 -0
  122. package/dist/styles/dots/definition.js.map +1 -0
  123. package/dist/styles/dots/motion.cjs +6 -0
  124. package/dist/styles/dots/motion.cjs.map +1 -0
  125. package/dist/styles/dots/motion.d.cts +2 -0
  126. package/dist/styles/dots/motion.d.ts +2 -0
  127. package/dist/styles/dots/motion.js +6 -0
  128. package/dist/styles/dots/motion.js.map +1 -0
  129. package/dist/styles/dots/options.cjs +33 -0
  130. package/dist/styles/dots/options.cjs.map +1 -0
  131. package/dist/styles/dots/options.d.cts +33 -0
  132. package/dist/styles/dots/options.d.ts +33 -0
  133. package/dist/styles/dots/options.js +32 -0
  134. package/dist/styles/dots/options.js.map +1 -0
  135. package/dist/styles/dots/webgl.cjs +113 -0
  136. package/dist/styles/dots/webgl.cjs.map +1 -0
  137. package/dist/styles/dots/webgl.d.cts +4 -0
  138. package/dist/styles/dots/webgl.d.ts +4 -0
  139. package/dist/styles/dots/webgl.js +113 -0
  140. package/dist/styles/dots/webgl.js.map +1 -0
  141. package/dist/styles/plus/canvas2d.cjs +88 -0
  142. package/dist/styles/plus/canvas2d.cjs.map +1 -0
  143. package/dist/styles/plus/canvas2d.d.cts +8 -0
  144. package/dist/styles/plus/canvas2d.d.ts +8 -0
  145. package/dist/styles/plus/canvas2d.js +88 -0
  146. package/dist/styles/plus/canvas2d.js.map +1 -0
  147. package/dist/styles/plus/definition.cjs +17 -0
  148. package/dist/styles/plus/definition.cjs.map +1 -0
  149. package/dist/styles/plus/definition.d.cts +9 -0
  150. package/dist/styles/plus/definition.d.ts +9 -0
  151. package/dist/styles/plus/definition.js +18 -0
  152. package/dist/styles/plus/definition.js.map +1 -0
  153. package/dist/styles/plus/motion.cjs +18 -0
  154. package/dist/styles/plus/motion.cjs.map +1 -0
  155. package/dist/styles/plus/motion.d.cts +5 -0
  156. package/dist/styles/plus/motion.d.ts +5 -0
  157. package/dist/styles/plus/motion.js +18 -0
  158. package/dist/styles/plus/motion.js.map +1 -0
  159. package/dist/styles/plus/options.cjs +45 -0
  160. package/dist/styles/plus/options.cjs.map +1 -0
  161. package/dist/styles/plus/options.d.cts +45 -0
  162. package/dist/styles/plus/options.d.ts +45 -0
  163. package/dist/styles/plus/options.js +43 -0
  164. package/dist/styles/plus/options.js.map +1 -0
  165. package/dist/styles/plus/turns.cjs +102 -0
  166. package/dist/styles/plus/turns.cjs.map +1 -0
  167. package/dist/styles/plus/turns.d.cts +17 -0
  168. package/dist/styles/plus/turns.d.ts +17 -0
  169. package/dist/styles/plus/turns.js +102 -0
  170. package/dist/styles/plus/turns.js.map +1 -0
  171. package/dist/styles/plus/webgl.cjs +121 -0
  172. package/dist/styles/plus/webgl.cjs.map +1 -0
  173. package/dist/styles/plus/webgl.d.cts +8 -0
  174. package/dist/styles/plus/webgl.d.ts +8 -0
  175. package/dist/styles/plus/webgl.js +121 -0
  176. package/dist/styles/plus/webgl.js.map +1 -0
  177. package/dist/styles/topo/canvas2d.cjs +77 -0
  178. package/dist/styles/topo/canvas2d.cjs.map +1 -0
  179. package/dist/styles/topo/canvas2d.d.cts +4 -0
  180. package/dist/styles/topo/canvas2d.d.ts +4 -0
  181. package/dist/styles/topo/canvas2d.js +77 -0
  182. package/dist/styles/topo/canvas2d.js.map +1 -0
  183. package/dist/styles/topo/contours.cjs +83 -0
  184. package/dist/styles/topo/contours.cjs.map +1 -0
  185. package/dist/styles/topo/contours.d.cts +9 -0
  186. package/dist/styles/topo/contours.d.ts +9 -0
  187. package/dist/styles/topo/contours.js +83 -0
  188. package/dist/styles/topo/contours.js.map +1 -0
  189. package/dist/styles/topo/definition.cjs +13 -0
  190. package/dist/styles/topo/definition.cjs.map +1 -0
  191. package/dist/styles/topo/definition.d.cts +7 -0
  192. package/dist/styles/topo/definition.d.ts +7 -0
  193. package/dist/styles/topo/definition.js +14 -0
  194. package/dist/styles/topo/definition.js.map +1 -0
  195. package/dist/styles/topo/field.cjs +118 -0
  196. package/dist/styles/topo/field.cjs.map +1 -0
  197. package/dist/styles/topo/field.d.cts +26 -0
  198. package/dist/styles/topo/field.d.ts +26 -0
  199. package/dist/styles/topo/field.js +118 -0
  200. package/dist/styles/topo/field.js.map +1 -0
  201. package/dist/styles/topo/options.cjs +39 -0
  202. package/dist/styles/topo/options.cjs.map +1 -0
  203. package/dist/styles/topo/options.d.cts +52 -0
  204. package/dist/styles/topo/options.d.ts +52 -0
  205. package/dist/styles/topo/options.js +38 -0
  206. package/dist/styles/topo/options.js.map +1 -0
  207. package/dist/styles/topo/terrain.cjs +22 -0
  208. package/dist/styles/topo/terrain.cjs.map +1 -0
  209. package/dist/styles/topo/terrain.d.cts +11 -0
  210. package/dist/styles/topo/terrain.d.ts +11 -0
  211. package/dist/styles/topo/terrain.js +22 -0
  212. package/dist/styles/topo/terrain.js.map +1 -0
  213. package/dist/styles/topo/webgl.cjs +150 -0
  214. package/dist/styles/topo/webgl.cjs.map +1 -0
  215. package/dist/styles/topo/webgl.d.cts +7 -0
  216. package/dist/styles/topo/webgl.d.ts +7 -0
  217. package/dist/styles/topo/webgl.js +150 -0
  218. package/dist/styles/topo/webgl.js.map +1 -0
  219. package/dist/topo/canvas2d.cjs +14 -0
  220. package/dist/topo/canvas2d.cjs.map +1 -0
  221. package/dist/topo/canvas2d.d.cts +5 -0
  222. package/dist/topo/canvas2d.d.ts +5 -0
  223. package/dist/topo/canvas2d.js +13 -0
  224. package/dist/topo/canvas2d.js.map +1 -0
  225. package/dist/topo/index.cjs +16 -0
  226. package/dist/topo/index.cjs.map +1 -0
  227. package/dist/topo/index.d.cts +5 -0
  228. package/dist/topo/index.d.ts +5 -0
  229. package/dist/topo/index.js +15 -0
  230. package/dist/topo/index.js.map +1 -0
  231. package/dist/topo/webgl.cjs +14 -0
  232. package/dist/topo/webgl.cjs.map +1 -0
  233. package/dist/topo/webgl.d.cts +5 -0
  234. package/dist/topo/webgl.d.ts +5 -0
  235. package/dist/topo/webgl.js +13 -0
  236. package/dist/topo/webgl.js.map +1 -0
  237. package/package.json +192 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bohdan Shcherbak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,203 @@
1
- # Temporary Holding Version
1
+ # interactive-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
+ Animated backgrounds that react to the pointer, for hero sections, cards and full pages. One `<canvas>`, no dependencies, under 7 kB gzipped for the style you use.
4
+
5
+ **[Live demo](https://bsh314.github.io/interactive-bg/)**
6
+
7
+ | `topo` | `dots` | `plus` |
8
+ | --- | --- | --- |
9
+ | ![topo](https://raw.githubusercontent.com/bsh314/interactive-bg/main/docs/topo.png) | ![dots](https://raw.githubusercontent.com/bsh314/interactive-bg/main/docs/dots.png) | ![plus](https://raw.githubusercontent.com/bsh314/interactive-bg/main/docs/plus.png) |
10
+ | Topographic contour lines that drift slowly. The terrain rises under the pointer. | A grid of dots. The pointer pushes them aside; a click sends out a wave. | A grid of "+" signs that turn to point at the pointer; a click sends out a wave. |
11
+
12
+ - **Small.** Import one style with one renderer and ship only that: 4.5–6.9 kB gzipped, engine included.
13
+ - **Cheap to run.** Idle animation renders at the lowest frame rate that still looks smooth, stops when nothing moves, and pauses off-screen and in background tabs. The grid styles idle on less CPU than an empty animated canvas.
14
+ - **GPU first.** WebGL2, with Canvas 2D as a fallback or as a deliberate choice.
15
+ - **Polite.** Respects `prefers-reduced-motion`, caps pixel density, never blocks clicks on your content.
16
+ - **Typed.** Every option is typed and can be changed at runtime.
17
+
18
+ ## Quick start
19
+
20
+ ```bash
21
+ npm install interactive-bg
22
+ ```
23
+
24
+ ```js
25
+ import { createBackground } from 'interactive-bg';
26
+ import { dots } from 'interactive-bg/dots';
27
+
28
+ createBackground('#hero', dots({ spacing: 24 }));
29
+ ```
30
+
31
+ The first argument is where it goes:
32
+
33
+ - an element or selector: the background fills that element (it gets `position: relative` if it was static);
34
+ - `null`: a fixed background behind the whole page;
35
+ - your own `<canvas>`: drawn into directly.
36
+
37
+ Without a bundler:
38
+
39
+ ```html
40
+ <script src="https://unpkg.com/interactive-bg"></script>
41
+ <script>
42
+ InteractiveBg.createBackground(null, InteractiveBg.topo());
43
+ </script>
44
+ ```
45
+
46
+ ## Ship only what you use
47
+
48
+ Every style comes in three builds. They have the same name and options, so switching is a one-line change.
49
+
50
+ ```js
51
+ import { topo } from 'interactive-bg/topo'; // WebGL2, falls back to Canvas 2D
52
+ import { topo } from 'interactive-bg/topo/webgl'; // WebGL2 only
53
+ import { topo } from 'interactive-bg/topo/canvas2d'; // Canvas 2D only
54
+ ```
55
+
56
+ What your app bundles, `createBackground` included (minified / gzipped):
57
+
58
+ | Style | Both renderers | WebGL only | Canvas 2D only |
59
+ | ------ | --------------- | --------------- | --------------- |
60
+ | `topo` | 20.0 / 8.1 kB | 16.5 / 6.9 kB | 12.5 / 5.3 kB |
61
+ | `dots` | 17.9 / 7.3 kB | 14.4 / 6.0 kB | 10.4 / 4.5 kB |
62
+ | `plus` | 19.3 / 8.0 kB | 16.2 / 6.8 kB | 11.7 / 5.0 kB |
63
+
64
+ With a single renderer there is no fallback: if the browser can't provide it, the background stays empty, `bg.renderer` is `null`, and your page carries on.
65
+
66
+ ## Performance
67
+
68
+ Full-screen 1920×1080 background in Chrome on an Apple M1 Pro.
69
+
70
+ | Style, renderer | Main thread, idle | Main thread, pointer moving | CPU, idle | CPU, pointer moving | Memory |
71
+ | ----------------- | ----------------- | --------------------------- | --------- | ------------------- | ------- |
72
+ | `topo`, WebGL | 1.5% | 1.7% | 7% | 12% | ~95 MB |
73
+ | `topo`, Canvas 2D | 4.5% | 10% | 10% | 21% | ~105 MB |
74
+ | `dots`, WebGL | 0.2% | 0.4% | 4.4% | 11% | ~90 MB |
75
+ | `dots`, Canvas 2D | 0.4% | 1.7% | 4.3% | 12% | ~70 MB |
76
+ | `plus`, WebGL | 0.1% | 0.9% | 3.6% | 11% | ~90 MB |
77
+ | `plus`, Canvas 2D | 0.4% | 2.5% | 3.9% | 13% | ~70 MB |
78
+ | Empty canvas, 30 fps, for reference | — | — | 6.4% | — | ~85 MB |
79
+
80
+ - **Main thread**: share of the page's main thread the background takes, so what's left for your app.
81
+ - **CPU** and **Memory**: the browser's renderer and GPU processes, as % of one core and MB above a blank page. Most of the memory is the canvas itself, which the browser needs for any full-screen canvas.
82
+
83
+ Reproduce with `npm run bench:memory` and `bench/realtime.html`; see [CONTRIBUTING.md](CONTRIBUTING.md#benchmarks).
84
+
85
+ ## API
86
+
87
+ ```js
88
+ const bg = createBackground(target, style, engineOptions);
89
+
90
+ bg.setOptions({ spacing: 32 }); // the style's options, any time
91
+ bg.setEngineOptions({ idleFps: 15 }); // engine options, any time (except renderer)
92
+ bg.pause();
93
+ bg.resume();
94
+ bg.destroy(); // removes the canvas, listeners and GPU context
95
+
96
+ bg.renderer; // 'webgl' | 'canvas2d' | null
97
+ ```
98
+
99
+ In React, create it in an effect and destroy it in the cleanup:
100
+
101
+ ```jsx
102
+ useEffect(() => {
103
+ const bg = createBackground(ref.current, dots());
104
+ return () => bg.destroy();
105
+ }, []);
106
+ ```
107
+
108
+ ## Engine options
109
+
110
+ Shared by every style.
111
+
112
+ | Option | Default | What it does |
113
+ | ---------------------- | -------- | ------------------------------------------------------------------------------------------------ |
114
+ | `backgroundColor` | `null` | Fill behind the pattern. `null` keeps the canvas transparent. |
115
+ | `fadeOutTop`, `fadeOutRight`, `fadeOutBottom`, `fadeOutLeft` | `0` | Fade the pattern out over this many px towards that edge. The background colour stays solid. |
116
+ | `interactive` | `true` | React to the pointer and to clicks. |
117
+ | `maxFps` | `60` | Frame-rate cap while reacting to the pointer or a click. `0` = uncapped. |
118
+ | `idleFps` | `30` | Frame-rate cap for the ambient animation. Styles may use less. |
119
+ | `maxDpr` | `1.5` | Pixel-density cap. Memory grows with its square; `1.5` looks the same as `2` for thin lines. |
120
+ | `respectReducedMotion` | `true` | Hold the ambient animation still for users who prefer reduced motion. |
121
+ | `renderer` | `'auto'` | `'webgl'` or `'canvas2d'` to try that one first. Fixed at creation. |
122
+
123
+ ## Style options
124
+
125
+ <details>
126
+ <summary><code>topo</code></summary>
127
+
128
+ | Option | Default | What it does |
129
+ | ----------------- | ------------ | ------------------------------------------------------------------- |
130
+ | `lineColor` | `#5b6576` | Colour of regular lines. |
131
+ | `majorLineColor` | `#7dd3a8` | Colour of every `majorEvery`-th line. |
132
+ | `lineOpacity` | `0.5` | Opacity of all lines, `0`–`1`. |
133
+ | `lineWidth` | `1` | Regular line width in px. |
134
+ | `majorLineWidth` | `1.75` | Major line width in px. |
135
+ | `majorEvery` | `5` | Every n-th line is a major line. `0` = none. |
136
+ | `levels` | `12` | Number of contour levels. Higher = denser. |
137
+ | `scale` | `0.0025` | Size of the landforms. Lower = larger and smoother. |
138
+ | `octaves` | `2` | Detail layers. Higher = rougher terrain. |
139
+ | `speed` | `0.025` | How fast the terrain changes. `0` = static. |
140
+ | `resolution` | `6` | Sampling cell size in px. Lower = smoother lines, more CPU. |
141
+ | `seed` | `1332056948` | Which landscape. Use `Math.random() * 2 ** 32` for a new one each load. |
142
+ | `pointerRadius` | `100` | Reach of the pointer in px. |
143
+ | `pointerStrength` | `0.35` | Height of the bump under the pointer. Negative = a crater. |
144
+
145
+ </details>
146
+
147
+ <details>
148
+ <summary><code>dots</code></summary>
149
+
150
+ | Option | Default | What it does |
151
+ | ----------------- | --------- | -------------------------------------------------------------------- |
152
+ | `color` | `#8b95a5` | Dot colour. |
153
+ | `opacity` | `0.6` | Opacity, `0`–`1`. |
154
+ | `size` | `1.6` | Dot radius in px. |
155
+ | `spacing` | `26` | Distance between dots in px. The grid keeps half of this as padding. |
156
+ | `jitter` | `1.2` | How far each dot wanders, in px. `0` = still. |
157
+ | `jitterSpeed` | `0.35` | Wander speed in cycles per second. |
158
+ | `pointerRadius` | `160` | Reach of the pointer in px. |
159
+ | `pointerStrength` | `22` | How far dots next to the pointer are pushed, in px. Negative = pulled in. |
160
+ | `waveStrength` | `10` | How far a click wave pushes dots, in px. `0` = no waves. |
161
+ | `waveSpeed` | `520` | Wave speed in px per second. |
162
+ | `waveWidth` | `60` | Width of the wave's ring in px. |
163
+ | `waveDuration` | `1.8` | Seconds until a wave has faded out. |
164
+
165
+ </details>
166
+
167
+ <details>
168
+ <summary><code>plus</code></summary>
169
+
170
+ | Option | Default | What it does |
171
+ | -------------- | --------- | ----------------------------------------------------------------------- |
172
+ | `color` | `#8b95a5` | Sign colour. |
173
+ | `opacity` | `0.6` | Opacity, `0`–`1`. |
174
+ | `size` | `4` | Arm length from the centre in px. |
175
+ | `weight` | `1.2` | Stroke weight in px. |
176
+ | `spacing` | `28` | Distance between signs in px. The grid keeps half of this as padding. |
177
+ | `jitter` | `1` | How far each sign wanders, in px. `0` = still. |
178
+ | `jitterSpeed` | `0.35` | Wander speed in cycles per second. |
179
+ | `pointerAlign` | `1` | How fully signs turn to point an arm at the pointer, `0`–`1`. |
180
+ | `turnTime` | `0.25` | How smoothly signs turn, in seconds. `0` = instantly. |
181
+ | `waveAlign` | `1` | How fully signs on a click wave turn to face the click, `0`–`1`. |
182
+ | `waveWeight` | `1.5` | Extra stroke weight on the wave's crest. `1` = double. |
183
+ | `waveSpeed` | `520` | Wave speed in px per second. |
184
+ | `waveWidth` | `60` | Width of the wave's ring in px. |
185
+ | `waveDuration` | `1.8` | Seconds until a wave has faded out. |
186
+
187
+ </details>
188
+
189
+ ## Your own style
190
+
191
+ The engine handles the canvas, sizing, frame loop, pointer, visibility, reduced motion, background colour and edge fades. A style only says what to draw, in WebGL2, Canvas 2D or both. See [docs/custom-styles.md](docs/custom-styles.md).
192
+
193
+ ## Browser support
194
+
195
+ Current Chrome, Edge, Firefox and Safari (15+). WebGL2 is used where available; every built-in style also has a Canvas 2D renderer.
196
+
197
+ ## Contributing
198
+
199
+ Development setup, project layout, benchmarks and releases: [CONTRIBUTING.md](CONTRIBUTING.md).
200
+
201
+ ## License
202
+
203
+ MIT
@@ -0,0 +1,119 @@
1
+ const require_surface = require("./surface.cjs");
2
+ // src/core/canvas2d-renderer.ts
3
+ var SIDES = [
4
+ "top",
5
+ "right",
6
+ "bottom",
7
+ "left"
8
+ ];
9
+ /**
10
+ * Draw a style with Canvas 2D. The engine clears to the background colour and
11
+ * sets the transform so the style draws in CSS pixels.
12
+ */
13
+ function canvas2dRenderer(createRenderer) {
14
+ return {
15
+ name: "canvas2d",
16
+ create(canvas, options) {
17
+ const ctx = canvas.getContext("2d");
18
+ if (!ctx) return null;
19
+ const renderer = createRenderer(ctx, options);
20
+ const gradients = SIDES.map(() => ({
21
+ gradient: null,
22
+ size: 0,
23
+ length: 0
24
+ }));
25
+ /**
26
+ * Erase the style's drawing gradually along one side: nothing `length` px in from the edge,
27
+ * all of it at the edge. Erasing multiplies, so overlapping sides combine.
28
+ */
29
+ const fadeSide = (side, length, width, height) => {
30
+ const size = side === 1 || side === 3 ? width : height;
31
+ const cache = gradients[side];
32
+ if (!cache.gradient || cache.size !== size || cache.length !== length) {
33
+ const [x0, y0, x1, y1] = [
34
+ [
35
+ 0,
36
+ length,
37
+ 0,
38
+ 0
39
+ ],
40
+ [
41
+ width - length,
42
+ 0,
43
+ width,
44
+ 0
45
+ ],
46
+ [
47
+ 0,
48
+ height - length,
49
+ 0,
50
+ height
51
+ ],
52
+ [
53
+ length,
54
+ 0,
55
+ 0,
56
+ 0
57
+ ]
58
+ ][side];
59
+ cache.gradient = ctx.createLinearGradient(x0, y0, x1, y1);
60
+ cache.gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
61
+ cache.gradient.addColorStop(1, "rgba(0, 0, 0, 1)");
62
+ cache.size = size;
63
+ cache.length = length;
64
+ }
65
+ const strip = Math.min(length, size);
66
+ ctx.fillStyle = cache.gradient;
67
+ if (side === 0) ctx.fillRect(0, 0, width, strip);
68
+ else if (side === 1) ctx.fillRect(width - strip, 0, strip, height);
69
+ else if (side === 2) ctx.fillRect(0, height - strip, width, strip);
70
+ else ctx.fillRect(0, 0, strip, height);
71
+ };
72
+ return {
73
+ name: "canvas2d",
74
+ update(next) {
75
+ renderer.update(next);
76
+ },
77
+ render(frame, backgroundColor, fade) {
78
+ const { width, height, dpr } = frame;
79
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
80
+ ctx.globalAlpha = 1;
81
+ ctx.globalCompositeOperation = "source-over";
82
+ ctx.clearRect(0, 0, width, height);
83
+ const fading = require_surface.fades(fade);
84
+ if (backgroundColor && !fading) {
85
+ ctx.fillStyle = backgroundColor;
86
+ ctx.fillRect(0, 0, width, height);
87
+ }
88
+ const busy = renderer.render(frame) === true;
89
+ if (fading) {
90
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
91
+ ctx.globalAlpha = 1;
92
+ ctx.globalCompositeOperation = "destination-out";
93
+ for (let side = 0; side < SIDES.length; side++) {
94
+ const length = fade[SIDES[side]];
95
+ if (length > 0) fadeSide(side, length, width, height);
96
+ }
97
+ if (backgroundColor) {
98
+ ctx.globalCompositeOperation = "destination-over";
99
+ ctx.fillStyle = backgroundColor;
100
+ ctx.fillRect(0, 0, width, height);
101
+ }
102
+ ctx.globalCompositeOperation = "source-over";
103
+ }
104
+ return busy;
105
+ },
106
+ clear() {
107
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
108
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
109
+ },
110
+ destroy() {
111
+ renderer.destroy?.();
112
+ }
113
+ };
114
+ }
115
+ };
116
+ }
117
+ exports.canvas2dRenderer = canvas2dRenderer;
118
+
119
+ //# sourceMappingURL=canvas2d-renderer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas2d-renderer.cjs","names":["fades"],"sources":["../../src/core/canvas2d-renderer.ts"],"sourcesContent":["import type { StyleRenderer } from './style';\nimport { fades, type Fade, type Renderer } from './surface';\n\nconst SIDES: readonly (keyof Fade)[] = ['top', 'right', 'bottom', 'left'];\n\n/**\n * Draw a style with Canvas 2D. The engine clears to the background colour and\n * sets the transform so the style draws in CSS pixels.\n */\nexport function canvas2dRenderer<O>(\n createRenderer: (ctx: CanvasRenderingContext2D, options: O) => StyleRenderer<O>,\n): Renderer<O> {\n return {\n name: 'canvas2d',\n create(canvas, options) {\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const renderer = createRenderer(ctx, options);\n\n // One gradient per side, kept while the canvas size and fade length stay the same.\n const gradients: { gradient: CanvasGradient | null; size: number; length: number }[] = SIDES.map(() => ({\n gradient: null,\n size: 0,\n length: 0,\n }));\n\n /**\n * Erase the style's drawing gradually along one side: nothing `length` px in from the edge,\n * all of it at the edge. Erasing multiplies, so overlapping sides combine.\n */\n const fadeSide = (side: number, length: number, width: number, height: number): void => {\n const horizontal = side === 1 || side === 3; // right, left\n const size = horizontal ? width : height;\n const cache = gradients[side];\n if (!cache.gradient || cache.size !== size || cache.length !== length) {\n // From the inner end of the strip to the edge.\n const [x0, y0, x1, y1] = [\n [0, length, 0, 0], // top\n [width - length, 0, width, 0], // right\n [0, height - length, 0, height], // bottom\n [length, 0, 0, 0], // left\n ][side];\n cache.gradient = ctx.createLinearGradient(x0, y0, x1, y1);\n cache.gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');\n cache.gradient.addColorStop(1, 'rgba(0, 0, 0, 1)');\n cache.size = size;\n cache.length = length;\n }\n const strip = Math.min(length, size);\n ctx.fillStyle = cache.gradient;\n if (side === 0) ctx.fillRect(0, 0, width, strip);\n else if (side === 1) ctx.fillRect(width - strip, 0, strip, height);\n else if (side === 2) ctx.fillRect(0, height - strip, width, strip);\n else ctx.fillRect(0, 0, strip, height);\n };\n\n return {\n name: 'canvas2d',\n update(next) {\n renderer.update(next);\n },\n render(frame, backgroundColor, fade) {\n const { width, height, dpr } = frame;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.globalAlpha = 1;\n ctx.globalCompositeOperation = 'source-over';\n ctx.clearRect(0, 0, width, height);\n // With a fade, the background goes in last, behind the faded drawing, so it stays solid.\n const fading = fades(fade);\n if (backgroundColor && !fading) {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, width, height);\n }\n const busy = renderer.render(frame) === true;\n if (fading) {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.globalAlpha = 1;\n ctx.globalCompositeOperation = 'destination-out';\n for (let side = 0; side < SIDES.length; side++) {\n const length = fade[SIDES[side]];\n if (length > 0) fadeSide(side, length, width, height);\n }\n if (backgroundColor) {\n ctx.globalCompositeOperation = 'destination-over';\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, width, height);\n }\n ctx.globalCompositeOperation = 'source-over';\n }\n return busy;\n },\n clear() {\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n },\n destroy() {\n renderer.destroy?.();\n },\n };\n },\n };\n}\n"],"mappings":";;AAGA,IAAM,QAAiC;CAAC;CAAO;CAAS;CAAU;AAAM;;;;;AAMxE,SAAgB,iBACd,gBACa;CACb,OAAO;EACL,MAAM;EACN,OAAO,QAAQ,SAAS;GACtB,MAAM,MAAM,OAAO,WAAW,IAAI;GAClC,IAAI,CAAC,KAAK,OAAO;GACjB,MAAM,WAAW,eAAe,KAAK,OAAO;GAG5C,MAAM,YAAiF,MAAM,WAAW;IACtG,UAAU;IACV,MAAM;IACN,QAAQ;GACV,EAAE;;;;;GAMF,MAAM,YAAY,MAAc,QAAgB,OAAe,WAAyB;IAEtF,MAAM,OADa,SAAS,KAAK,SAAS,IAChB,QAAQ;IAClC,MAAM,QAAQ,UAAU;IACxB,IAAI,CAAC,MAAM,YAAY,MAAM,SAAS,QAAQ,MAAM,WAAW,QAAQ;KAErE,MAAM,CAAC,IAAI,IAAI,IAAI,MAAM;MACvB;OAAC;OAAG;OAAQ;OAAG;MAAC;MAChB;OAAC,QAAQ;OAAQ;OAAG;OAAO;MAAC;MAC5B;OAAC;OAAG,SAAS;OAAQ;OAAG;MAAM;MAC9B;OAAC;OAAQ;OAAG;OAAG;MAAC;KAClB,CAAC,CAAC;KACF,MAAM,WAAW,IAAI,qBAAqB,IAAI,IAAI,IAAI,EAAE;KACxD,MAAM,SAAS,aAAa,GAAG,kBAAkB;KACjD,MAAM,SAAS,aAAa,GAAG,kBAAkB;KACjD,MAAM,OAAO;KACb,MAAM,SAAS;IACjB;IACA,MAAM,QAAQ,KAAK,IAAI,QAAQ,IAAI;IACnC,IAAI,YAAY,MAAM;IACtB,IAAI,SAAS,GAAG,IAAI,SAAS,GAAG,GAAG,OAAO,KAAK;SAC1C,IAAI,SAAS,GAAG,IAAI,SAAS,QAAQ,OAAO,GAAG,OAAO,MAAM;SAC5D,IAAI,SAAS,GAAG,IAAI,SAAS,GAAG,SAAS,OAAO,OAAO,KAAK;SAC5D,IAAI,SAAS,GAAG,GAAG,OAAO,MAAM;GACvC;GAEA,OAAO;IACL,MAAM;IACN,OAAO,MAAM;KACX,SAAS,OAAO,IAAI;IACtB;IACA,OAAO,OAAO,iBAAiB,MAAM;KACnC,MAAM,EAAE,OAAO,QAAQ,QAAQ;KAC/B,IAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;KACrC,IAAI,cAAc;KAClB,IAAI,2BAA2B;KAC/B,IAAI,UAAU,GAAG,GAAG,OAAO,MAAM;KAEjC,MAAM,SAASA,gBAAAA,MAAM,IAAI;KACzB,IAAI,mBAAmB,CAAC,QAAQ;MAC9B,IAAI,YAAY;MAChB,IAAI,SAAS,GAAG,GAAG,OAAO,MAAM;KAClC;KACA,MAAM,OAAO,SAAS,OAAO,KAAK,MAAM;KACxC,IAAI,QAAQ;MACV,IAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;MACrC,IAAI,cAAc;MAClB,IAAI,2BAA2B;MAC/B,KAAK,IAAI,OAAO,GAAG,OAAO,MAAM,QAAQ,QAAQ;OAC9C,MAAM,SAAS,KAAK,MAAM;OAC1B,IAAI,SAAS,GAAG,SAAS,MAAM,QAAQ,OAAO,MAAM;MACtD;MACA,IAAI,iBAAiB;OACnB,IAAI,2BAA2B;OAC/B,IAAI,YAAY;OAChB,IAAI,SAAS,GAAG,GAAG,OAAO,MAAM;MAClC;MACA,IAAI,2BAA2B;KACjC;KACA,OAAO;IACT;IACA,QAAQ;KACN,IAAI,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;KACjC,IAAI,UAAU,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;IACjD;IACA,UAAU;KACR,SAAS,UAAU;IACrB;GACF;EACF;CACF;AACF"}
@@ -0,0 +1,7 @@
1
+ import type { StyleRenderer } from './style.cjs';
2
+ import { type Renderer } from './surface.cjs';
3
+ /**
4
+ * Draw a style with Canvas 2D. The engine clears to the background colour and
5
+ * sets the transform so the style draws in CSS pixels.
6
+ */
7
+ export declare function canvas2dRenderer<O>(createRenderer: (ctx: CanvasRenderingContext2D, options: O) => StyleRenderer<O>): Renderer<O>;
@@ -0,0 +1,7 @@
1
+ import type { StyleRenderer } from './style.js';
2
+ import { type Renderer } from './surface.js';
3
+ /**
4
+ * Draw a style with Canvas 2D. The engine clears to the background colour and
5
+ * sets the transform so the style draws in CSS pixels.
6
+ */
7
+ export declare function canvas2dRenderer<O>(createRenderer: (ctx: CanvasRenderingContext2D, options: O) => StyleRenderer<O>): Renderer<O>;
@@ -0,0 +1,119 @@
1
+ import { fades } from "./surface.js";
2
+ // src/core/canvas2d-renderer.ts
3
+ var SIDES = [
4
+ "top",
5
+ "right",
6
+ "bottom",
7
+ "left"
8
+ ];
9
+ /**
10
+ * Draw a style with Canvas 2D. The engine clears to the background colour and
11
+ * sets the transform so the style draws in CSS pixels.
12
+ */
13
+ function canvas2dRenderer(createRenderer) {
14
+ return {
15
+ name: "canvas2d",
16
+ create(canvas, options) {
17
+ const ctx = canvas.getContext("2d");
18
+ if (!ctx) return null;
19
+ const renderer = createRenderer(ctx, options);
20
+ const gradients = SIDES.map(() => ({
21
+ gradient: null,
22
+ size: 0,
23
+ length: 0
24
+ }));
25
+ /**
26
+ * Erase the style's drawing gradually along one side: nothing `length` px in from the edge,
27
+ * all of it at the edge. Erasing multiplies, so overlapping sides combine.
28
+ */
29
+ const fadeSide = (side, length, width, height) => {
30
+ const size = side === 1 || side === 3 ? width : height;
31
+ const cache = gradients[side];
32
+ if (!cache.gradient || cache.size !== size || cache.length !== length) {
33
+ const [x0, y0, x1, y1] = [
34
+ [
35
+ 0,
36
+ length,
37
+ 0,
38
+ 0
39
+ ],
40
+ [
41
+ width - length,
42
+ 0,
43
+ width,
44
+ 0
45
+ ],
46
+ [
47
+ 0,
48
+ height - length,
49
+ 0,
50
+ height
51
+ ],
52
+ [
53
+ length,
54
+ 0,
55
+ 0,
56
+ 0
57
+ ]
58
+ ][side];
59
+ cache.gradient = ctx.createLinearGradient(x0, y0, x1, y1);
60
+ cache.gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
61
+ cache.gradient.addColorStop(1, "rgba(0, 0, 0, 1)");
62
+ cache.size = size;
63
+ cache.length = length;
64
+ }
65
+ const strip = Math.min(length, size);
66
+ ctx.fillStyle = cache.gradient;
67
+ if (side === 0) ctx.fillRect(0, 0, width, strip);
68
+ else if (side === 1) ctx.fillRect(width - strip, 0, strip, height);
69
+ else if (side === 2) ctx.fillRect(0, height - strip, width, strip);
70
+ else ctx.fillRect(0, 0, strip, height);
71
+ };
72
+ return {
73
+ name: "canvas2d",
74
+ update(next) {
75
+ renderer.update(next);
76
+ },
77
+ render(frame, backgroundColor, fade) {
78
+ const { width, height, dpr } = frame;
79
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
80
+ ctx.globalAlpha = 1;
81
+ ctx.globalCompositeOperation = "source-over";
82
+ ctx.clearRect(0, 0, width, height);
83
+ const fading = fades(fade);
84
+ if (backgroundColor && !fading) {
85
+ ctx.fillStyle = backgroundColor;
86
+ ctx.fillRect(0, 0, width, height);
87
+ }
88
+ const busy = renderer.render(frame) === true;
89
+ if (fading) {
90
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
91
+ ctx.globalAlpha = 1;
92
+ ctx.globalCompositeOperation = "destination-out";
93
+ for (let side = 0; side < SIDES.length; side++) {
94
+ const length = fade[SIDES[side]];
95
+ if (length > 0) fadeSide(side, length, width, height);
96
+ }
97
+ if (backgroundColor) {
98
+ ctx.globalCompositeOperation = "destination-over";
99
+ ctx.fillStyle = backgroundColor;
100
+ ctx.fillRect(0, 0, width, height);
101
+ }
102
+ ctx.globalCompositeOperation = "source-over";
103
+ }
104
+ return busy;
105
+ },
106
+ clear() {
107
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
108
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
109
+ },
110
+ destroy() {
111
+ renderer.destroy?.();
112
+ }
113
+ };
114
+ }
115
+ };
116
+ }
117
+ export { canvas2dRenderer };
118
+
119
+ //# sourceMappingURL=canvas2d-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas2d-renderer.js","names":[],"sources":["../../src/core/canvas2d-renderer.ts"],"sourcesContent":["import type { StyleRenderer } from './style';\nimport { fades, type Fade, type Renderer } from './surface';\n\nconst SIDES: readonly (keyof Fade)[] = ['top', 'right', 'bottom', 'left'];\n\n/**\n * Draw a style with Canvas 2D. The engine clears to the background colour and\n * sets the transform so the style draws in CSS pixels.\n */\nexport function canvas2dRenderer<O>(\n createRenderer: (ctx: CanvasRenderingContext2D, options: O) => StyleRenderer<O>,\n): Renderer<O> {\n return {\n name: 'canvas2d',\n create(canvas, options) {\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const renderer = createRenderer(ctx, options);\n\n // One gradient per side, kept while the canvas size and fade length stay the same.\n const gradients: { gradient: CanvasGradient | null; size: number; length: number }[] = SIDES.map(() => ({\n gradient: null,\n size: 0,\n length: 0,\n }));\n\n /**\n * Erase the style's drawing gradually along one side: nothing `length` px in from the edge,\n * all of it at the edge. Erasing multiplies, so overlapping sides combine.\n */\n const fadeSide = (side: number, length: number, width: number, height: number): void => {\n const horizontal = side === 1 || side === 3; // right, left\n const size = horizontal ? width : height;\n const cache = gradients[side];\n if (!cache.gradient || cache.size !== size || cache.length !== length) {\n // From the inner end of the strip to the edge.\n const [x0, y0, x1, y1] = [\n [0, length, 0, 0], // top\n [width - length, 0, width, 0], // right\n [0, height - length, 0, height], // bottom\n [length, 0, 0, 0], // left\n ][side];\n cache.gradient = ctx.createLinearGradient(x0, y0, x1, y1);\n cache.gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');\n cache.gradient.addColorStop(1, 'rgba(0, 0, 0, 1)');\n cache.size = size;\n cache.length = length;\n }\n const strip = Math.min(length, size);\n ctx.fillStyle = cache.gradient;\n if (side === 0) ctx.fillRect(0, 0, width, strip);\n else if (side === 1) ctx.fillRect(width - strip, 0, strip, height);\n else if (side === 2) ctx.fillRect(0, height - strip, width, strip);\n else ctx.fillRect(0, 0, strip, height);\n };\n\n return {\n name: 'canvas2d',\n update(next) {\n renderer.update(next);\n },\n render(frame, backgroundColor, fade) {\n const { width, height, dpr } = frame;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.globalAlpha = 1;\n ctx.globalCompositeOperation = 'source-over';\n ctx.clearRect(0, 0, width, height);\n // With a fade, the background goes in last, behind the faded drawing, so it stays solid.\n const fading = fades(fade);\n if (backgroundColor && !fading) {\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, width, height);\n }\n const busy = renderer.render(frame) === true;\n if (fading) {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.globalAlpha = 1;\n ctx.globalCompositeOperation = 'destination-out';\n for (let side = 0; side < SIDES.length; side++) {\n const length = fade[SIDES[side]];\n if (length > 0) fadeSide(side, length, width, height);\n }\n if (backgroundColor) {\n ctx.globalCompositeOperation = 'destination-over';\n ctx.fillStyle = backgroundColor;\n ctx.fillRect(0, 0, width, height);\n }\n ctx.globalCompositeOperation = 'source-over';\n }\n return busy;\n },\n clear() {\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n },\n destroy() {\n renderer.destroy?.();\n },\n };\n },\n };\n}\n"],"mappings":";;AAGA,IAAM,QAAiC;CAAC;CAAO;CAAS;CAAU;AAAM;;;;;AAMxE,SAAgB,iBACd,gBACa;CACb,OAAO;EACL,MAAM;EACN,OAAO,QAAQ,SAAS;GACtB,MAAM,MAAM,OAAO,WAAW,IAAI;GAClC,IAAI,CAAC,KAAK,OAAO;GACjB,MAAM,WAAW,eAAe,KAAK,OAAO;GAG5C,MAAM,YAAiF,MAAM,WAAW;IACtG,UAAU;IACV,MAAM;IACN,QAAQ;GACV,EAAE;;;;;GAMF,MAAM,YAAY,MAAc,QAAgB,OAAe,WAAyB;IAEtF,MAAM,OADa,SAAS,KAAK,SAAS,IAChB,QAAQ;IAClC,MAAM,QAAQ,UAAU;IACxB,IAAI,CAAC,MAAM,YAAY,MAAM,SAAS,QAAQ,MAAM,WAAW,QAAQ;KAErE,MAAM,CAAC,IAAI,IAAI,IAAI,MAAM;MACvB;OAAC;OAAG;OAAQ;OAAG;MAAC;MAChB;OAAC,QAAQ;OAAQ;OAAG;OAAO;MAAC;MAC5B;OAAC;OAAG,SAAS;OAAQ;OAAG;MAAM;MAC9B;OAAC;OAAQ;OAAG;OAAG;MAAC;KAClB,CAAC,CAAC;KACF,MAAM,WAAW,IAAI,qBAAqB,IAAI,IAAI,IAAI,EAAE;KACxD,MAAM,SAAS,aAAa,GAAG,kBAAkB;KACjD,MAAM,SAAS,aAAa,GAAG,kBAAkB;KACjD,MAAM,OAAO;KACb,MAAM,SAAS;IACjB;IACA,MAAM,QAAQ,KAAK,IAAI,QAAQ,IAAI;IACnC,IAAI,YAAY,MAAM;IACtB,IAAI,SAAS,GAAG,IAAI,SAAS,GAAG,GAAG,OAAO,KAAK;SAC1C,IAAI,SAAS,GAAG,IAAI,SAAS,QAAQ,OAAO,GAAG,OAAO,MAAM;SAC5D,IAAI,SAAS,GAAG,IAAI,SAAS,GAAG,SAAS,OAAO,OAAO,KAAK;SAC5D,IAAI,SAAS,GAAG,GAAG,OAAO,MAAM;GACvC;GAEA,OAAO;IACL,MAAM;IACN,OAAO,MAAM;KACX,SAAS,OAAO,IAAI;IACtB;IACA,OAAO,OAAO,iBAAiB,MAAM;KACnC,MAAM,EAAE,OAAO,QAAQ,QAAQ;KAC/B,IAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;KACrC,IAAI,cAAc;KAClB,IAAI,2BAA2B;KAC/B,IAAI,UAAU,GAAG,GAAG,OAAO,MAAM;KAEjC,MAAM,SAAS,MAAM,IAAI;KACzB,IAAI,mBAAmB,CAAC,QAAQ;MAC9B,IAAI,YAAY;MAChB,IAAI,SAAS,GAAG,GAAG,OAAO,MAAM;KAClC;KACA,MAAM,OAAO,SAAS,OAAO,KAAK,MAAM;KACxC,IAAI,QAAQ;MACV,IAAI,aAAa,KAAK,GAAG,GAAG,KAAK,GAAG,CAAC;MACrC,IAAI,cAAc;MAClB,IAAI,2BAA2B;MAC/B,KAAK,IAAI,OAAO,GAAG,OAAO,MAAM,QAAQ,QAAQ;OAC9C,MAAM,SAAS,KAAK,MAAM;OAC1B,IAAI,SAAS,GAAG,SAAS,MAAM,QAAQ,OAAO,MAAM;MACtD;MACA,IAAI,iBAAiB;OACnB,IAAI,2BAA2B;OAC/B,IAAI,YAAY;OAChB,IAAI,SAAS,GAAG,GAAG,OAAO,MAAM;MAClC;MACA,IAAI,2BAA2B;KACjC;KACA,OAAO;IACT;IACA,QAAQ;KACN,IAAI,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;KACjC,IAAI,UAAU,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;IACjD;IACA,UAAU;KACR,SAAS,UAAU;IACrB;GACF;EACF;CACF;AACF"}
@@ -0,0 +1,46 @@
1
+ // src/core/color.ts
2
+ var TRANSPARENT = [
3
+ 0,
4
+ 0,
5
+ 0,
6
+ 0
7
+ ];
8
+ var probe = null;
9
+ var cache = /* @__PURE__ */ new Map();
10
+ /**
11
+ * Resolve any CSS colour to straight (non-premultiplied) sRGB components in 0–1,
12
+ * by letting the browser paint it into a 1×1 canvas. Results are cached.
13
+ */
14
+ function parseColor(color) {
15
+ if (!color || color === "transparent") return TRANSPARENT;
16
+ const cached = cache.get(color);
17
+ if (cached) return cached;
18
+ if (!probe) {
19
+ const canvas = document.createElement("canvas");
20
+ canvas.width = canvas.height = 1;
21
+ probe = canvas.getContext("2d", { willReadFrequently: true });
22
+ if (!probe) return [
23
+ 0,
24
+ 0,
25
+ 0,
26
+ 1
27
+ ];
28
+ }
29
+ probe.clearRect(0, 0, 1, 1);
30
+ probe.fillStyle = "#000";
31
+ probe.fillStyle = color;
32
+ probe.fillRect(0, 0, 1, 1);
33
+ const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;
34
+ const rgba = [
35
+ r / 255,
36
+ g / 255,
37
+ b / 255,
38
+ a / 255
39
+ ];
40
+ if (cache.size > 64) cache.clear();
41
+ cache.set(color, rgba);
42
+ return rgba;
43
+ }
44
+ exports.parseColor = parseColor;
45
+
46
+ //# sourceMappingURL=color.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.cjs","names":[],"sources":["../../src/core/color.ts"],"sourcesContent":["export type RGBA = readonly [r: number, g: number, b: number, a: number];\n\nconst TRANSPARENT: RGBA = [0, 0, 0, 0];\n\nlet probe: CanvasRenderingContext2D | null = null;\nconst cache = new Map<string, RGBA>();\n\n/**\n * Resolve any CSS colour to straight (non-premultiplied) sRGB components in 0–1,\n * by letting the browser paint it into a 1×1 canvas. Results are cached.\n */\nexport function parseColor(color: string | null): RGBA {\n if (!color || color === 'transparent') return TRANSPARENT;\n const cached = cache.get(color);\n if (cached) return cached;\n\n if (!probe) {\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 1;\n probe = canvas.getContext('2d', { willReadFrequently: true });\n if (!probe) return [0, 0, 0, 1];\n }\n probe.clearRect(0, 0, 1, 1);\n // An invalid colour leaves fillStyle unchanged, so it falls back to black.\n probe.fillStyle = '#000';\n probe.fillStyle = color;\n probe.fillRect(0, 0, 1, 1);\n const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;\n const rgba: RGBA = [r / 255, g / 255, b / 255, a / 255];\n if (cache.size > 64) cache.clear();\n cache.set(color, rgba);\n return rgba;\n}\n"],"mappings":";AAEA,IAAM,cAAoB;CAAC;CAAG;CAAG;CAAG;AAAC;AAErC,IAAI,QAAyC;AAC7C,IAAM,wBAAQ,IAAI,IAAkB;;;;;AAMpC,SAAgB,WAAW,OAA4B;CACrD,IAAI,CAAC,SAAS,UAAU,eAAe,OAAO;CAC9C,MAAM,SAAS,MAAM,IAAI,KAAK;CAC9B,IAAI,QAAQ,OAAO;CAEnB,IAAI,CAAC,OAAO;EACV,MAAM,SAAS,SAAS,cAAc,QAAQ;EAC9C,OAAO,QAAQ,OAAO,SAAS;EAC/B,QAAQ,OAAO,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;EAC5D,IAAI,CAAC,OAAO,OAAO;GAAC;GAAG;GAAG;GAAG;EAAC;CAChC;CACA,MAAM,UAAU,GAAG,GAAG,GAAG,CAAC;CAE1B,MAAM,YAAY;CAClB,MAAM,YAAY;CAClB,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC;CACzB,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,MAAM,aAAa,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC;CACpD,MAAM,OAAa;EAAC,IAAI;EAAK,IAAI;EAAK,IAAI;EAAK,IAAI;CAAG;CACtD,IAAI,MAAM,OAAO,IAAI,MAAM,MAAM;CACjC,MAAM,IAAI,OAAO,IAAI;CACrB,OAAO;AACT"}
@@ -0,0 +1,6 @@
1
+ export type RGBA = readonly [r: number, g: number, b: number, a: number];
2
+ /**
3
+ * Resolve any CSS colour to straight (non-premultiplied) sRGB components in 0–1,
4
+ * by letting the browser paint it into a 1×1 canvas. Results are cached.
5
+ */
6
+ export declare function parseColor(color: string | null): RGBA;
@@ -0,0 +1,6 @@
1
+ export type RGBA = readonly [r: number, g: number, b: number, a: number];
2
+ /**
3
+ * Resolve any CSS colour to straight (non-premultiplied) sRGB components in 0–1,
4
+ * by letting the browser paint it into a 1×1 canvas. Results are cached.
5
+ */
6
+ export declare function parseColor(color: string | null): RGBA;
@@ -0,0 +1,46 @@
1
+ // src/core/color.ts
2
+ var TRANSPARENT = [
3
+ 0,
4
+ 0,
5
+ 0,
6
+ 0
7
+ ];
8
+ var probe = null;
9
+ var cache = /* @__PURE__ */ new Map();
10
+ /**
11
+ * Resolve any CSS colour to straight (non-premultiplied) sRGB components in 0–1,
12
+ * by letting the browser paint it into a 1×1 canvas. Results are cached.
13
+ */
14
+ function parseColor(color) {
15
+ if (!color || color === "transparent") return TRANSPARENT;
16
+ const cached = cache.get(color);
17
+ if (cached) return cached;
18
+ if (!probe) {
19
+ const canvas = document.createElement("canvas");
20
+ canvas.width = canvas.height = 1;
21
+ probe = canvas.getContext("2d", { willReadFrequently: true });
22
+ if (!probe) return [
23
+ 0,
24
+ 0,
25
+ 0,
26
+ 1
27
+ ];
28
+ }
29
+ probe.clearRect(0, 0, 1, 1);
30
+ probe.fillStyle = "#000";
31
+ probe.fillStyle = color;
32
+ probe.fillRect(0, 0, 1, 1);
33
+ const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;
34
+ const rgba = [
35
+ r / 255,
36
+ g / 255,
37
+ b / 255,
38
+ a / 255
39
+ ];
40
+ if (cache.size > 64) cache.clear();
41
+ cache.set(color, rgba);
42
+ return rgba;
43
+ }
44
+ export { parseColor };
45
+
46
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","names":[],"sources":["../../src/core/color.ts"],"sourcesContent":["export type RGBA = readonly [r: number, g: number, b: number, a: number];\n\nconst TRANSPARENT: RGBA = [0, 0, 0, 0];\n\nlet probe: CanvasRenderingContext2D | null = null;\nconst cache = new Map<string, RGBA>();\n\n/**\n * Resolve any CSS colour to straight (non-premultiplied) sRGB components in 0–1,\n * by letting the browser paint it into a 1×1 canvas. Results are cached.\n */\nexport function parseColor(color: string | null): RGBA {\n if (!color || color === 'transparent') return TRANSPARENT;\n const cached = cache.get(color);\n if (cached) return cached;\n\n if (!probe) {\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 1;\n probe = canvas.getContext('2d', { willReadFrequently: true });\n if (!probe) return [0, 0, 0, 1];\n }\n probe.clearRect(0, 0, 1, 1);\n // An invalid colour leaves fillStyle unchanged, so it falls back to black.\n probe.fillStyle = '#000';\n probe.fillStyle = color;\n probe.fillRect(0, 0, 1, 1);\n const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;\n const rgba: RGBA = [r / 255, g / 255, b / 255, a / 255];\n if (cache.size > 64) cache.clear();\n cache.set(color, rgba);\n return rgba;\n}\n"],"mappings":";AAEA,IAAM,cAAoB;CAAC;CAAG;CAAG;CAAG;AAAC;AAErC,IAAI,QAAyC;AAC7C,IAAM,wBAAQ,IAAI,IAAkB;;;;;AAMpC,SAAgB,WAAW,OAA4B;CACrD,IAAI,CAAC,SAAS,UAAU,eAAe,OAAO;CAC9C,MAAM,SAAS,MAAM,IAAI,KAAK;CAC9B,IAAI,QAAQ,OAAO;CAEnB,IAAI,CAAC,OAAO;EACV,MAAM,SAAS,SAAS,cAAc,QAAQ;EAC9C,OAAO,QAAQ,OAAO,SAAS;EAC/B,QAAQ,OAAO,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;EAC5D,IAAI,CAAC,OAAO,OAAO;GAAC;GAAG;GAAG;GAAG;EAAC;CAChC;CACA,MAAM,UAAU,GAAG,GAAG,GAAG,CAAC;CAE1B,MAAM,YAAY;CAClB,MAAM,YAAY;CAClB,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC;CACzB,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,MAAM,aAAa,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC;CACpD,MAAM,OAAa;EAAC,IAAI;EAAK,IAAI;EAAK,IAAI;EAAK,IAAI;CAAG;CACtD,IAAI,MAAM,OAAO,IAAI,MAAM,MAAM;CACjC,MAAM,IAAI,OAAO,IAAI;CACrB,OAAO;AACT"}