@basmilius/sparkle 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/index.d.mts +98 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1322 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +53 -0
- package/src/canvas.ts +135 -0
- package/src/confetti/consts.ts +26 -0
- package/src/confetti/index.ts +2 -0
- package/src/confetti/simulation.ts +221 -0
- package/src/confetti/types.ts +53 -0
- package/src/distance.ts +8 -0
- package/src/fireworks/consts.ts +4 -0
- package/src/fireworks/explosion.ts +227 -0
- package/src/fireworks/firework.ts +123 -0
- package/src/fireworks/index.ts +3 -0
- package/src/fireworks/simulation.ts +493 -0
- package/src/fireworks/spark.ts +50 -0
- package/src/fireworks/types.ts +260 -0
- package/src/index.ts +4 -0
- package/src/point.ts +4 -0
- package/src/snow/consts.ts +3 -0
- package/src/snow/index.ts +2 -0
- package/src/snow/simulation.ts +301 -0
- package/src/snow/snowflake.ts +13 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#canvas","#context","#frameRate","#target","#delta","#then","#isStopped","#ticks","#height","#width","#current","#frame","#now","MULBERRY","#scale","#createParticle","MULBERRY","#tickParticle","#particles","MULBERRY","#position","#angle","#brightness","#config","#decay","#hue","#lineWidth","#shape","#trail","#type","#alpha","MULBERRY","#speed","#vz","#hasCrackled","#hasSplit","#sparkleTimer","#depthScale","#drawShape","#z","#drawStarPath","#drawDiamondPath","#position","#velocity","#hue","#size","#decay","#friction","#gravity","#alpha","MULBERRY","#position","#startPosition","#acceleration","#angle","#baseSize","#brightness","MULBERRY","#distance","#hue","#tailWidth","#trail","#pendingSparks","#speed","#distanceTraveled","#sparkTimer","MULBERRY","#autoSpawn","#baseSize","#scale","#tailWidth","#sparks","#explosions","#fireworks","#hue","#createExplosion","#createFirework","#pickVariant","#createSaturnExplosion","#createDahliaExplosion","#createHeartExplosion","#createSpiralExplosion","#createFlowerExplosion","#createConcentricExplosion","#positionRandom","#scale","#size","#speed","#baseOpacity","#maxParticles","#parseColor","#sprites","#createSprites","#snowflakes","#createSnowflake","#ratio","#time","#createCrystalSprite"],"sources":["../src/canvas.ts","../src/confetti/consts.ts","../src/confetti/simulation.ts","../src/fireworks/consts.ts","../src/fireworks/types.ts","../src/fireworks/explosion.ts","../src/distance.ts","../src/fireworks/spark.ts","../src/fireworks/firework.ts","../src/fireworks/simulation.ts","../src/snow/consts.ts","../src/snow/simulation.ts"],"sourcesContent":["export class LimitedFrameRateCanvas {\n readonly #canvas: HTMLCanvasElement;\n readonly #context: CanvasRenderingContext2D;\n readonly #frameRate: number;\n readonly #target: number;\n #current: number = 0;\n #delta: number = 0;\n #frame: number = 0;\n #now: number = 0;\n #then: number = 0;\n #ticks: number = 0;\n #isStopped: boolean = true;\n #height: number = 540;\n #width: number = 960;\n\n get canvas(): HTMLCanvasElement {\n return this.#canvas;\n }\n\n get context(): CanvasRenderingContext2D {\n return this.#context;\n }\n\n get delta(): number {\n return this.#delta;\n }\n\n get deltaFactor(): number {\n return this.#then === 0 ? 1 : this.#target / this.#delta;\n }\n\n get frameRate(): number {\n return this.#frameRate;\n }\n\n get isSmall(): boolean {\n return innerWidth < 991; // dirty little fix :-)\n }\n\n get isTicking(): boolean {\n return !this.#isStopped;\n }\n\n get ticks(): number {\n return this.#ticks;\n }\n\n get height(): number {\n return this.#height;\n }\n\n get width(): number {\n return this.#width;\n }\n\n constructor(canvas: HTMLCanvasElement, frameRate: number, options: CanvasRenderingContext2DSettings = {colorSpace: 'display-p3'}) {\n this.#canvas = canvas;\n this.#context = canvas.getContext('2d', options);\n this.#frameRate = frameRate;\n this.#target = 1000 / frameRate;\n\n this.onVisibilityChange = this.onVisibilityChange.bind(this);\n this.onResize = this.onResize.bind(this);\n\n document.addEventListener('visibilitychange', this.onVisibilityChange, {passive: true});\n window.addEventListener('resize', this.onResize, {passive: true});\n }\n\n loop(): void {\n if (this.#isStopped) {\n return;\n }\n\n this.#current = Date.now();\n this.#frame = requestAnimationFrame(this.loop.bind(this));\n\n if (this.#then > 0 && this.#current - this.#then + 1 < this.#target) {\n return;\n }\n\n this.#now = this.#current;\n this.#delta = this.#now - this.#then;\n\n ++this.#ticks;\n\n this.tick();\n this.draw();\n\n this.#then = this.#now;\n }\n\n start(): void {\n this.onResize();\n\n this.#isStopped = false;\n this.#frame = requestAnimationFrame(this.loop.bind(this));\n }\n\n stop(): void {\n this.#isStopped = true;\n cancelAnimationFrame(this.#frame);\n }\n\n draw(): void {\n throw new Error('LimitedFrameRateCanvas::draw() should be overwritten.');\n }\n\n tick(): void {\n throw new Error('LimitedFrameRateCanvas::tick() should be overwritten.');\n }\n\n destroy(): void {\n this.stop();\n document.removeEventListener('visibilitychange', this.onVisibilityChange);\n window.removeEventListener('resize', this.onResize);\n }\n\n onResize(): void {\n const {width, height} = this.#canvas.getBoundingClientRect();\n this.#height = height;\n this.#width = width;\n }\n\n onVisibilityChange(): void {\n cancelAnimationFrame(this.#frame);\n\n if (document.visibilityState === 'visible') {\n this.#then = 0;\n this.start();\n } else {\n this.#then = 0;\n this.stop();\n }\n }\n}\n","import { mulberry32 } from '@basmilius/utils';\nimport type { Config } from './types';\n\nexport const DEFAULT_CONFIG: Config = {\n angle: 90,\n colors: [\n '#26ccff',\n '#a25afd',\n '#ff5e7e',\n '#88ff5a',\n '#fcff42',\n '#ffa62d',\n '#ff36ff'\n ],\n decay: 0.9,\n gravity: 1,\n particles: 50,\n shapes: ['circle', 'diamond', 'ribbon', 'square', 'star', 'triangle'],\n spread: 45,\n ticks: 200,\n startVelocity: 45,\n x: 0.5,\n y: 0.5\n};\n\nexport const MULBERRY = mulberry32(13);\n","import { hexToRGB } from '@basmilius/utils';\nimport { LimitedFrameRateCanvas } from '../canvas';\nimport { DEFAULT_CONFIG, MULBERRY } from './consts';\nimport type { Config, Particle, ParticleConfig, Shape } from './types';\n\nconst TWO_PI = Math.PI * 2;\n\n// Precomputed unit-size (size=1) Path2D objects per shape.\n// Size is encoded into the context transform, so each path is drawn once and reused every frame.\nconst SHAPE_PATHS: Record<Shape, Path2D> = {\n circle: (() => {\n const path = new Path2D();\n path.ellipse(0, 0, 0.6, 1, 0, 0, TWO_PI);\n return path;\n })(),\n diamond: (() => {\n const path = new Path2D();\n path.moveTo(0, -1);\n path.lineTo(0.6, 0);\n path.lineTo(0, 1);\n path.lineTo(-0.6, 0);\n path.closePath();\n return path;\n })(),\n ribbon: (() => {\n const path = new Path2D();\n path.rect(-0.2, -1, 0.4, 2);\n return path;\n })(),\n square: (() => {\n const path = new Path2D();\n path.rect(-0.7, -0.7, 1.4, 1.4);\n return path;\n })(),\n star: (() => {\n const path = new Path2D();\n for (let i = 0; i < 10; i++) {\n const r = i % 2 === 0 ? 1 : 0.42;\n const angle = (i * Math.PI / 5) - Math.PI / 2;\n if (i === 0) path.moveTo(r * Math.cos(angle), r * Math.sin(angle));\n else path.lineTo(r * Math.cos(angle), r * Math.sin(angle));\n }\n path.closePath();\n return path;\n })(),\n triangle: (() => {\n const path = new Path2D();\n for (let i = 0; i < 3; i++) {\n const angle = (i * 2 * Math.PI / 3) - Math.PI / 2;\n if (i === 0) path.moveTo(Math.cos(angle), Math.sin(angle));\n else path.lineTo(Math.cos(angle), Math.sin(angle));\n }\n path.closePath();\n return path;\n })()\n};\n\nexport interface ConfettiSimulationConfig {\n readonly scale?: number;\n readonly canvasOptions?: CanvasRenderingContext2DSettings;\n}\n\nexport class ConfettiSimulation extends LimitedFrameRateCanvas {\n\n readonly #scale: number;\n #particles: Particle[] = [];\n\n constructor(canvas: HTMLCanvasElement, config: ConfettiSimulationConfig = {}) {\n super(canvas, 60, config.canvasOptions ?? {colorSpace: 'display-p3'});\n\n this.#scale = config.scale ?? 1;\n\n this.canvas.style.position = 'absolute';\n this.canvas.style.top = '0';\n this.canvas.style.left = '0';\n this.canvas.style.height = '100%';\n this.canvas.style.width = '100%';\n }\n\n fire(config: Partial<Config>): void {\n this.onResize();\n this.draw();\n\n const resolved = { ...DEFAULT_CONFIG, ...config };\n const { angle, colors, decay, gravity, shapes, spread, startVelocity, ticks, x, y } = resolved;\n const numberOfParticles = Math.max(1, resolved.particles);\n\n for (let i = 0; i < numberOfParticles; i++) {\n const particle = this.#createParticle({\n angle,\n color: hexToRGB(colors[Math.floor(MULBERRY.next() * colors.length)]),\n decay,\n gravity: gravity * this.#scale,\n shape: shapes[Math.floor(MULBERRY.next() * shapes.length)],\n spread,\n startVelocity: startVelocity * this.#scale,\n ticks,\n x: this.width * x,\n y: this.height * y\n });\n\n this.#tickParticle(particle);\n this.#particles.push(particle);\n }\n\n if (!this.isTicking) {\n this.start();\n }\n }\n\n draw(): void {\n const { context, width, height } = this;\n context.clearRect(0, 0, width, height);\n\n const particles = this.#particles;\n\n for (let i = 0; i < particles.length; i++) {\n const p = particles[i];\n const flipCos = Math.cos(p.flipAngle);\n const size = p.size;\n\n // Encode translate + rotate + scale(flipCos, 1) + scale(size, size) in a single setTransform call,\n // avoiding save()/translate()/rotate()/scale()/restore() — 5 API calls replaced by 1.\n context.setTransform(\n p.rotCos * flipCos * size,\n p.rotSin * flipCos * size,\n -p.rotSin * size,\n p.rotCos * size,\n p.x,\n p.y\n );\n context.globalAlpha = 1 - p.tick / p.totalTicks;\n context.fillStyle = p.colorStr;\n context.fill(SHAPE_PATHS[p.shape]);\n }\n\n context.resetTransform();\n }\n\n tick(): void {\n const particles = this.#particles;\n let alive = 0;\n\n // Normalize to 60fps-equivalent units so physics is frame-rate independent.\n // dt ≈ 1.0 at 60fps, 0.5 at 120fps, 2.0 at 30fps.\n // Cap at 200ms to avoid large jumps after tab switches or dropped frames.\n const dt = this.delta > 0 && this.delta < 200 ? this.delta / (1000 / 60) : 1;\n\n // Single pass: tick live particles and compact the array in-place.\n // Avoids filter() allocation and a separate forEach pass.\n for (let i = 0; i < particles.length; i++) {\n const p = particles[i];\n\n if (p.tick < p.totalTicks) {\n this.#tickParticle(p, dt);\n particles[alive++] = p;\n }\n }\n\n particles.length = alive;\n\n if (alive === 0) {\n this.stop();\n }\n }\n\n onResize(): void {\n super.onResize();\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n }\n\n #createParticle(config: ParticleConfig): Particle {\n const launchAngle = -(config.angle * Math.PI / 180)\n + (0.5 * config.spread * Math.PI / 180)\n - (MULBERRY.next() * config.spread * Math.PI / 180);\n\n const speed = config.startVelocity * (0.5 + MULBERRY.next());\n const rotAngle = MULBERRY.next() * TWO_PI;\n\n return {\n colorStr: `rgb(${config.color[0]}, ${config.color[1]}, ${config.color[2]})`,\n decay: config.decay - 0.05 + MULBERRY.next() * 0.1,\n flipAngle: MULBERRY.next() * TWO_PI,\n flipSpeed: 0.03 + MULBERRY.next() * 0.05,\n gravity: config.gravity,\n rotAngle,\n rotCos: Math.cos(rotAngle),\n rotSin: Math.sin(rotAngle),\n rotSpeed: (MULBERRY.next() - 0.5) * 0.06,\n shape: config.shape,\n size: (5 + MULBERRY.next() * 5) * this.#scale,\n swing: MULBERRY.next() * TWO_PI,\n swingAmp: 0.5 + MULBERRY.next() * 1.5,\n swingSpeed: 0.025 + MULBERRY.next() * 0.035,\n tick: 0,\n totalTicks: config.ticks,\n vx: Math.cos(launchAngle) * speed,\n vy: Math.sin(launchAngle) * speed,\n x: config.x,\n y: config.y\n };\n }\n\n // dt defaults to 1 (60fps-equivalent) for the initial kick in fire() before the loop starts.\n #tickParticle(particle: Particle, dt: number = 1): void {\n const decayFactor = Math.pow(particle.decay, dt);\n particle.vx *= decayFactor;\n particle.vy *= decayFactor;\n particle.vy += particle.gravity * 0.35 * dt;\n particle.swing += particle.swingSpeed * dt;\n particle.x += (particle.vx + particle.swingAmp * Math.cos(particle.swing)) * dt;\n particle.y += particle.vy * dt;\n particle.rotAngle += particle.rotSpeed * dt;\n particle.rotCos = Math.cos(particle.rotAngle);\n particle.rotSin = Math.sin(particle.rotAngle);\n particle.flipAngle += particle.flipSpeed * dt;\n particle.tick += dt;\n }\n\n}\n","import { mulberry32, type Mulberry32 } from '@basmilius/utils';\n\nexport const MULBERRY: Mulberry32 = mulberry32(13);\nexport const FIREWORK_TRAIL_MEMORY = 6;\n","export type ExplosionType = 'peony' | 'chrysanthemum' | 'willow' | 'ring' | 'palm' | 'crackle' | 'crossette' | 'dahlia' | 'brocade' | 'horsetail' | 'strobe' | 'heart' | 'spiral' | 'flower';\n\nexport type FireworkVariant = ExplosionType | 'saturn' | 'concentric';\n\nexport type ParticleShape = 'line' | 'circle' | 'star' | 'diamond';\n\nexport interface ExplosionConfig {\n readonly particleCount: [number, number];\n readonly speed: [number, number];\n readonly friction: number;\n readonly gravity: number;\n readonly decay: [number, number];\n readonly trailMemory: number;\n readonly hueVariation: number;\n readonly brightness: [number, number];\n readonly lineWidthScale: number;\n readonly shape: ParticleShape;\n readonly sparkle: boolean;\n readonly strobe: boolean;\n readonly spread3d: boolean;\n readonly glowSize: number;\n}\n\nexport interface FireworkSimulationConfig {\n readonly scale?: number;\n readonly autoSpawn?: boolean;\n readonly canvasOptions?: CanvasRenderingContext2DSettings;\n}\n\nexport const FIREWORK_VARIANTS: FireworkVariant[] = [\n 'peony', 'chrysanthemum', 'willow', 'ring', 'palm', 'crackle', 'crossette',\n 'saturn', 'dahlia', 'brocade', 'horsetail', 'strobe', 'heart', 'spiral', 'flower', 'concentric'\n];\n\nexport const EXPLOSION_CONFIGS: Record<ExplosionType, ExplosionConfig> = {\n peony: {\n particleCount: [50, 70],\n speed: [2, 10],\n friction: 0.96,\n gravity: 0.8,\n decay: [0.012, 0.025],\n trailMemory: 3,\n hueVariation: 30,\n brightness: [50, 80],\n lineWidthScale: 0.8,\n shape: 'circle',\n sparkle: false,\n strobe: false,\n spread3d: true,\n glowSize: 12\n },\n chrysanthemum: {\n particleCount: [80, 120],\n speed: [3, 12],\n friction: 0.975,\n gravity: 0.5,\n decay: [0.006, 0.012],\n trailMemory: 6,\n hueVariation: 20,\n brightness: [55, 85],\n lineWidthScale: 0.5,\n shape: 'line',\n sparkle: true,\n strobe: false,\n spread3d: true,\n glowSize: 15\n },\n willow: {\n particleCount: [50, 70],\n speed: [3, 10],\n friction: 0.988,\n gravity: 1.5,\n decay: [0.004, 0.008],\n trailMemory: 10,\n hueVariation: 15,\n brightness: [60, 90],\n lineWidthScale: 0.4,\n shape: 'line',\n sparkle: false,\n strobe: false,\n spread3d: false,\n glowSize: 10\n },\n ring: {\n particleCount: [40, 60],\n speed: [6, 8],\n friction: 0.96,\n gravity: 0.4,\n decay: [0.012, 0.022],\n trailMemory: 4,\n hueVariation: 10,\n brightness: [55, 80],\n lineWidthScale: 0.7,\n shape: 'diamond',\n sparkle: false,\n strobe: false,\n spread3d: false,\n glowSize: 14\n },\n palm: {\n particleCount: [20, 30],\n speed: [5, 12],\n friction: 0.97,\n gravity: 1.2,\n decay: [0.006, 0.014],\n trailMemory: 6,\n hueVariation: 20,\n brightness: [55, 85],\n lineWidthScale: 0.6,\n shape: 'line',\n sparkle: false,\n strobe: false,\n spread3d: false,\n glowSize: 12\n },\n crackle: {\n particleCount: [40, 55],\n speed: [2, 8],\n friction: 0.955,\n gravity: 0.8,\n decay: [0.012, 0.025],\n trailMemory: 2,\n hueVariation: 25,\n brightness: [60, 90],\n lineWidthScale: 0.6,\n shape: 'star',\n sparkle: false,\n strobe: false,\n spread3d: true,\n glowSize: 8\n },\n crossette: {\n particleCount: [16, 20],\n speed: [5, 9],\n friction: 0.965,\n gravity: 0.6,\n decay: [0.006, 0.014],\n trailMemory: 4,\n hueVariation: 15,\n brightness: [55, 85],\n lineWidthScale: 0.7,\n shape: 'circle',\n sparkle: false,\n strobe: false,\n spread3d: true,\n glowSize: 12\n },\n dahlia: {\n particleCount: [48, 80],\n speed: [3, 9],\n friction: 0.965,\n gravity: 0.7,\n decay: [0.010, 0.020],\n trailMemory: 4,\n hueVariation: 5,\n brightness: [55, 85],\n lineWidthScale: 0.7,\n shape: 'circle',\n sparkle: false,\n strobe: false,\n spread3d: true,\n glowSize: 12\n },\n brocade: {\n particleCount: [60, 80],\n speed: [3, 9],\n friction: 0.98,\n gravity: 1.3,\n decay: [0.004, 0.010],\n trailMemory: 10,\n hueVariation: 10,\n brightness: [60, 90],\n lineWidthScale: 0.4,\n shape: 'line',\n sparkle: true,\n strobe: false,\n spread3d: false,\n glowSize: 10\n },\n horsetail: {\n particleCount: [30, 40],\n speed: [8, 14],\n friction: 0.975,\n gravity: 2.0,\n decay: [0.004, 0.010],\n trailMemory: 12,\n hueVariation: 15,\n brightness: [60, 90],\n lineWidthScale: 0.5,\n shape: 'line',\n sparkle: false,\n strobe: false,\n spread3d: false,\n glowSize: 10\n },\n strobe: {\n particleCount: [40, 55],\n speed: [2, 8],\n friction: 0.96,\n gravity: 0.7,\n decay: [0.010, 0.020],\n trailMemory: 2,\n hueVariation: 10,\n brightness: [75, 95],\n lineWidthScale: 0.6,\n shape: 'circle',\n sparkle: false,\n strobe: true,\n spread3d: true,\n glowSize: 10\n },\n heart: {\n particleCount: [60, 80],\n speed: [3, 5],\n friction: 0.965,\n gravity: 0.3,\n decay: [0.008, 0.016],\n trailMemory: 4,\n hueVariation: 15,\n brightness: [55, 85],\n lineWidthScale: 0.7,\n shape: 'circle',\n sparkle: false,\n strobe: false,\n spread3d: false,\n glowSize: 12\n },\n spiral: {\n particleCount: [45, 60],\n speed: [2, 10],\n friction: 0.97,\n gravity: 0.4,\n decay: [0.008, 0.016],\n trailMemory: 5,\n hueVariation: 10,\n brightness: [55, 85],\n lineWidthScale: 0.6,\n shape: 'circle',\n sparkle: false,\n strobe: false,\n spread3d: false,\n glowSize: 12\n },\n flower: {\n particleCount: [70, 90],\n speed: [3, 7],\n friction: 0.965,\n gravity: 0.3,\n decay: [0.008, 0.016],\n trailMemory: 4,\n hueVariation: 20,\n brightness: [55, 85],\n lineWidthScale: 0.7,\n shape: 'circle',\n sparkle: false,\n strobe: false,\n spread3d: false,\n glowSize: 12\n }\n};\n","import type { Point } from '../point';\nimport { MULBERRY } from './consts';\nimport { EXPLOSION_CONFIGS, type ExplosionConfig, type ExplosionType, type ParticleShape } from './types';\n\nconst PERSPECTIVE = 800;\n\nexport class Explosion {\n readonly #position: Point;\n readonly #angle: number;\n readonly #brightness: number;\n readonly #config: ExplosionConfig;\n readonly #decay: number;\n readonly #hue: number;\n readonly #lineWidth: number;\n readonly #shape: ParticleShape;\n readonly #trail: Point[] = [];\n readonly #type: ExplosionType;\n #alpha: number = 1;\n #depthScale: number = 1;\n #hasCrackled: boolean = false;\n #hasSplit: boolean = false;\n #speed: number;\n #sparkleTimer: number = 0;\n #vz: number;\n #z: number = 0;\n\n get angle(): number {\n return this.#angle;\n }\n\n get hue(): number {\n return this.#hue;\n }\n\n get isDead(): boolean {\n return this.#alpha <= 0;\n }\n\n get position(): Point {\n return this.#position;\n }\n\n get type(): ExplosionType {\n return this.#type;\n }\n\n constructor(position: Point, hue: number, lineWidth: number, type: ExplosionType, scale: number = 1, angle?: number, speed?: number, vz?: number) {\n const config = EXPLOSION_CONFIGS[type];\n\n this.#config = config;\n this.#type = type;\n this.#shape = config.shape;\n this.#position = {...position};\n this.#alpha = 1;\n this.#angle = angle ?? MULBERRY.nextBetween(0, Math.PI * 2);\n this.#brightness = MULBERRY.nextBetween(config.brightness[0], config.brightness[1]);\n this.#decay = MULBERRY.nextBetween(config.decay[0], config.decay[1]);\n this.#hue = hue + MULBERRY.nextBetween(-config.hueVariation, config.hueVariation);\n this.#lineWidth = lineWidth * config.lineWidthScale;\n this.#speed = (speed ?? MULBERRY.nextBetween(config.speed[0], config.speed[1])) * scale;\n\n if (vz !== undefined) {\n this.#vz = vz * scale;\n } else if (config.spread3d) {\n this.#vz = MULBERRY.nextBetween(-this.#speed * 0.5, this.#speed * 0.5);\n } else {\n this.#vz = 0;\n }\n\n for (let i = 0; i < config.trailMemory; i++) {\n this.#trail.push({...position});\n }\n }\n\n checkCrackle(): boolean {\n if (this.#type !== 'crackle' || this.#hasCrackled) {\n return false;\n }\n\n if (this.#alpha <= this.#decay * 3) {\n this.#hasCrackled = true;\n return true;\n }\n\n return false;\n }\n\n checkSplit(): boolean {\n if (this.#type !== 'crossette' || this.#hasSplit) {\n return false;\n }\n\n if (this.#alpha < 0.5) {\n this.#hasSplit = true;\n return true;\n }\n\n return false;\n }\n\n draw(ctx: CanvasRenderingContext2D): void {\n if (this.#config.strobe && this.#sparkleTimer % 6 < 3) {\n return;\n }\n\n const ds = this.#depthScale;\n const trailEnd = this.#trail[this.#trail.length - 1];\n const effectiveWidth = this.#shape === 'line' ? this.#lineWidth * ds : this.#lineWidth * 0.4 * ds;\n const effectiveAlpha = this.#alpha * Math.min(ds, 1.2);\n\n ctx.save();\n ctx.lineCap = 'round';\n\n if (this.#trail.length > 2) {\n for (let i = this.#trail.length - 1; i > 0; i--) {\n const progress = i / this.#trail.length;\n const alpha = (1 - progress) * effectiveAlpha * 0.5;\n const width = effectiveWidth * (1 - progress * 0.4);\n\n ctx.beginPath();\n ctx.moveTo(this.#trail[i].x, this.#trail[i].y);\n ctx.lineTo(this.#trail[i - 1].x, this.#trail[i - 1].y);\n ctx.lineWidth = width;\n ctx.strokeStyle = `hsla(${this.#hue}, 100%, ${this.#brightness * 0.7}%, ${alpha})`;\n ctx.stroke();\n }\n }\n\n ctx.beginPath();\n ctx.moveTo(trailEnd.x, trailEnd.y);\n ctx.lineTo(this.#position.x, this.#position.y);\n ctx.lineWidth = effectiveWidth;\n ctx.strokeStyle = `hsla(${this.#hue}, 100%, ${this.#brightness}%, ${effectiveAlpha})`;\n ctx.stroke();\n\n if (this.#shape !== 'line') {\n this.#drawShape(ctx, ds, effectiveAlpha);\n }\n\n if (this.#config.sparkle && this.#sparkleTimer % 4 < 2) {\n ctx.beginPath();\n ctx.arc(this.#position.x, this.#position.y, this.#lineWidth * 0.8 * ds, 0, Math.PI * 2);\n ctx.fillStyle = `hsla(${this.#hue}, 30%, 95%, ${effectiveAlpha * 0.9})`;\n ctx.fill();\n }\n\n ctx.restore();\n }\n\n tick(): void {\n this.#trail.pop();\n this.#trail.unshift({...this.#position});\n\n this.#speed *= this.#config.friction;\n this.#vz *= this.#config.friction;\n\n this.#position.x += Math.cos(this.#angle) * this.#speed;\n this.#position.y += Math.sin(this.#angle) * this.#speed + this.#config.gravity;\n this.#z += this.#vz;\n\n this.#depthScale = PERSPECTIVE / (PERSPECTIVE + this.#z);\n\n this.#alpha -= this.#decay;\n this.#sparkleTimer++;\n }\n\n #drawShape(ctx: CanvasRenderingContext2D, ds: number, alpha: number): void {\n const size = this.#lineWidth * 1.2 * ds;\n const color = `hsla(${this.#hue}, 100%, ${this.#brightness}%, ${alpha})`;\n\n switch (this.#shape) {\n case 'circle':\n ctx.beginPath();\n ctx.arc(this.#position.x, this.#position.y, size * 0.5, 0, Math.PI * 2);\n ctx.fillStyle = color;\n ctx.fill();\n break;\n\n case 'star':\n this.#drawStarPath(ctx, this.#position.x, this.#position.y, size * 0.7, 4, this.#sparkleTimer * 0.15);\n ctx.fillStyle = `hsla(${this.#hue}, 60%, ${Math.min(this.#brightness + 10, 100)}%, ${alpha})`;\n ctx.fill();\n break;\n\n case 'diamond':\n this.#drawDiamondPath(ctx, this.#position.x, this.#position.y, size * 0.6, this.#angle);\n ctx.fillStyle = color;\n ctx.fill();\n break;\n }\n }\n\n #drawStarPath(ctx: CanvasRenderingContext2D, cx: number, cy: number, radius: number, points: number, rotation: number): void {\n const innerRadius = radius * 0.4;\n const totalPoints = points * 2;\n\n ctx.beginPath();\n\n for (let i = 0; i < totalPoints; i++) {\n const r = i % 2 === 0 ? radius : innerRadius;\n const angle = (i * Math.PI / points) + rotation;\n const px = cx + Math.cos(angle) * r;\n const py = cy + Math.sin(angle) * r;\n\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n\n ctx.closePath();\n }\n\n #drawDiamondPath(ctx: CanvasRenderingContext2D, cx: number, cy: number, size: number, rotation: number): void {\n const cos = Math.cos(rotation);\n const sin = Math.sin(rotation);\n const hw = size * 0.5;\n\n ctx.beginPath();\n ctx.moveTo(cx + cos * size - sin * 0, cy + sin * size + cos * 0);\n ctx.lineTo(cx + cos * 0 - sin * hw, cy + sin * 0 + cos * hw);\n ctx.lineTo(cx + cos * -size - sin * 0, cy + sin * -size + cos * 0);\n ctx.lineTo(cx + cos * 0 - sin * -hw, cy + sin * 0 + cos * -hw);\n ctx.closePath();\n }\n}\n","import type { Point } from './point';\n\nexport function distance(a: Point, b: Point): number {\n let x = a.x - b.x;\n let y = a.y - b.y;\n\n return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));\n}\n","import type { Point } from '../point';\nimport { MULBERRY } from './consts';\n\nexport class Spark {\n readonly #position: Point;\n readonly #velocity: Point;\n readonly #hue: number;\n readonly #size: number;\n readonly #decay: number;\n readonly #friction: number = 0.94;\n readonly #gravity: number = 0.6;\n #alpha: number = 1;\n\n get isDead(): boolean {\n return this.#alpha <= 0;\n }\n\n get position(): Point {\n return this.#position;\n }\n\n constructor(position: Point, hue: number, velocityX: number = 0, velocityY: number = 0) {\n this.#position = {...position};\n this.#hue = hue + MULBERRY.nextBetween(-20, 20);\n this.#size = MULBERRY.nextBetween(0.5, 1.5);\n this.#decay = MULBERRY.nextBetween(0.03, 0.08);\n this.#velocity = {\n x: velocityX + MULBERRY.nextBetween(-1.5, 1.5),\n y: velocityY + MULBERRY.nextBetween(-2, 0.5)\n };\n }\n\n draw(ctx: CanvasRenderingContext2D): void {\n ctx.beginPath();\n ctx.arc(this.#position.x, this.#position.y, this.#size, 0, Math.PI * 2);\n ctx.fillStyle = `hsla(${this.#hue}, 80%, 70%, ${this.#alpha})`;\n ctx.fill();\n }\n\n tick(): void {\n this.#velocity.x *= this.#friction;\n this.#velocity.y *= this.#friction;\n this.#velocity.y += this.#gravity;\n\n this.#position.x += this.#velocity.x;\n this.#position.y += this.#velocity.y;\n\n this.#alpha -= this.#decay;\n }\n}\n","import { distance } from '../distance';\nimport type { Point } from '../point';\nimport { FIREWORK_TRAIL_MEMORY, MULBERRY } from './consts';\nimport { Spark } from './spark';\n\nexport class Firework extends EventTarget {\n readonly #position: Point;\n readonly #startPosition: Point;\n readonly #acceleration: number = 1.05;\n readonly #angle: number;\n readonly #baseSize: number;\n readonly #brightness: number = MULBERRY.nextBetween(55, 75);\n readonly #distance: number;\n readonly #hue: number;\n readonly #tailWidth: number;\n readonly #trail: Point[] = [];\n #distanceTraveled: number = 0;\n #speed: number = 1;\n #sparkTimer: number = 0;\n #pendingSparks: Spark[] = [];\n\n get position(): Point {\n return this.#position;\n }\n\n get hue(): number {\n return this.#hue;\n }\n\n constructor(start: Point, target: Point, hue: number, tailWidth: number, baseSize: number) {\n super();\n\n this.#hue = hue;\n this.#tailWidth = tailWidth;\n this.#baseSize = baseSize;\n this.#position = {...start};\n this.#startPosition = {...start};\n this.#angle = Math.atan2(target.y - start.y, target.x - start.x);\n this.#distance = distance(start, target);\n\n for (let i = 0; i < FIREWORK_TRAIL_MEMORY; i++) {\n this.#trail.push({...start});\n }\n }\n\n collectSparks(): Spark[] {\n const sparks = this.#pendingSparks;\n this.#pendingSparks = [];\n return sparks;\n }\n\n draw(ctx: CanvasRenderingContext2D): void {\n ctx.save();\n ctx.lineCap = 'round';\n\n for (let i = this.#trail.length - 1; i > 0; i--) {\n const progress = i / this.#trail.length;\n const alpha = (1 - progress) * 0.8;\n const width = this.#tailWidth * (1 - progress * 0.5);\n const hue = this.#hue + MULBERRY.nextBetween(-15, 15);\n\n ctx.beginPath();\n ctx.moveTo(this.#trail[i].x, this.#trail[i].y);\n ctx.lineTo(this.#trail[i - 1].x, this.#trail[i - 1].y);\n ctx.lineWidth = width;\n ctx.strokeStyle = `hsla(${hue}, 100%, ${this.#brightness}%, ${alpha})`;\n ctx.stroke();\n }\n\n ctx.shadowBlur = this.#baseSize * 4;\n ctx.shadowColor = `hsl(${this.#hue}, 100%, 60%)`;\n\n ctx.beginPath();\n ctx.moveTo(this.#trail[0].x, this.#trail[0].y);\n ctx.lineTo(this.#position.x, this.#position.y);\n ctx.lineWidth = this.#tailWidth;\n ctx.strokeStyle = `hsl(${this.#hue}, 100%, ${this.#brightness}%)`;\n ctx.stroke();\n\n ctx.shadowBlur = this.#baseSize * 6;\n ctx.shadowColor = `hsl(${this.#hue}, 80%, 80%)`;\n ctx.beginPath();\n ctx.arc(this.#position.x, this.#position.y, this.#tailWidth * 0.6, 0, Math.PI * 2);\n ctx.fillStyle = `hsl(${this.#hue}, 20%, 92%)`;\n ctx.fill();\n\n ctx.restore();\n }\n\n tick(): void {\n this.#trail.pop();\n this.#trail.unshift({...this.#position});\n\n this.#speed *= this.#acceleration;\n\n const vx = Math.cos(this.#angle) * this.#speed;\n const vy = Math.sin(this.#angle) * this.#speed;\n\n this.#distanceTraveled = distance(this.#startPosition, {\n x: this.#position.x + vx,\n y: this.#position.y + vy\n });\n\n if (this.#distanceTraveled >= this.#distance) {\n this.dispatchEvent(new CustomEvent('remove'));\n return;\n }\n\n this.#position.x += vx;\n this.#position.y += vy;\n\n this.#sparkTimer++;\n\n if (this.#sparkTimer % 3 === 0) {\n this.#pendingSparks.push(new Spark(\n this.#position,\n this.#hue,\n -vx * 0.1,\n -vy * 0.1\n ));\n }\n }\n}\n","import { LimitedFrameRateCanvas } from '../canvas';\nimport type { Point } from '../point';\nimport { MULBERRY } from './consts';\nimport { Explosion } from './explosion';\nimport { Firework } from './firework';\nimport { Spark } from './spark';\nimport { EXPLOSION_CONFIGS, type ExplosionType, type FireworkSimulationConfig, type FireworkVariant } from './types';\n\nexport class FireworkSimulation extends LimitedFrameRateCanvas {\n #explosions: Explosion[] = [];\n #fireworks: Firework[] = [];\n #sparks: Spark[] = [];\n #hue: number = 120;\n #positionRandom = MULBERRY.fork();\n readonly #autoSpawn: boolean;\n readonly #baseSize: number;\n readonly #scale: number;\n readonly #tailWidth: number;\n\n constructor(canvas: HTMLCanvasElement, config: FireworkSimulationConfig = {}) {\n super(canvas, 60, config.canvasOptions ?? {colorSpace: 'display-p3'});\n\n const scale = config.scale ?? 1;\n this.#autoSpawn = config.autoSpawn ?? true;\n this.#baseSize = 5 * scale;\n this.#scale = scale;\n this.#tailWidth = 2 * scale;\n\n this.canvas.style.position = 'absolute';\n this.canvas.style.top = '0';\n this.canvas.style.left = '0';\n this.canvas.style.height = '100%';\n this.canvas.style.width = '100%';\n }\n\n draw(): void {\n if (this.canvas.width !== this.width || this.canvas.height !== this.height) {\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n }\n\n this.context.clearRect(0, 0, this.width, this.height);\n this.context.globalCompositeOperation = 'lighter';\n\n for (const spark of this.#sparks) {\n spark.draw(this.context);\n }\n\n for (const explosion of this.#explosions) {\n explosion.draw(this.context);\n }\n\n for (const firework of this.#fireworks) {\n firework.draw(this.context);\n }\n }\n\n fireExplosion(variant: FireworkVariant, position?: Point): void {\n const pos = position ?? {x: this.width / 2, y: this.height * 0.4};\n this.#hue = MULBERRY.nextBetween(0, 360);\n this.#createExplosion(pos, this.#hue, variant);\n }\n\n tick(): void {\n if (this.#autoSpawn && this.#fireworks.length < 6 && this.ticks % (this.isSmall ? 60 : 30) === 0) {\n let count = MULBERRY.nextBetween(1, 100) < 10 ? 2 : 1;\n\n while (count--) {\n this.#hue = MULBERRY.nextBetween(0, 360);\n this.#createFirework();\n }\n }\n\n for (const firework of this.#fireworks) {\n firework.tick();\n this.#sparks.push(...firework.collectSparks());\n }\n\n for (const explosion of this.#explosions) {\n explosion.tick();\n }\n\n for (const spark of this.#sparks) {\n spark.tick();\n }\n\n const newExplosions: Explosion[] = [];\n const newSparks: Spark[] = [];\n\n for (const explosion of this.#explosions) {\n if (explosion.checkSplit()) {\n for (let i = 0; i < 4; i++) {\n const angle = explosion.angle + (Math.PI / 2) * i + Math.PI / 4;\n\n newExplosions.push(new Explosion(\n explosion.position,\n explosion.hue,\n this.#baseSize * 0.6,\n 'peony',\n this.#scale,\n angle,\n MULBERRY.nextBetween(3, 6)\n ));\n }\n }\n\n if (explosion.checkCrackle()) {\n for (let j = 0; j < 8; j++) {\n newSparks.push(new Spark(\n explosion.position,\n explosion.hue + MULBERRY.nextBetween(-30, 30)\n ));\n }\n }\n }\n\n this.#explosions.push(...newExplosions);\n this.#sparks.push(...newSparks);\n\n this.#explosions = this.#explosions.filter(e => !e.isDead);\n this.#sparks = this.#sparks.filter(s => !s.isDead);\n }\n\n #createExplosion(position: Point, hue: number, variant?: FireworkVariant): void {\n const selected = variant ?? this.#pickVariant();\n\n if (selected === 'saturn') {\n this.#createSaturnExplosion(position, hue);\n return;\n }\n\n if (selected === 'dahlia') {\n this.#createDahliaExplosion(position, hue);\n return;\n }\n\n if (selected === 'heart') {\n this.#createHeartExplosion(position, hue);\n return;\n }\n\n if (selected === 'spiral') {\n this.#createSpiralExplosion(position, hue);\n return;\n }\n\n if (selected === 'flower') {\n this.#createFlowerExplosion(position, hue);\n return;\n }\n\n if (selected === 'concentric') {\n this.#createConcentricExplosion(position, hue);\n return;\n }\n\n const type: ExplosionType = selected;\n const config = EXPLOSION_CONFIGS[type];\n const particleCount = Math.floor(MULBERRY.nextBetween(config.particleCount[0], config.particleCount[1]));\n\n const effectiveHue = type === 'brocade'\n ? MULBERRY.nextBetween(35, 50)\n : hue;\n\n for (let i = 0; i < particleCount; i++) {\n let angle: number | undefined;\n let speed: number | undefined;\n\n if (type === 'ring') {\n angle = (i / particleCount) * Math.PI * 2;\n speed = MULBERRY.nextBetween(config.speed[0], config.speed[1]) * 0.5 + config.speed[0] * 0.5;\n } else if (type === 'palm' || type === 'horsetail') {\n const spread = type === 'horsetail' ? Math.PI / 8 : Math.PI / 5;\n angle = -Math.PI / 2 + MULBERRY.nextBetween(-spread, spread);\n }\n\n this.#explosions.push(new Explosion(position, effectiveHue, this.#baseSize, type, this.#scale, angle, speed));\n }\n }\n\n #createSaturnExplosion(position: Point, hue: number): void {\n const velocity = MULBERRY.nextBetween(4, 6);\n\n // Outer shell — evenly spaced circle\n const shellCount = Math.floor(MULBERRY.nextBetween(25, 35));\n\n for (let i = 0; i < shellCount; i++) {\n const rad = (i / shellCount) * Math.PI * 2;\n\n this.#explosions.push(new Explosion(\n position,\n hue,\n this.#baseSize,\n 'peony',\n this.#scale,\n rad + MULBERRY.nextBetween(-0.05, 0.05),\n velocity + MULBERRY.nextBetween(-0.25, 0.25)\n ));\n }\n\n // Filled interior — random speeds from 0 to velocity for sphere fill\n const fillCount = Math.floor(MULBERRY.nextBetween(40, 60));\n\n for (let i = 0; i < fillCount; i++) {\n const rad = MULBERRY.nextBetween(0, Math.PI * 2);\n const speed = velocity * MULBERRY.nextBetween(0, 1);\n\n this.#explosions.push(new Explosion(\n position,\n hue,\n this.#baseSize,\n 'peony',\n this.#scale,\n rad,\n speed\n ));\n }\n\n // Elliptical ring — rotated 2D ellipse with z-depth\n const ringRotation = MULBERRY.nextBetween(0, Math.PI * 2);\n const ringCount = Math.floor(MULBERRY.nextBetween(40, 55));\n const ringVx = velocity * MULBERRY.nextBetween(2, 3);\n const ringVy = velocity * 0.6;\n\n for (let i = 0; i < ringCount; i++) {\n const rad = (i / ringCount) * Math.PI * 2;\n\n const cx = Math.cos(rad) * ringVx + MULBERRY.nextBetween(-0.25, 0.25);\n const cy = Math.sin(rad) * ringVy + MULBERRY.nextBetween(-0.25, 0.25);\n\n const cosR = Math.cos(ringRotation);\n const sinR = Math.sin(ringRotation);\n const vx = cx * cosR - cy * sinR;\n const vy = cx * sinR + cy * cosR;\n\n const screenAngle = Math.atan2(vy, vx);\n const screenSpeed = Math.sqrt(vx * vx + vy * vy);\n const vz = Math.sin(rad) * velocity * 0.8;\n\n this.#explosions.push(new Explosion(\n position,\n hue + 60,\n this.#baseSize,\n 'ring',\n this.#scale,\n screenAngle,\n screenSpeed,\n vz\n ));\n }\n }\n\n #createDahliaExplosion(position: Point, hue: number): void {\n const petalCount = Math.floor(MULBERRY.nextBetween(6, 9));\n const particlesPerPetal = Math.floor(MULBERRY.nextBetween(8, 12));\n\n for (let petal = 0; petal < petalCount; petal++) {\n const baseAngle = (petal / petalCount) * Math.PI * 2;\n const petalHue = hue + (petal % 2 === 0 ? 25 : -25);\n\n for (let i = 0; i < particlesPerPetal; i++) {\n const angle = baseAngle + MULBERRY.nextBetween(-0.3, 0.3);\n\n this.#explosions.push(new Explosion(\n position,\n petalHue,\n this.#baseSize,\n 'dahlia',\n this.#scale,\n angle\n ));\n }\n }\n }\n\n #createHeartExplosion(position: Point, hue: number): void {\n const velocity = MULBERRY.nextBetween(3, 5);\n const count = Math.floor(MULBERRY.nextBetween(60, 80));\n const rotation = MULBERRY.nextBetween(-0.3, 0.3);\n\n for (let i = 0; i < count; i++) {\n const t = (i / count) * Math.PI * 2;\n\n // Parametric heart curve\n const hx = 16 * Math.pow(Math.sin(t), 3);\n const hy = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));\n\n const scale = velocity / 16;\n const vx = hx * scale;\n const vy = hy * scale;\n\n // Rotate slightly\n const cosR = Math.cos(rotation);\n const sinR = Math.sin(rotation);\n const rvx = vx * cosR - vy * sinR;\n const rvy = vx * sinR + vy * cosR;\n\n const angle = Math.atan2(rvy, rvx);\n const speed = Math.sqrt(rvx * rvx + rvy * rvy);\n\n this.#explosions.push(new Explosion(\n position,\n hue,\n this.#baseSize,\n 'heart',\n this.#scale,\n angle,\n Math.max(0.1, speed + MULBERRY.nextBetween(-0.15, 0.15))\n ));\n }\n }\n\n #createSpiralExplosion(position: Point, hue: number): void {\n const arms = Math.floor(MULBERRY.nextBetween(3, 5));\n const particlesPerArm = Math.floor(MULBERRY.nextBetween(15, 20));\n const twist = MULBERRY.nextBetween(2, 3.5);\n const baseRotation = MULBERRY.nextBetween(0, Math.PI * 2);\n\n for (let arm = 0; arm < arms; arm++) {\n const baseAngle = baseRotation + (arm / arms) * Math.PI * 2;\n const armHue = hue + arm * (360 / arms / 3);\n\n for (let i = 0; i < particlesPerArm; i++) {\n const progress = i / particlesPerArm;\n const angle = baseAngle + progress * twist;\n const speed = 2 + progress * 8;\n\n this.#explosions.push(new Explosion(\n position,\n armHue,\n this.#baseSize,\n 'spiral',\n this.#scale,\n angle,\n speed + MULBERRY.nextBetween(-0.3, 0.3)\n ));\n }\n }\n }\n\n #createFlowerExplosion(position: Point, hue: number): void {\n const velocity = MULBERRY.nextBetween(4, 7);\n const count = Math.floor(MULBERRY.nextBetween(70, 90));\n const petals = Math.floor(MULBERRY.nextBetween(2, 4));\n const rotation = MULBERRY.nextBetween(0, Math.PI * 2);\n\n for (let i = 0; i < count; i++) {\n const t = (i / count) * Math.PI * 2;\n\n // Rose curve: r = |cos(n * theta)|\n const r = Math.abs(Math.cos(petals * t));\n const speed = velocity * r;\n\n if (speed < 0.3) {\n continue;\n }\n\n this.#explosions.push(new Explosion(\n position,\n hue + MULBERRY.nextBetween(-15, 15),\n this.#baseSize,\n 'flower',\n this.#scale,\n t + rotation,\n speed + MULBERRY.nextBetween(-0.2, 0.2)\n ));\n }\n }\n\n #createConcentricExplosion(position: Point, hue: number): void {\n const outerCount = Math.floor(MULBERRY.nextBetween(35, 50));\n const outerSpeed = MULBERRY.nextBetween(7, 10);\n\n for (let i = 0; i < outerCount; i++) {\n const angle = (i / outerCount) * Math.PI * 2;\n\n this.#explosions.push(new Explosion(\n position,\n hue,\n this.#baseSize,\n 'ring',\n this.#scale,\n angle + MULBERRY.nextBetween(-0.05, 0.05),\n outerSpeed + MULBERRY.nextBetween(-0.25, 0.25)\n ));\n }\n\n const innerCount = Math.floor(MULBERRY.nextBetween(25, 35));\n const innerSpeed = MULBERRY.nextBetween(3, 5);\n\n for (let i = 0; i < innerCount; i++) {\n const angle = (i / innerCount) * Math.PI * 2;\n\n this.#explosions.push(new Explosion(\n position,\n hue + 120,\n this.#baseSize,\n 'ring',\n this.#scale,\n angle + MULBERRY.nextBetween(-0.05, 0.05),\n innerSpeed + MULBERRY.nextBetween(-0.25, 0.25)\n ));\n }\n }\n\n #createFirework(position?: Point): void {\n const hue = this.#hue;\n const targetX = position?.x || this.#positionRandom.nextBetween(this.width * .1, this.width * .9);\n const targetY = position?.y || this.height * .1 + this.#positionRandom.nextBetween(0, this.height * .5);\n const startX = this.width * 0.3 + this.#positionRandom.nextBetween(0, this.width * 0.4);\n\n const firework = new Firework(\n {x: startX, y: this.height},\n {x: targetX, y: targetY},\n hue,\n this.#tailWidth,\n this.#baseSize\n );\n\n firework.addEventListener('remove', () => {\n this.#fireworks.splice(this.#fireworks.indexOf(firework), 1);\n this.#createExplosion(firework.position, hue);\n }, {once: true});\n\n this.#fireworks.push(firework);\n }\n\n #pickVariant(): FireworkVariant {\n const roll = MULBERRY.nextBetween(0, 100);\n\n if (roll < 12) {\n return 'peony';\n }\n\n if (roll < 22) {\n return 'chrysanthemum';\n }\n\n if (roll < 29) {\n return 'willow';\n }\n\n if (roll < 34) {\n return 'ring';\n }\n\n if (roll < 39) {\n return 'palm';\n }\n\n if (roll < 44) {\n return 'crackle';\n }\n\n if (roll < 48) {\n return 'crossette';\n }\n\n if (roll < 55) {\n return 'saturn';\n }\n\n if (roll < 62) {\n return 'dahlia';\n }\n\n if (roll < 67) {\n return 'brocade';\n }\n\n if (roll < 71) {\n return 'horsetail';\n }\n\n if (roll < 75) {\n return 'strobe';\n }\n\n if (roll < 82) {\n return 'heart';\n }\n\n if (roll < 89) {\n return 'spiral';\n }\n\n if (roll < 94) {\n return 'flower';\n }\n\n return 'concentric';\n }\n}\n","import { mulberry32 } from '@basmilius/utils';\n\nexport const MULBERRY = mulberry32(13);\n","import { LimitedFrameRateCanvas } from '../canvas';\nimport { MULBERRY } from './consts';\nimport type { Snowflake } from './snowflake';\n\nexport interface SnowSimulationConfig {\n readonly fillStyle?: string;\n readonly particles?: number;\n readonly scale?: number;\n readonly size?: number;\n readonly speed?: number;\n readonly canvasOptions?: CanvasRenderingContext2DSettings;\n}\n\nconst SPRITE_SIZE = 64;\nconst SPRITE_CENTER = SPRITE_SIZE / 2;\nconst SPRITE_RADIUS = SPRITE_SIZE / 2;\n\nexport class SnowSimulation extends LimitedFrameRateCanvas {\n readonly #scale: number;\n readonly #size: number;\n readonly #speed: number;\n readonly #baseOpacity: number;\n #maxParticles: number;\n #time: number = 0;\n #ratio: number = 1;\n #snowflakes: Snowflake[] = [];\n #sprites: HTMLCanvasElement[] = [];\n\n constructor(canvas: HTMLCanvasElement, config: SnowSimulationConfig = {}) {\n super(canvas, 60, config.canvasOptions ?? {colorSpace: 'display-p3'});\n\n this.#scale = config.scale ?? 1;\n this.#maxParticles = config.particles ?? 200;\n this.#size = (config.size ?? 9) * this.#scale;\n this.#speed = config.speed ?? 2;\n\n const {r, g, b, a} = this.#parseColor(config.fillStyle ?? 'rgb(255 255 255 / .75)');\n this.#baseOpacity = a;\n\n this.canvas.style.position = 'absolute';\n this.canvas.style.top = '0';\n this.canvas.style.left = '0';\n this.canvas.style.height = '100%';\n this.canvas.style.width = '100%';\n\n if (this.isSmall) {\n this.#maxParticles = Math.floor(this.#maxParticles / 2);\n }\n\n this.#sprites = this.#createSprites(r, g, b);\n\n for (let i = 0; i < this.#maxParticles; ++i) {\n this.#snowflakes.push(this.#createSnowflake(true));\n }\n }\n\n draw(): void {\n this.canvas.height = this.height;\n this.canvas.width = this.width;\n\n const ctx = this.context;\n ctx.clearRect(0, 0, this.width, this.height);\n\n for (const snowflake of this.#snowflakes) {\n const px = snowflake.x * this.width;\n const py = snowflake.y * this.height;\n const displayRadius = snowflake.radius * snowflake.depth * this.#ratio;\n const displaySize = displayRadius * 2;\n\n if (displaySize < 0.5) {\n continue;\n }\n\n ctx.globalAlpha = this.#baseOpacity * (0.15 + snowflake.depth * 0.85);\n\n if (snowflake.spriteIndex === 3) {\n ctx.save();\n ctx.translate(px, py);\n ctx.rotate(snowflake.rotation);\n ctx.drawImage(\n this.#sprites[snowflake.spriteIndex],\n -displayRadius,\n -displayRadius,\n displaySize,\n displaySize\n );\n ctx.restore();\n } else {\n ctx.drawImage(\n this.#sprites[snowflake.spriteIndex],\n px - displayRadius,\n py - displayRadius,\n displaySize,\n displaySize\n );\n }\n }\n\n ctx.globalAlpha = 1;\n }\n\n tick(): void {\n const speedFactor = (this.height / (420 * this.#ratio) / this.#speed) * this.deltaFactor;\n\n this.#time += 0.015 * speedFactor;\n\n // Multi-frequency wind for organic movement\n const wind = Math.sin(this.#time * 0.7) * 0.5\n + Math.sin(this.#time * 1.9 + 3) * 0.25\n + Math.sin(this.#time * 4.3 + 1) * 0.1;\n\n for (let index = 0; index < this.#snowflakes.length; index++) {\n const snowflake = this.#snowflakes[index];\n\n // Individual swing oscillation\n const swing = Math.sin(this.#time * snowflake.swingFrequency + snowflake.swingOffset) * snowflake.swingAmplitude;\n\n // Horizontal: personal swing + global wind (deeper = more wind influence)\n snowflake.x += (swing + wind * snowflake.depth * 2) / (4000 * speedFactor);\n\n // Vertical: individual speed + depth + size influence\n snowflake.y += (snowflake.fallSpeed * 2 + snowflake.depth + snowflake.radius * 0.15) / (700 * speedFactor);\n\n // Rotation (only visually relevant for crystal sprites)\n snowflake.rotation += snowflake.rotationSpeed / speedFactor;\n\n // Recycle out-of-bounds particles\n if (snowflake.x > 1.15 || snowflake.x < -0.15 || snowflake.y > 1.05) {\n const recycled = this.#createSnowflake(false);\n\n if (index % 3 > 0) {\n recycled.x = MULBERRY.next();\n recycled.y = -0.05 - MULBERRY.next() * 0.15;\n } else if (wind > 0.2) {\n recycled.x = -0.15;\n recycled.y = MULBERRY.next() * 0.8;\n } else if (wind < -0.2) {\n recycled.x = 1.15;\n recycled.y = MULBERRY.next() * 0.8;\n } else {\n recycled.x = MULBERRY.next();\n recycled.y = -0.05 - MULBERRY.next() * 0.15;\n }\n\n this.#snowflakes[index] = recycled;\n }\n }\n }\n\n #parseColor(fillStyle: string): {r: number; g: number; b: number; a: number} {\n const canvas = document.createElement('canvas');\n canvas.width = 1;\n canvas.height = 1;\n const ctx = canvas.getContext('2d')!;\n ctx.fillStyle = fillStyle;\n ctx.fillRect(0, 0, 1, 1);\n const data = ctx.getImageData(0, 0, 1, 1).data;\n return {r: data[0], g: data[1], b: data[2], a: data[3] / 255};\n }\n\n #createSprites(r: number, g: number, b: number): HTMLCanvasElement[] {\n const sprites: HTMLCanvasElement[] = [];\n\n const gradientProfiles: [number, number][][] = [\n // 0: Soft glow\n [[0, 0.8], [0.3, 0.4], [0.7, 0.1], [1, 0]],\n // 1: Bright center\n [[0, 1], [0.15, 0.7], [0.5, 0.2], [1, 0]],\n // 2: Compact dot\n [[0, 0.9], [0.25, 0.5], [0.5, 0.1], [1, 0]]\n ];\n\n for (const profile of gradientProfiles) {\n const canvas = document.createElement('canvas');\n canvas.width = SPRITE_SIZE;\n canvas.height = SPRITE_SIZE;\n const ctx = canvas.getContext('2d')!;\n\n const gradient = ctx.createRadialGradient(\n SPRITE_CENTER, SPRITE_CENTER, 0,\n SPRITE_CENTER, SPRITE_CENTER, SPRITE_RADIUS\n );\n\n for (const [stop, alpha] of profile) {\n gradient.addColorStop(stop, `rgba(${r}, ${g}, ${b}, ${alpha})`);\n }\n\n ctx.fillStyle = gradient;\n ctx.beginPath();\n ctx.arc(SPRITE_CENTER, SPRITE_CENTER, SPRITE_RADIUS, 0, Math.PI * 2);\n ctx.fill();\n\n sprites.push(canvas);\n }\n\n // 3: Crystal snowflake\n sprites.push(this.#createCrystalSprite(r, g, b));\n\n return sprites;\n }\n\n #createCrystalSprite(r: number, g: number, b: number): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.width = SPRITE_SIZE;\n canvas.height = SPRITE_SIZE;\n const ctx = canvas.getContext('2d')!;\n\n // Soft glow base\n const glow = ctx.createRadialGradient(\n SPRITE_CENTER, SPRITE_CENTER, 0,\n SPRITE_CENTER, SPRITE_CENTER, SPRITE_RADIUS\n );\n glow.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.6)`);\n glow.addColorStop(0.25, `rgba(${r}, ${g}, ${b}, 0.25)`);\n glow.addColorStop(0.6, `rgba(${r}, ${g}, ${b}, 0.05)`);\n glow.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`);\n ctx.fillStyle = glow;\n ctx.beginPath();\n ctx.arc(SPRITE_CENTER, SPRITE_CENTER, SPRITE_RADIUS, 0, Math.PI * 2);\n ctx.fill();\n\n // Crystal arms\n ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, 0.7)`;\n ctx.lineWidth = 1.5;\n ctx.lineCap = 'round';\n\n const armLength = SPRITE_RADIUS * 0.75;\n\n for (let arm = 0; arm < 6; arm++) {\n const angle = (arm / 6) * Math.PI * 2 - Math.PI / 2;\n const tipX = SPRITE_CENTER + Math.cos(angle) * armLength;\n const tipY = SPRITE_CENTER + Math.sin(angle) * armLength;\n\n // Main arm\n ctx.beginPath();\n ctx.moveTo(SPRITE_CENTER, SPRITE_CENTER);\n ctx.lineTo(tipX, tipY);\n ctx.stroke();\n\n // Side branches at 40% and 65% along the arm\n for (const position of [0.4, 0.65]) {\n const branchX = SPRITE_CENTER + Math.cos(angle) * armLength * position;\n const branchY = SPRITE_CENTER + Math.sin(angle) * armLength * position;\n const branchLength = armLength * (0.4 - position * 0.3);\n\n for (const side of [-1, 1]) {\n const branchAngle = angle + side * Math.PI / 3;\n ctx.beginPath();\n ctx.moveTo(branchX, branchY);\n ctx.lineTo(\n branchX + Math.cos(branchAngle) * branchLength,\n branchY + Math.sin(branchAngle) * branchLength\n );\n ctx.stroke();\n }\n }\n }\n\n // Center dot\n const centerGlow = ctx.createRadialGradient(\n SPRITE_CENTER, SPRITE_CENTER, 0,\n SPRITE_CENTER, SPRITE_CENTER, SPRITE_RADIUS * 0.12\n );\n centerGlow.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.9)`);\n centerGlow.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`);\n ctx.fillStyle = centerGlow;\n ctx.beginPath();\n ctx.arc(SPRITE_CENTER, SPRITE_CENTER, SPRITE_RADIUS * 0.12, 0, Math.PI * 2);\n ctx.fill();\n\n return canvas;\n }\n\n #createSnowflake(initialSpread: boolean): Snowflake {\n const depth = 0.3 + MULBERRY.next() * 0.7;\n const radius = MULBERRY.next() * this.#size + 2 * this.#scale;\n\n let spriteIndex: number;\n if (depth > 0.85 && radius > this.#size * 0.6 && MULBERRY.next() > 0.65) {\n spriteIndex = 3;\n } else if (depth < 0.45) {\n spriteIndex = 2;\n } else {\n spriteIndex = MULBERRY.next() > 0.5 ? 0 : 1;\n }\n\n return {\n x: MULBERRY.next(),\n y: initialSpread ? MULBERRY.next() * 2 - 1 : -0.05 - MULBERRY.next() * 0.15,\n depth,\n radius,\n rotation: MULBERRY.next() * Math.PI * 2,\n rotationSpeed: (MULBERRY.next() - 0.5) * 0.03,\n swingAmplitude: 0.3 + MULBERRY.next() * 0.7,\n swingFrequency: 0.5 + MULBERRY.next() * 1.5,\n swingOffset: MULBERRY.next() * Math.PI * 2,\n fallSpeed: 0.5 + MULBERRY.next() * 0.5,\n spriteIndex\n };\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@basmilius/sparkle",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"email": "bas@mili.us",
|
|
7
|
+
"name": "Bas Milius",
|
|
8
|
+
"url": "https://bas.dev"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/basmilius/visual-effects.git"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsdown",
|
|
16
|
+
"dev": "vite",
|
|
17
|
+
"docs:dev": "vitepress dev docs",
|
|
18
|
+
"docs:build": "vitepress build docs"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=23"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"provenance": false
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"main": "./dist/index.mjs",
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"typings": "./dist/index.d.mts",
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.mts",
|
|
39
|
+
"default": "./dist/index.mjs"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@basmilius/utils": "^3.15.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"tsdown": "^0.21.4",
|
|
47
|
+
"typescript": "^6.0.2",
|
|
48
|
+
"vite": "^8.0.3",
|
|
49
|
+
"vitepress": "^1.6.3",
|
|
50
|
+
"vitepress-plugin-example": "^1.4.0",
|
|
51
|
+
"vitepress-plugin-render": "^1.4.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/canvas.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export class LimitedFrameRateCanvas {
|
|
2
|
+
readonly #canvas: HTMLCanvasElement;
|
|
3
|
+
readonly #context: CanvasRenderingContext2D;
|
|
4
|
+
readonly #frameRate: number;
|
|
5
|
+
readonly #target: number;
|
|
6
|
+
#current: number = 0;
|
|
7
|
+
#delta: number = 0;
|
|
8
|
+
#frame: number = 0;
|
|
9
|
+
#now: number = 0;
|
|
10
|
+
#then: number = 0;
|
|
11
|
+
#ticks: number = 0;
|
|
12
|
+
#isStopped: boolean = true;
|
|
13
|
+
#height: number = 540;
|
|
14
|
+
#width: number = 960;
|
|
15
|
+
|
|
16
|
+
get canvas(): HTMLCanvasElement {
|
|
17
|
+
return this.#canvas;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get context(): CanvasRenderingContext2D {
|
|
21
|
+
return this.#context;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get delta(): number {
|
|
25
|
+
return this.#delta;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get deltaFactor(): number {
|
|
29
|
+
return this.#then === 0 ? 1 : this.#target / this.#delta;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get frameRate(): number {
|
|
33
|
+
return this.#frameRate;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get isSmall(): boolean {
|
|
37
|
+
return innerWidth < 991; // dirty little fix :-)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get isTicking(): boolean {
|
|
41
|
+
return !this.#isStopped;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get ticks(): number {
|
|
45
|
+
return this.#ticks;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get height(): number {
|
|
49
|
+
return this.#height;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get width(): number {
|
|
53
|
+
return this.#width;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
constructor(canvas: HTMLCanvasElement, frameRate: number, options: CanvasRenderingContext2DSettings = {colorSpace: 'display-p3'}) {
|
|
57
|
+
this.#canvas = canvas;
|
|
58
|
+
this.#context = canvas.getContext('2d', options);
|
|
59
|
+
this.#frameRate = frameRate;
|
|
60
|
+
this.#target = 1000 / frameRate;
|
|
61
|
+
|
|
62
|
+
this.onVisibilityChange = this.onVisibilityChange.bind(this);
|
|
63
|
+
this.onResize = this.onResize.bind(this);
|
|
64
|
+
|
|
65
|
+
document.addEventListener('visibilitychange', this.onVisibilityChange, {passive: true});
|
|
66
|
+
window.addEventListener('resize', this.onResize, {passive: true});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
loop(): void {
|
|
70
|
+
if (this.#isStopped) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.#current = Date.now();
|
|
75
|
+
this.#frame = requestAnimationFrame(this.loop.bind(this));
|
|
76
|
+
|
|
77
|
+
if (this.#then > 0 && this.#current - this.#then + 1 < this.#target) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.#now = this.#current;
|
|
82
|
+
this.#delta = this.#now - this.#then;
|
|
83
|
+
|
|
84
|
+
++this.#ticks;
|
|
85
|
+
|
|
86
|
+
this.tick();
|
|
87
|
+
this.draw();
|
|
88
|
+
|
|
89
|
+
this.#then = this.#now;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
start(): void {
|
|
93
|
+
this.onResize();
|
|
94
|
+
|
|
95
|
+
this.#isStopped = false;
|
|
96
|
+
this.#frame = requestAnimationFrame(this.loop.bind(this));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
stop(): void {
|
|
100
|
+
this.#isStopped = true;
|
|
101
|
+
cancelAnimationFrame(this.#frame);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
draw(): void {
|
|
105
|
+
throw new Error('LimitedFrameRateCanvas::draw() should be overwritten.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
tick(): void {
|
|
109
|
+
throw new Error('LimitedFrameRateCanvas::tick() should be overwritten.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
destroy(): void {
|
|
113
|
+
this.stop();
|
|
114
|
+
document.removeEventListener('visibilitychange', this.onVisibilityChange);
|
|
115
|
+
window.removeEventListener('resize', this.onResize);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onResize(): void {
|
|
119
|
+
const {width, height} = this.#canvas.getBoundingClientRect();
|
|
120
|
+
this.#height = height;
|
|
121
|
+
this.#width = width;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
onVisibilityChange(): void {
|
|
125
|
+
cancelAnimationFrame(this.#frame);
|
|
126
|
+
|
|
127
|
+
if (document.visibilityState === 'visible') {
|
|
128
|
+
this.#then = 0;
|
|
129
|
+
this.start();
|
|
130
|
+
} else {
|
|
131
|
+
this.#then = 0;
|
|
132
|
+
this.stop();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { mulberry32 } from '@basmilius/utils';
|
|
2
|
+
import type { Config } from './types';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_CONFIG: Config = {
|
|
5
|
+
angle: 90,
|
|
6
|
+
colors: [
|
|
7
|
+
'#26ccff',
|
|
8
|
+
'#a25afd',
|
|
9
|
+
'#ff5e7e',
|
|
10
|
+
'#88ff5a',
|
|
11
|
+
'#fcff42',
|
|
12
|
+
'#ffa62d',
|
|
13
|
+
'#ff36ff'
|
|
14
|
+
],
|
|
15
|
+
decay: 0.9,
|
|
16
|
+
gravity: 1,
|
|
17
|
+
particles: 50,
|
|
18
|
+
shapes: ['circle', 'diamond', 'ribbon', 'square', 'star', 'triangle'],
|
|
19
|
+
spread: 45,
|
|
20
|
+
ticks: 200,
|
|
21
|
+
startVelocity: 45,
|
|
22
|
+
x: 0.5,
|
|
23
|
+
y: 0.5
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const MULBERRY = mulberry32(13);
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { hexToRGB } from '@basmilius/utils';
|
|
2
|
+
import { LimitedFrameRateCanvas } from '../canvas';
|
|
3
|
+
import { DEFAULT_CONFIG, MULBERRY } from './consts';
|
|
4
|
+
import type { Config, Particle, ParticleConfig, Shape } from './types';
|
|
5
|
+
|
|
6
|
+
const TWO_PI = Math.PI * 2;
|
|
7
|
+
|
|
8
|
+
// Precomputed unit-size (size=1) Path2D objects per shape.
|
|
9
|
+
// Size is encoded into the context transform, so each path is drawn once and reused every frame.
|
|
10
|
+
const SHAPE_PATHS: Record<Shape, Path2D> = {
|
|
11
|
+
circle: (() => {
|
|
12
|
+
const path = new Path2D();
|
|
13
|
+
path.ellipse(0, 0, 0.6, 1, 0, 0, TWO_PI);
|
|
14
|
+
return path;
|
|
15
|
+
})(),
|
|
16
|
+
diamond: (() => {
|
|
17
|
+
const path = new Path2D();
|
|
18
|
+
path.moveTo(0, -1);
|
|
19
|
+
path.lineTo(0.6, 0);
|
|
20
|
+
path.lineTo(0, 1);
|
|
21
|
+
path.lineTo(-0.6, 0);
|
|
22
|
+
path.closePath();
|
|
23
|
+
return path;
|
|
24
|
+
})(),
|
|
25
|
+
ribbon: (() => {
|
|
26
|
+
const path = new Path2D();
|
|
27
|
+
path.rect(-0.2, -1, 0.4, 2);
|
|
28
|
+
return path;
|
|
29
|
+
})(),
|
|
30
|
+
square: (() => {
|
|
31
|
+
const path = new Path2D();
|
|
32
|
+
path.rect(-0.7, -0.7, 1.4, 1.4);
|
|
33
|
+
return path;
|
|
34
|
+
})(),
|
|
35
|
+
star: (() => {
|
|
36
|
+
const path = new Path2D();
|
|
37
|
+
for (let i = 0; i < 10; i++) {
|
|
38
|
+
const r = i % 2 === 0 ? 1 : 0.42;
|
|
39
|
+
const angle = (i * Math.PI / 5) - Math.PI / 2;
|
|
40
|
+
if (i === 0) path.moveTo(r * Math.cos(angle), r * Math.sin(angle));
|
|
41
|
+
else path.lineTo(r * Math.cos(angle), r * Math.sin(angle));
|
|
42
|
+
}
|
|
43
|
+
path.closePath();
|
|
44
|
+
return path;
|
|
45
|
+
})(),
|
|
46
|
+
triangle: (() => {
|
|
47
|
+
const path = new Path2D();
|
|
48
|
+
for (let i = 0; i < 3; i++) {
|
|
49
|
+
const angle = (i * 2 * Math.PI / 3) - Math.PI / 2;
|
|
50
|
+
if (i === 0) path.moveTo(Math.cos(angle), Math.sin(angle));
|
|
51
|
+
else path.lineTo(Math.cos(angle), Math.sin(angle));
|
|
52
|
+
}
|
|
53
|
+
path.closePath();
|
|
54
|
+
return path;
|
|
55
|
+
})()
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export interface ConfettiSimulationConfig {
|
|
59
|
+
readonly scale?: number;
|
|
60
|
+
readonly canvasOptions?: CanvasRenderingContext2DSettings;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class ConfettiSimulation extends LimitedFrameRateCanvas {
|
|
64
|
+
|
|
65
|
+
readonly #scale: number;
|
|
66
|
+
#particles: Particle[] = [];
|
|
67
|
+
|
|
68
|
+
constructor(canvas: HTMLCanvasElement, config: ConfettiSimulationConfig = {}) {
|
|
69
|
+
super(canvas, 60, config.canvasOptions ?? {colorSpace: 'display-p3'});
|
|
70
|
+
|
|
71
|
+
this.#scale = config.scale ?? 1;
|
|
72
|
+
|
|
73
|
+
this.canvas.style.position = 'absolute';
|
|
74
|
+
this.canvas.style.top = '0';
|
|
75
|
+
this.canvas.style.left = '0';
|
|
76
|
+
this.canvas.style.height = '100%';
|
|
77
|
+
this.canvas.style.width = '100%';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fire(config: Partial<Config>): void {
|
|
81
|
+
this.onResize();
|
|
82
|
+
this.draw();
|
|
83
|
+
|
|
84
|
+
const resolved = { ...DEFAULT_CONFIG, ...config };
|
|
85
|
+
const { angle, colors, decay, gravity, shapes, spread, startVelocity, ticks, x, y } = resolved;
|
|
86
|
+
const numberOfParticles = Math.max(1, resolved.particles);
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < numberOfParticles; i++) {
|
|
89
|
+
const particle = this.#createParticle({
|
|
90
|
+
angle,
|
|
91
|
+
color: hexToRGB(colors[Math.floor(MULBERRY.next() * colors.length)]),
|
|
92
|
+
decay,
|
|
93
|
+
gravity: gravity * this.#scale,
|
|
94
|
+
shape: shapes[Math.floor(MULBERRY.next() * shapes.length)],
|
|
95
|
+
spread,
|
|
96
|
+
startVelocity: startVelocity * this.#scale,
|
|
97
|
+
ticks,
|
|
98
|
+
x: this.width * x,
|
|
99
|
+
y: this.height * y
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.#tickParticle(particle);
|
|
103
|
+
this.#particles.push(particle);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!this.isTicking) {
|
|
107
|
+
this.start();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
draw(): void {
|
|
112
|
+
const { context, width, height } = this;
|
|
113
|
+
context.clearRect(0, 0, width, height);
|
|
114
|
+
|
|
115
|
+
const particles = this.#particles;
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < particles.length; i++) {
|
|
118
|
+
const p = particles[i];
|
|
119
|
+
const flipCos = Math.cos(p.flipAngle);
|
|
120
|
+
const size = p.size;
|
|
121
|
+
|
|
122
|
+
// Encode translate + rotate + scale(flipCos, 1) + scale(size, size) in a single setTransform call,
|
|
123
|
+
// avoiding save()/translate()/rotate()/scale()/restore() — 5 API calls replaced by 1.
|
|
124
|
+
context.setTransform(
|
|
125
|
+
p.rotCos * flipCos * size,
|
|
126
|
+
p.rotSin * flipCos * size,
|
|
127
|
+
-p.rotSin * size,
|
|
128
|
+
p.rotCos * size,
|
|
129
|
+
p.x,
|
|
130
|
+
p.y
|
|
131
|
+
);
|
|
132
|
+
context.globalAlpha = 1 - p.tick / p.totalTicks;
|
|
133
|
+
context.fillStyle = p.colorStr;
|
|
134
|
+
context.fill(SHAPE_PATHS[p.shape]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
context.resetTransform();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
tick(): void {
|
|
141
|
+
const particles = this.#particles;
|
|
142
|
+
let alive = 0;
|
|
143
|
+
|
|
144
|
+
// Normalize to 60fps-equivalent units so physics is frame-rate independent.
|
|
145
|
+
// dt ≈ 1.0 at 60fps, 0.5 at 120fps, 2.0 at 30fps.
|
|
146
|
+
// Cap at 200ms to avoid large jumps after tab switches or dropped frames.
|
|
147
|
+
const dt = this.delta > 0 && this.delta < 200 ? this.delta / (1000 / 60) : 1;
|
|
148
|
+
|
|
149
|
+
// Single pass: tick live particles and compact the array in-place.
|
|
150
|
+
// Avoids filter() allocation and a separate forEach pass.
|
|
151
|
+
for (let i = 0; i < particles.length; i++) {
|
|
152
|
+
const p = particles[i];
|
|
153
|
+
|
|
154
|
+
if (p.tick < p.totalTicks) {
|
|
155
|
+
this.#tickParticle(p, dt);
|
|
156
|
+
particles[alive++] = p;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
particles.length = alive;
|
|
161
|
+
|
|
162
|
+
if (alive === 0) {
|
|
163
|
+
this.stop();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
onResize(): void {
|
|
168
|
+
super.onResize();
|
|
169
|
+
this.canvas.width = this.width;
|
|
170
|
+
this.canvas.height = this.height;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#createParticle(config: ParticleConfig): Particle {
|
|
174
|
+
const launchAngle = -(config.angle * Math.PI / 180)
|
|
175
|
+
+ (0.5 * config.spread * Math.PI / 180)
|
|
176
|
+
- (MULBERRY.next() * config.spread * Math.PI / 180);
|
|
177
|
+
|
|
178
|
+
const speed = config.startVelocity * (0.5 + MULBERRY.next());
|
|
179
|
+
const rotAngle = MULBERRY.next() * TWO_PI;
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
colorStr: `rgb(${config.color[0]}, ${config.color[1]}, ${config.color[2]})`,
|
|
183
|
+
decay: config.decay - 0.05 + MULBERRY.next() * 0.1,
|
|
184
|
+
flipAngle: MULBERRY.next() * TWO_PI,
|
|
185
|
+
flipSpeed: 0.03 + MULBERRY.next() * 0.05,
|
|
186
|
+
gravity: config.gravity,
|
|
187
|
+
rotAngle,
|
|
188
|
+
rotCos: Math.cos(rotAngle),
|
|
189
|
+
rotSin: Math.sin(rotAngle),
|
|
190
|
+
rotSpeed: (MULBERRY.next() - 0.5) * 0.06,
|
|
191
|
+
shape: config.shape,
|
|
192
|
+
size: (5 + MULBERRY.next() * 5) * this.#scale,
|
|
193
|
+
swing: MULBERRY.next() * TWO_PI,
|
|
194
|
+
swingAmp: 0.5 + MULBERRY.next() * 1.5,
|
|
195
|
+
swingSpeed: 0.025 + MULBERRY.next() * 0.035,
|
|
196
|
+
tick: 0,
|
|
197
|
+
totalTicks: config.ticks,
|
|
198
|
+
vx: Math.cos(launchAngle) * speed,
|
|
199
|
+
vy: Math.sin(launchAngle) * speed,
|
|
200
|
+
x: config.x,
|
|
201
|
+
y: config.y
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// dt defaults to 1 (60fps-equivalent) for the initial kick in fire() before the loop starts.
|
|
206
|
+
#tickParticle(particle: Particle, dt: number = 1): void {
|
|
207
|
+
const decayFactor = Math.pow(particle.decay, dt);
|
|
208
|
+
particle.vx *= decayFactor;
|
|
209
|
+
particle.vy *= decayFactor;
|
|
210
|
+
particle.vy += particle.gravity * 0.35 * dt;
|
|
211
|
+
particle.swing += particle.swingSpeed * dt;
|
|
212
|
+
particle.x += (particle.vx + particle.swingAmp * Math.cos(particle.swing)) * dt;
|
|
213
|
+
particle.y += particle.vy * dt;
|
|
214
|
+
particle.rotAngle += particle.rotSpeed * dt;
|
|
215
|
+
particle.rotCos = Math.cos(particle.rotAngle);
|
|
216
|
+
particle.rotSin = Math.sin(particle.rotAngle);
|
|
217
|
+
particle.flipAngle += particle.flipSpeed * dt;
|
|
218
|
+
particle.tick += dt;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
}
|