diff --git a/bun.lock b/bun.lock
index 518a511..d8ab72a 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,21 +5,22 @@
"dependencies": {
"@elysiajs/cors": "^1.4.0",
"@elysiajs/eden": "^1.4.4",
- "@tailwindcss/vite": "^4.1.14",
- "daisyui": "^5.3.7",
- "elysia": "^1.4.12",
+ "@tailwindcss/vite": "^4.1.16",
+ "daisyui": "^5.4.3",
+ "elysia": "^1.4.15",
"memoirist": "^0.4.0",
- "tailwindcss": "^4.1.14",
+ "ml-levenberg-marquardt": "^5.0.0",
+ "tailwindcss": "^4.1.16",
},
"devDependencies": {
- "@sveltejs/kit": "^2.47.1",
+ "@sveltejs/kit": "^2.48.4",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
- "@types/bun": "^1.3.0",
- "svelte": "^5.40.2",
+ "@types/bun": "^1.3.1",
+ "svelte": "^5.43.3",
"svelte-adapter-bun": "^0.5.2",
"svelte-check": "^4.3.3",
"typescript": "^5.9.3",
- "vite": "^7.1.10",
+ "vite": "^7.1.12",
},
},
},
@@ -150,41 +151,41 @@
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
- "@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="],
+ "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
- "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="],
+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="],
- "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="],
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="],
- "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="],
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="],
- "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="],
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="],
- "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="],
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="],
- "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="],
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="],
- "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="],
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="],
- "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="],
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="],
- "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="],
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="],
- "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="],
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="],
- "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="],
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="],
- "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="],
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="],
- "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="],
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
- "@tailwindcss/vite": ["@tailwindcss/vite@4.1.16", "", { "dependencies": { "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "tailwindcss": "4.1.16" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg=="],
+ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.17", "", { "dependencies": { "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "tailwindcss": "4.1.17" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA=="],
"@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
- "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
+ "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
@@ -200,7 +201,7 @@
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
- "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
+ "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
@@ -210,7 +211,7 @@
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
- "daisyui": ["daisyui@5.4.3", "", {}, "sha512-dfDCJnN4utErGoWfElgdEE252FtfHV9Mxj5Dq1+JzUq3nVkluWdF3JYykP0Xy/y/yArnPXYztO1tLNCow3kjmg=="],
+ "daisyui": ["daisyui@5.4.7", "", {}, "sha512-2wYO61vTPCXk7xEBgnzLZAYoE0xS5IRLu/GSq0vORpB+cTrtubdx69NnA0loc0exvCY1s2fYL4lGZtFHe2ohNQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@@ -250,6 +251,8 @@
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "is-any-array": ["is-any-array@2.0.1", "", {}, "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ=="],
+
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -286,6 +289,16 @@
"memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
+ "ml-array-max": ["ml-array-max@1.2.4", "", { "dependencies": { "is-any-array": "^2.0.0" } }, "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ=="],
+
+ "ml-array-min": ["ml-array-min@1.2.3", "", { "dependencies": { "is-any-array": "^2.0.0" } }, "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q=="],
+
+ "ml-array-rescale": ["ml-array-rescale@1.3.7", "", { "dependencies": { "is-any-array": "^2.0.0", "ml-array-max": "^1.2.4", "ml-array-min": "^1.2.3" } }, "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ=="],
+
+ "ml-levenberg-marquardt": ["ml-levenberg-marquardt@5.0.0", "", { "dependencies": { "is-any-array": "^2.0.1", "ml-matrix": "^6.12.1" } }, "sha512-vCFoO2DyYKGZFp+KxofN2cAvSc/3gufJHj/oaDHA2nV+NZH509fD7m35zQR2ZXA8fepxpVrwgCAQ0aAIsV2WtA=="],
+
+ "ml-matrix": ["ml-matrix@6.12.1", "", { "dependencies": { "is-any-array": "^2.0.1", "ml-array-rescale": "^1.3.7" } }, "sha512-TJ+8eOFdp+INvzR4zAuwBQJznDUfktMtOB6g/hUcGh3rcyjxbz4Te57Pgri8Q9bhSQ7Zys4IYOGhFdnlgeB6Lw=="],
+
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
@@ -316,13 +329,13 @@
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
- "svelte": ["svelte@5.43.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ=="],
+ "svelte": ["svelte@5.43.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-HQoZArIewxQVNedseDsgMgnRSC4XOXczxXLF9rOJaPIJkg58INOPUiL8aEtzqZIXNSZJyw8NmqObwg/voajiHQ=="],
"svelte-adapter-bun": ["svelte-adapter-bun@0.5.2", "", { "dependencies": { "tiny-glob": "^0.2.9" } }, "sha512-xEtFgaal6UgrCwwkSIcapO9kopoFNUYCYqyKCikdqxX9bz2TDYnrWQZ7qBnkunMxi1HOIERUCvTcebYGiarZLA=="],
"svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="],
- "tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="],
+ "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
@@ -340,15 +353,17 @@
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
- "vite": ["vite@7.1.12", "", { "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-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug=="],
+ "vite": ["vite@7.2.2", "", { "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-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ=="],
"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=="],
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
+ "@tailwindcss/node/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
@@ -359,5 +374,9 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"elysia/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
}
}
diff --git a/package.json b/package.json
index 60e504c..361c09a 100644
--- a/package.json
+++ b/package.json
@@ -3,12 +3,12 @@
"devDependencies": {
"@sveltejs/kit": "^2.48.4",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
- "@types/bun": "^1.3.1",
- "svelte": "^5.43.3",
+ "@types/bun": "^1.3.2",
+ "svelte": "^5.43.5",
"svelte-adapter-bun": "^0.5.2",
"svelte-check": "^4.3.3",
"typescript": "^5.9.3",
- "vite": "^7.1.12"
+ "vite": "^7.2.2"
},
"scripts": {
"dev": "vite dev",
@@ -21,10 +21,11 @@
"dependencies": {
"@elysiajs/cors": "^1.4.0",
"@elysiajs/eden": "^1.4.4",
- "@tailwindcss/vite": "^4.1.16",
- "daisyui": "^5.4.3",
+ "@tailwindcss/vite": "^4.1.17",
+ "daisyui": "^5.4.7",
"elysia": "^1.4.15",
"memoirist": "^0.4.0",
- "tailwindcss": "^4.1.16"
+ "ml-levenberg-marquardt": "^5.0.0",
+ "tailwindcss": "^4.1.17"
}
}
diff --git a/src/lib/components/StandardsTable.svelte b/src/lib/components/StandardsTable.svelte
index 09ee9c8..8280e87 100644
--- a/src/lib/components/StandardsTable.svelte
+++ b/src/lib/components/StandardsTable.svelte
@@ -1,131 +1,131 @@
-
-
-
- {#if selected.cfg.activity[selected.activity].enableGeneration && allStandards
- .byActivity(selected.activity)
- .getMetadata().generators.length}
- (Generated data: {allStandards
- .byActivity(selected.activity)
- .getMetadata()
- .generators.map((g) => g.metric)
- .join(", ")})
- {:else}
- (Generated data: NONE)
- {/if}
-
-
-
+
+
+
+ {#if selected.cfg.activity[selected.activity].enableGeneration && allStandards
+ .byActivity(selected.activity)
+ .getMetadata().generators.length}
+ (Generated data: {allStandards
+ .byActivity(selected.activity)
+ .getMetadata()
+ .generators.map((g) => g.metric)
+ .join(", ")})
+ {:else}
+ (Generated data: NONE)
+ {/if}
+
+
+
- {#if !selected.metrics.age}
- {#each allStandards.agesFor(selected.activity, selected.metrics.gender) as age}
- {@render standardsTable(selected.activity, {
- ...selected.metrics,
- age,
- })}
- {/each}
- {:else}
- {@render standardsTable(selected.activity, selected.metrics)}
- {/if}
+ {#if !selected.metrics.age}
+ {#each allStandards.agesFor(selected.activity, selected.metrics.gender) as age}
+ {@render standardsTable(selected.activity, {
+ ...selected.metrics,
+ age,
+ })}
+ {/each}
+ {:else}
+ {@render standardsTable(selected.activity, selected.metrics)}
+ {/if}
{#snippet standardsTable(activity: Activity, metrics: Metrics)}
- {#snippet standardsTableRow(standard: Standard)}
-
- |
- {standard.metrics.weight
- ? parseFloat(kgToLb(standard.metrics.weight).toFixed(2))
- : "None"} |
- {#each range(selected.cfg.global.maxLevel) as lvl}
- {getLvlValue(activity, standard, lvl)} |
- {/each}
-
- {/snippet}
-
-
{metrics.age}
-
-
-
-
- | Body weight |
- {#each range(selected.cfg.global.maxLevel) as lvl}
- {lvl} |
- {/each}
-
-
-
- {#if metrics.weight}
- {@const standard = allStandards
- .byActivity(activity)
- .byGender(metrics.gender)
- .byAge(metrics.age)
- .byWeight(metrics.weight)
- .getOneInterpolated()}
- {@render standardsTableRow(standard)}
- {:else}
- {@const standards = allStandards
- .byActivity(activity)
- .byGender(metrics.gender)
- .byAge(metrics.age)
- .getAllInterpolated({ normalizeForLb: true })}
- {#each standards as standard}
- {@render standardsTableRow(standard)}
- {/each}
- {/if}
-
-
-
-
+ {#snippet standardsTableRow(standard: Standard)}
+
+ |
+ {standard.metrics.weight
+ ? parseFloat(kgToLb(standard.metrics.weight).toFixed(2))
+ : "None"} |
+ {#each range(selected.cfg.global.maxLevel).map((i) => ++i) as lvl}
+ {getLvlValue(activity, standard, lvl)} |
+ {/each}
+
+ {/snippet}
+
+
{metrics.age}
+
+
+
+
+ | Body weight |
+ {#each range(selected.cfg.global.maxLevel).map((i) => ++i) as lvl}
+ {lvl} |
+ {/each}
+
+
+
+ {#if metrics.weight}
+ {@const standard = allStandards
+ .byActivity(activity)
+ .byGender(metrics.gender)
+ .byAge(metrics.age)
+ .byWeight(metrics.weight)
+ .getOneInterpolated()}
+ {@render standardsTableRow(standard)}
+ {:else}
+ {@const standards = allStandards
+ .byActivity(activity)
+ .byGender(metrics.gender)
+ .byAge(metrics.age)
+ .getAllInterpolated({ normalizeForLb: true })}
+ {#each standards as standard}
+ {@render standardsTableRow(standard)}
+ {/each}
+ {/if}
+
+
+
+
{/snippet}
diff --git a/src/lib/data/analyze.ts b/src/lib/data/analyze.ts
new file mode 100644
index 0000000..ae7a1af
--- /dev/null
+++ b/src/lib/data/analyze.ts
@@ -0,0 +1,92 @@
+import {
+ type ActivityStandards,
+ type Levels,
+} from "$lib/services/calculator/main";
+import { Activity, clamp, Gender, range } from "$lib/services/calculator/util";
+import allStandards from "./standards.json";
+import { levenbergMarquardt as LM } from "ml-levenberg-marquardt";
+
+const activityStandards = allStandards as ActivityStandards;
+
+for (const activity of Object.values(Activity)) {
+ const DATASET_MAX_LEVEL = 5;
+ const NEW_LEVEL_COUNT = 2;
+ const RATIO_CLAMP = 1.4;
+
+ function expDecayModel([A, B, C]: number[]) {
+ return (i: number) => A * Math.exp(-B * i) + C;
+ }
+
+ for (const gender of Object.values(Gender)) {
+ const standardsByGender = activityStandards[activity].standards.filter(
+ (s) => s["metrics"].gender === gender,
+ );
+ const ages = [...new Set(standardsByGender.map((s) => s.metrics.age))];
+ for (const age of ages) {
+ const standards = standardsByGender.filter(
+ (s) => s.metrics.age === age,
+ );
+
+ const data = {
+ x: [] as number[],
+ y: [] as number[],
+ };
+
+ const isIncreasing =
+ standards[0].levels["2"] > standards[0].levels["1"];
+
+ for (const i of range(DATASET_MAX_LEVEL).slice(0, -1)) {
+ const level = i + 1;
+ const progressionRatios: number[] = [];
+ for (const standard of standards) {
+ const curr = standard.levels[level];
+ const next = standard.levels[level];
+ progressionRatios.push(
+ isIncreasing ? next / curr : curr / next,
+ );
+ }
+
+ const sum = progressionRatios.reduce((p, c) => p + c, 0);
+ const progressionRatioAvg = sum / progressionRatios.length;
+
+ data.x.push(i);
+ data.y.push(progressionRatioAvg);
+ }
+
+ const fittedParams = LM(data, expDecayModel, {
+ initialValues: [0.4, 0.5, 1.1],
+ minValues: [0.0, 0.0, 1.02],
+ maxValues: [1.0, 2.0, 1.25],
+ maxIterations: 200,
+ });
+ const getDecayRatio = (i: number) =>
+ clamp(
+ expDecayModel(fittedParams.parameterValues)(i),
+ 1,
+ RATIO_CLAMP,
+ );
+
+ // UPDATE THE DATA
+ for (const standard of standards) {
+ const newLevels: Levels = {};
+
+ let prev = standard.levels["1"];
+ for (const i of range(NEW_LEVEL_COUNT)) {
+ const level = i + 1;
+ const ratio = getDecayRatio(level - NEW_LEVEL_COUNT);
+ prev = isIncreasing ? prev / ratio : prev * ratio;
+ newLevels[level] = Math.round(prev);
+ }
+
+ const oldLevels = standard.levels as Levels;
+ for (const i of range(DATASET_MAX_LEVEL)) {
+ const level = i + 1;
+ newLevels[level + NEW_LEVEL_COUNT] = oldLevels[level];
+ }
+ standard.levels = newLevels;
+ console.log(newLevels);
+ }
+ }
+ }
+}
+// Bun.write("./newStandards.json", JSON.stringify(allStandards, null, 2));
diff --git a/src/lib/services/calculator/main.ts b/src/lib/services/calculator/main.ts
index dbb6673..5f09ef0 100644
--- a/src/lib/services/calculator/main.ts
+++ b/src/lib/services/calculator/main.ts
@@ -1,14 +1,17 @@
import {
Activity,
Attribute,
+ clamp,
Gender,
getAvgWeight,
kgToLb,
lbToKg,
+ range,
type ActivityPerformance,
type Player,
type StandardUnit,
} from "./util";
+import { levenbergMarquardt as LM } from "ml-levenberg-marquardt";
// SOURCES
// Squat, Bench, Dead Lift:
@@ -217,6 +220,7 @@ export type StandardsConfig = {
enableGeneration: boolean;
difficultyModifier: number;
peakAge: number;
+ stretch: number;
}
>;
};
@@ -242,6 +246,7 @@ export class Standards {
difficultyModifier:
activity === Activity.BroadJump ? 0.05 : 0,
peakAge: 27,
+ stretch: 0,
},
]),
) as Record;
@@ -255,9 +260,104 @@ export class Standards {
// prepare data
this.activityStandards = structuredClone(activityStandards);
- for (const activity of Object.keys(
- this.activityStandards,
- ) as Activity[]) {
+
+ // STRETCH
+ for (const activity of Object.values(Activity)) {
+ const BASE_MAX_LEVEL = 5;
+ const newLevelCount = Math.max(
+ Math.round(this.cfg.activity[activity].stretch),
+ 0,
+ );
+
+ function expDecayModel([A, B, C]: number[]) {
+ return (i: number) => A * Math.exp(-B * i) + C;
+ }
+
+ for (const gender of Object.values(Gender)) {
+ const standardsByGender = this.activityStandards[
+ activity
+ ].standards.filter((s) => s["metrics"].gender === gender);
+ const ages = [
+ ...new Set(standardsByGender.map((s) => s.metrics.age)),
+ ];
+ for (const age of ages) {
+ const standards = standardsByGender.filter(
+ (s) => s.metrics.age === age,
+ );
+
+ const data = {
+ x: [] as number[],
+ y: [] as number[],
+ };
+
+ const isIncreasing =
+ standards[0].levels["2"] > standards[0].levels["1"];
+
+ for (const i of range(BASE_MAX_LEVEL).slice(0, -1)) {
+ const level = i + 1;
+ const progressionRatios: number[] = [];
+ for (const standard of standards) {
+ const curr = standard.levels[level];
+ const next = standard.levels[level + 1];
+ progressionRatios.push(
+ isIncreasing ? next / curr : curr / next,
+ );
+ }
+
+ const sum = progressionRatios.reduce(
+ (p, c) => p + c,
+ 0,
+ );
+ const progressionRatioAvg =
+ sum / progressionRatios.length;
+
+ data.x.push(i);
+ data.y.push(progressionRatioAvg);
+ }
+
+ const fittedParams = LM(data, expDecayModel, {
+ initialValues: [0.4, 0.5, 1.1],
+ minValues: [0.0, 0.0, 1.02],
+ maxValues: [1.0, 2.0, 1.2],
+ maxIterations: 200,
+ });
+ const getDecayRatio = (i: number) =>
+ expDecayModel(fittedParams.parameterValues)(i);
+
+ // UPDATE THE DATA
+ for (const standard of standards) {
+ const newLevels: Levels = {};
+
+ let prev = standard.levels["1"];
+ for (const i of range(newLevelCount).reverse()) {
+ const level = i + 1;
+ const ratio = getDecayRatio(-i);
+ prev = isIncreasing ? prev / ratio : prev * ratio;
+ newLevels[level] = Math.round(prev);
+ }
+
+ const oldLevels = standard.levels as Levels;
+ for (const i of range(BASE_MAX_LEVEL)) {
+ const level = i + 1;
+ newLevels[level + newLevelCount] = oldLevels[level];
+ }
+
+ prev = standard.levels[BASE_MAX_LEVEL];
+ for (const i of range(newLevelCount)) {
+ const level =
+ BASE_MAX_LEVEL + newLevelCount + (i + 1);
+ const ratio = getDecayRatio(level - 1);
+ prev = isIncreasing ? prev * ratio : prev / ratio;
+ newLevels[level] = Math.round(prev);
+ }
+ standard.levels = newLevels;
+ }
+ }
+ }
+ }
+
+ // EXPAND, COMPRESS, SKEW
+ for (const activity of Object.values(Activity)) {
for (const standard of this.activityStandards[activity].standards) {
// expand
let i = 1;
diff --git a/src/lib/services/calculator/util.ts b/src/lib/services/calculator/util.ts
index 72f00f8..1828b7c 100644
--- a/src/lib/services/calculator/util.ts
+++ b/src/lib/services/calculator/util.ts
@@ -66,7 +66,10 @@ export const msToTime = (ms: number, includeMs = false): string => {
};
export const range = (length: number) =>
- Array.from({ length: length }, (_, i) => i + 1);
+ Array.from({ length: length }, (_, i) => i);
+
+export const clamp = (x: number, lo: number, hi: number) =>
+ Math.max(lo, Math.min(hi, x));
export const getAvgWeight = (gender: Gender, age: number) => {
type AvgWeightData = {
diff --git a/src/lib/test/scratch.ts b/src/lib/test/scratch.ts
deleted file mode 100644
index e072977..0000000
--- a/src/lib/test/scratch.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-import {
- Activity,
- ftToCm,
- Gender,
- getAvgWeight,
- inToCm,
- kgToLb,
- lbToKg,
- minToMs,
- secToMs,
- type ActivityPerformance,
- type Player,
-} from "$lib/services/calculator/util";
-import PrintfulService from "$lib/services/commerce/printful";
-import SyncService from "$lib/services/commerce/sync";
-import { FetchError, type DeepPartial } from "$lib/services/commerce/util/misc";
-import type { Printful } from "$lib/services/commerce/util/types";
-import WebflowService from "$lib/services/commerce/webflow";
-
-const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
-
-const player: Player = {
- metrics: {
- age: 25,
- weight: lbToKg(190),
- gender: Gender.Male,
- },
-};
-
-const computedPerformances: ActivityPerformance[] = [
- // STRENGTH
- {
- activity: Activity.BenchPress,
- performance: lbToKg(225),
- },
- {
- activity: Activity.Deadlift,
- performance: lbToKg(270),
- },
- {
- activity: Activity.BackSquat,
- performance: lbToKg(190),
- },
- // POWER
- {
- activity: Activity.BroadJump,
- performance: ftToCm(105) + inToCm(5),
- },
- // ENDURANCE
- {
- activity: Activity.Run,
- performance: minToMs(7) + secToMs(15),
- },
- // AGILITY
- {
- activity: Activity.ConeDrill,
- performance: secToMs(9),
- },
-];
-
-const pOrders = await PrintfulService.Orders.getAll();
-console.log(pOrders);
-
-// wProduct.product.fieldData.name = "Rogue Title Hoodie [White] / XS";
-//
-// WebflowService.Products.update("68e66dc4633e577c91eda727",);
-
-// const printfulProducts = await PrintfulService.Products.getAll();
-// const webflowProducts = await WebflowService.Products.getAll();
-//
-// for (const printfulProduct of printfulProducts) {
-// if (!printfulProduct.name.includes("T-Shirt") || printfulProduct.name.includes("[White]") || printfulProduct.name.includes("[Black]")) {
-// continue;
-// }
-//
-// console.log(printfulProduct.name);
-//
-// const fullPrintfulProduct = await PrintfulService.Products.get(printfulProduct.id);
-// if (!fullPrintfulProduct) {
-// console.log("Could not get full product, skipped");
-// continue;
-// }
-//
-// const existingWebflowProduct = webflowProducts.find(p => fullPrintfulProduct.sync_product.name.includes(p.product.fieldData.name))
-// if (!existingWebflowProduct) {
-// console.log("Could not get associated webflow product, skipped");
-// continue;
-// }
-//
-// const newPrintfulVariants: DeepPartial[] = [];
-// for (const printfulVariant of fullPrintfulProduct.sync_variants) {
-// console.log(printfulVariant.name);
-// const associatedWebflowSku = existingWebflowProduct.skus
-// .find(sku => sku.fieldData["sku-values"]?.["color"] === SyncService.findColorInProductName(printfulVariant.name) &&
-// sku.fieldData["sku-values"]?.["size"] === printfulVariant.size);
-// if (associatedWebflowSku) {
-// console.log('Found associated webflow sku: ' + associatedWebflowSku.fieldData.name);
-// newPrintfulVariants.push({
-// "id": printfulVariant.id, // printful variant id
-// "external_id": String(associatedWebflowSku.id), // webflow variant id
-// });
-// }
-// else {
-// console.log("Could not get associated webflow sku");
-// }
-// }
-//
-// if (!newPrintfulVariants.length) {
-// console.log("No new printful variants, skipped");
-// continue;
-// }
-//
-// await sleep(3000);
-// try {
-// await PrintfulService.Products.update(printfulProduct.id, {
-// "sync_product": {
-// "id": printfulProduct.id,
-// "external_id": existingWebflowProduct.product.id + "-" + SyncService.findColorInProductName(printfulProduct.name)
-// },
-// "sync_variants": newPrintfulVariants
-// });
-// } catch (err) {
-// if (err instanceof FetchError) {
-// console.error(err.message, err.payload);
-// }
-// }
-// }
-// console.log("DONE");
-
-// await WebflowService.Products.update(wProduct?.product.id!, wProduct!);
-//
-// const sku = {
-// "fieldData": {
-// "name": "Test",
-// "slug": "test-test",
-// "sku-values": {
-// "color": "black",
-// "size": "L",
-// },
-// "price": {
-// "value": +0 * 100,
-// "unit": "USD",
-// },
-// "main-image": "https://cdn.prod.website-files.com/675a3fbe966e58ea9aa710e4/68d09d3d8e1e8bad26a9cf07_knight-title-tank-white-10.png"
-// }
-// }
-//
-//
-//
-// const skuId = "68d080a5d90e8263e52242e9";
-//
-// const colors = ["Black", "White", "Red"];
-
-// const sku = product.skus[0];
-//
-// sku.fieldData["more-images"] = [
-// {
-// "url": "https://website-media.bladeandbrawn.com/product-images/henry-iv-flag-black-10.png",
-// },
-// {
-// "url": "https://website-media.bladeandbrawn.com/product-images/henry-iv-flag-black-20.png",
-// },
-// ];
-
-// console.log("Fetching...");
-// const res = await fetch(`${Webflow.API_SITES_URL}/products/${productId}/skus/${skuId}`, {
-// method: "PATCH",
-// headers: {
-// "Content-Type": "application/json",
-// "Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`
-// },
-// body: JSON.stringify({
-// "sku": sku
-// })
-// });
-// console.log(res);
-//
-// const productAfter = (await Webflow.Products.getAll())[0];
-//
-// console.log(productAfter.skus[0].fieldData["more-images"]);
-
-// const newStandards: Standards = {
-// "Back Squat": [],
-// "Deadlift": [],
-// "Bench Press": [],
-// "Run": [],
-// "Dash": [],
-// "Treadmill Dash": [],
-// "Broad Jump": [],
-// "Cone Drill": [],
-// };
-//
-// for (const item of standards) {
-// if (!newStandards[item.activityType])
-// continue;
-//
-// newStandards[item.activityType].push({
-// metrics: {
-// age: item.age,
-// weight: item.bodyWeight,
-// gender: item.gender
-// },
-// levels: {
-// physicallyActive: item.physicallyActive,
-// beginner: item.beginner,
-// intermediate: item.intermediate,
-// advanced: item.advanced,
-// elite: item.elite
-// }
-// });
-// }
-//
-// Bun.write("./newStandards.json", JSON.stringify(newStandards, null, 2));
diff --git a/src/routes/calculator/+page.svelte b/src/routes/calculator/+page.svelte
index 7fbd538..4828642 100644
--- a/src/routes/calculator/+page.svelte
+++ b/src/routes/calculator/+page.svelte
@@ -50,6 +50,9 @@
peakAge: String(
defaultStandards.cfg.activity[activity].peakAge,
),
+ stretch: String(
+ defaultStandards.cfg.activity[activity].stretch,
+ ),
},
];
}),
@@ -80,6 +83,7 @@
difficultyModifier:
+input.cfg.activity[activity].difficultyModifier,
peakAge: +input.cfg.activity[activity].peakAge,
+ stretch: +input.cfg.activity[activity].stretch,
},
]),
),
@@ -143,6 +147,22 @@
bind:value={input.cfg.global.maxLevel}
/>
+
+