From ea49c1c41a643cbe96c874049d6f7d7f4b9dc776 Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:56:46 +0200 Subject: [PATCH] balls: adding initial version of balls --- balls/package-lock.json | 1293 +++++++++++++++++++++++++++++++++++++++ balls/package.json | 19 + balls/src/index.html | 22 + balls/src/js/main.js | 858 ++++++++++++++++++++++++++ balls/src/js/plugins.js | 24 + balls/vite.config.js | 9 + canvas-common/common.js | 194 +++++- img/balls.png | Bin 0 -> 7083 bytes index.html | 1 + 9 files changed, 2419 insertions(+), 1 deletion(-) create mode 100644 balls/package-lock.json create mode 100644 balls/package.json create mode 100644 balls/src/index.html create mode 100644 balls/src/js/main.js create mode 100644 balls/src/js/plugins.js create mode 100644 balls/vite.config.js create mode 100644 img/balls.png diff --git a/balls/package-lock.json b/balls/package-lock.json new file mode 100644 index 0000000..d0e08bc --- /dev/null +++ b/balls/package-lock.json @@ -0,0 +1,1293 @@ +{ + "name": "balls", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "balls", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "canvas-common": "file:../canvas-common" + }, + "devDependencies": { + "vite": "^5.1.5" + } + }, + "../canvas-common": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/canvas-common": { + "resolved": "../canvas-common", + "link": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + }, + "dependencies": { + "@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "dev": true, + "optional": true + }, + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "canvas-common": { + "version": "file:../canvas-common" + }, + "esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "requires": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "@types/estree": "1.0.8", + "fsevents": "~2.3.2" + } + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "requires": { + "esbuild": "^0.21.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + } + } + } +} diff --git a/balls/package.json b/balls/package.json new file mode 100644 index 0000000..98e2f21 --- /dev/null +++ b/balls/package.json @@ -0,0 +1,19 @@ +{ + "name": "balls", + "version": "1.0.0", + "description": "", + "private": true, + "type": "module", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "canvas-common": "file:../canvas-common" + }, + "devDependencies": { + "vite": "^5.1.5" + } +} diff --git a/balls/src/index.html b/balls/src/index.html new file mode 100644 index 0000000..5cabbff --- /dev/null +++ b/balls/src/index.html @@ -0,0 +1,22 @@ + + + + + + balls + + + + + + + + + diff --git a/balls/src/js/main.js b/balls/src/js/main.js new file mode 100644 index 0000000..26afa7d --- /dev/null +++ b/balls/src/js/main.js @@ -0,0 +1,858 @@ +import { + toRad, getMousePos, addRGBStyle, docReady, createNormalizedVector, angleBetweenTwoVectors, dotProduct +} from "canvas-common"; + +var imageData = {}; +var ctx = {}; +var canvas = {}; +var mousePos = {} + + +var config = { + size: { + width: window.innerWidth, + height: window.innerHeight + }, + balls: { + rowElements: window.innerWidth/(window.innerHeight/10), + rows: 10, + horizontalSize: window.innerHeight / 10, + verticalSize: window.innerHeight / 10, + balls: 10, + speed: 10, + reboundBuffChance: 0.25, + portalChance: 0.07, + simulationDepth: 5000, + restartTaps: 3, + tapsFactor: 2, + resetTapsDecreaseInterval: 2 + }, + general: { + fps: 30 + } +}; + +var mobile = 'ontouchstart' in window; + + +if(mobile){ + config.balls.tapsFactor = 3; +} + +var timeouts = []; + +var toSpawn = 0; +var spawning = false; +var newSpawnSet = false; + +var mouseStart = {}; +var mouseStop = {}; +var animationId = {}; + +var balls = []; + +var rects = []; +var gameOver = false; + +var buffs = []; +var portals = []; +var reboundBalls = 0; +var mouseDown = false; +var newStartPoint; + +var fourtyFiveDegrees = toRad(45); +var portalCounter = 1; +var oneHundredThirtyFriveDegrees = toRad(135); +var oneHundredEightyDegrees = toRad(180); + +var colors = [ + { + r: 0xf2, g: 0x69, b: 0x12 + }, { + r: 0xf3, g: 0x8c, b: 0x14 + }, { + r: 0xf4, g: 0xaf, b: 0x16 + }, { + r: 0xf4, g: 0xd1, b: 0x17 + }, { + r: 0xf5, g: 0xf3, b: 0x19 + }, { + r: 0xd7, g: 0xf6, b: 0x1b + }, { + r: 0xb7, g: 0xf6, b: 0x1d + }, { + r: 0x98, g: 0xf7, b: 0x1f + }, { + r: 0x79, g: 0xf8, b: 0x21 + }, { + r: 0x5a, g: 0xf8, b: 0x23 + } +]; + +colors.forEach(addRGBStyle); + +var restartRect = { + x: 0, y: 0, width: config.size.width * 0.2, height: config.size.height * 0.2 +}; + +function isInRect(point, rect){ + return point.x > rect.x && point.x < (rect.x + rect.width) && point.y > rect.y && rect.y < (rect.y + rect.height); +} + +var doRestart = 0; +var restarting = false; + +function setPoint(event){ + + var point = getMousePos(canvas, event); + if(isInRect(point, restartRect)){ + doRestart++; + console.log(doRestart) + if(doRestart >= config.balls.restartTaps * config.balls.tapsFactor){ + console.log('Restarting'); + timeouts.forEach(function(timeout){ + clearTimeout(timeout); + }); + timeouts = []; + restarting = true; + rects = []; + buffs = []; + portals = []; + balls = []; + portalCounter = 1; + config.balls.balls = 10; + mouseDown = false; + mouseStop = {}; + spawnStuff(); + doRestart = 0; + // verrrrry cheap hack + // I couldnt get the mouse events to stop, because on mobile twice as much are sent + // so after resetting it, balls still spawned, because the event was triggered and therefore came into the lower + // lines of code... and spawned balls + timeouts.push(setTimeout(function(){ + restarting = false; + spawning = false; + console.log('reseting restarting flag...') + }, 250)); + return; + } + } + //setShaft(event) + mouseStart = newStartPoint; + if(spawning) return; + if(!mouseDown){ + mouseDown = true; + mouseStop = point; + } else { + mouseDown = false; + spawning = true; + setTip(event); + } +} + +function startBall(cnt){ + toSpawn = cnt; + if(cnt == 0) return; + var vec = createNormalizedVector(mouseStop, mouseStart); + var ball = { + x: mouseStart.x, + y: mouseStart.y, + radius: 4, + vec: vec, + simulation: false, + maxBounces: -1 + }; + if(!restarting) { + balls.push(ball); + timeouts.push(setTimeout(function(){ + if(restarting) return; + startBall(cnt - 1); + }, 250)) + } +} + +function setShaft(event) { + mouseStart = getMousePos(canvas, event); +} + +function setTip(event) { + + toSpawn = config.balls.balls; + startBall(config.balls.balls); +} + +function updateCanvas() { + ctx.clearRect(0, 0, config.size.width, config.size.height); + if(gameOver){ + ctx.fillText('gameover: ' + config.balls.balls, config.size.width / 2, config.size.height / 2); + } + + var caseApplied = false; + objectsToDisplay.forEach(function(object){ + if(object.startCondition()){ + caseApplied = true; + object.running = true; + object.applied = true; + } + if(object.condition() && object.running){ + caseApplied = true; + object.fun(); + object.applied = true; + } + else { + object.running = false; + if(object.applied){ + object.postFun(); + } + object.applied = false; + } + }); + + if(!caseApplied){ + if(!gameOver && !restarting){ + paintRects(); + paintPortals(); + paintBuffs(); + paintBalls(); + paintIndicator(); + ballsAct(); + } + } + setTimeout(function () { + animationId = requestAnimationFrame(updateCanvas); + }, 1000 / config.general.fps) +} + +function paintPortals(){ + portals.forEach(paintPortal) +} +function paintPortal(portal){ + ctx.beginPath(); + ctx.fillStyle = 'orange'; + ctx.rect(portal.source.xPos, portal.source.yPos, portal.source.width, portal.source.height); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'lightBlue'; + ctx.rect(portal.target.xPos, portal.target.yPos, portal.target.width, portal.target.height); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.fillText(portal.source.linkedWith, portal.source.xPos, portal.source.yPos + portal.source.height / 2); + ctx.fillText(portal.target.linkedWith, portal.target.xPos, portal.target.yPos + portal.target.height / 2); + ctx.fill(); + +} + +var objectsToDisplay = []; +objectsToDisplay.push({ + fun: drawPlaceHolder, + startCondition: function(){ + return rects.length == 0 + }, + condition: function(){ + return placeHolder.y > 0 + }, + applied: false, + running: false, + postFun: function(){ + rectsAct(); + newStartPoint = {x: balls[0].x, y: config.size.height - 25}; + balls = []; + }, + reset: function(){ + placeHolder = {x: config.size.width / 2, y: config.size.height} + } +}); + +function paintBuffs(){ + buffs.forEach(paintBuff); +} + +function paintBuff(buff){ + ctx.beginPath(); + ctx.moveTo(buff.xPos, buff.yPos); + ctx.fillStyle = buildBuffGradient(buff); + ctx.rect(buff.xPos, buff.yPos, buff.width, buff.height); + ctx.fill(); + ctx.fillStyle = 'black'; + ctx.fillText(buff.duration, buff.xPos + 20, buff.yPos + 20); + ctx.stroke(); +} + +function paintPrediction(){ + var vec = createNormalizedVector(mouseStop, mouseStart); + var prediction = { + x: mouseStart.x, + y: mouseStart.y, + radius: 4, + vec: vec, + simulation: true, + bounces: 2 + }; + + ctx.beginPath(); + ctx.strokeStyle = 'black'; + ctx.moveTo(prediction.x, prediction.y); + var cnt = 0; + while(prediction.bounces > 0 && cnt < config.balls.simulationDepth) { + ballAct(prediction); + ctx.lineTo(prediction.x, prediction.y); + cnt++; + } + ctx.stroke(); + +} + +function paintIndicator(){ + ctx.beginPath(); + ctx.strokeStyle='black'; + ctx.fillStyle='black'; + + if(mouseDown){ + paintPrediction(); + } + ctx.beginPath(); + ctx.fillStyle = 'lightBlue'; + ctx.rect(0, config.size.height - 30, (balls.length / config.balls.balls) * config.size.width / 2, 10); + ctx.fill(); + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.fillText(toSpawn, 0, config.size.height - 20); + ctx.fill(); + + if(reboundBalls > 0){ + ctx.fillStyle = 'yellow'; + ctx.rect(0, config.size.height - 50, reboundBalls * 10, 10); + ctx.fill(); + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.fillText(reboundBalls, 0, config.size.height - 40); + ctx.fill(); + } +} + +function rectsAct(){ + rects.forEach(function(rect){ + rect.yPos += config.balls.verticalSize; + if(rect.yPos > config.size.height - 50){ + gameOver = true; + } + }); + portals.forEach(function(portal, index, array){ + portal.source.yPos += config.balls.verticalSize; + portal.target.yPos += config.balls.verticalSize; + if(portal.source.yPos > config.size.height - 50){ + array.splice(index, 1); + return; + } + if(portal.target.yPos > config.size.height - 50){ + array.splice(index, 1); + } + }); + buffs.forEach(function(buff, index, array){ + buff.yPos += config.balls.verticalSize; + if(buff.yPos > config.size.height - 50){ + array.splice(index, 1); + } + }); + config.balls.balls++; + toSpawn = config.balls.balls; + spawnStuff(); + updateCookieStorage(); + spawning = false; +} + +function updateCookieStorage(){ + var objectToStore = { + rects: rects, + balls: config.balls.balls, + buffs: buffs, + portals: portals + }; + + Cookies.remove('balls'); + Cookies.set('balls', objectToStore); +} + +function paintBalls(){ + balls.forEach(paintBall) +} + +function paintRects(){ + rects.forEach(paintRect) +} + +function paintRect(rect){ + ctx.beginPath(); + ctx.strokeStyle="#ffffff"; + var col = getColor(rect.points, rect.maxPoints); + if(col == undefined){ + ctx.fillStyle = 'white' + } else { + ctx.fillStyle = col.styleRGB; + } + ctx.rect(rect.xPos, rect.yPos, rect.width, rect.height); + ctx.fill(); + ctx.fillStyle = 'black'; + ctx.fillText(rect.points, rect.xPos + 20, rect.yPos + 20); + ctx.stroke(); +} + +function getColor(value, maxValue){ + var percent = value / maxValue; + return colors[(colors.length * percent - 1)<<0]; +} + +function paintBall(ball){ + ctx.beginPath(); + ctx.fillStyle = 'black'; + ctx.arc(ball.x,ball.y,ball.radius,0,2*Math.PI); + ctx.fill(); +} + +function ballsAct(){ + balls.forEach(ballAct); + newSpawnSet = false; + if(rects.length == 0){ + objectsToDisplay[0].reset(); + return; + } + balls.forEach(function(ball, index, array){ + if(ball.done){ + array.splice(index, 1); + } + if(array.length == 0 && spawning){ + rectsAct(); + } + }); + +} + +var placeHolder = {x: config.size.width / 2, y: config.size.height}; + +function drawPlaceHolder(){ + ctx.fillText('Any idea for a better animation?', placeHolder.x, placeHolder.y); + ctx.fill(); + placeHolder.y -= 5; +} + +function ballAct(ball){ + ball.x = ball.x + ball.vec.x * config.balls.speed; + ball.y = ball.y + ball.vec.y * config.balls.speed; + + if ((ball.x + ball.radius) > config.size.width || (ball.x - ball.radius) < 0) { + ball.vec.x *= -1; + if(ball.bounces > 0){ + ball.bounces--; + } + } + + if (ball.y - ball.radius < 0) { + ball.vec.y *= -1; + if(ball.bounces > 0){ + ball.bounces--; + } + } + + if((ball.y + ball.radius) > config.size.height && !ball.simulation) { + if(reboundBalls <= 0){ + if(!newSpawnSet){ + newStartPoint = {x: ball.x, y: config.size.height - 25}; + newSpawnSet = true; + } + ball.done = true; + } else { + reboundBalls--; + ball.vec.y *= -1; + } + } + + var nextX = ball.x; + var nextY = ball.y; + + var ballBottomY = nextY + ball.radius; + var ballLeftX = nextX - ball.radius; + var ballTopY = nextY - ball.radius; + var ballRightX = nextX + ball.radius; + + var found = false; + rects.forEach(function(rect, index, array){ + var topRightX = rect.xPos + rect.width; + //var bottomRightX = rect.xPos + rect.width; + //var bottomRightY = rect.yPos + rect.height; + var bottomLeftY = rect.yPos + rect.height; + + if(ballLeftX < topRightX && ballRightX > rect.xPos && ballBottomY > rect.yPos && ballTopY < bottomLeftY) { + var centerX = rect.xPos + rect.width / 2; + var centerY = rect.yPos + rect.height / 2; + var rectCenterToBallCenter = createNormalizedVector(ball, {x: centerX, y: centerY}); + var normalLevel = createNormalizedVector({x: centerX + 10, y: centerY}, {x: centerX, y: centerY}); + var angle = angleBetweenTwoVectors(rectCenterToBallCenter, normalLevel); + if(angle < fourtyFiveDegrees){ + ball.vec.x *= -1; + } else if(angle < oneHundredThirtyFriveDegrees){ + ball.vec.y *= -1; + } else if(angle < oneHundredEightyDegrees){ + ball.vec.x *= -1; + } + if(ball.bounces > 0){ + ball.bounces--; + } + + + nextX = ball.x + ball.vec.x * config.balls.speed; + nextY = ball.y + ball.vec.y * config.balls.speed; + + ballBottomY = nextY + ball.radius; + ballLeftX = nextX - ball.radius; + ballTopY = nextY - ball.radius; + ballRightX = nextX + ball.radius; + + if(ballLeftX < topRightX && ballRightX > rect.xPos && ballBottomY > rect.yPos && ballTopY < bottomLeftY) { + var centerX2 = rect.xPos + rect.width / 2; + var centerY2 = rect.yPos + rect.height / 2; + var rectCenterToBallCenter2 = createNormalizedVector(ball, {x: centerX2, y: centerY2}); + var normalLevel2 = createNormalizedVector({x: centerX2 + 10, y: centerY2}, {x: centerX2, y: centerY2}); + var angle2 = angleBetweenTwoVectors(rectCenterToBallCenter2, normalLevel2); + + if (angle2 < fourtyFiveDegrees) { + ball.vec.y *= -1; + } else if (angle2 < oneHundredThirtyFriveDegrees) { + ball.vec.y *= -1; + } else if (angle2 < oneHundredEightyDegrees) { + ball.vec.x *= -1; + } + } + if(!ball.simulation) { + rect.points--; + } + found = true; + if(rect.points <= 0 && !ball.simulation){ + array.splice(index, 1); + } + } else { + return; + } + + }); + + found = false; + + buffs.forEach(function(buff, index, array){ + if(found || buff.points == 0) return; + var topRightX = buff.xPos + buff.width; + //var bottomRightX = buff.xPos + buff.width; + //var bottomRightY = buff.yPos + buff.height; + var bottomLeftY = buff.yPos + buff.height; + + if(ballLeftX < topRightX && ballRightX > buff.xPos && ballBottomY > buff.yPos && ballTopY < bottomLeftY && !ball.simulation) { + buff.points--; + found = true; + buff.effect(ball); + array.splice(index, 1); + } else { + return; + } + + }); + found = false; + portals.forEach(function(portal, index, array){ + function portalCollision(portal, ball){ + if(found) return; + var topRightX = portal.xPos + portal.width; + //var bottomRightX = portal.xPos + portal.width; + //var bottomRightY = portal.yPos + portal.height; + var bottomLeftY = portal.yPos + portal.height; + if(ballLeftX < topRightX && ballRightX > portal.xPos && ballBottomY > portal.yPos && ballTopY < bottomLeftY) { + found = true; + portal.effect(ball); + } else { + return; + } + } + + portalCollision(portal.source, ball); + if(!found){ + portalCollision(portal.target, ball) + } + + + }) +} + +function spawnStuff(){ + if(Math.random() < config.balls.reboundBuffChance){ + var buff = { + xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize, + yPos: 1 * config.balls.verticalSize, + height: config.balls.verticalSize - 5, + width: config.balls.horizontalSize - 5, + duration: (Math.random() * config.balls.balls + 1) << 0, + effect: function(ball) { + reboundBalls += buff.duration; + } + }; + buffs.push(buff); + } + + if(Math.random() < config.balls.portalChance) + { + var source = { + xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize, + yPos: ((Math.random() * config.balls.rows) << 0) * config.balls.verticalSize + config.balls.verticalSize / 2 - 7.5, + height: 15, + width: config.balls.verticalSize - 5, + linkedWith: portalCounter + }; + var target = { + xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize, + yPos: ((Math.random() * config.balls.rows) << 0) * config.balls.verticalSize + config.balls.verticalSize / 2 - 7.5, + height: 15, + width: config.balls.verticalSize - 5, + linkedWith: portalCounter + }; + + portalCounter++; + var centerX = target.xPos + target.width / 2; + var centerY = target.yPos + target.height / 2; + var base = createNormalizedVector({x: centerX, y: centerY}, {x : centerX + 10, y: centerY}); + var lowerRight = createNormalizedVector({x: centerX, y: centerY}, {x: target.xPos + target.width, y: target.yPos + target.height}); + var lowerLeft = createNormalizedVector({x: centerX, y: centerY}, {x: target.xPos, y: target.yPos + target.height}); + target.angles = [angleBetweenTwoVectors(base, lowerRight), angleBetweenTwoVectors(base, lowerLeft)]; + source.angles = [angleBetweenTwoVectors(base, lowerRight), angleBetweenTwoVectors(base, lowerLeft)]; + + var blocked = false; + if(target.yPos == source.yPos && target.xPos == source.xPos){ + blocked = true; + } + if(!blocked) + blocked = blocked || overlaysRect(source); + if(!blocked) + blocked = blocked || overlaysBuff(source); + if(!blocked) + blocked = blocked || overlaysPortal(source); + if(!blocked) + blocked = blocked || overlaysRect(target); + if(!blocked) + blocked = blocked || overlaysBuff(target); + if(!blocked) + blocked = blocked || overlaysPortal(target); + + // orange + source.effect = function(ball) { + collisionWithPortal(ball, source, target); + }; + + //blue + target.effect = function(ball) { + collisionWithPortal(ball, target, source); + }; + + var portal = { + source: source, + target: target + }; + if(!blocked){ + portals.push(portal) + } + } + + var rectAmount = ((Math.random() * 9) << 0) + 1; + for(var i = 0; i < rectAmount; i++){ + var rect = { + xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize, + yPos: 1 * config.balls.verticalSize, + height: config.balls.verticalSize - 5, + width: config.balls.horizontalSize - 5, + points: config.balls.balls, + maxPoints: config.balls.balls + }; + var exists = false; + exists = exists || overlaysRect(rect); + exists = exists || overlaysBuff(rect); + exists = exists || overlaysPortal(rect); + + + if(!exists){ + rects.push(rect); + } + } + +} + +function setEndPoint(event){ + if(mouseDown){ + mouseStop = getMousePos(canvas, event); + } +} + +function overlaysRect(rect){ + var exists = false; + rects.forEach(function(rectToTest){ + if(exists) return; + if(rectToTest.xPos == rect.xPos && rectToTest.yPos == rect.yPos){ + exists = true; + } + }); + + return exists; +} + +function overlaysBuff(rect){ + var exists = false; + buffs.forEach(function(buffToTest){ + if(exists) return; + if(buffToTest.xPos == rect.xPos && buffToTest.yPos == rect.yPos){ + exists = true; + } + }); + return exists; +} + +function overlaysPortal(rect){ + var exists = false; + portals.forEach(function (portalToTest){ + if(exists) return; + var sourceCenter = { + x: portalToTest.source.xPos + portalToTest.source.width / 2, + y: portalToTest.source.yPos + portalToTest.source.height / 2 + }; + if(sourceCenter.x > rect.xPos && sourceCenter.x < (rect.xPos + rect.width) && sourceCenter.y > rect.yPos && sourceCenter.y < (rect.yPos + rect.height)){ + exists = true; + } + var targetCenter = { + x: portalToTest.target.xPos + portalToTest.target.width / 2, + y: portalToTest.target.yPos + portalToTest.target.height / 2 + }; + if(targetCenter.x > rect.xPos && targetCenter.x < (rect.xPos + rect.width) && targetCenter.y > rect.yPos && targetCenter.y < (rect.yPos + rect.height)){ + exists = true; + } + }); + + return exists; +} + +function buildBuffGradient(buff){ + var yellowMagenta = ctx.createRadialGradient(buff.xPos + buff.width / 2, buff.yPos + buff.height / 2, 0, buff.xPos + buff.width / 2, buff.yPos + buff.height / 2, buff.width / 2); + yellowMagenta.addColorStop(0 ,"yellow"); + yellowMagenta.addColorStop(1, "DarkMagenta"); + return yellowMagenta; +} + +function collisionWithPortal(ball, thisOne, partner){ + var centerX = thisOne.xPos + thisOne.width / 2; + var centerY = thisOne.yPos + thisOne.height / 2; + + var xOffset = ball.x - thisOne.xPos; + var yOffset = ball.y - thisOne.yPos; + var rectCenterToBallCenter = createNormalizedVector(ball, {x: centerX, y: centerY}); + var normalLevel = createNormalizedVector({x: centerX + 10, y: centerY}, {x: centerX, y: centerY}); + var angle = angleBetweenTwoVectors(rectCenterToBallCenter, normalLevel); + if(angle < thisOne.angles[0]){ + ball.x = partner.xPos - ball.radius; + ball.y = partner.yPos + yOffset; + } else if(angle < thisOne.angles[1]){ + if(ball.vec.y > 0){ + ball.y = partner.yPos + partner.height + ball.radius; + ball.x = partner.xPos + xOffset; + } else { + ball.y = partner.yPos - ball.radius; + ball.x = partner.xPos + xOffset; + } + } else if(angle < oneHundredEightyDegrees){ + ball.x = partner.xPos + partner.width + ball.radius; + ball.y = partner.yPos + yOffset; + } +} + + + +docReady(function () { + newStartPoint = {x: config.size.width / 2, y: config.size.height - 25}; + toSpawn = config.balls.balls; + canvas = document.getElementById('canvas') + ctx = canvas.getContext("2d"); + canvas.width = config.size.width; + canvas.height = config.size.height; + canvas.addEventListener("mousedown", setPoint, false); + canvas.addEventListener("mouseup", setPoint, false); + canvas.addEventListener("mousemove", setEndPoint, false); + // Set up touch events for mobile, etc + canvas.addEventListener("touchstart", function (e) { + if(restarting) return; + mousePos = getTouchPos(canvas, e); + var touch = e.touches[0]; + var mouseEvent = new MouseEvent("mousedown", { + clientX: touch.clientX, + clientY: touch.clientY + }); + canvas.dispatchEvent(mouseEvent); + }, false); + + canvas.addEventListener("touchend", function (e) { + if(restarting) return; + var mouseEvent = new MouseEvent("mouseup", { + }); + canvas.dispatchEvent(mouseEvent); + }, false); + canvas.addEventListener("touchmove", function (e) { + if(restarting) return; + var touch = e.touches[0]; + + mousePos = getTouchPos(canvas, e); + var mouseEvent = new MouseEvent("mousemove", { + clientX: touch.clientX, + clientY: touch.clientY + }); + canvas.dispatchEvent(mouseEvent); + }, false); + +// Get the position of a touch relative to the canvas + function getTouchPos(canvasDom, touchEvent) { + var rect = canvasDom.getBoundingClientRect(); + return { + x: touchEvent.touches[0].clientX - rect.left, + y: touchEvent.touches[0].clientY - rect.top + }; + } + var possibleBalls = Cookies.getJSON('balls'); + if(possibleBalls == undefined){ + spawnStuff(); + } else { + buffs = possibleBalls.buffs; + portals = possibleBalls.portals; + rects = possibleBalls.rects; + + // it doesnt store functions + buffs.forEach(function(buff){ + buff.effect = function(ball) { + reboundBalls += buff.duration; + } + }); + + portals.forEach(function(portal){ + portal.source.effect = function(ball) { + collisionWithPortal(ball, portal.source, portal.target); + }; + + //blue + portal.target.effect = function(ball) { + collisionWithPortal(ball, portal.target, portal.source); + }; + }); + + + config.balls.balls = possibleBalls.balls; + } + requestAnimationFrame(updateCanvas); + + setInterval(function(){ + if(doRestart > 0){ + doRestart--; + } + }, config.balls.resetTapsDecreaseInterval * 1000) +}); + + diff --git a/balls/src/js/plugins.js b/balls/src/js/plugins.js new file mode 100644 index 0000000..f887480 --- /dev/null +++ b/balls/src/js/plugins.js @@ -0,0 +1,24 @@ +// Avoid `console` errors in browsers that lack a console. +(function() { + var method; + var noop = function () {}; + var methods = [ + 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', + 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', + 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', + 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn' + ]; + var length = methods.length; + var console = (window.console = window.console || {}); + + while (length--) { + method = methods[length]; + + // Only stub undefined methods. + if (!console[method]) { + console[method] = noop; + } + } +}()); + +// Place any jQuery/helper plugins in here. diff --git a/balls/vite.config.js b/balls/vite.config.js new file mode 100644 index 0000000..df7a114 --- /dev/null +++ b/balls/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + base: './', + root: 'src', + build: { + outDir: '../../dist/balls' + } +}) diff --git a/canvas-common/common.js b/canvas-common/common.js index d40af53..cf0e6d8 100644 --- a/canvas-common/common.js +++ b/canvas-common/common.js @@ -123,4 +123,196 @@ export function randomNumberButAtLeast(range, min) { export function getIndexForCoordinate(config, x, y) { return (y * config.size.width + x) * 4; -} \ No newline at end of file +} + +export function createNormalizedVector(tip, shaft) { + let vect = createVector(tip, shaft); + + let dist = pointDistance(tip, shaft); + vect.x /= dist; + vect.y /= dist; + return vect; +} + +// only normalized vectors +export function angleBetweenTwoVectors(vectorA, vectorB){ + return Math.acos(dotProduct(vectorA, vectorB)); +} + +export function dotProduct(vector1, vector2) { + return vector1.x * vector2.x + vector1.y * vector2.y; +} + + +/*! + * JavaScript Cookie v2.2.0 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +/*! + * JavaScript Cookie v2.2.0 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +;(function (factory) { + var registeredInModuleLoader = false; + if (typeof define === 'function' && define.amd) { + define(factory); + registeredInModuleLoader = true; + } + if (typeof exports === 'object') { + module.exports = factory(); + registeredInModuleLoader = true; + } + if (!registeredInModuleLoader) { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function () { + window.Cookies = OldCookies; + return api; + }; + } +}(function () { + function extend () { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[ i ]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function init (converter) { + function api (key, value, attributes) { + var result; + if (typeof document === 'undefined') { + return; + } + + // Write + + if (arguments.length > 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); + attributes.expires = expires; + } + + // We're using "expires" because "max-age" is not supported by IE + attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; + + try { + result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + if (!converter.write) { + value = encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + } else { + value = converter.write(value, key); + } + + key = encodeURIComponent(String(key)); + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); + key = key.replace(/[\(\)]/g, escape); + + var stringifiedAttributes = ''; + + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue; + } + stringifiedAttributes += '; ' + attributeName; + if (attributes[attributeName] === true) { + continue; + } + stringifiedAttributes += '=' + attributes[attributeName]; + } + return (document.cookie = key + '=' + value + stringifiedAttributes); + } + + // Read + + if (!key) { + result = {}; + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling "get()" + var cookies = document.cookie ? document.cookie.split('; ') : []; + var rdecode = /(%[0-9A-Z]{2})+/g; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + if (!this.json && cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + var name = parts[0].replace(rdecode, decodeURIComponent); + cookie = converter.read ? + converter.read(cookie, name) : converter(cookie, name) || + cookie.replace(rdecode, decodeURIComponent); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + if (key === name) { + result = cookie; + break; + } + + if (!key) { + result[name] = cookie; + } + } catch (e) {} + } + + return result; + } + + api.set = api; + api.get = function (key) { + return api.call(api, key); + }; + api.getJSON = function () { + return api.apply({ + json: true + }, [].slice.call(arguments)); + }; + api.defaults = {}; + + api.remove = function (key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.withConverter = init; + + return api; + } + + return init(function () {}); +})); diff --git a/img/balls.png b/img/balls.png new file mode 100644 index 0000000000000000000000000000000000000000..0d67f5898e9d5e6931d00e45797eb7962d5a37d0 GIT binary patch literal 7083 zcmeHMYgAKNmcABi#3~fJ%hdw%NYNs(l_K&G2!voYQpy-ZThoe2grNn}Y7|I#g-3#8 z2~*8eB990mmKIVoJW?hCMjo*$8VH0$3l$=H5D5toNk{_0Kpu1HUh{9}&rGdZYkKkH ztb5m4XYaG`KHuH@J9~fg2lVk>pX~hv0DxU_Cyspu03WSxjvd>cwC4(_Qf9yl9!~@j5c-Zq+Z^S-Ux9N6(Cxts&_}8y5slb?GVqm zFYfie_$0Qp)afrrW8W=vKFgjgx>kFz@W#>Ke6|1B_3s}2>bGwnjDL%M=ezo>dZ|)9 zFK+LdEM!l0doh~A6ILy~(e&n*2vL8-qn^e$>)rX$I-~P!c=a{_xO^(q5dca9?s)^i z=r@G`Z~(fv4Y=F#y(3VH-oWx_#$5Vk1 z+n|Xm?T5=~rljTF1>Z&ak503b#$kJau^tCxvmc z4Vi|~f)c`C@^5`lc66Mz4n&Z`L^UbnQoTwQ((-X1FCnxXn7QhuJ^-tfkL2?hnP`0g z^T96)F$s=P>d1YJ=1~I2Cf_7N_Rkig>I8XE>XNhjo|rPl>P2lAE8-t{ubWcq1SUT! ztvSuDZf9mmGEn;EY(yfY8V)~)%`JEv%gq_dxiVt0_;UT-htot3U})*||BhJu5nY zsIXjhptWT%qD3d;>S(EWAMP8)lz5&`7>~$Y{i_ zJ$C{I@+&n;rLC!JYyx9bo-MnCU;g>v+R#J6pt`|5gTL-eTN*_<(dC&I65nmR8g0a0io99i-*tm7cl!D;*k!O1bENm{qG}gcA zsxFvU`jaD|yOxxS_^Wd~J6zBO^H$f}iM3AANY_>1DfKXlVm8Nh+urAyT3fKpBg%Dh z;pZ@`=?iTMf%-g#k`|<#6L~9d(Z#|EP9f4rpcHE+b#rZUnMiT60~dw(&8o$G=UP1I z#F2O3#`zh2xH$0T(u3(3-dt0*zymu6i(=JFhuf)IXZDKVthSg)O-e}`dMMio+?D1O z)m6~Xr3HBeiT9!z`y{k9Qg&S>UoXuq^JUR|k{bOI5h47;Ud)XT{_56NwQZGxyFhIf z?+?Fc+jayM53@D-qdBL#oq6v$S)ibHmWA3AgHHA!6c-8J85W#!g1nfy#5<_Exusm* zYaZnz;PyHzcr~sr&^>`~&_;|!YH(4-g`ci`P#v5Y^qG)O*r9yOPremn6Q1&9EK#@e zPRyFekYk;6IlH zspfeH%Nd<`JeejP#!~4X%!uN_ie4Eak9h)g4%#%C=tYx;f4ce)-~qGOs?w9q(i^D0 za?D0^WIH)xFPhWSs=Z(?;Z0P;3AJN;fC5*?Kb8yL1*b;m%%FdqQ@K`OtJCrb+oz4a^|5GwBH= z^=J7*_!g^cJo9nL`0MygY_B@f1IXH5*{+#Sv2<;)jnJ{36KQ$ZMyi}BAUu1!Q3U

wTJ77j|&a0Abd>^y{Ye@q` zzbJ z@m;(>lW?^GYLl@mHTuszB%twmvarYtuAzPq%dpz`OdciW;hXj+HMUdf(SyHp0FDm& zh>Pt0bjzd*HaBFbExU}rP`KVz-cH5sg4@3(w%VYe7}iwRl8g=KgcHX;Rk2RIioUB- zL#cFnnvOqCg*@8$I%OY_)q5(x3}cyUzA|qoXB2@@@DrE(iJ-y}0$jIP)Blt)+Prph zRXYFxaqd_Y0NmaU(&ay(UxDWa2Y*puZfo-I!y#<33droC5Qu372#6>0mQ|tMMj^@X+awE>UMa)t?=oP33ilwJT6lUcz*!I;?!#jAJLhu4uNa6VLps z;!Rw+j(+stp=;xnLv)?K$CWj-CLK==-(AM{=bGBm)L)g|T)Lnrhx1dl)p8-f;D^5{ zi>_O(!V(Pa_7Z}d@axo}M*cpCNO>#%UkeKy$MnhF3}o{S$<$vt^>qk;{WZ9@M=tR! z3r?LHY7svk&C;GXyl@O_zpf*+OyWzdY>7Lk=RZ~J=dbblVK`^5I17JUznq81KY}~p z&S9xmeG&0$arP$fc1r)HE@`asZKaN{2f2YTNut`GoT3_L2o3)R9lW`1VDh~@S#5Ia zz}VkqciX1?b#{L21EWuQ{v_KfJ(FxP1RdYdgjGWl_&G?W!bM;hnP4GSrdBpum)ZvEg? zff>jPy@CAo>e1`gxh5DnzrJ~G7$fsy_PYf|_Up!g0h$j;>oJAJcZQLnB3fetRVZ(1 z&2*t=#uF5a3y#koqpLv)&_8?nW4aMX#$;UV8vd)(E{GRrHOe=(CK0KGda^$TX$A95 zNqP^_4Zuopfl32q0zwwXelgOf4 zskzoV8z>rLj!L=rCGd0Vl#)VdJR$@hRpbK1T_a)Z8;5!YZO{zj{KLeIq?oc`8{B8{ zy5r9|Zuo0~O4uN|p-a4QEsWyJ(9*xOzBW=2tIHutvgg3m@*=%e`5Xo#EbTKQEZ4;@ zgLh4x%h@{+wR>1fheoLR>0Fn^ij{eiwY-C(p@C5tDXo=ko0!f-15cYe6Zjo+B!5g4 zU{sJF)|W>w%cdL>!R(af*Wy1cDIwNdlR8ij_%Su(%p!eM)*a+M?*oMFq7f|e;mGk6 z4tocni;8shGe3jrl%xN|La8&m8zFxD;wvM&MgDl(Vu&kvSz7mIJFvfZ{C5mIjL&)W ze)~++l7vgK2pFx?p2D0^rRDV{q^U12v|Tu`#*^;@fSH|JY4U$f5subXHah@OTLk0p zgJArZba9h)Je{UuHuwU%8QaXw3iUSym8`%r!t<{Zwom;~UtxZ+63mV!T?Vu1%fr>P zkKrH{UHQQRmTR}lpj##2t@8ITTCCk_Cv3G+{_k5U61&6A+g{Solar system simulation
Recursive bubbles
Fireworks + Balls \ No newline at end of file