bubble-maker 0.0.0-stage → 2.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.es.md +47 -0
- package/README.md +149 -2
- package/dist/bubble-maker.global.js +1 -0
- package/dist/bubbles.d.ts +82 -0
- package/dist/color.d.ts +4 -0
- package/dist/draw.d.ts +28 -0
- package/dist/engine.d.ts +80 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +736 -0
- package/dist/index.js.map +7 -0
- package/dist/palettes.d.ts +51 -0
- package/package.json +52 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013-2026 José Manuel Ligero Márquez (neoligero)
|
|
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.es.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Bubble Maker 2.0
|
|
2
|
+
|
|
3
|
+
Burbujas de jabón para la web: crecen al tocarlas, se empujan unas a otras, se mueven con el cursor y se comprimen en un panal de hexágonos. Sin dependencias, unos 14 KB minificado y totalmente configurable.
|
|
4
|
+
|
|
5
|
+
**[Demo en vivo](https://neoligero.github.io/bubble-maker-2.0/)** · [English](README.md) · [Bubble Maker 1.0 (2013)](https://github.com/neoligero/Bubble_maker)
|
|
6
|
+
|
|
7
|
+
Bubble Maker nació en 2013 como la navegación de una web personal: cada sección era una burbuja que crecía al pasar el ratón y empujaba a sus vecinas con temporizadores encadenados. La 2.0 es una reescritura desde cero: un pequeño motor de física más un pintor. Es lo que da vida a [neoligero.com](https://www.neoligero.com).
|
|
8
|
+
|
|
9
|
+
## Instalación
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install bubble-maker
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Uso rápido
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<canvas id="burbujas" style="width: 100%; height: 480px"></canvas>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { createBubbles } from 'bubble-maker';
|
|
23
|
+
|
|
24
|
+
const burbujas = createBubbles('#burbujas', {
|
|
25
|
+
reaction: 7, // 0 quietas … 3 solo crece al pasar … 10 muy vivas
|
|
26
|
+
palettes: { pearl: { body: '#fdf3f6' } }, // colores
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
burbujas.compress(); // todas al panal
|
|
30
|
+
burbujas.release(); // y vuelven a ser burbujas
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Qué se puede configurar
|
|
34
|
+
|
|
35
|
+
Todo es opcional. Las más útiles:
|
|
36
|
+
|
|
37
|
+
- **`reaction`** (0–10): cuánto reaccionan al cursor. `0` nada, `3` solo crece la que tocas, `10` el aire alrededor del cursor las empuja y arrastra.
|
|
38
|
+
- **`count`, `maxBubbles`, `size`, `kinds`**: cuántas, el límite, su tamaño y la mezcla de tipos (perla, oro, cristal o los tuyos).
|
|
39
|
+
- **`palettes`**: colores de cada tipo (centro iluminado, cuerpo, borde, aro), transparencia, brillo, nácar, aura. Acepta `#hex`, `rgb()` y `rgba()`. Puedes cambiar un solo campo de una paleta existente o crear tipos nuevos.
|
|
40
|
+
- **`physics`**: `stiffness` (cuánto vuelven a su sitio), `damping` (cuánto se calman), `iterations` y `gap` (separación entre burbujas).
|
|
41
|
+
- **`hoverGrow`, `float`, `lights`, `dark`, `flex`, `honeycomb`, `background`, `seed`…**
|
|
42
|
+
|
|
43
|
+
La tabla completa de opciones, la API (`add`, `remove`, `compress`, `release`, `shake`, `setReaction`, `setPalette`, `destroy`…) y el uso a bajo nivel (`BubbleField` y `paintBubble`, por ejemplo para convertir un menú HTML en burbujas) están en el [README en inglés](README.md).
|
|
44
|
+
|
|
45
|
+
## Licencia
|
|
46
|
+
|
|
47
|
+
[MIT](LICENSE) © 2013–2026 José Manuel Ligero Márquez
|
package/README.md
CHANGED
|
@@ -1,3 +1,150 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bubble Maker 2.0
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Soap bubbles for the web: bubbles that grow when you touch them, push each other out of the way, drift with the cursor and pack into a honeycomb of hexagons. No dependencies, about 14 KB minified, fully configurable.
|
|
4
|
+
|
|
5
|
+
**[Live demo](https://neoligero.github.io/bubble-maker-2.0/)** · [Español](README.es.md) · [Bubble Maker 1.0 (2013)](https://github.com/neoligero/Bubble_maker)
|
|
6
|
+
|
|
7
|
+
Bubble Maker started in 2013 as the navigation of a personal website: every section was a bubble that grew on hover and pushed its neighbours with chained timeouts. Version 2.0 is a rewrite from scratch as a small physics engine plus a painter, and it powers [neoligero.com](https://www.neoligero.com).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install bubble-maker
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
or, without a bundler:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/bubble-maker/dist/bubble-maker.global.js"></script>
|
|
19
|
+
<script>
|
|
20
|
+
BubbleMaker.createBubbles(document.querySelector('canvas'));
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<canvas id="bubbles" style="width: 100%; height: 480px"></canvas>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { createBubbles } from 'bubble-maker';
|
|
32
|
+
|
|
33
|
+
const bubbles = createBubbles('#bubbles', {
|
|
34
|
+
reaction: 7, // 0 still … 3 grow on hover … 10 very lively
|
|
35
|
+
palettes: { pearl: { body: '#fdf3f6' } },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
bubbles.compress(); // pack everything into a honeycomb
|
|
39
|
+
bubbles.release(); // …and let the bubbles go again
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`createBubbles` gives you a canvas that floats, reacts to mouse and touch, creates a bubble on click, pauses when it is off screen, respects `prefers-reduced-motion` and handles high-DPI screens.
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
Everything is optional.
|
|
47
|
+
|
|
48
|
+
| Option | Default | What it does |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `count` | `'auto'` | Bubbles at the start, or `'auto'` to fit the canvas (20–48). |
|
|
51
|
+
| `maxBubbles` | `90` | Limit when visitors add more. |
|
|
52
|
+
| `size` | `[0.035, 0.11]` | Radius range, as a fraction of the canvas size (phones get larger bubbles). |
|
|
53
|
+
| `kinds` | pearl 0.62, gold 0.24, glass 0.14 | Relative weight of each palette in new bubbles. |
|
|
54
|
+
| `palettes` | built-ins | Colours, see below. Merged field by field. |
|
|
55
|
+
| `reaction` | `5` | How much bubbles react to the cursor: `0` nothing, `3` only the bubble under the cursor grows, `10` the air around the cursor pushes bubbles away and drags them along. |
|
|
56
|
+
| `hoverGrow` | `1.6` | Radius multiplier of the bubble under the cursor. |
|
|
57
|
+
| `float` | `0.015` | Idle drift, as a fraction of the canvas size. `0` keeps them still. |
|
|
58
|
+
| `physics` | see below | `stiffness`, `damping`, `iterations`, `gap`. |
|
|
59
|
+
| `lights` | two above the canvas | `[{ x, y }]` in canvas pixels, or `(width, height) => lights`. Bubbles are lit by the nearest one. |
|
|
60
|
+
| `dark` | `false` | For dark backgrounds: a faint room shows through each bubble. |
|
|
61
|
+
| `flex` | `true` | Bubbles stretch along their motion. |
|
|
62
|
+
| `clickToCreate` | `true` | Clicking an empty spot creates a bubble. |
|
|
63
|
+
| `honeycomb` | `0.045` | Cell size of the honeycomb, as a fraction of the canvas size. |
|
|
64
|
+
| `background` | `null` | Fill colour for each frame; `null` keeps the canvas transparent. |
|
|
65
|
+
| `maxDpr` | `2` | Cap on the device pixel ratio. |
|
|
66
|
+
| `seed` | `7` | Seed of the pseudo-random layout: the same one on every load. |
|
|
67
|
+
| `respectReducedMotion` | `true` | Start as a still honeycomb for visitors who prefer reduced motion. |
|
|
68
|
+
| `onCreate` | – | Called with each bubble created by a click. |
|
|
69
|
+
|
|
70
|
+
Physics defaults for `createBubbles`: `{ stiffness: 18, damping: 3.2, iterations: 4, gap: 2 }`. Higher `stiffness` pulls bubbles back home faster, higher `damping` calms them, `iterations` is the number of collision passes per step, `gap` the breathing room in pixels.
|
|
71
|
+
|
|
72
|
+
### The object you get back
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
bubbles.add({ x, y, radius, kind }) // create a bubble
|
|
76
|
+
bubbles.remove(bubble) bubbles.clear()
|
|
77
|
+
bubbles.compress() bubbles.release() bubbles.toggle()
|
|
78
|
+
bubbles.shake(strength = 2400)
|
|
79
|
+
bubbles.setReaction(8)
|
|
80
|
+
bubbles.setPalette('gold', { body: '#caa' }) // live
|
|
81
|
+
bubbles.options.hoverGrow = 2 // live: hoverGrow, float, flex, dark, background, clickToCreate
|
|
82
|
+
bubbles.field // the physics field
|
|
83
|
+
bubbles.bubbles bubbles.compressed
|
|
84
|
+
bubbles.resize() bubbles.start() bubbles.stop() bubbles.destroy()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Colours
|
|
88
|
+
|
|
89
|
+
A palette describes a kind of bubble. Colours go from the lit centre to the rim and accept `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb()` and `rgba()`.
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
import { registerPalettes } from 'bubble-maker';
|
|
93
|
+
|
|
94
|
+
registerPalettes({
|
|
95
|
+
// change one field of a built-in…
|
|
96
|
+
pearl: { body: '#fdf3f6' },
|
|
97
|
+
// …or add a new kind (it must be complete)
|
|
98
|
+
rose: {
|
|
99
|
+
light: '#fffafc', body: '#fbe0ea', edge: '#f4bdd0', rim: '#e59ab4',
|
|
100
|
+
opacity: [0.95, 0.9, 0.92, 0.95], // centre, body, edge, rim once settled
|
|
101
|
+
clarity: 0.4, // 0 solid … 1 very see-through while floating
|
|
102
|
+
face: ['#fdeef3', '#ebc3d2'], // lit and shaded side of the hexagon tile
|
|
103
|
+
shine: 1, // optional: highlights, 0..1
|
|
104
|
+
nacre: { color: '255,226,214', alpha: 0.28 }, // optional: iridescence, or null
|
|
105
|
+
glow: '244, 189, 208', // optional: aura colour "r, g, b"
|
|
106
|
+
caustic: { color: '255,248,228', alpha: 0.55 }, // optional: light focused through the bubble
|
|
107
|
+
rimShade: 'rgba(255,248,230,0.72)', // optional
|
|
108
|
+
rimWidth: 0.06, // optional: fraction of the radius
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
createBubbles(canvas, { kinds: { rose: 3, pearl: 1 } });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Built-in palettes: `pearl`, `gold` and `glass`. `getPalette(kind)`, `listPalettes()` and `resetPalettes()` are also exported.
|
|
116
|
+
|
|
117
|
+
## Lower level
|
|
118
|
+
|
|
119
|
+
`createBubbles` is built from two parts you can use on their own, for example to turn a menu made of DOM elements into bubbles:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
import { BubbleField, paintBubble } from 'bubble-maker';
|
|
123
|
+
|
|
124
|
+
const field = new BubbleField({ stiffness: 26, damping: 4, iterations: 4, gap: 6, reaction: 5 });
|
|
125
|
+
const a = field.add({ x: 200, y: 120, base: 50, kind: 'gold', grow: 1.35 });
|
|
126
|
+
field.pointer.x = mouseX; field.pointer.y = mouseY; field.pointer.inside = true;
|
|
127
|
+
|
|
128
|
+
function frame(dt) {
|
|
129
|
+
field.step(dt); // seconds
|
|
130
|
+
for (const b of field.bubbles) paintBubble(ctx, b, { lights: [{ x: 80, y: -300 }] });
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
A bubble has `x, y` (position), `r` (current radius), `base` (resting radius), `grow`, `hx, hy` (the point it springs back to: move it to move the bubble), `shape` (`0` circle … `1` hexagon), `alpha`, `ghost` (ignores collisions), `fixed` and `kind`. `field.hit(x, y)` finds the bubble under a point and `field.kick(bubble, vx, vy)` pushes it. Also exported: `prng`, `clamp`, `lerp`, `smooth`, `easeInOut`, `cellPath`, `parseColor`.
|
|
135
|
+
|
|
136
|
+
## Browsers
|
|
137
|
+
|
|
138
|
+
Any browser with Canvas 2D and `PointerEvent`. ES2020 output, TypeScript types included.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm install
|
|
144
|
+
npm test # builds and runs the tests (node:test)
|
|
145
|
+
npm run demo # builds the demo into docs/ (served by GitHub Pages)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
[MIT](LICENSE) © 2013–2026 José Manuel Ligero Márquez
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var BubbleMaker=(()=>{var ee=Object.defineProperty;var Me=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var Pe=Object.prototype.hasOwnProperty;var Be=(e,o)=>{for(var n in o)ee(e,n,{get:o[n],enumerable:!0})},ke=(e,o,n,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of we(o))!Pe.call(e,r)&&r!==n&&ee(e,r,{get:()=>o[r],enumerable:!(t=Me(o,r))||t.enumerable});return e};var Ce=e=>ke(ee({},"__esModule",{value:!0}),e);var Ie={};Be(Ie,{BubbleField:()=>D,cellPath:()=>X,clamp:()=>te,createBubbles:()=>xe,defaultPalettes:()=>_,easeInOut:()=>he,getPalette:()=>J,lerp:()=>W,listPalettes:()=>pe,paintBubble:()=>Q,parseColor:()=>V,prng:()=>U,registerPalettes:()=>Y,resetPalettes:()=>ge,smooth:()=>K});var D=class{constructor(o={}){this.bubbles=[];this.pointer={x:-1e5,y:-1e5,inside:!1};this.focused=null;this.hoverEnabled=!0;this.bounds=null;this.pv={x:0,y:0};this.last={x:-1e5,y:-1e5,t:0};this.stiffness=o.stiffness??40,this.damping=o.damping??6,this.iterations=o.iterations??3,this.gap=o.gap??2,this.reaction=o.reaction??3}add(o){let n={px:o.x,py:o.y,r:o.base,grow:1.55,hover:0,hx:o.x,hy:o.y,shape:0,alpha:1,ghost:!1,fixed:!1,kind:"pearl",...o};return this.bubbles.push(n),n}kick(o,n,t,r=1/60){o.px-=n*r,o.py-=t*r}hit(o,n){for(let t=this.bubbles.length-1;t>=0;t--){let r=this.bubbles[t],i=o-r.x,b=n-r.y;if(i*i+b*b<r.r*r.r&&r.alpha>.2)return r}return null}step(o){let n=Math.min(o,.03333333333333333),t=this.bubbles,r=a=>1-Math.exp(-n*a),i=this.hoverEnabled&&this.pointer.inside?this.hit(this.pointer.x,this.pointer.y):null,b=Math.exp(-this.damping*n),f=Math.min(Math.max(this.reaction,0),10),g=Math.min(f/3,1)*(1+Math.max(f-3,0)/7*.6),c=Math.max(f-2,0)/8,d=this.pointer;if(this.last.t+=n,d.x!==this.last.x||d.y!==this.last.y){if(this.last.x>-1e4&&d.inside){let a=Math.max(this.last.t,.004166666666666667);this.pv.x+=((d.x-this.last.x)/a-this.pv.x)*.6,this.pv.y+=((d.y-this.last.y)/a-this.pv.y)*.6}this.last.x=d.x,this.last.y=d.y,this.last.t=0}else this.last.t>.06&&(this.pv.x*=1-r(10),this.pv.y*=1-r(10));let L=c>0&&this.hoverEnabled&&d.inside,x=60+160*c;for(let a of t){let S=a===i||a===this.focused?1:0;a.hover+=(S-a.hover)*r(9);let p=a.base*(1+(a.grow-1)*g*a.hover);if(a.r+=(p-a.r)*r(11),a.fixed){a.px=a.x,a.py=a.y;continue}let u=(a.x-a.px)*b,h=(a.y-a.py)*b,y=this.stiffness*(a.hx-a.x),C=this.stiffness*(a.hy-a.y);if(L&&a.alpha>.2&&a!==i){let v=a.x-d.x,M=a.y-d.y,P=Math.hypot(v,M)||1,B=P-a.r;if(B>0&&B<x){let $=(1-B/x)**2;y+=v/P*1600*c*$+(this.pv.x-u/n)*6*c*$,C+=M/P*1600*c*$+(this.pv.y-h/n)*6*c*$}}a.px=a.x,a.py=a.y,a.x+=u+y*n*n,a.y+=h+C*n*n}for(let a=0;a<this.iterations;a++)for(let S=0;S<t.length;S++){let p=t[S];if(!p.ghost)for(let u=S+1;u<t.length;u++){let h=t[u];if(h.ghost)continue;let y=h.x-p.x,C=h.y-p.y,v=p.r+h.r+this.gap,M=y*y+C*C;if(M>=v*v||M<1e-6)continue;let P=Math.sqrt(M),B=v-P,$=y/P,F=C/P,G=p.r*p.r*(1+p.hover*4),T=h.r*h.r*(1+h.hover*4),A=p.fixed?0:T/(G+T),q=h.fixed?0:G/(G+T);p.fixed&&h.fixed||(p.fixed&&(q=1),h.fixed&&(A=1),p.x-=$*B*A,p.y-=F*B*A,h.x+=$*B*q,h.y+=F*B*q)}}let w=this.bounds;if(w)for(let a of t)a.fixed||(a.x=Math.min(Math.max(a.x,w.x+a.r),w.x+w.w-a.r),a.y=Math.min(Math.max(a.y,w.y+a.r),w.y+w.h-a.r))}};function U(e=2011){let o=e>>>0;return()=>{o=o+1831565813>>>0;let n=o;return n=Math.imul(n^n>>>15,n|1),n^=n+Math.imul(n^n>>>7,n|61),((n^n>>>14)>>>0)/4294967296}}var te=(e,o=0,n=1)=>Math.min(Math.max(e,o),n),W=(e,o,n)=>e+(o-e)*n,K=(e,o,n)=>{let t=te((n-e)/(o-e));return t*t*(3-2*t)},he=e=>e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2;function V(e){let o=e.trim().toLowerCase();if(o.startsWith("#")){let r=o.slice(1);if((r.length===3||r.length===4)&&(r=[...r].map(b=>b+b).join("")),r.length!==6&&r.length!==8)throw new Error(`bubble-maker: invalid colour "${e}"`);let i=parseInt(r,16);if(Number.isNaN(i))throw new Error(`bubble-maker: invalid colour "${e}"`);return r.length===6?[i>>16&255,i>>8&255,i&255,1]:[i>>>24&255,i>>16&255,i>>8&255,(i&255)/255]}let n=/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+%?))?\s*\)$/.exec(o);if(!n)throw new Error(`bubble-maker: invalid colour "${e}"`);let t=n[4]===void 0?1:n[4].endsWith("%")?parseFloat(n[4])/100:parseFloat(n[4]);return[Math.round(+n[1]),Math.round(+n[2]),Math.round(+n[3]),Math.min(1,Math.max(0,t))]}var fe=(e,o=1)=>`rgba(${e[0]},${e[1]},${e[2]},${(e[3]*o).toFixed(3)})`;var me=e=>({...e,opacity:[...e.opacity],face:[...e.face]}),_=Object.freeze({pearl:{light:"#fffffd",body:"#faf7f0",edge:"#eee6d6",rim:"#dcd0ba",opacity:[.96,.9,.92,.95],clarity:.3,face:["#fdfbf6","#e6ddcc"],shine:1,nacre:{color:"255,226,214",alpha:.28},caustic:{color:"255,248,228",alpha:.55},rimShade:"rgba(255,248,230,0.72)",rimWidth:.06},gold:{light:"#f2dfac",body:"#dcbe7c",edge:"#c4a15a",rim:"#ac8b44",opacity:[1,1,1,1],clarity:.3,face:["#e4cb92","#b99650"],shine:.62,nacre:null,glow:"240, 206, 128",caustic:{color:"255,234,184",alpha:.38},rimShade:"rgba(255,236,186,0.55)",rimWidth:.06},glass:{light:"#ffffff",body:"#fffdf5",edge:"#fffaec",rim:"#ffffff",opacity:[.04,.1,.38,.7],clarity:0,face:["rgba(255,255,255,0.35)","rgba(234,216,168,0.25)"],shine:1,nacre:{color:"255,214,170",alpha:.22},caustic:{color:"255,248,228",alpha:.55},rimShade:"rgba(255,248,230,0.72)",rimWidth:.045}}),N=Object.fromEntries(Object.entries(_).map(([e,o])=>[e,me(o)])),ne=0,re=()=>ne;function Y(e){for(let[o,n]of Object.entries(e)){let r={...N[o]??{},...n};for(let i of["light","body","edge","rim","opacity","clarity","face"])if(r[i]===void 0)throw new Error(`bubble-maker: palette "${o}" is missing "${i}"`);N[o]=r}ne++}function J(e){return N[e]??N.pearl}function pe(){return Object.keys(N)}function ge(){N=Object.fromEntries(Object.entries(_).map(([e,o])=>[e,me(o)])),ne++}var Se=Math.sqrt(3)/2,ae=Math.PI*2;function X(e,o,n,t,r){let i=t*(1-r),b=(t-i)/Se;if(e.beginPath(),b<.01){e.arc(o,n,t,0,ae);return}for(let f=0;f<6;f++){let g=Math.PI/3*f;e.arc(o+b*Math.cos(g),n+b*Math.sin(g),i,g-Math.PI/6,g+Math.PI/6)}e.closePath()}var Re=[.35,.75,.45,.2],oe=new Map,ye=-1;function Ee(e){let o=[e.light,e.body,e.edge,e.rim].map(V),n=e.caustic?.color??"255,248,228";return{stops:[0,.62,.9,1].map((t,r)=>[t,o[r],e.opacity[r]*(1-e.clarity*Re[r]),e.opacity[r]]),face:[e.face[0],e.face[1]],nacre:e.nacre===null?null:e.nacre?[e.nacre.color,e.nacre.alpha]:["255,226,214",.28],causticTint:n,causticAlpha:e.caustic?.alpha??.55,glow:e.glow??null,shine:e.shine??1,rimShade:e.rimShade??"rgba(255,248,230,0.72)",rimWidth:e.rimWidth??.06}}function Oe(e){ye!==re()&&(oe.clear(),ye=re());let o=oe.get(e);return o||(o=Ee(J(e)),oe.set(e,o)),o}function Le(e,o,n){let t=n[0],r=1/0;for(let i of n){let b=(i.x-e)**2+(i.y-o)**2;b<r&&(r=b,t=i)}return t}var $e=["rgba(250,244,232,0.5)","rgba(226,212,186,0.62)"];function Q(e,o,n={}){let t=o.r,r=o.shape;if(t<.5||o.alpha<=.01)return;let i=Oe(o.kind),b=n.lights?.length?n.lights:[{x:o.x-1e3,y:o.y-1600}],f=Le(o.x,o.y,b),g=f.x-o.x,c=f.y-o.y,d=Math.hypot(g,c)||1;g/=d,c/=d,e.save(),e.globalAlpha=o.alpha,e.translate(o.x,o.y);let L=o.x-o.px,x=o.y-o.py,w=n.flex?Math.min(Math.hypot(L,x)/(t*1.5+4),.12)*(1-r):0;if(w>.01){let u=Math.atan2(x,L);e.rotate(u),e.scale(1+w*.6,1-w*.4),e.rotate(-u)}let a;if(r>.6)a=e.createLinearGradient(g*t,c*t,-g*t,-c*t),a.addColorStop(0,i.face[0]),a.addColorStop(1,i.face[1]);else{a=e.createRadialGradient(g*t*.35,c*t*.35,t*.05,0,0,t);let u=Math.min(1,r/.6),h=u*u*(3-2*u);for(let[y,C,v,M]of i.stops)a.addColorStop(y,fe(C,v+(M-v)*h));if(n.dark){let[y,C]=n.room??$e,v=e.createRadialGradient(g*t*.3,c*t*.3,0,0,0,t);v.addColorStop(0,y),v.addColorStop(1,C),X(e,0,0,t,r),e.fillStyle=v,e.fill()}}if(n.wallShadow&&r>.6&&(e.shadowColor=`rgba(96, 72, 30, ${.32*(r-.6)/.4})`,e.shadowBlur=t*.7,e.shadowOffsetX=-g*t*.18,e.shadowOffsetY=t*.22),i.glow&&n.glow&&(e.shadowColor=`rgba(${i.glow}, ${.65*n.glow})`,e.shadowBlur=t*1.8*n.glow,e.shadowOffsetX=0,e.shadowOffsetY=0),X(e,0,0,t,r),e.fillStyle=a,e.fill(),e.shadowColor="transparent",e.shadowBlur=0,e.shadowOffsetX=0,e.shadowOffsetY=0,e.save(),X(e,0,0,t,r),e.clip(),i.nacre&&r<.7){let u=e.createRadialGradient(-g*t*.55,-c*t*.55,0,-g*t*.55,-c*t*.55,t*.9);u.addColorStop(0,`rgba(${i.nacre[0]},${i.nacre[1]})`),u.addColorStop(.5,"rgba(214,236,240,0.14)"),u.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=u,e.fillRect(-t,-t,t*2,t*2)}let S=1-Math.min(1,r/.75);for(let u of b){let h=u.x-o.x,y=u.y-o.y,C=Math.hypot(h,y)||1;h/=C,y/=C;let v=u===f?1:.55,M=h*t*.52,P=y*t*.52,B=t*(.16+.1*v),$=t*(.07+.05*v),F=e.createRadialGradient(M,P,0,M,P,B),G=Math.min(1,i.shine*v*(.35+.65*S));if(F.addColorStop(0,`rgba(255,255,255,${G})`),F.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=F,e.beginPath(),e.ellipse(M,P,B,$,Math.atan2(y,h)+Math.PI/2,0,ae),e.fill(),v===1&&S>0&&t>6){let T=e.createRadialGradient(M,P,0,M,P,t*.075);T.addColorStop(0,`rgba(255,255,255,${(.95*S).toFixed(3)})`),T.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=T,e.beginPath(),e.ellipse(M,P,t*.075,t*.05,Math.atan2(y,h)+Math.PI/2,0,ae),e.fill()}}if(S>0){let u=-g*t*.58,h=-c*t*.58,y=e.createRadialGradient(u,h,0,u,h,t*.42);y.addColorStop(0,`rgba(${i.causticTint},${(i.causticAlpha*S).toFixed(3)})`),y.addColorStop(1,`rgba(${i.causticTint},0)`),e.fillStyle=y,e.fillRect(-t,-t,t*2,t*2)}if(r>.5){let u=e.createLinearGradient(g*t,c*t,-g*t,-c*t);u.addColorStop(0,`rgba(255,255,255,${.32*(r-.5)*2})`),u.addColorStop(.45,"rgba(255,255,255,0)"),e.fillStyle=u,e.fillRect(-t,-t,t*2,t*2)}e.restore();let p=e.createLinearGradient(g*t,c*t,-g*t,-c*t);p.addColorStop(0,"rgba(255,255,255,1)"),p.addColorStop(.35,"rgba(255,255,255,0)"),p.addColorStop(.7,"rgba(255,255,255,0)"),p.addColorStop(1,i.rimShade),e.lineWidth=Math.max(.8,t*i.rimWidth),e.strokeStyle=p,X(e,0,0,t-e.lineWidth/2,r),e.stroke(),e.restore()}var Te={pearl:.62,gold:.24,glass:.14};function xe(e,o={}){let n=typeof e=="string"?document.querySelector(e):e,t=n?.getContext("2d");if(!n||!t)throw new Error("bubble-maker: a <canvas> element is needed");let r={count:"auto",maxBubbles:90,size:[.035,.11],kinds:Te,reaction:5,hoverGrow:1.6,float:.015,flex:!0,clickToCreate:!0,honeycomb:.045,background:null,maxDpr:2,seed:7,respectReducedMotion:!0,dark:!1,...o};r.palettes&&Y(r.palettes);let i=U(r.seed),b=new D({stiffness:18,damping:3.2,iterations:4,gap:2,...r.physics,reaction:r.reaction}),f=new Map,g=r.respectReducedMotion&&typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches,c=0,d=0,L=1,x=!1,w=0,a=!1,S=!0,p=0,u=0,h=!1,y=()=>{let s=Object.entries(r.kinds),l=0;for(let[,R]of s)l+=R;let k=i()*l;for(let[R,m]of s)if((k-=m)<=0)return R;return s[0]?.[0]??"pearl"},C=()=>c<600,v=()=>L*(C()?r.size[0]*1.5+i()*r.size[1]*.7:r.size[0]+i()*(r.size[1]-r.size[0]));function M(s={}){let l=s.x??W(c*.08,c*.92,i()),k=s.y??W(d*.12,d*.88,i()),R=s.radius??v(),m=b.add({x:l,y:k,base:R*.2,kind:s.kind??y(),grow:r.hoverGrow});return f.set(m,{x:l,y:k,r:R,phase:i()*Math.PI*2}),x&&B(),m}function P(){let s=n.getBoundingClientRect();c=s.width,d=s.height;let l=Math.min(window.devicePixelRatio||1,r.maxDpr);n.width=Math.max(1,Math.round(c*l)),n.height=Math.max(1,Math.round(d*l)),t.setTransform(l,0,0,l,0,0),L=Math.min(c,d*1.4),b.bounds={x:0,y:0,w:c,h:d};for(let k of f.values())k.x=Math.min(k.x,c-10),k.y=Math.min(k.y,d-10);x&&B()}function B(){let s=[...f.keys()],l=L*(C()?r.honeycomb*1.5:r.honeycomb),k=(2*l+2)/Math.sqrt(3),R=[],m=Math.ceil(Math.sqrt(s.length))+3;for(let O=-m;O<=m;O++)for(let I=-m;I<=m;I++){let j=c/2+1.5*k*O,z=d/2+Math.sqrt(3)*k*(I+O/2);j<l||j>c-l||z<l||z>d-l||R.push({x:j,y:z,d:Math.hypot(j-c/2,(z-d/2)*1.2)})}R.sort((O,I)=>O.d-I.d);let E=new Set(s);for(let O of f.values())delete O.slot;for(let O of R){if(!E.size)break;let I=null,j=1/0;for(let z of E){let de=(z.x-O.x)**2+(z.y-O.y)**2;de<j&&(j=de,I=z)}if(!I)break;E.delete(I),f.get(I).slot={x:O.x,y:O.y,a:l}}}let $=()=>typeof r.lights=="function"?r.lights(c,d):r.lights??[{x:c*.3,y:-d*.35},{x:c*.72,y:-d*.3}];function F(s){if(!a||h)return;let l=Math.min(Math.max((s-p)/1e3,0),.25);p=s;let k=s/1e3;w=W(w,x?1:0,1-Math.exp(-l*3)),b.hoverEnabled=!x;for(let[m,E]of f)x&&E.slot?(m.hx=E.slot.x,m.hy=E.slot.y,m.base=W(m.base,E.slot.a,.12)):(m.hx=E.x+Math.sin(k*.7+E.phase)*L*r.float,m.hy=E.y+Math.cos(k*.6+E.phase)*L*r.float,m.base=W(m.base,E.r,.08)),m.shape=K(.35,1,w),m.grow=r.hoverGrow;let R=Math.min(8,Math.max(1,Math.ceil(l*60)));for(let m=0;m<R;m++)b.step(l/R);G(),S?u=requestAnimationFrame(F):a=!1}function G(){r.background?(t.fillStyle=r.background,t.fillRect(0,0,c,d)):t.clearRect(0,0,c,d);let s=$();for(let l of f.keys())Q(t,l,{lights:s,glow:w*.5,wallShadow:w>.4,flex:r.flex,dark:r.dark})}function T(){a||h||(a=!0,p=performance.now(),u=requestAnimationFrame(F))}function A(){a=!1,cancelAnimationFrame(u)}let q=s=>{let l=n.getBoundingClientRect();b.pointer.x=s.clientX-l.left,b.pointer.y=s.clientY-l.top,b.pointer.inside=!0},se=s=>q(s),ie=()=>b.pointer.inside=!1,le=s=>s.pointerType!=="mouse"&&q(s),Z=0,H=s=>{s.pointerType!=="mouse"&&(clearTimeout(Z),Z=window.setTimeout(()=>b.pointer.inside=!1,450))},ce=s=>{if(!r.clickToCreate)return;let l=n.getBoundingClientRect(),k=s.clientX-l.left,R=s.clientY-l.top;if(b.hit(k,R)||f.size>=r.maxBubbles)return;let m=M({x:k,y:R,radius:L*(C()?.05+i()*.06:.03+i()*.05)});r.onCreate?.(m)};n.addEventListener("pointermove",se),n.addEventListener("pointerleave",ie),n.addEventListener("pointerdown",le),n.addEventListener("pointerup",H),n.addEventListener("pointercancel",H),n.addEventListener("click",ce);let be=typeof ResizeObserver=="function"?new ResizeObserver(()=>P()):null;be?.observe(n);let ue=typeof IntersectionObserver=="function"?new IntersectionObserver(([s])=>{S=s.isIntersecting,S&&T()}):null;ue?.observe(n),P();let ve=r.count==="auto"?Math.round(Math.min(48,Math.max(20,c*d/9e3))):r.count;for(let s=0;s<ve;s++)M();for(let s=0;s<200;s++)b.step(1/60);if(g){x=!0,w=1,B();for(let[s,l]of f)s.x=s.px=l.slot?.x??s.x,s.y=s.py=l.slot?.y??s.y,s.base=s.r=l.slot?.a??s.base,s.shape=1;G()}else T();return{field:b,options:r,get bubbles(){return[...f.keys()]},get compressed(){return x},add:M,remove(s){f.delete(s);let l=b.bubbles.indexOf(s);l>=0&&b.bubbles.splice(l,1),x&&B()},clear(){f.clear(),b.bubbles.length=0},compress(){x=!0,B()},release(){x=!1},toggle(){return x=!x,x&&B(),x},shake(s=2400){x=!1;for(let l of f.keys())b.kick(l,(i()-.5)*s,(i()-.5)*s)},setReaction(s){b.reaction=s},setPalette(s,l){Y({[s]:l})},resize:P,start:T,stop:A,destroy(){h=!0,A(),clearTimeout(Z),be?.disconnect(),ue?.disconnect(),n.removeEventListener("pointermove",se),n.removeEventListener("pointerleave",ie),n.removeEventListener("pointerdown",le),n.removeEventListener("pointerup",H),n.removeEventListener("pointercancel",H),n.removeEventListener("click",ce),f.clear(),b.bubbles.length=0}}}return Ce(Ie);})();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { BubbleField, type Bubble, type FieldOptions } from './engine';
|
|
2
|
+
import { type Light } from './draw';
|
|
3
|
+
import { type BubblePalette } from './palettes';
|
|
4
|
+
export interface BubblesOptions {
|
|
5
|
+
/** How many bubbles to start with, or 'auto' to fit the canvas area. Default 'auto'. */
|
|
6
|
+
count?: number | 'auto';
|
|
7
|
+
/** Hard limit when the visitor adds more. Default 90. */
|
|
8
|
+
maxBubbles?: number;
|
|
9
|
+
/** Radius range as a fraction of the canvas size. Default [0.035, 0.11] (larger on phones). */
|
|
10
|
+
size?: readonly [number, number];
|
|
11
|
+
/** Relative weight of each palette kind in new bubbles. Default pearl 62 %, gold 24 %, glass 14 %. */
|
|
12
|
+
kinds?: Record<string, number>;
|
|
13
|
+
/** Registers or overrides palettes (merged field by field). */
|
|
14
|
+
palettes?: Record<string, Partial<BubblePalette>>;
|
|
15
|
+
/** Physics of the field. */
|
|
16
|
+
physics?: FieldOptions;
|
|
17
|
+
/** How much the bubbles react to the cursor, 0 (nothing) … 3 (grow on hover) … 10 (very lively). Default 5. */
|
|
18
|
+
reaction?: number;
|
|
19
|
+
/** Radius multiplier of the bubble under the cursor at maximum reaction. Default 1.6. */
|
|
20
|
+
hoverGrow?: number;
|
|
21
|
+
/** Idle drift, as a fraction of the canvas size. 0 keeps them still. Default 0.015. */
|
|
22
|
+
float?: number;
|
|
23
|
+
/** Light sources in canvas pixels, or a function of the canvas size. Default: two lights above the canvas. */
|
|
24
|
+
lights?: Light[] | ((width: number, height: number) => Light[]);
|
|
25
|
+
/** Paint for dark backgrounds (a faint room shows through each bubble). */
|
|
26
|
+
dark?: boolean;
|
|
27
|
+
/** Flexible glass: bubbles stretch along their motion. Default true. */
|
|
28
|
+
flex?: boolean;
|
|
29
|
+
/** Clicking an empty spot creates a bubble. Default true. */
|
|
30
|
+
clickToCreate?: boolean;
|
|
31
|
+
/** Honeycomb cell size as a fraction of the canvas size. Default 0.045 (larger on phones). */
|
|
32
|
+
honeycomb?: number;
|
|
33
|
+
/** Fill the canvas with a colour each frame. Default null (transparent). */
|
|
34
|
+
background?: string | null;
|
|
35
|
+
/** Cap on the device pixel ratio. Default 2. */
|
|
36
|
+
maxDpr?: number;
|
|
37
|
+
/** Seed of the pseudo-random layout, so it is the same on every load. Default 7. */
|
|
38
|
+
seed?: number;
|
|
39
|
+
/** Start as a still honeycomb when the visitor prefers reduced motion. Default true. */
|
|
40
|
+
respectReducedMotion?: boolean;
|
|
41
|
+
/** Called when a bubble is created by a click. */
|
|
42
|
+
onCreate?: (bubble: Bubble) => void;
|
|
43
|
+
}
|
|
44
|
+
export interface BubbleCanvas {
|
|
45
|
+
/** The physics field: add, remove or inspect bubbles directly. */
|
|
46
|
+
readonly field: BubbleField;
|
|
47
|
+
readonly bubbles: Bubble[];
|
|
48
|
+
readonly compressed: boolean;
|
|
49
|
+
/** Options in use. Changing hoverGrow, float, flex, dark, background or clickToCreate takes effect on the next frame. */
|
|
50
|
+
readonly options: {
|
|
51
|
+
hoverGrow: number;
|
|
52
|
+
float: number;
|
|
53
|
+
flex: boolean;
|
|
54
|
+
dark: boolean;
|
|
55
|
+
background: string | null;
|
|
56
|
+
clickToCreate: boolean;
|
|
57
|
+
};
|
|
58
|
+
/** Adds a bubble at a position (default: random). `radius` is in pixels. */
|
|
59
|
+
add(init?: {
|
|
60
|
+
x?: number;
|
|
61
|
+
y?: number;
|
|
62
|
+
radius?: number;
|
|
63
|
+
kind?: string;
|
|
64
|
+
}): Bubble;
|
|
65
|
+
remove(b: Bubble): void;
|
|
66
|
+
clear(): void;
|
|
67
|
+
/** Packs every bubble into a honeycomb of hexagonal tiles. */
|
|
68
|
+
compress(): void;
|
|
69
|
+
/** Lets the tiles go back to being bubbles. */
|
|
70
|
+
release(): void;
|
|
71
|
+
toggle(): boolean;
|
|
72
|
+
/** Gives every bubble a random push. Default strength 2400 px/s. */
|
|
73
|
+
shake(strength?: number): void;
|
|
74
|
+
setReaction(level: number): void;
|
|
75
|
+
/** Changes colours at runtime. */
|
|
76
|
+
setPalette(kind: string, palette: Partial<BubblePalette>): void;
|
|
77
|
+
resize(): void;
|
|
78
|
+
start(): void;
|
|
79
|
+
stop(): void;
|
|
80
|
+
destroy(): void;
|
|
81
|
+
}
|
|
82
|
+
export declare function createBubbles(target: HTMLCanvasElement | string, options?: BubblesOptions): BubbleCanvas;
|
package/dist/color.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type Rgba = [number, number, number, number];
|
|
2
|
+
/** Parses #rgb, #rgba, #rrggbb, #rrggbbaa, rgb() and rgba() into [r, g, b, a] (a in 0..1). */
|
|
3
|
+
export declare function parseColor(input: string): Rgba;
|
|
4
|
+
export declare const rgbaString: (c: Rgba, alpha?: number) => string;
|
package/dist/draw.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Bubble } from './engine';
|
|
2
|
+
/**
|
|
3
|
+
* Traces a cell that morphs from a circle (t = 0) to a flat-top hexagon
|
|
4
|
+
* (t = 1) while keeping the same apothem `a`: a hexagon of circumradius R
|
|
5
|
+
* whose corners are rounded with radius c, where c shrinks as t grows.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cellPath(ctx: CanvasRenderingContext2D, x: number, y: number, a: number, t: number): void;
|
|
8
|
+
/** A light in the scene, in canvas pixels. Bubbles are lit by the nearest one. */
|
|
9
|
+
export interface Light {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PaintOptions {
|
|
14
|
+
/** Light sources. Default: one far up and to the left of each bubble. */
|
|
15
|
+
lights?: Light[];
|
|
16
|
+
/** 0..1 aura around bubbles whose palette defines `glow`. */
|
|
17
|
+
glow?: number;
|
|
18
|
+
/** Hexagons mounted on a wall cast a soft shadow. */
|
|
19
|
+
wallShadow?: boolean;
|
|
20
|
+
/** Stretch along the motion (flexible glass). */
|
|
21
|
+
flex?: boolean;
|
|
22
|
+
/** On a dark background: carry a faint room inside the bubble, as the marble does on a light one. */
|
|
23
|
+
dark?: boolean;
|
|
24
|
+
/** Colours of that room, centre and edge. */
|
|
25
|
+
room?: readonly [string, string];
|
|
26
|
+
}
|
|
27
|
+
/** Paints one bubble (or hexagonal tile) on a 2D canvas. Colours come from the palette of `b.kind`. */
|
|
28
|
+
export declare function paintBubble(ctx: CanvasRenderingContext2D, b: Bubble, o?: PaintOptions): void;
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bubble Maker 2.0 — physics core.
|
|
3
|
+
*
|
|
4
|
+
* Rewrite of the 2013–2015 Bubble Maker (github.com/neoligero/Bubble_maker).
|
|
5
|
+
* The original grew a bubble on hover and pushed its neighbours with chained
|
|
6
|
+
* 200 ms timeouts, splitting the push by |dx|/(|dx|+|dy|). This version runs
|
|
7
|
+
* a continuous simulation instead:
|
|
8
|
+
* - Verlet integration with a damped spring towards each bubble's home,
|
|
9
|
+
* - position-based collision relaxation along the true centre-to-centre
|
|
10
|
+
* direction, weighted by mass (area), so chain reactions resolve in-frame,
|
|
11
|
+
* - hover / focus growth with eased radius, touch friendly,
|
|
12
|
+
* - the cursor moves the air: nearby bubbles drift away and follow its wake,
|
|
13
|
+
* with a single 0–10 `reaction` dial,
|
|
14
|
+
* - a `shape` value (0 circle → 1 hexagon) used by renderers to compress
|
|
15
|
+
* bubbles into a honeycomb.
|
|
16
|
+
*/
|
|
17
|
+
export interface Bubble {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
px: number;
|
|
21
|
+
py: number;
|
|
22
|
+
r: number;
|
|
23
|
+
base: number;
|
|
24
|
+
grow: number;
|
|
25
|
+
hover: number;
|
|
26
|
+
hx: number;
|
|
27
|
+
hy: number;
|
|
28
|
+
shape: number;
|
|
29
|
+
alpha: number;
|
|
30
|
+
ghost: boolean;
|
|
31
|
+
fixed: boolean;
|
|
32
|
+
kind: string;
|
|
33
|
+
el?: HTMLElement;
|
|
34
|
+
}
|
|
35
|
+
export interface FieldOptions {
|
|
36
|
+
stiffness?: number;
|
|
37
|
+
damping?: number;
|
|
38
|
+
iterations?: number;
|
|
39
|
+
gap?: number;
|
|
40
|
+
reaction?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare class BubbleField {
|
|
43
|
+
bubbles: Bubble[];
|
|
44
|
+
pointer: {
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
inside: boolean;
|
|
48
|
+
};
|
|
49
|
+
focused: Bubble | null;
|
|
50
|
+
hoverEnabled: boolean;
|
|
51
|
+
bounds: {
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
w: number;
|
|
55
|
+
h: number;
|
|
56
|
+
} | null;
|
|
57
|
+
stiffness: number;
|
|
58
|
+
damping: number;
|
|
59
|
+
iterations: number;
|
|
60
|
+
gap: number;
|
|
61
|
+
reaction: number;
|
|
62
|
+
private pv;
|
|
63
|
+
private last;
|
|
64
|
+
constructor(o?: FieldOptions);
|
|
65
|
+
add(init: Partial<Bubble> & {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
base: number;
|
|
69
|
+
}): Bubble;
|
|
70
|
+
/** Gives a bubble an instant velocity (px/s). */
|
|
71
|
+
kick(b: Bubble, vx: number, vy: number, dt?: number): void;
|
|
72
|
+
hit(x: number, y: number): Bubble | null;
|
|
73
|
+
step(dtIn: number): void;
|
|
74
|
+
}
|
|
75
|
+
/** Small deterministic PRNG so layouts are stable between reloads. */
|
|
76
|
+
export declare function prng(seed?: number): () => number;
|
|
77
|
+
export declare const clamp: (v: number, a?: number, b?: number) => number;
|
|
78
|
+
export declare const lerp: (a: number, b: number, t: number) => number;
|
|
79
|
+
export declare const smooth: (e0: number, e1: number, x: number) => number;
|
|
80
|
+
export declare const easeInOut: (t: number) => number;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { BubbleField, prng, clamp, lerp, smooth, easeInOut } from './engine';
|
|
2
|
+
export type { Bubble, FieldOptions } from './engine';
|
|
3
|
+
export { paintBubble, cellPath } from './draw';
|
|
4
|
+
export type { Light, PaintOptions } from './draw';
|
|
5
|
+
export { defaultPalettes, getPalette, listPalettes, registerPalettes, resetPalettes } from './palettes';
|
|
6
|
+
export type { BubblePalette, PaletteMap } from './palettes';
|
|
7
|
+
export { parseColor } from './color';
|
|
8
|
+
export { createBubbles } from './bubbles';
|
|
9
|
+
export type { BubbleCanvas, BubblesOptions } from './bubbles';
|