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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/engine.ts", "../src/color.ts", "../src/palettes.ts", "../src/draw.ts", "../src/bubbles.ts"],
4
+ "sourcesContent": ["/**\n * Bubble Maker 2.0 \u2014 physics core.\n *\n * Rewrite of the 2013\u20132015 Bubble Maker (github.com/neoligero/Bubble_maker).\n * The original grew a bubble on hover and pushed its neighbours with chained\n * 200 ms timeouts, splitting the push by |dx|/(|dx|+|dy|). This version runs\n * a continuous simulation instead:\n * - Verlet integration with a damped spring towards each bubble's home,\n * - position-based collision relaxation along the true centre-to-centre\n * direction, weighted by mass (area), so chain reactions resolve in-frame,\n * - hover / focus growth with eased radius, touch friendly,\n * - the cursor moves the air: nearby bubbles drift away and follow its wake,\n * with a single 0\u201310 `reaction` dial,\n * - a `shape` value (0 circle \u2192 1 hexagon) used by renderers to compress\n * bubbles into a honeycomb.\n */\nexport interface Bubble {\n x: number;\n y: number;\n px: number; // previous position (Verlet)\n py: number;\n r: number; // current radius (apothem when hexagonal)\n base: number; // resting radius\n grow: number; // hover multiplier, e.g. 1.6\n hover: number; // eased 0..1\n hx: number; // spring home\n hy: number;\n shape: number; // 0 circle \u2026 1 hexagon\n alpha: number;\n ghost: boolean; // ignores collisions\n fixed: boolean;\n kind: string;\n el?: HTMLElement;\n}\n\nexport interface FieldOptions {\n stiffness?: number; // spring strength (1/s\u00B2)\n damping?: number; // velocity damping (1/s)\n iterations?: number; // collision relaxation passes per step\n gap?: number; // breathing room between bubbles (px)\n reaction?: number; // 0 still \u2026 3 grow on hover only \u2026 10 very lively\n}\n\nexport class BubbleField {\n bubbles: Bubble[] = [];\n pointer = { x: -1e5, y: -1e5, inside: false };\n focused: Bubble | null = null;\n hoverEnabled = true;\n bounds: { x: number; y: number; w: number; h: number } | null = null;\n stiffness: number;\n damping: number;\n iterations: number;\n gap: number;\n reaction: number;\n private pv = { x: 0, y: 0 }; // pointer velocity (px/s), eased\n private last = { x: -1e5, y: -1e5, t: 0 };\n\n constructor(o: FieldOptions = {}) {\n this.stiffness = o.stiffness ?? 40;\n this.damping = o.damping ?? 6;\n this.iterations = o.iterations ?? 3;\n this.gap = o.gap ?? 2;\n this.reaction = o.reaction ?? 3;\n }\n\n add(init: Partial<Bubble> & { x: number; y: number; base: number }): Bubble {\n const b: Bubble = {\n px: init.x,\n py: init.y,\n r: init.base,\n grow: 1.55,\n hover: 0,\n hx: init.x,\n hy: init.y,\n shape: 0,\n alpha: 1,\n ghost: false,\n fixed: false,\n kind: 'pearl',\n ...init,\n };\n this.bubbles.push(b);\n return b;\n }\n\n /** Gives a bubble an instant velocity (px/s). */\n kick(b: Bubble, vx: number, vy: number, dt = 1 / 60) {\n b.px -= vx * dt;\n b.py -= vy * dt;\n }\n\n hit(x: number, y: number): Bubble | null {\n for (let i = this.bubbles.length - 1; i >= 0; i--) {\n const b = this.bubbles[i];\n const dx = x - b.x;\n const dy = y - b.y;\n if (dx * dx + dy * dy < b.r * b.r && b.alpha > 0.2) return b;\n }\n return null;\n }\n\n step(dtIn: number) {\n const dt = Math.min(dtIn, 1 / 30);\n const bs = this.bubbles;\n const ease = (rate: number) => 1 - Math.exp(-dt * rate);\n const hovered = this.hoverEnabled && this.pointer.inside ? this.hit(this.pointer.x, this.pointer.y) : null;\n const friction = Math.exp(-this.damping * dt);\n // the dial: 0 nothing, 3 the classic hover growth, up to 10 more growth and a stronger air current\n const level = Math.min(Math.max(this.reaction, 0), 10);\n const growScale = Math.min(level / 3, 1) * (1 + (Math.max(level - 3, 0) / 7) * 0.6);\n const air = Math.max(level - 2, 0) / 8;\n const pt = this.pointer;\n this.last.t += dt;\n if (pt.x !== this.last.x || pt.y !== this.last.y) {\n if (this.last.x > -1e4 && pt.inside) {\n const t = Math.max(this.last.t, 1 / 240);\n this.pv.x += ((pt.x - this.last.x) / t - this.pv.x) * 0.6;\n this.pv.y += ((pt.y - this.last.y) / t - this.pv.y) * 0.6;\n }\n this.last.x = pt.x;\n this.last.y = pt.y;\n this.last.t = 0;\n } else if (this.last.t > 0.06) {\n this.pv.x *= 1 - ease(10);\n this.pv.y *= 1 - ease(10);\n }\n const blowing = air > 0 && this.hoverEnabled && pt.inside;\n const reach = 60 + 160 * air;\n\n for (const b of bs) {\n const on = b === hovered || b === this.focused ? 1 : 0;\n b.hover += (on - b.hover) * ease(9);\n const target = b.base * (1 + (b.grow - 1) * growScale * b.hover);\n b.r += (target - b.r) * ease(11);\n if (b.fixed) {\n b.px = b.x;\n b.py = b.y;\n continue;\n }\n const vx = (b.x - b.px) * friction;\n const vy = (b.y - b.py) * friction;\n let ax = this.stiffness * (b.hx - b.x);\n let ay = this.stiffness * (b.hy - b.y);\n if (blowing && b.alpha > 0.2 && b !== hovered) {\n const dx = b.x - pt.x;\n const dy = b.y - pt.y;\n const d = Math.hypot(dx, dy) || 1;\n const gap = d - b.r; // the air pushes from outside; the bubble under the cursor only grows\n if (gap > 0 && gap < reach) {\n const f = (1 - gap / reach) ** 2;\n // drift away from the cursor, and get carried along by its wake (air drag)\n ax += (dx / d) * 1600 * air * f + (this.pv.x - vx / dt) * 6 * air * f;\n ay += (dy / d) * 1600 * air * f + (this.pv.y - vy / dt) * 6 * air * f;\n }\n }\n b.px = b.x;\n b.py = b.y;\n b.x += vx + ax * dt * dt;\n b.y += vy + ay * dt * dt;\n }\n\n for (let it = 0; it < this.iterations; it++) {\n for (let i = 0; i < bs.length; i++) {\n const a = bs[i];\n if (a.ghost) continue;\n for (let j = i + 1; j < bs.length; j++) {\n const b = bs[j];\n if (b.ghost) continue;\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const min = a.r + b.r + this.gap;\n const d2 = dx * dx + dy * dy;\n if (d2 >= min * min || d2 < 1e-6) continue;\n const d = Math.sqrt(d2);\n const overlap = min - d;\n const nx = dx / d;\n const ny = dy / d;\n // heavier (bigger or hovered) bubbles push the lighter ones\n const ma = a.r * a.r * (1 + a.hover * 4);\n const mb = b.r * b.r * (1 + b.hover * 4);\n let wa = a.fixed ? 0 : mb / (ma + mb);\n let wb = b.fixed ? 0 : ma / (ma + mb);\n if (a.fixed && b.fixed) continue;\n if (a.fixed) wb = 1;\n if (b.fixed) wa = 1;\n a.x -= nx * overlap * wa;\n a.y -= ny * overlap * wa;\n b.x += nx * overlap * wb;\n b.y += ny * overlap * wb;\n }\n }\n }\n\n const box = this.bounds;\n if (box) {\n for (const b of bs) {\n if (b.fixed) continue;\n b.x = Math.min(Math.max(b.x, box.x + b.r), box.x + box.w - b.r);\n b.y = Math.min(Math.max(b.y, box.y + b.r), box.y + box.h - b.r);\n }\n }\n }\n}\n\n/** Small deterministic PRNG so layouts are stable between reloads. */\nexport function prng(seed = 2011) {\n let s = seed >>> 0;\n return () => {\n s = (s + 0x6d2b79f5) >>> 0;\n let t = s;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nexport const clamp = (v: number, a = 0, b = 1) => Math.min(Math.max(v, a), b);\nexport const lerp = (a: number, b: number, t: number) => a + (b - a) * t;\nexport const smooth = (e0: number, e1: number, x: number) => {\n const t = clamp((x - e0) / (e1 - e0));\n return t * t * (3 - 2 * t);\n};\nexport const easeInOut = (t: number) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);\n", "export type Rgba = [number, number, number, number];\n\n/** Parses #rgb, #rgba, #rrggbb, #rrggbbaa, rgb() and rgba() into [r, g, b, a] (a in 0..1). */\nexport function parseColor(input: string): Rgba {\n const s = input.trim().toLowerCase();\n if (s.startsWith('#')) {\n let h = s.slice(1);\n if (h.length === 3 || h.length === 4) h = [...h].map((c) => c + c).join('');\n if (h.length !== 6 && h.length !== 8) throw new Error(`bubble-maker: invalid colour \"${input}\"`);\n const v = parseInt(h, 16);\n if (Number.isNaN(v)) throw new Error(`bubble-maker: invalid colour \"${input}\"`);\n return h.length === 6\n ? [(v >> 16) & 255, (v >> 8) & 255, v & 255, 1]\n : [(v >>> 24) & 255, (v >> 16) & 255, (v >> 8) & 255, ((v & 255) / 255)];\n }\n const m = /^rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)(?:[\\s,/]+([\\d.]+%?))?\\s*\\)$/.exec(s);\n if (!m) throw new Error(`bubble-maker: invalid colour \"${input}\"`);\n const a = m[4] === undefined ? 1 : m[4].endsWith('%') ? parseFloat(m[4]) / 100 : parseFloat(m[4]);\n return [Math.round(+m[1]), Math.round(+m[2]), Math.round(+m[3]), Math.min(1, Math.max(0, a))];\n}\n\nexport const rgbaString = (c: Rgba, alpha = 1) => `rgba(${c[0]},${c[1]},${c[2]},${(c[3] * alpha).toFixed(3)})`;\n", "/**\n * A bubble colour scheme. Colours go from the lit centre to the rim and accept any of\n * #rgb, #rrggbb, #rrggbbaa, rgb() and rgba(). Only the first seven fields are required.\n */\nexport interface BubblePalette {\n /** Lit centre. */\n light: string;\n body: string;\n edge: string;\n /** The rim, where the film is thickest. */\n rim: string;\n /** Opacity at [centre, body, edge, rim] once the bubble settles (e.g. into a honeycomb). */\n opacity: readonly [number, number, number, number];\n /** 0 solid \u2026 1 very see-through while it floats. */\n clarity: number;\n /** Lit and shaded side of the flat face it takes when it becomes a hexagon. */\n face: readonly [string, string];\n /** Strength of the specular highlights, 0..1. Default 1. */\n shine?: number;\n /** A whisper of iridescence near the shadow side, or null for none. */\n nacre?: { color: string; alpha: number } | null;\n /** Aura colour for `glow`, as \"r,g,b\". Default warm gold. */\n glow?: string;\n /** Tint of the light focused through the bubble, as \"r,g,b\", and its strength 0..1. */\n caustic?: { color: string; alpha: number };\n /** Shade of the rim on the side away from the light. */\n rimShade?: string;\n /** Rim thickness as a fraction of the radius. Default 0.06. */\n rimWidth?: number;\n}\n\nexport type PaletteMap = Record<string, BubblePalette>;\n\nconst clone = (p: BubblePalette): BubblePalette => ({ ...p, opacity: [...p.opacity] as BubblePalette['opacity'], face: [...p.face] as BubblePalette['face'] });\n\n/** The three schemes the original site ships with. */\nexport const defaultPalettes: Readonly<PaletteMap> = Object.freeze({\n // lustrous pearl: milky, slightly translucent, faint nacre at the edge\n pearl: {\n light: '#fffffd', body: '#faf7f0', edge: '#eee6d6', rim: '#dcd0ba',\n opacity: [0.96, 0.9, 0.92, 0.95], clarity: 0.3, face: ['#fdfbf6', '#e6ddcc'],\n shine: 1, nacre: { color: '255,226,214', alpha: 0.28 }, caustic: { color: '255,248,228', alpha: 0.55 },\n rimShade: 'rgba(255,248,230,0.72)', rimWidth: 0.06,\n },\n // champagne: soft satin, a touch of transparency, no metallic bands\n gold: {\n light: '#f2dfac', body: '#dcbe7c', edge: '#c4a15a', rim: '#ac8b44',\n opacity: [1, 1, 1, 1], clarity: 0.3, face: ['#e4cb92', '#b99650'],\n shine: 0.62, nacre: null, glow: '240, 206, 128', caustic: { color: '255,234,184', alpha: 0.38 },\n rimShade: 'rgba(255,236,186,0.55)', rimWidth: 0.06,\n },\n // crystal / soap: the room shows through, the edge catches the light\n glass: {\n light: '#ffffff', body: '#fffdf5', edge: '#fffaec', rim: '#ffffff',\n opacity: [0.04, 0.1, 0.38, 0.7], clarity: 0, face: ['rgba(255,255,255,0.35)', 'rgba(234,216,168,0.25)'],\n shine: 1, nacre: { color: '255,214,170', alpha: 0.22 }, caustic: { color: '255,248,228', alpha: 0.55 },\n rimShade: 'rgba(255,248,230,0.72)', rimWidth: 0.045,\n },\n} satisfies PaletteMap);\n\nlet current: PaletteMap = Object.fromEntries(Object.entries(defaultPalettes).map(([k, v]) => [k, clone(v)]));\nlet version = 0;\n\n/** Increments whenever palettes change, so painters can drop their caches. */\nexport const paletteVersion = () => version;\n\n/**\n * Adds or overrides palettes. A kind that already exists is merged field by field, so you\n * can change just `body` on 'pearl'; a new kind must be complete.\n */\nexport function registerPalettes(palettes: Record<string, Partial<BubblePalette>>): void {\n for (const [kind, p] of Object.entries(palettes)) {\n const base = current[kind];\n const merged = { ...(base ?? {}), ...p } as BubblePalette;\n for (const f of ['light', 'body', 'edge', 'rim', 'opacity', 'clarity', 'face'] as const) {\n if (merged[f] === undefined) throw new Error(`bubble-maker: palette \"${kind}\" is missing \"${f}\"`);\n }\n current[kind] = merged;\n }\n version++;\n}\n\n/** Returns the palette for a kind, falling back to 'pearl'. */\nexport function getPalette(kind: string): BubblePalette {\n return current[kind] ?? current.pearl;\n}\n\nexport function listPalettes(): string[] {\n return Object.keys(current);\n}\n\n/** Restores the three built-in palettes. */\nexport function resetPalettes(): void {\n current = Object.fromEntries(Object.entries(defaultPalettes).map(([k, v]) => [k, clone(v)]));\n version++;\n}\n", "import type { Bubble } from './engine';\nimport { parseColor, rgbaString, type Rgba } from './color';\nimport { getPalette, paletteVersion, type BubblePalette } from './palettes';\n\nconst SIN60 = Math.sqrt(3) / 2;\nconst TAU = Math.PI * 2;\n\n/**\n * Traces a cell that morphs from a circle (t = 0) to a flat-top hexagon\n * (t = 1) while keeping the same apothem `a`: a hexagon of circumradius R\n * whose corners are rounded with radius c, where c shrinks as t grows.\n */\nexport function cellPath(ctx: CanvasRenderingContext2D, x: number, y: number, a: number, t: number) {\n const c = a * (1 - t);\n const R = (a - c) / SIN60;\n ctx.beginPath();\n if (R < 0.01) {\n ctx.arc(x, y, a, 0, TAU);\n return;\n }\n for (let i = 0; i < 6; i++) {\n const th = (Math.PI / 3) * i;\n ctx.arc(x + R * Math.cos(th), y + R * Math.sin(th), c, th - Math.PI / 6, th + Math.PI / 6);\n }\n ctx.closePath();\n}\n\n/** A light in the scene, in canvas pixels. Bubbles are lit by the nearest one. */\nexport interface Light {\n x: number;\n y: number;\n}\n\nexport interface PaintOptions {\n /** Light sources. Default: one far up and to the left of each bubble. */\n lights?: Light[];\n /** 0..1 aura around bubbles whose palette defines `glow`. */\n glow?: number;\n /** Hexagons mounted on a wall cast a soft shadow. */\n wallShadow?: boolean;\n /** Stretch along the motion (flexible glass). */\n flex?: boolean;\n /** On a dark background: carry a faint room inside the bubble, as the marble does on a light one. */\n dark?: boolean;\n /** Colours of that room, centre and edge. */\n room?: readonly [string, string];\n}\n\n// how much each stop (centre \u2192 rim) lets the background through while floating\nconst SEE = [0.35, 0.75, 0.45, 0.2];\ntype Prepared = {\n stops: [number, Rgba, number, number][];\n face: [string, string];\n nacre: [string, number] | null;\n causticTint: string;\n causticAlpha: number;\n glow: string | null;\n shine: number;\n rimShade: string;\n rimWidth: number;\n};\nconst prepared = new Map<string, Prepared>();\nlet seen = -1;\n\nfunction prepare(pal: BubblePalette): Prepared {\n const cols = [pal.light, pal.body, pal.edge, pal.rim].map(parseColor);\n const tint = (pal.caustic?.color ?? '255,248,228');\n return {\n stops: [0, 0.62, 0.9, 1].map((st, i) => [st, cols[i], pal.opacity[i] * (1 - pal.clarity * SEE[i]), pal.opacity[i]] as [number, Rgba, number, number]),\n face: [pal.face[0], pal.face[1]],\n nacre: pal.nacre === null ? null : pal.nacre ? [pal.nacre.color, pal.nacre.alpha] : ['255,226,214', 0.28],\n causticTint: tint,\n causticAlpha: pal.caustic?.alpha ?? 0.55,\n glow: pal.glow ?? null,\n shine: pal.shine ?? 1,\n rimShade: pal.rimShade ?? 'rgba(255,248,230,0.72)',\n rimWidth: pal.rimWidth ?? 0.06,\n };\n}\n\nfunction palette(kind: string): Prepared {\n if (seen !== paletteVersion()) {\n prepared.clear();\n seen = paletteVersion();\n }\n let p = prepared.get(kind);\n if (!p) {\n p = prepare(getPalette(kind));\n prepared.set(kind, p);\n }\n return p;\n}\n\nfunction nearestLight(x: number, y: number, lights: Light[]) {\n let best = lights[0];\n let bd = Infinity;\n for (const l of lights) {\n const d = (l.x - x) ** 2 + (l.y - y) ** 2;\n if (d < bd) {\n bd = d;\n best = l;\n }\n }\n return best;\n}\n\nconst ROOM: readonly [string, string] = ['rgba(250,244,232,0.5)', 'rgba(226,212,186,0.62)'];\n\n/** Paints one bubble (or hexagonal tile) on a 2D canvas. Colours come from the palette of `b.kind`. */\nexport function paintBubble(ctx: CanvasRenderingContext2D, b: Bubble, o: PaintOptions = {}) {\n const a = b.r;\n const t = b.shape;\n if (a < 0.5 || b.alpha <= 0.01) return;\n const P = palette(b.kind);\n const lights = o.lights?.length ? o.lights : [{ x: b.x - 1000, y: b.y - 1600 }];\n const L = nearestLight(b.x, b.y, lights);\n let lx = L.x - b.x;\n let ly = L.y - b.y;\n const ll = Math.hypot(lx, ly) || 1;\n lx /= ll;\n ly /= ll;\n\n ctx.save();\n ctx.globalAlpha = b.alpha;\n ctx.translate(b.x, b.y);\n\n // flexible glass: stretch along the motion, only while still a bubble\n const vx = b.x - b.px;\n const vy = b.y - b.py;\n const stretch = o.flex ? Math.min(Math.hypot(vx, vy) / (a * 1.5 + 4), 0.12) * (1 - t) : 0;\n if (stretch > 0.01) {\n const ang = Math.atan2(vy, vx);\n ctx.rotate(ang);\n ctx.scale(1 + stretch * 0.6, 1 - stretch * 0.4);\n ctx.rotate(-ang);\n }\n\n // body, lit from the nearest light\n let body: CanvasGradient;\n if (t > 0.6) {\n body = ctx.createLinearGradient(lx * a, ly * a, -lx * a, -ly * a);\n body.addColorStop(0, P.face[0]);\n body.addColorStop(1, P.face[1]);\n } else {\n body = ctx.createRadialGradient(lx * a * 0.35, ly * a * 0.35, a * 0.05, 0, 0, a);\n // floating bubbles are see-through; they firm up as they become tiles\n const k = Math.min(1, t / 0.6);\n const solid = k * k * (3 - 2 * k);\n for (const [s, c, clear, firm] of P.stops) body.addColorStop(s, rgbaString(c, clear + (firm - clear) * solid));\n if (o.dark) {\n const [r0, r1] = o.room ?? ROOM;\n const room = ctx.createRadialGradient(lx * a * 0.3, ly * a * 0.3, 0, 0, 0, a);\n room.addColorStop(0, r0);\n room.addColorStop(1, r1);\n cellPath(ctx, 0, 0, a, t);\n ctx.fillStyle = room;\n ctx.fill();\n }\n }\n if (o.wallShadow && t > 0.6) {\n ctx.shadowColor = `rgba(96, 72, 30, ${(0.32 * (t - 0.6)) / 0.4})`;\n ctx.shadowBlur = a * 0.7;\n ctx.shadowOffsetX = -lx * a * 0.18;\n ctx.shadowOffsetY = a * 0.22;\n }\n if (P.glow && o.glow) {\n ctx.shadowColor = `rgba(${P.glow}, ${0.65 * o.glow})`;\n ctx.shadowBlur = a * 1.8 * o.glow;\n ctx.shadowOffsetX = 0;\n ctx.shadowOffsetY = 0;\n }\n cellPath(ctx, 0, 0, a, t);\n ctx.fillStyle = body;\n ctx.fill();\n ctx.shadowColor = 'transparent';\n ctx.shadowBlur = 0;\n ctx.shadowOffsetX = 0;\n ctx.shadowOffsetY = 0;\n\n // keep every reflection inside the silhouette\n ctx.save();\n cellPath(ctx, 0, 0, a, t);\n ctx.clip();\n\n // nacre: a whisper of iridescence\n if (P.nacre && t < 0.7) {\n const n = ctx.createRadialGradient(-lx * a * 0.55, -ly * a * 0.55, 0, -lx * a * 0.55, -ly * a * 0.55, a * 0.9);\n n.addColorStop(0, `rgba(${P.nacre[0]},${P.nacre[1]})`);\n n.addColorStop(0.5, 'rgba(214,236,240,0.14)');\n n.addColorStop(1, 'rgba(255,255,255,0)');\n ctx.fillStyle = n;\n ctx.fillRect(-a, -a, a * 2, a * 2);\n }\n\n // reflections of every light, like windows on a bubble\n const bubbleness = 1 - Math.min(1, t / 0.75);\n for (const l of lights) {\n let dx = l.x - b.x;\n let dy = l.y - b.y;\n const d = Math.hypot(dx, dy) || 1;\n dx /= d;\n dy /= d;\n const near = l === L ? 1 : 0.55;\n const px = dx * a * 0.52;\n const py = dy * a * 0.52;\n const w = a * (0.16 + 0.1 * near);\n const h = a * (0.07 + 0.05 * near);\n const g = ctx.createRadialGradient(px, py, 0, px, py, w);\n const strength = Math.min(1, P.shine * near * (0.35 + 0.65 * bubbleness));\n g.addColorStop(0, `rgba(255,255,255,${strength})`);\n g.addColorStop(1, 'rgba(255,255,255,0)');\n ctx.fillStyle = g;\n ctx.beginPath();\n ctx.ellipse(px, py, w, h, Math.atan2(dy, dx) + Math.PI / 2, 0, TAU);\n ctx.fill();\n // crisp specular core of the main light: the gloss of wet glass\n if (near === 1 && bubbleness > 0 && a > 6) {\n const core = ctx.createRadialGradient(px, py, 0, px, py, a * 0.075);\n core.addColorStop(0, `rgba(255,255,255,${(0.95 * bubbleness).toFixed(3)})`);\n core.addColorStop(1, 'rgba(255,255,255,0)');\n ctx.fillStyle = core;\n ctx.beginPath();\n ctx.ellipse(px, py, a * 0.075, a * 0.05, Math.atan2(dy, dx) + Math.PI / 2, 0, TAU);\n ctx.fill();\n }\n }\n\n // caustic: light focused through the bubble on the far side\n if (bubbleness > 0) {\n const cx = -lx * a * 0.58;\n const cy = -ly * a * 0.58;\n const c = ctx.createRadialGradient(cx, cy, 0, cx, cy, a * 0.42);\n c.addColorStop(0, `rgba(${P.causticTint},${(P.causticAlpha * bubbleness).toFixed(3)})`);\n c.addColorStop(1, `rgba(${P.causticTint},0)`);\n ctx.fillStyle = c;\n ctx.fillRect(-a, -a, a * 2, a * 2);\n }\n\n // satin sheen across a finished hexagon face\n if (t > 0.5) {\n const s = ctx.createLinearGradient(lx * a, ly * a, -lx * a, -ly * a);\n s.addColorStop(0, `rgba(255,255,255,${0.32 * (t - 0.5) * 2})`);\n s.addColorStop(0.45, 'rgba(255,255,255,0)');\n ctx.fillStyle = s;\n ctx.fillRect(-a, -a, a * 2, a * 2);\n }\n ctx.restore();\n\n // discontinuous luminous rim (Fresnel): bright towards the light, a bounce opposite\n const rim = ctx.createLinearGradient(lx * a, ly * a, -lx * a, -ly * a);\n rim.addColorStop(0, 'rgba(255,255,255,1)');\n rim.addColorStop(0.35, 'rgba(255,255,255,0)');\n rim.addColorStop(0.7, 'rgba(255,255,255,0)');\n rim.addColorStop(1, P.rimShade);\n ctx.lineWidth = Math.max(0.8, a * P.rimWidth);\n ctx.strokeStyle = rim;\n cellPath(ctx, 0, 0, a - ctx.lineWidth / 2, t);\n ctx.stroke();\n\n ctx.restore();\n}\n", "import { BubbleField, lerp, prng, smooth, type Bubble, type FieldOptions } from './engine';\nimport { paintBubble, type Light } from './draw';\nimport { registerPalettes, type BubblePalette } from './palettes';\n\nexport interface BubblesOptions {\n /** How many bubbles to start with, or 'auto' to fit the canvas area. Default 'auto'. */\n count?: number | 'auto';\n /** Hard limit when the visitor adds more. Default 90. */\n maxBubbles?: number;\n /** Radius range as a fraction of the canvas size. Default [0.035, 0.11] (larger on phones). */\n size?: readonly [number, number];\n /** Relative weight of each palette kind in new bubbles. Default pearl 62 %, gold 24 %, glass 14 %. */\n kinds?: Record<string, number>;\n /** Registers or overrides palettes (merged field by field). */\n palettes?: Record<string, Partial<BubblePalette>>;\n /** Physics of the field. */\n physics?: FieldOptions;\n /** How much the bubbles react to the cursor, 0 (nothing) \u2026 3 (grow on hover) \u2026 10 (very lively). Default 5. */\n reaction?: number;\n /** Radius multiplier of the bubble under the cursor at maximum reaction. Default 1.6. */\n hoverGrow?: number;\n /** Idle drift, as a fraction of the canvas size. 0 keeps them still. Default 0.015. */\n float?: number;\n /** Light sources in canvas pixels, or a function of the canvas size. Default: two lights above the canvas. */\n lights?: Light[] | ((width: number, height: number) => Light[]);\n /** Paint for dark backgrounds (a faint room shows through each bubble). */\n dark?: boolean;\n /** Flexible glass: bubbles stretch along their motion. Default true. */\n flex?: boolean;\n /** Clicking an empty spot creates a bubble. Default true. */\n clickToCreate?: boolean;\n /** Honeycomb cell size as a fraction of the canvas size. Default 0.045 (larger on phones). */\n honeycomb?: number;\n /** Fill the canvas with a colour each frame. Default null (transparent). */\n background?: string | null;\n /** Cap on the device pixel ratio. Default 2. */\n maxDpr?: number;\n /** Seed of the pseudo-random layout, so it is the same on every load. Default 7. */\n seed?: number;\n /** Start as a still honeycomb when the visitor prefers reduced motion. Default true. */\n respectReducedMotion?: boolean;\n /** Called when a bubble is created by a click. */\n onCreate?: (bubble: Bubble) => void;\n}\n\nexport interface BubbleCanvas {\n /** The physics field: add, remove or inspect bubbles directly. */\n readonly field: BubbleField;\n readonly bubbles: Bubble[];\n readonly compressed: boolean;\n /** Options in use. Changing hoverGrow, float, flex, dark, background or clickToCreate takes effect on the next frame. */\n readonly options: { hoverGrow: number; float: number; flex: boolean; dark: boolean; background: string | null; clickToCreate: boolean };\n /** Adds a bubble at a position (default: random). `radius` is in pixels. */\n add(init?: { x?: number; y?: number; radius?: number; kind?: string }): Bubble;\n remove(b: Bubble): void;\n clear(): void;\n /** Packs every bubble into a honeycomb of hexagonal tiles. */\n compress(): void;\n /** Lets the tiles go back to being bubbles. */\n release(): void;\n toggle(): boolean;\n /** Gives every bubble a random push. Default strength 2400 px/s. */\n shake(strength?: number): void;\n setReaction(level: number): void;\n /** Changes colours at runtime. */\n setPalette(kind: string, palette: Partial<BubblePalette>): void;\n resize(): void;\n start(): void;\n stop(): void;\n destroy(): void;\n}\n\ntype Meta = { x: number; y: number; r: number; phase: number; slot?: { x: number; y: number; a: number } };\nconst DEFAULT_KINDS = { pearl: 0.62, gold: 0.24, glass: 0.14 };\n\nexport function createBubbles(target: HTMLCanvasElement | string, options: BubblesOptions = {}): BubbleCanvas {\n const canvas = typeof target === 'string' ? document.querySelector<HTMLCanvasElement>(target) : target;\n const ctx = canvas?.getContext('2d');\n if (!canvas || !ctx) throw new Error('bubble-maker: a <canvas> element is needed');\n const o = {\n count: 'auto' as number | 'auto', maxBubbles: 90, size: [0.035, 0.11] as readonly [number, number], kinds: DEFAULT_KINDS as Record<string, number>,\n reaction: 5, hoverGrow: 1.6, float: 0.015, flex: true, clickToCreate: true, honeycomb: 0.045, background: null as string | null,\n maxDpr: 2, seed: 7, respectReducedMotion: true, dark: false, ...options,\n };\n if (o.palettes) registerPalettes(o.palettes);\n const rand = prng(o.seed);\n const field = new BubbleField({ stiffness: 18, damping: 3.2, iterations: 4, gap: 2, ...o.physics, reaction: o.reaction });\n const meta = new Map<Bubble, Meta>();\n const reducedMotion = o.respectReducedMotion && typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n let W = 0;\n let H = 0;\n let unit = 1;\n let compressed = false;\n let morph = 0;\n let running = false;\n let visible = true;\n let last = 0;\n let raf = 0;\n let destroyed = false;\n\n const pickKind = () => {\n const entries = Object.entries(o.kinds);\n let total = 0;\n for (const [, w] of entries) total += w;\n let r = rand() * total;\n for (const [k, w] of entries) if ((r -= w) <= 0) return k;\n return entries[0]?.[0] ?? 'pearl';\n };\n const phone = () => W < 600;\n const randomRadius = () => unit * (phone() ? o.size[0] * 1.5 + rand() * o.size[1] * 0.7 : o.size[0] + rand() * (o.size[1] - o.size[0]));\n\n function add(init: { x?: number; y?: number; radius?: number; kind?: string } = {}) {\n const x = init.x ?? lerp(W * 0.08, W * 0.92, rand());\n const y = init.y ?? lerp(H * 0.12, H * 0.88, rand());\n const r = init.radius ?? randomRadius();\n const b = field.add({ x, y, base: r * 0.2, kind: init.kind ?? pickKind(), grow: o.hoverGrow });\n meta.set(b, { x, y, r, phase: rand() * Math.PI * 2 });\n if (compressed) lattice();\n return b;\n }\n\n function resize() {\n const r = canvas!.getBoundingClientRect();\n W = r.width;\n H = r.height;\n const dpr = Math.min(window.devicePixelRatio || 1, o.maxDpr);\n canvas!.width = Math.max(1, Math.round(W * dpr));\n canvas!.height = Math.max(1, Math.round(H * dpr));\n ctx!.setTransform(dpr, 0, 0, dpr, 0, 0);\n unit = Math.min(W, H * 1.4);\n field.bounds = { x: 0, y: 0, w: W, h: H };\n for (const m of meta.values()) {\n m.x = Math.min(m.x, W - 10);\n m.y = Math.min(m.y, H - 10);\n }\n if (compressed) lattice();\n }\n\n /** Honeycomb slots (flat-top, axial coordinates), filled from the centre outwards. */\n function lattice() {\n const list = [...meta.keys()];\n const a = unit * (phone() ? o.honeycomb * 1.5 : o.honeycomb);\n const R = (2 * a + 2) / Math.sqrt(3);\n const slots: { x: number; y: number; d: number }[] = [];\n const n = Math.ceil(Math.sqrt(list.length)) + 3;\n for (let q = -n; q <= n; q++) {\n for (let r = -n; r <= n; r++) {\n const x = W / 2 + 1.5 * R * q;\n const y = H / 2 + Math.sqrt(3) * R * (r + q / 2);\n if (x < a || x > W - a || y < a || y > H - a) continue;\n slots.push({ x, y, d: Math.hypot(x - W / 2, (y - H / 2) * 1.2) });\n }\n }\n slots.sort((s1, s2) => s1.d - s2.d);\n const pool = new Set(list);\n for (const m of meta.values()) delete m.slot;\n for (const s of slots) {\n if (!pool.size) break;\n let best: Bubble | null = null;\n let bd = Infinity;\n for (const b of pool) {\n const d = (b.x - s.x) ** 2 + (b.y - s.y) ** 2;\n if (d < bd) {\n bd = d;\n best = b;\n }\n }\n if (!best) break;\n pool.delete(best);\n meta.get(best)!.slot = { x: s.x, y: s.y, a };\n }\n }\n\n const lightsNow = (): Light[] => (typeof o.lights === 'function' ? o.lights(W, H) : o.lights ?? [{ x: W * 0.3, y: -H * 0.35 }, { x: W * 0.72, y: -H * 0.3 }]);\n\n function frame(now: number) {\n if (!running || destroyed) return;\n const dt = Math.min(Math.max((now - last) / 1000, 0), 0.25); // a clock that jumps back never breaks the physics\n last = now;\n const t = now / 1000;\n morph = lerp(morph, compressed ? 1 : 0, 1 - Math.exp(-dt * 3));\n field.hoverEnabled = !compressed;\n for (const [b, m] of meta) {\n if (compressed && m.slot) {\n b.hx = m.slot.x;\n b.hy = m.slot.y;\n b.base = lerp(b.base, m.slot.a, 0.12);\n } else {\n b.hx = m.x + Math.sin(t * 0.7 + m.phase) * unit * o.float;\n b.hy = m.y + Math.cos(t * 0.6 + m.phase) * unit * o.float;\n b.base = lerp(b.base, m.r, 0.08);\n }\n b.shape = smooth(0.35, 1, morph);\n b.grow = o.hoverGrow;\n }\n const steps = Math.min(8, Math.max(1, Math.ceil(dt * 60)));\n for (let i = 0; i < steps; i++) field.step(dt / steps);\n draw();\n if (visible) raf = requestAnimationFrame(frame);\n else running = false;\n }\n\n function draw() {\n if (o.background) {\n ctx!.fillStyle = o.background;\n ctx!.fillRect(0, 0, W, H);\n } else ctx!.clearRect(0, 0, W, H);\n const lights = lightsNow();\n for (const b of meta.keys()) paintBubble(ctx!, b, { lights, glow: morph * 0.5, wallShadow: morph > 0.4, flex: o.flex, dark: o.dark });\n }\n\n function start() {\n if (running || destroyed) return;\n running = true;\n last = performance.now();\n raf = requestAnimationFrame(frame);\n }\n function stop() {\n running = false;\n cancelAnimationFrame(raf);\n }\n\n const pointerAt = (e: PointerEvent) => {\n const r = canvas.getBoundingClientRect();\n field.pointer.x = e.clientX - r.left;\n field.pointer.y = e.clientY - r.top;\n field.pointer.inside = true;\n };\n const onMove = (e: PointerEvent) => pointerAt(e);\n const onLeave = () => (field.pointer.inside = false);\n const onDown = (e: PointerEvent) => e.pointerType !== 'mouse' && pointerAt(e); // touch: pressing grows a bubble\n let releaseTimer = 0;\n const onUp = (e: PointerEvent) => {\n if (e.pointerType === 'mouse') return;\n clearTimeout(releaseTimer);\n releaseTimer = window.setTimeout(() => (field.pointer.inside = false), 450);\n };\n const onClick = (e: MouseEvent) => {\n if (!o.clickToCreate) return;\n const r = canvas.getBoundingClientRect();\n const x = e.clientX - r.left;\n const y = e.clientY - r.top;\n if (field.hit(x, y)) return; // tapping a bubble grows it instead of creating one\n if (meta.size >= o.maxBubbles) return;\n const b = add({ x, y, radius: unit * (phone() ? 0.05 + rand() * 0.06 : 0.03 + rand() * 0.05) });\n o.onCreate?.(b);\n };\n canvas.addEventListener('pointermove', onMove);\n canvas.addEventListener('pointerleave', onLeave);\n canvas.addEventListener('pointerdown', onDown);\n canvas.addEventListener('pointerup', onUp);\n canvas.addEventListener('pointercancel', onUp);\n canvas.addEventListener('click', onClick);\n\n const ro = typeof ResizeObserver === 'function' ? new ResizeObserver(() => resize()) : null;\n ro?.observe(canvas);\n const io =\n typeof IntersectionObserver === 'function'\n ? new IntersectionObserver(([entry]) => {\n visible = entry.isIntersecting;\n if (visible) start();\n })\n : null;\n io?.observe(canvas);\n\n resize();\n const n = o.count === 'auto' ? Math.round(Math.min(48, Math.max(20, (W * H) / 9000))) : o.count;\n for (let i = 0; i < n; i++) add();\n for (let i = 0; i < 200; i++) field.step(1 / 60); // settle the layout before the first frame\n if (reducedMotion) {\n compressed = true;\n morph = 1;\n lattice();\n for (const [b, m] of meta) {\n b.x = b.px = m.slot?.x ?? b.x;\n b.y = b.py = m.slot?.y ?? b.y;\n b.base = b.r = m.slot?.a ?? b.base;\n b.shape = 1;\n }\n draw();\n } else start();\n\n return {\n field,\n options: o,\n get bubbles() {\n return [...meta.keys()];\n },\n get compressed() {\n return compressed;\n },\n add,\n remove(b) {\n meta.delete(b);\n const i = field.bubbles.indexOf(b);\n if (i >= 0) field.bubbles.splice(i, 1);\n if (compressed) lattice();\n },\n clear() {\n meta.clear();\n field.bubbles.length = 0;\n },\n compress() {\n compressed = true;\n lattice();\n },\n release() {\n compressed = false;\n },\n toggle() {\n compressed = !compressed;\n if (compressed) lattice();\n return compressed;\n },\n shake(strength = 2400) {\n compressed = false;\n for (const b of meta.keys()) field.kick(b, (rand() - 0.5) * strength, (rand() - 0.5) * strength);\n },\n setReaction(level) {\n field.reaction = level;\n },\n setPalette(kind, palette) {\n registerPalettes({ [kind]: palette });\n },\n resize,\n start,\n stop,\n destroy() {\n destroyed = true;\n stop();\n clearTimeout(releaseTimer);\n ro?.disconnect();\n io?.disconnect();\n canvas.removeEventListener('pointermove', onMove);\n canvas.removeEventListener('pointerleave', onLeave);\n canvas.removeEventListener('pointerdown', onDown);\n canvas.removeEventListener('pointerup', onUp);\n canvas.removeEventListener('pointercancel', onUp);\n canvas.removeEventListener('click', onClick);\n meta.clear();\n field.bubbles.length = 0;\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["n", "palette"]
7
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * A bubble colour scheme. Colours go from the lit centre to the rim and accept any of
3
+ * #rgb, #rrggbb, #rrggbbaa, rgb() and rgba(). Only the first seven fields are required.
4
+ */
5
+ export interface BubblePalette {
6
+ /** Lit centre. */
7
+ light: string;
8
+ body: string;
9
+ edge: string;
10
+ /** The rim, where the film is thickest. */
11
+ rim: string;
12
+ /** Opacity at [centre, body, edge, rim] once the bubble settles (e.g. into a honeycomb). */
13
+ opacity: readonly [number, number, number, number];
14
+ /** 0 solid … 1 very see-through while it floats. */
15
+ clarity: number;
16
+ /** Lit and shaded side of the flat face it takes when it becomes a hexagon. */
17
+ face: readonly [string, string];
18
+ /** Strength of the specular highlights, 0..1. Default 1. */
19
+ shine?: number;
20
+ /** A whisper of iridescence near the shadow side, or null for none. */
21
+ nacre?: {
22
+ color: string;
23
+ alpha: number;
24
+ } | null;
25
+ /** Aura colour for `glow`, as "r,g,b". Default warm gold. */
26
+ glow?: string;
27
+ /** Tint of the light focused through the bubble, as "r,g,b", and its strength 0..1. */
28
+ caustic?: {
29
+ color: string;
30
+ alpha: number;
31
+ };
32
+ /** Shade of the rim on the side away from the light. */
33
+ rimShade?: string;
34
+ /** Rim thickness as a fraction of the radius. Default 0.06. */
35
+ rimWidth?: number;
36
+ }
37
+ export type PaletteMap = Record<string, BubblePalette>;
38
+ /** The three schemes the original site ships with. */
39
+ export declare const defaultPalettes: Readonly<PaletteMap>;
40
+ /** Increments whenever palettes change, so painters can drop their caches. */
41
+ export declare const paletteVersion: () => number;
42
+ /**
43
+ * Adds or overrides palettes. A kind that already exists is merged field by field, so you
44
+ * can change just `body` on 'pearl'; a new kind must be complete.
45
+ */
46
+ export declare function registerPalettes(palettes: Record<string, Partial<BubblePalette>>): void;
47
+ /** Returns the palette for a kind, falling back to 'pearl'. */
48
+ export declare function getPalette(kind: string): BubblePalette;
49
+ export declare function listPalettes(): string[];
50
+ /** Restores the three built-in palettes. */
51
+ export declare function resetPalettes(): void;
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "bubble-maker",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.0",
4
+ "description": "Soap-bubble physics and rendering for the web: bubbles that grow, push each other and pack into a honeycomb. Framework-free, tiny, fully configurable.",
5
+ "keywords": [
6
+ "bubbles",
7
+ "physics",
8
+ "canvas",
9
+ "animation",
10
+ "honeycomb",
11
+ "hexagon",
12
+ "ui",
13
+ "interactive"
14
+ ],
15
+ "author": "José Manuel Ligero Márquez (neoligero)",
16
+ "license": "MIT",
17
+ "type": "module",
18
+ "sideEffects": false,
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./iife": "./dist/bubble-maker.global.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "README.es.md",
33
+ "LICENSE"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/neoligero/bubble-maker-2.0.git"
38
+ },
39
+ "homepage": "https://github.com/neoligero/bubble-maker-2.0#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/neoligero/bubble-maker-2.0/issues"
42
+ },
43
+ "scripts": {
44
+ "build": "node scripts/build.mjs",
45
+ "test": "npm run build && node --test test/*.test.mjs",
46
+ "demo": "node scripts/build.mjs --demo",
47
+ "prepublishOnly": "npm test",
48
+ "prepare": "node scripts/build.mjs"
49
+ },
50
+ "devDependencies": {
51
+ "esbuild": "^0.25.0",
52
+ "typescript": "^5.6.0"
53
+ }
54
+ }