export type BurstMotionStyle = | 'explode' | 'bounce' | 'rise' | 'sparkle' | 'splash' | 'spiral' | 'punch' | 'scatter' | 'shower' | 'firework'; export type EmojiBurstProfile = { style: BurstMotionStyle; particleCount: number; gravity: number; drag: number; speedMin: number; speedMax: number; launchUpMin: number; launchUpMax: number; spinMin: number; spinMax: number; fontSizeMin: number; fontSizeMax: number; heroFontSizeMin: number; heroFontSizeMax: number; companions?: string[]; companionChance?: number; /** Bias burst angle in radians. 0 = right, -π/2 = up. */ angleBias?: number; /** Limit spawn to a cone (radians). Omit for full 360°. */ angleSpread?: number; twinkle?: boolean; wobble?: boolean; /** Full-viewport overlay (Box2D pile for fireworks). */ fullscreen?: boolean; }; const DEFAULT_PROFILE: EmojiBurstProfile = { style: 'explode', particleCount: 36, gravity: 400, drag: 0.935, speedMin: 240, speedMax: 560, launchUpMin: 40, launchUpMax: 130, spinMin: -180, spinMax: 180, fontSizeMin: 14, fontSizeMax: 26, heroFontSizeMin: 26, heroFontSizeMax: 34, }; const PROFILE_OVERRIDES: Record> = { '😆': { style: 'bounce', particleCount: 28, gravity: 520, speedMin: 180, speedMax: 360, launchUpMin: 120, launchUpMax: 220, spinMin: -240, spinMax: 240, wobble: true, }, '😂': { style: 'bounce', particleCount: 30, gravity: 500, speedMin: 160, speedMax: 340, launchUpMin: 110, launchUpMax: 210, spinMin: -220, spinMax: 220, wobble: true, }, '🤣': { style: 'bounce', particleCount: 32, gravity: 480, speedMin: 200, speedMax: 380, launchUpMin: 130, launchUpMax: 240, wobble: true, }, '🔥': { style: 'rise', particleCount: 24, gravity: -120, drag: 0.96, speedMin: 80, speedMax: 200, launchUpMin: 60, launchUpMax: 160, spinMin: -90, spinMax: 90, fontSizeMin: 16, fontSizeMax: 28, twinkle: true, wobble: true, companions: ['✨'], companionChance: 0.35, }, '❤️': { style: 'rise', particleCount: 22, gravity: -80, drag: 0.97, speedMin: 60, speedMax: 150, launchUpMin: 40, launchUpMax: 110, spinMin: -40, spinMax: 40, fontSizeMin: 14, fontSizeMax: 24, companions: ['💕', '💖'], companionChance: 0.4, }, '💕': { style: 'rise', particleCount: 20, gravity: -70, drag: 0.97, speedMin: 50, speedMax: 140, launchUpMin: 35, launchUpMax: 100, spinMin: -35, spinMax: 35, }, '💖': { style: 'rise', particleCount: 20, gravity: -90, drag: 0.968, speedMin: 55, speedMax: 150, launchUpMin: 45, launchUpMax: 115, twinkle: true, }, '⭐': { style: 'sparkle', particleCount: 26, gravity: 40, drag: 0.94, speedMin: 100, speedMax: 260, launchUpMin: 20, launchUpMax: 80, spinMin: -120, spinMax: 120, twinkle: true, companions: ['✨'], companionChance: 0.5, }, '✨': { style: 'sparkle', particleCount: 30, gravity: 30, drag: 0.945, speedMin: 90, speedMax: 240, launchUpMin: 15, launchUpMax: 70, spinMin: -200, spinMax: 200, twinkle: true, }, '💀': { style: 'scatter', particleCount: 20, gravity: 620, drag: 0.92, speedMin: 200, speedMax: 420, launchUpMin: 20, launchUpMax: 90, spinMin: -360, spinMax: 360, fontSizeMin: 16, fontSizeMax: 28, }, '🎉': { style: 'shower', particleCount: 40, gravity: 280, drag: 0.93, speedMin: 200, speedMax: 480, launchUpMin: 80, launchUpMax: 200, companions: ['🎊', '✨', '🎈'], companionChance: 0.45, }, '🎊': { style: 'shower', particleCount: 38, gravity: 260, drag: 0.932, speedMin: 190, speedMax: 460, launchUpMin: 70, launchUpMax: 190, companions: ['🎉', '✨'], companionChance: 0.4, }, '👏': { style: 'punch', particleCount: 18, gravity: 320, speedMin: 220, speedMax: 400, launchUpMin: 30, launchUpMax: 100, angleBias: -Math.PI / 2, angleSpread: Math.PI * 0.85, spinMin: -100, spinMax: 100, }, '👍': { style: 'punch', particleCount: 14, gravity: 380, speedMin: 260, speedMax: 440, launchUpMin: 100, launchUpMax: 200, angleBias: -Math.PI / 2, angleSpread: Math.PI * 0.55, spinMin: -60, spinMax: 60, }, '💯': { style: 'punch', particleCount: 16, gravity: 300, speedMin: 200, speedMax: 380, launchUpMin: 140, launchUpMax: 240, angleBias: -Math.PI / 2, angleSpread: Math.PI * 0.45, fontSizeMin: 16, fontSizeMax: 30, wobble: true, }, '💦': { style: 'splash', particleCount: 32, gravity: 540, drag: 0.925, speedMin: 280, speedMax: 520, launchUpMin: 160, launchUpMax: 300, spinMin: -140, spinMax: 140, angleBias: -Math.PI / 2, angleSpread: Math.PI * 1.1, }, '🌊': { style: 'splash', particleCount: 28, gravity: 420, drag: 0.93, speedMin: 220, speedMax: 460, launchUpMin: 100, launchUpMax: 220, angleBias: -Math.PI / 2, angleSpread: Math.PI, companions: ['💧'], companionChance: 0.3, }, '⚡': { style: 'scatter', particleCount: 14, gravity: 180, drag: 0.88, speedMin: 380, speedMax: 680, launchUpMin: 10, launchUpMax: 60, spinMin: -30, spinMax: 30, fontSizeMin: 18, fontSizeMax: 32, angleSpread: Math.PI * 1.2, }, '🌀': { style: 'spiral', particleCount: 24, gravity: 60, drag: 0.96, speedMin: 120, speedMax: 260, launchUpMin: 0, launchUpMax: 40, spinMin: -420, spinMax: 420, }, '🤯': { style: 'explode', particleCount: 34, gravity: 360, speedMin: 280, speedMax: 580, launchUpMin: 60, launchUpMax: 160, companions: ['💥', '✨', '⭐'], companionChance: 0.5, }, '😭': { style: 'splash', particleCount: 26, gravity: 480, drag: 0.94, speedMin: 140, speedMax: 300, launchUpMin: 40, launchUpMax: 120, angleBias: Math.PI / 2, angleSpread: Math.PI * 0.7, companions: ['💧'], companionChance: 0.55, }, '🥳': { style: 'shower', particleCount: 36, gravity: 250, speedMin: 180, speedMax: 440, launchUpMin: 90, launchUpMax: 210, companions: ['🎉', '🎊', '🎈'], companionChance: 0.4, }, '🎆': { style: 'firework', fullscreen: true, particleCount: 320, gravity: 980, drag: 1, speedMin: 420, speedMax: 980, launchUpMin: 180, launchUpMax: 420, spinMin: -520, spinMax: 520, fontSizeMin: 16, fontSizeMax: 30, heroFontSizeMin: 40, heroFontSizeMax: 52, companions: ['🎇', '✨', '💥', '⭐', '🎉'], companionChance: 0.55, }, '🎇': { style: 'firework', fullscreen: true, particleCount: 280, gravity: 960, drag: 1, speedMin: 380, speedMax: 920, launchUpMin: 160, launchUpMax: 400, spinMin: -480, spinMax: 480, fontSizeMin: 14, fontSizeMax: 28, heroFontSizeMin: 36, heroFontSizeMax: 48, companions: ['🎆', '✨', '⭐', '💥'], companionChance: 0.5, twinkle: true, }, '🐱': { style: 'bounce', particleCount: 20, gravity: 440, speedMin: 150, speedMax: 320, launchUpMin: 90, launchUpMax: 180, spinMin: -160, spinMax: 160, }, '🐶': { style: 'bounce', particleCount: 20, gravity: 460, speedMin: 160, speedMax: 330, launchUpMin: 85, launchUpMax: 175, wobble: true, }, }; export function getEmojiBurstProfile(emoji: string): EmojiBurstProfile { const override = PROFILE_OVERRIDES[emoji]; if (!override) return DEFAULT_PROFILE; return { ...DEFAULT_PROFILE, ...override }; } export function isFullscreenBurstEmoji(emoji: string): boolean { return getEmojiBurstProfile(emoji).fullscreen === true; } export function pickParticleEmoji(profile: EmojiBurstProfile, primaryEmoji: string): string { if (!profile.companions?.length || !profile.companionChance) { return primaryEmoji; } if (Math.random() >= profile.companionChance) { return primaryEmoji; } const companion = profile.companions[Math.floor(Math.random() * profile.companions.length)]; return companion ?? primaryEmoji; } export function sampleBurstAngle(profile: EmojiBurstProfile): number { if (profile.angleSpread === undefined) { return Math.random() * Math.PI * 2; } const bias = profile.angleBias ?? -Math.PI / 2; const halfSpread = profile.angleSpread / 2; return bias + (Math.random() - 0.5) * profile.angleSpread * (0.6 + Math.random() * 0.4); }