commit 104f0998e1a620e96e3ad16118aaea24183e4c98 Author: kolaflash Date: Tue Nov 25 19:32:27 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c401c63 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "bun.runtime": "~/.bun/bin/bun", + "bun.debugTerminal.enabled": true, + "bun.debugTerminal.stopOnEntry": false, + "editor.formatOnSave": true, + "editor.defaultFormatter": "biomejs.biome", + "editor.codeActionsOnSave": { + "source.organizeImports.biome": "explicit", + "source.fixAll.biome": "explicit" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..248132a --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# nage diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..f5f0197 --- /dev/null +++ b/biome.json @@ -0,0 +1,13 @@ +{ + "linter": { + "rules": { + "a11y": "off", + "style": { + "noNonNullAssertion": "off" + }, + "suspicious": { + "noAssignInExpressions": "off" + } + } + } +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..b62a74e --- /dev/null +++ b/bun.lock @@ -0,0 +1,291 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "name": "nage", + "devDependencies": { + "@biomejs/biome": "latest", + "@types/bun": "latest", + "lz-string": "latest", + "solid-js": "latest", + "vite": "latest", + "vite-plugin-solid": "latest", + }, + "peerDependencies": { + "typescript": "latest", + }, + }, + }, + "packages": { + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.4", "", {}, "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="], + + "@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + + "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + + "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], + + "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + + "@biomejs/biome": ["@biomejs/biome@2.3.7", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.7", "@biomejs/cli-darwin-x64": "2.3.7", "@biomejs/cli-linux-arm64": "2.3.7", "@biomejs/cli-linux-arm64-musl": "2.3.7", "@biomejs/cli-linux-x64": "2.3.7", "@biomejs/cli-linux-x64-musl": "2.3.7", "@biomejs/cli-win32-arm64": "2.3.7", "@biomejs/cli-win32-x64": "2.3.7" }, "bin": { "biome": "bin/biome" } }, "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.7", "", { "os": "win32", "cpu": "x64" }, "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.0", "", { "os": "android", "cpu": "arm" }, "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.0", "", { "os": "android", "cpu": "arm64" }, "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.0", "", { "os": "linux", "cpu": "arm" }, "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.0", "", { "os": "linux", "cpu": "arm" }, "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.0", "", { "os": "linux", "cpu": "x64" }, "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.0", "", { "os": "none", "cpu": "arm64" }, "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="], + + "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.1", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2", "validate-html-nesting": "^1.2.1" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-b4iHuirqK7RgaMzB2Lsl7MqrlDgQtVRSSazyrmx7wB3T759ggGjod5Rkok5MfHjQXhR7tRPmdwoeGPqBnW2KfA=="], + + "babel-preset-solid": ["babel-preset-solid@1.9.9", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.1" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.8" }, "optionalPeers": ["solid-js"] }, "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.6", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw=="], + + "browserslist": ["browserslist@4.26.2", "", { "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", "electron-to-chromium": "^1.5.218", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A=="], + + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001743", "", {}, "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.222", "", {}, "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w=="], + + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="], + + "is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "node-releases": ["node-releases@2.0.21", "", {}, "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="], + + "seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], + + "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], + + "solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "validate-html-nesting": ["validate-html-nesting@1.2.3", "", {}, "sha512-kdkWdCl6eCeLlRShJKbjVOU2kFKxMF8Ghu50n+crEoyx+VKm3FxAxF9z4DCy6+bbTOqNW0+jcIYRnjoIRzigRw=="], + + "vite": ["vite@7.2.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w=="], + + "vite-plugin-solid": ["vite-plugin-solid@2.11.10", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw=="], + + "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], + } +} diff --git a/engine/Entity.ts b/engine/Entity.ts new file mode 100644 index 0000000..5400a54 --- /dev/null +++ b/engine/Entity.ts @@ -0,0 +1,36 @@ +import { loadImage } from "#/lib/loadImage.ts"; +import { createSprite, type Sprite } from "#/lib/Sprite.ts"; +import { createVector, type Vector } from "#/lib/Vector.ts"; + +export type Entity = { + id: string; + src: string; + width: number; + height: number; +}; + +export type EntityInstance = { + position: Vector; + width: number; + height: number; + sprite: Sprite; +}; + +export function createEntity( + id: string, + src: string, + width: number = 16, + height: number = 16, +): Entity { + return { id, width, height, src }; +} + +export async function loadEntity(name: string): Promise { + const entity = (await import(`#/entities/${name}/index.ts`)).default; + + const { width, height } = entity; + const sprite = createSprite(await loadImage(entity.src), 2, 4); + const position = createVector(); + + return { position, width, height, sprite }; +} diff --git a/engine/Scene.ts b/engine/Scene.ts new file mode 100644 index 0000000..6cf8025 --- /dev/null +++ b/engine/Scene.ts @@ -0,0 +1,62 @@ +export type SceneData = { + id: string; + width: number; + height: number; + entities: string[]; +}; + +export type Scene = { + data: SceneData; + process: Process; +}; + +export type Process = (ctx: CanvasRenderingContext2D, delta: number) => void; + +export type SceneChangeCallback = (from: Scene, to: Scene) => void; + +let sceneChangeCallback: SceneChangeCallback = (): void => {}; + +const sceneDataMap = new Map(); +const sceneProcessMap = new Map(); + +export const currentScene: Scene = { + data: { + id: "", + width: 0, + height: 0, + entities: [], + }, + process: () => {}, +}; + +export function useScene(data: SceneData): { + process: (fn: Process) => void; +} { + sceneDataMap.set(data.id, data); + + return { + process(fn: Process): void { + sceneProcessMap.set(data.id, fn); + }, + }; +} + +export function setSceneChangeCallback(callback: SceneChangeCallback) { + sceneChangeCallback = callback; +} + +export function changeScene(scene: Scene): void { + sceneChangeCallback(currentScene, scene); + currentScene.data = scene.data; + currentScene.process = scene.process; +} + +export async function loadScene(name: string): Promise { + await import(`#/scenes/${name}/index.ts`); + + const data = sceneDataMap.get(name)!; + currentScene.data = data; + + const process = sceneProcessMap.get(name)!; + currentScene.process = process; +} diff --git a/engine/Viewport.ts b/engine/Viewport.ts new file mode 100644 index 0000000..9ebab2c --- /dev/null +++ b/engine/Viewport.ts @@ -0,0 +1,86 @@ +import { createVector, type Vector } from "#/lib/Vector.ts"; + +export type Viewport = { + initialScale: number; + imageSmoothing: CanvasImageSmoothing; + translation: Vector; + ctx: CanvasRenderingContext2D; + canvas: HTMLCanvasElement; + gameContainer: HTMLElement; + canvasContainer: HTMLElement; +}; + +export const viewport: Viewport = { + initialScale: 4, + imageSmoothing: { + imageSmoothingEnabled: false, + imageSmoothingQuality: "low", + }, + translation: createVector(), + ctx: null as unknown as CanvasRenderingContext2D, + canvas: null as unknown as HTMLCanvasElement, + gameContainer: null as unknown as HTMLElement, + canvasContainer: null as unknown as HTMLElement, +}; + +export function initViewport(): void { + const gameContainer = document.createElement("div"); + gameContainer.id = "gameContainer"; + + const canvasContainer = document.createElement("div"); + canvasContainer.id = "canvasContainer"; + + const canvas = document.createElement("canvas"); + viewport.ctx = canvas.getContext("2d", { + alpha: false, + }) as CanvasRenderingContext2D; + + viewport.canvas = canvas; + viewport.canvasContainer = canvasContainer; + viewport.gameContainer = gameContainer; + + canvasContainer.appendChild(canvas); + gameContainer.appendChild(canvasContainer); + document.body.appendChild(gameContainer); +} + +export function setScale(scale: number): void { + viewport.initialScale = scale; + self.dispatchEvent(new Event("resize")); +} + +export function resizeCanvas(width: number, height: number): void { + const { + initialScale, + imageSmoothing, + ctx, + canvas, + canvasContainer, + gameContainer, + } = viewport; + + const containerWidth = (gameContainer.offsetWidth / initialScale) | 0; + const containerHeight = (gameContainer.offsetHeight / initialScale) | 0; + + canvas.width = Math.min(width, containerWidth); + canvas.height = Math.min(height, containerHeight); + + ctx.imageSmoothingEnabled = imageSmoothing.imageSmoothingEnabled; + ctx.imageSmoothingQuality = imageSmoothing.imageSmoothingQuality; + + const canvasWidth = + canvas.width * initialScale - (containerWidth % initialScale); + const canvasHeight = + canvas.height * initialScale - (containerHeight % initialScale); + + canvasContainer.style = `width: ${canvasWidth}px; height: ${canvasHeight}px;`; +} + +export function resetCtx() { + const { ctx, canvas, translation } = viewport; + + ctx.restore(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.translate(-(translation.x | 0), -(translation.y | 0)); +} diff --git a/entities/testEntity/data.json b/entities/testEntity/data.json new file mode 100644 index 0000000..522e0d8 --- /dev/null +++ b/entities/testEntity/data.json @@ -0,0 +1,6 @@ +{ + "id": "testEntity", + "src": "/assets/char-idle.png", + "width": 16, + "height": 16 +} diff --git a/entities/testEntity/index.ts b/entities/testEntity/index.ts new file mode 100644 index 0000000..c4d470d --- /dev/null +++ b/entities/testEntity/index.ts @@ -0,0 +1,4 @@ +import { createEntity } from "#/engine/Entity.ts"; +import data from "./data.json"; + +export default createEntity(data.id, data.src, data.width, data.height); diff --git a/index.html b/index.html new file mode 100644 index 0000000..67361d9 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + + + + nage + + + diff --git a/lib/Graph.ts b/lib/Graph.ts new file mode 100644 index 0000000..924c519 --- /dev/null +++ b/lib/Graph.ts @@ -0,0 +1,119 @@ +import type { Vector } from "./Vector.ts"; + +export type Node = T; +export type Edge = [Node, Node]; +export type Graph = Map, Array>>; + +type PositionPartial = { + position: Vector; +}; + +export function drawNode( + node: Node, + ctx: CanvasRenderingContext2D, + color: string, +): void { + ctx.lineWidth = 2; + ctx.strokeStyle = color; + ctx.beginPath(); + ctx.arc(node.position.x, node.position.y, 8, 0, 2 * Math.PI); + ctx.stroke(); +} + +export function getNodeByPosition( + nodes: Array>, + position: Vector, +): Node | null { + for (const node of nodes) { + if ( + node.position.x <= position.x + 16 && + node.position.x >= position.x - 16 && + node.position.y <= position.y + 16 && + node.position.y >= position.y - 16 + ) { + return node; + } + } + + return null; +} + +export function drawEdge( + edge: Edge, + ctx: CanvasRenderingContext2D, + color: string, +): void { + ctx.lineWidth = 2; + ctx.strokeStyle = color; + ctx.beginPath(); + ctx.moveTo(edge[0].position.x, edge[0].position.y); + ctx.lineTo(edge[1].position.x, edge[1].position.y); + ctx.stroke(); +} + +export function createGraph( + nodes: Array>, + edges: Array>, +): Graph { + const graph: Graph = new Map(); + + for (const node of nodes) { + const neighbours = edges.reduce( + (acc, edge) => { + if (edge[0] === node) { + acc.push(edge[1]); + return acc; + } + + if (edge[1] === node) { + acc.push(edge[0]); + return acc; + } + + return acc; + }, + [] as Array>, + ); + + if (neighbours.length > 0) { + graph.set(node, neighbours); + } + } + + return graph; +} + +export function originDFS(origin: Node, graph: Graph): Array> { + const visited: Array> = [origin]; + + if (!graph.has(origin)) { + return visited; + } + + innerDFS(origin, graph, visited); + + return visited; +} + +function innerDFS( + node: Node, + graph: Graph, + visited: Array>, +): void { + const neighbours = graph.get(node)!; + for (const node of neighbours) { + if (visited.includes(node)) { + continue; + } + + visited.push(node); + innerDFS(node, graph, visited); + } +} + +export function getNeighbours( + graph: Graph, + node: Node, +): Array> { + return graph.has(node) ? graph.get(node)! : []; +} diff --git a/lib/Segment.ts b/lib/Segment.ts new file mode 100644 index 0000000..09d27a1 --- /dev/null +++ b/lib/Segment.ts @@ -0,0 +1,72 @@ +import { lerp } from "./lerp.ts"; +import type { Vector } from "./Vector.ts"; + +export type Segment = [Vector, Vector]; + +export type SegmentIntersection = { + position: Vector; + offset: number; +}; + +export function createSegmentIntersection(): SegmentIntersection { + return { + position: { + x: Number.NaN, + y: Number.NaN, + }, + offset: Number.NaN, + }; +} + +export function setSegmentIntersection( + segmentIntersection: SegmentIntersection, + a: Segment, + b: Segment, +): void { + const bottom = + (b[1].y - b[0].y) * (a[1].x - a[0].x) - + (b[1].x - b[0].x) * (a[1].y - a[0].y); + + if (bottom === 0) { + segmentIntersection.position.x = Number.NaN; + segmentIntersection.position.y = Number.NaN; + segmentIntersection.offset = Number.NaN; + + return; + } + + const tTop = + (b[1].x - b[0].x) * (a[0].y - b[0].y) - + (b[1].y - b[0].y) * (a[0].x - b[0].x); + const uTop = + (b[0].y - a[0].y) * (a[0].x - a[1].x) - + (b[0].x - a[0].x) * (a[0].y - a[1].y); + + const t = tTop / bottom; + const u = uTop / bottom; + + if (t < 0 || t > 1 || u < 0 || u > 1) { + segmentIntersection.position.x = Number.NaN; + segmentIntersection.position.y = Number.NaN; + segmentIntersection.offset = Number.NaN; + + return; + } + + segmentIntersection.position.x = lerp(a[0].x, a[1].x, t); + segmentIntersection.position.y = lerp(a[0].y, a[1].y, t); + segmentIntersection.offset = t; +} + +export function drawSegment( + segment: Segment, + ctx: CanvasRenderingContext2D, + color: string, +): void { + ctx.lineWidth = 2; + ctx.strokeStyle = color; + ctx.beginPath(); + ctx.moveTo(segment[0].x, segment[0].y); + ctx.lineTo(segment[1].x, segment[1].y); + ctx.stroke(); +} diff --git a/lib/Sprite.ts b/lib/Sprite.ts new file mode 100644 index 0000000..6d26e51 --- /dev/null +++ b/lib/Sprite.ts @@ -0,0 +1,46 @@ +export type Sprite = { + image: HTMLImageElement; + xFrames: number; + yFrames: number; + frameWidth: number; + frameHeight: number; + x: number; + y: number; +}; + +export function createSprite( + image: HTMLImageElement, + xFrames: number, + yFrames: number, +): Sprite { + return { + image, + xFrames, + yFrames, + frameWidth: image.width / xFrames, + frameHeight: image.height / yFrames, + x: 0, + y: 0, + }; +} + +export function drawSprite( + ctx: CanvasRenderingContext2D, + sprite: Sprite, + x: number, + y: number, + width: number, + height: number, +): void { + ctx.drawImage( + sprite.image, + sprite.x, + sprite.y, + sprite.frameWidth, + sprite.frameHeight, + x, + y, + width, + height, + ); +} diff --git a/lib/Vector.ts b/lib/Vector.ts new file mode 100644 index 0000000..c24323a --- /dev/null +++ b/lib/Vector.ts @@ -0,0 +1,80 @@ +export type Vector = { + x: number; + y: number; +}; + +export function createVector(x: number = 0, y: number = 0): Vector { + return { x, y }; +} + +export function fromPolar(direction: number, magnitude: number): Vector { + return { + x: Math.cos(direction) * magnitude, + y: Math.sin(direction) * magnitude, + }; +} + +export function getDirection(vector: Vector): number { + return Math.atan2(vector.y, vector.x); +} + +export function getMagnitude(vector: Vector): number { + return Math.sqrt(vector.x * vector.x + vector.y * vector.y); +} + +export function getDistance(a: Vector, b: Vector): number { + const dx = a.x - b.x; + const dy = a.y - b.y; + + return Math.sqrt(dx * dx + dy * dy); +} + +export function getSquared(vector: Vector): number { + return vector.x * vector.x + vector.y * vector.y; +} + +export function setDistanceNormal(normal: Vector, a: Vector, b: Vector): void { + const dx = a.x - b.x; + const dy = a.y - b.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance > 0) { + normal.x = dx / distance; + normal.y = dy / distance; + } +} + +export function getDotProduct(a: Vector, b: Vector): number { + return a.x * b.x + a.y * b.y; +} + +export function getPerpDotProduct(a: Vector, b: Vector): number { + return a.x * b.x - a.y * b.y; +} + +export function scale(vector: Vector, scalar: number): void { + vector.x *= scalar; + vector.y *= scalar; +} + +export function normalize(vector: Vector): void { + const magnitude = Math.sqrt(vector.x * vector.x + vector.y * vector.y); + + if (magnitude > 0) { + const scalar = 1 / magnitude; + vector.x *= scalar; + vector.y *= scalar; + } +} + +export function isZero(vector: Vector): boolean { + return vector.x === 0 && vector.y === 0; +} + +export function isBelowThreshold(vector: Vector, threshold: number): boolean { + return Math.abs(vector.x) < threshold && Math.abs(vector.y) < threshold; +} + +export function equals(a: Vector, b: Vector): boolean { + return a.x === b.x && a.y === b.y; +} diff --git a/lib/lerp.ts b/lib/lerp.ts new file mode 100644 index 0000000..9ad15b4 --- /dev/null +++ b/lib/lerp.ts @@ -0,0 +1,7 @@ +export function lerp(a: number, b: number, t: number): number { + return a + (b - a) * t; +} + +export function lerpPrecice(a: number, b: number, t: number): number { + return a * (1 - t) + b * t; +} diff --git a/lib/loadImage.ts b/lib/loadImage.ts new file mode 100644 index 0000000..798d4ad --- /dev/null +++ b/lib/loadImage.ts @@ -0,0 +1,12 @@ +export function loadImage(src: string): Promise { + return new Promise((resolve) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = () => { + console.error(image.src); + return resolve(image); + }; + + image.src = src; + }); +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..90cc0d9 --- /dev/null +++ b/main.ts @@ -0,0 +1,34 @@ +import { currentScene, loadScene } from "#/engine/Scene.ts"; +import { + initViewport, + resetCtx, + resizeCanvas, + viewport, +} from "#/engine/Viewport.ts"; + +let then = self.performance.now(); +let delta = 0; +function animate(timestamp: number): void { + delta = timestamp - then; + then = timestamp; + + resetCtx(); + currentScene.process(viewport.ctx, delta); + requestAnimationFrame(animate); +} + +(async () => { + if (import.meta.env.DEV) { + import("#/tools/main.tsx").then((m) => m.use(viewport.gameContainer)); + } + + initViewport(); + await loadScene("testScene"); + + resizeCanvas(currentScene.data.width, currentScene.data.height); + self.addEventListener("resize", () => { + resizeCanvas(currentScene.data.width, currentScene.data.height); + }); + + animate(delta); +})(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..55effe4 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "nage", + "scripts": { + "dev": "vite" + }, + "devDependencies": { + "@biomejs/biome": "2.3.7", + "@types/bun": "1.3.3", + "lz-string": "1.5.0", + "solid-js": "1.9.10", + "vite": "7.2.4", + "vite-plugin-solid": "2.11.10" + }, + "peerDependencies": { + "typescript": "5.9.3" + }, + "private": true, + "type": "module" +} diff --git a/public/assets/bgm.wav b/public/assets/bgm.wav new file mode 100644 index 0000000..fccd683 Binary files /dev/null and b/public/assets/bgm.wav differ diff --git a/public/assets/char-idle.png b/public/assets/char-idle.png new file mode 100644 index 0000000..79c6232 Binary files /dev/null and b/public/assets/char-idle.png differ diff --git a/public/assets/char-walk.png b/public/assets/char-walk.png new file mode 100644 index 0000000..3336233 Binary files /dev/null and b/public/assets/char-walk.png differ diff --git a/public/assets/portal.png b/public/assets/portal.png new file mode 100644 index 0000000..cd09fd2 Binary files /dev/null and b/public/assets/portal.png differ diff --git a/public/assets/portal2.png b/public/assets/portal2.png new file mode 100644 index 0000000..408d4ab Binary files /dev/null and b/public/assets/portal2.png differ diff --git a/public/assets/tiles.png b/public/assets/tiles.png new file mode 100644 index 0000000..0b7196d Binary files /dev/null and b/public/assets/tiles.png differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..cad25e7 Binary files /dev/null and b/public/favicon.png differ diff --git a/scenes/testScene/data.json b/scenes/testScene/data.json new file mode 100644 index 0000000..8cf94b8 --- /dev/null +++ b/scenes/testScene/data.json @@ -0,0 +1,6 @@ +{ + "id": "testScene", + "width": 48, + "height": 48, + "entities": ["testEntity"] +} diff --git a/scenes/testScene/index.ts b/scenes/testScene/index.ts new file mode 100644 index 0000000..6b6a7f6 --- /dev/null +++ b/scenes/testScene/index.ts @@ -0,0 +1,18 @@ +import { type EntityInstance, loadEntity } from "#/engine/Entity.ts"; +import { useScene } from "#/engine/Scene.ts"; +import { drawSprite } from "#/lib/Sprite.ts"; +import data from "./data.json"; + +const { process } = useScene(data); + +const entities: Array = []; +for (const entity of data.entities) { + const instance = await loadEntity(entity); + entities.push(instance); +} + +process((ctx, _delta) => { + for (const instance of entities) { + drawSprite(ctx, instance.sprite, 0, 0, 16, 16); + } +}); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..515ea2b --- /dev/null +++ b/styles.css @@ -0,0 +1,29 @@ +body { + position: fixed; + top: 0; + left: 0; + height: 100dvh; + width: 100dvw; + background: #161616; + margin: 0; +} + +#gameContainer { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +#canvasContainer { + position: relative; + + canvas { + display: block; + image-rendering: pixelated; + user-select: none; + height: 100%; + width: 100%; + } +} diff --git a/tools/controls/RangeSlider.tsx b/tools/controls/RangeSlider.tsx new file mode 100644 index 0000000..bc439ca --- /dev/null +++ b/tools/controls/RangeSlider.tsx @@ -0,0 +1,27 @@ +import type { Component, JSX } from "solid-js"; +import style from "./controls.module.css"; + +export const RangeSlider: Component<{ + name: string; + min: number; + max: number; + step: number; + value: number; + children?: JSX.Element; + onChange?: (value: number) => void; + onInput?: (value: number) => void; +}> = (props) => ( + +); diff --git a/tools/controls/controls.module.css b/tools/controls/controls.module.css new file mode 100644 index 0000000..4fd07cf --- /dev/null +++ b/tools/controls/controls.module.css @@ -0,0 +1,70 @@ +.rangeSlider { + width: 100%; + color: var(--c-fg-2); + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + gap: var(--sp-2); + font-family: var(--font-sans); + font-weight: bold; + padding: var(--sp-2); +} + +.rangeSlider [type="range"] { + cursor: pointer; + appearance: none; + background: transparent; + border-radius: var(--sp-2); + height: var(--sp-3); + flex: auto; + + &:focus { + outline: 0; + } + + &::-webkit-slider-runnable-track { + width: 100%; + height: var(--sp-2); + border-radius: var(--sp-3); + color: transparent; + background: var(--c-bg-3); + border: none; + } + &::-webkit-slider-thumb { + height: var(--sp-3); + width: var(--sp-3); + border-radius: var(--sp-3); + border: none; + background: var(--c-fg-1); + margin-top: calc(var(--sp-1) * -1); + --webkit-appearance: none; + appearance: none; + } + &:focus::-webkit-slider-thumb { + outline-width: 2px; + outline-style: solid; + outline-color: var(--c-primary); + } + + &::-moz-range-track { + width: 100%; + height: var(--sp-2); + border-radius: var(--sp-3); + color: transparent; + background: var(--c-bg-3); + border: none; + } + &::-moz-range-thumb { + height: var(--sp-3); + width: var(--sp-3); + border-radius: var(--sp-3); + border: none; + background: var(--c-fg-1); + } + &:focus::-moz-range-thumb { + outline-width: 2px; + outline-style: solid; + outline-color: var(--c-primary); + } +} diff --git a/tools/main.module.css b/tools/main.module.css new file mode 100644 index 0000000..e5c7594 --- /dev/null +++ b/tools/main.module.css @@ -0,0 +1,105 @@ +.devTools { + --font-sans: + ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol", "Noto Color Emoji"; + --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; + --font-mono: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + + --rgb-blurple: 101, 114, 245; + --c-blurple: rgb(var(--rgb-blurple)); + + --rgb-fuchsia: 238, 69, 158; + --c-fuchsia: rgb(var(--rgb-fuchsia)); + + --rgb-green: 35, 165, 89; + --c-green: rgb(var(--rgb-green)); + + --rgb-yellow: 240, 178, 50; + --c-yellow: rgb(var(--rgb-yellow)); + + --rgb-orange: 255, 144, 0; + --c-orange: rgb(var(--rgb-orange)); + + --rgb-red: 218, 55, 60; + --c-red: rgb(var(--rgb-red)); + + --rgb-cyan: 95, 205, 228; + --c-cyan: rgb(var(--rgb-cyan)); + + --rgb-pink: 246, 42, 245; + --c-pink: rgb(var(--rgb-pink)); + + --rgb-gray: 117, 126, 138; + --c-gray: rgb(var(--rgb-gray)); + + --rgb-white: 255, 255, 255; + --c-white: rgb(var(--rgb-white)); + + --rgb-black: 30, 31, 34; + --c-black: rgb(var(--rgb-black)); + + --c-primary: var(--c-blurple); + --c-secondary: var(--c-fuchsia); + --c-alert: var(--c-orange); + --c-error: var(--c-red); + + --c-bg-0: rgb(30, 31, 34); + --c-bg-1: rgb(43, 45, 49); + --c-bg-2: rgb(49, 51, 56); + --c-bg-3: rgb(64, 66, 73); + + --c-fg-0: rgb(255 255 225); + --c-fg-1: rgb(219 222 225); + --c-fg-2: rgb(181 186 193); + --c-fg-3: rgb(113 116 120); + + --sp-1: 4px; + --sp-2: 8px; + --sp-3: 16px; + --sp-4: 24px; + --sp-5: 32px; + --sp-6: 48px; + + height: 100%; + min-width: 1px; + max-width: 100%; + position: absolute; + top: 0; + left: 0; + order: -1; + font-size: medium; + font-family: var(--font-sans); + + *, + *::after, + *::before { + box-sizing: border-box; + } + + *:focus { + outline-width: 2px; + outline-style: solid; + outline-color: var(--c-primary); + } +} + +.toolBar { + overflow: hidden; + border-right: 1px solid var(--c-fg-3); + background: var(--c-bg-1); + color: var(--c-fg-1); + width: 100%; + height: 100%; +} + +.toolBarResize { + position: absolute; + top: 0; + right: -3px; + width: 5px; + height: 100%; + cursor: ew-resize; + user-select: none; +} diff --git a/tools/main.tsx b/tools/main.tsx new file mode 100644 index 0000000..83987c0 --- /dev/null +++ b/tools/main.tsx @@ -0,0 +1,63 @@ +import { type Component, createSignal, onMount } from "solid-js"; +import { render } from "solid-js/web"; +import { setScale, viewport } from "#/engine/Viewport.ts"; +import { RangeSlider } from "#/tools/controls/RangeSlider.tsx"; +import style from "./main.module.css"; + +function adjustGameContainer(gameContainer: HTMLElement, width: number): void { + gameContainer.style = `position: relative; top: 0; left: ${width}px; width: ${document.body.offsetWidth - width}px;`; +} + +const DevTools: Component<{ gameContainer: HTMLElement }> = ({ + gameContainer, +}) => { + const [width, setWidth] = createSignal(320); + let isResizing = false; + + const startResizing = (): void => { + isResizing = true; + }; + + const stopResizing = (): void => { + isResizing = false; + }; + + const handleMouseMove = (event: MouseEvent) => { + if (isResizing) { + setWidth(event.clientX); + self.dispatchEvent(new Event("resize")); + } + }; + + adjustGameContainer(gameContainer, width()); + + onMount(() => { + self.addEventListener("resize", () => { + adjustGameContainer(gameContainer, width()); + }); + self.dispatchEvent(new Event("resize")); + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", stopResizing); + }); + + return ( +
+
+ +
+
+
+ ); +}; + +export function use(container: HTMLElement): void { + render(() => , document.body); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..64c79cc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "composite": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + "paths": { + "#/*": ["./*"] + } + } +} diff --git a/vite-env.d.ts b/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..fefd00f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "vite"; +import solidPlugin from "vite-plugin-solid"; + +export default defineConfig({ + server: { + host: "localhost", + port: 3080, + hmr: false, + }, + build: { target: "esnext" }, + plugins: [solidPlugin()], + resolve: { + alias: { + "#/": fileURLToPath(new URL("./", import.meta.url)), + }, + }, +});