diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 65fc1f75891b4d43652b6ff749339626688ed2c5..8944e612c6ca61353e8899677e4e0b2a1949dcd4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,7 +24,6 @@ check:
     - tags
   only:
     - merge_requests
-    - /^release\/.+$/
     - main
   script: task check
 
@@ -34,7 +33,6 @@ test:
     - tags
   only:
     - merge_requests
-    - /^release\/.+$/
     - main
   script: task test
 
@@ -48,30 +46,22 @@ a11y:
   except:
     - tags
   only:
-    - /^release\/.+$/
     - main
   script: task a11y
 
-merge_release_preview:
-  stage: merge
-  only:
-    - merge_requests
-  script: task release:preview
-
 publish-dev:
   stage: publish
+  when: manual
   only:
-    - /^release\/.+$/
+    - merge_requests
   script:
     - git config user.email "gitlab@s-v.de"
     - git config user.name "CI"
     - git remote set-url origin https://oauth2:$GIT_PUSH_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git
-    - git checkout "$CI_COMMIT_REF_NAME"
-    - git reset --hard origin/"$CI_COMMIT_REF_NAME"
-    - git fetch --prune --prune-tags -f
     - bun x npm config set //gitlab.s-v.de/api/v4/projects/1560/packages/npm/:_authToken="$CI_JOB_TOKEN"
     - bun x npm config list
     - task prerelease
+    - git push origin -o ci.skip origin $CI_COMMIT_REF_NAME && git push --tags
 
 publish:
   stage: publish
@@ -83,12 +73,10 @@ publish:
     - git config user.email "gitlab@s-v.de"
     - git config user.name "CI"
     - git remote set-url origin https://oauth2:$GIT_PUSH_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git
-    - git checkout "$CI_COMMIT_REF_NAME"
-    - git reset --hard origin/"$CI_COMMIT_REF_NAME"
-    - git fetch --prune --prune-tags -f
     - bun x npm config set //gitlab.s-v.de/api/v4/projects/1560/packages/npm/:_authToken="$CI_JOB_TOKEN"
     - bun x npm config list
     - task release
+    - git push origin -o ci.skip origin $CI_COMMIT_REF_NAME && git push --tags
 
 pages:
   stage: docs
diff --git a/bun.lock b/bun.lock
index 845b0acd7b9f7cd8435708b99f1d8f5ae472a040..bb4d956f4fc6c3612082e61134a04fbfe9ed73aa 100644
--- a/bun.lock
+++ b/bun.lock
@@ -11,7 +11,7 @@
         "@types/web": "^0.0.123",
         "fast-glob": "^3.3.2",
         "npm": "^10.7.0",
-        "tsup": "^8.1.0",
+        "tsup": "^8.4.0",
         "turbo": "^2.0.1",
       },
     },
@@ -61,7 +61,7 @@
     },
     "packages/components": {
       "name": "@sv/components",
-      "version": "1.7.3",
+      "version": "1.8.1",
       "bin": "./bin/cli.js",
       "dependencies": {
         "chalk": "^5.3.0",
@@ -77,7 +77,7 @@
     },
     "packages/elements": {
       "name": "@sv/elements",
-      "version": "2.2.0",
+      "version": "2.4.2-dev.1",
       "dependencies": {
         "lit": "^3.1.3",
       },
@@ -321,55 +321,55 @@
 
     "@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="],
 
-    "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+    "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ=="],
 
-    "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+    "@esbuild/android-arm": ["@esbuild/android-arm@0.25.0", "", { "os": "android", "cpu": "arm" }, "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g=="],
 
-    "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+    "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.0", "", { "os": "android", "cpu": "arm64" }, "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g=="],
 
-    "@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+    "@esbuild/android-x64": ["@esbuild/android-x64@0.25.0", "", { "os": "android", "cpu": "x64" }, "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg=="],
 
-    "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+    "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw=="],
 
-    "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+    "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg=="],
 
-    "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+    "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w=="],
 
-    "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+    "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A=="],
 
-    "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+    "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.0", "", { "os": "linux", "cpu": "arm" }, "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg=="],
 
-    "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+    "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg=="],
 
-    "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+    "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg=="],
 
-    "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+    "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.0", "", { "os": "linux", "cpu": "none" }, "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw=="],
 
-    "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+    "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.0", "", { "os": "linux", "cpu": "none" }, "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ=="],
 
-    "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+    "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw=="],
 
-    "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+    "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.0", "", { "os": "linux", "cpu": "none" }, "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA=="],
 
-    "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+    "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA=="],
 
-    "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+    "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw=="],
 
-    "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+    "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.0", "", { "os": "none", "cpu": "arm64" }, "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw=="],
 
-    "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+    "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.0", "", { "os": "none", "cpu": "x64" }, "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA=="],
 
-    "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+    "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw=="],
 
-    "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+    "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg=="],
 
-    "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+    "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg=="],
 
-    "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+    "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw=="],
 
-    "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+    "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA=="],
 
-    "@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+    "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ=="],
 
     "@expressive-code/core": ["@expressive-code/core@0.40.2", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-gXY3v7jbgz6nWKvRpoDxK4AHUPkZRuJsM79vHX/5uhV9/qX6Qnctp/U/dMHog/LCVXcuOps+5nRmf1uxQVPb3w=="],
 
@@ -1161,7 +1161,7 @@
 
     "esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
 
-    "esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+    "esbuild": ["esbuild@0.25.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.0", "@esbuild/android-arm": "0.25.0", "@esbuild/android-arm64": "0.25.0", "@esbuild/android-x64": "0.25.0", "@esbuild/darwin-arm64": "0.25.0", "@esbuild/darwin-x64": "0.25.0", "@esbuild/freebsd-arm64": "0.25.0", "@esbuild/freebsd-x64": "0.25.0", "@esbuild/linux-arm": "0.25.0", "@esbuild/linux-arm64": "0.25.0", "@esbuild/linux-ia32": "0.25.0", "@esbuild/linux-loong64": "0.25.0", "@esbuild/linux-mips64el": "0.25.0", "@esbuild/linux-ppc64": "0.25.0", "@esbuild/linux-riscv64": "0.25.0", "@esbuild/linux-s390x": "0.25.0", "@esbuild/linux-x64": "0.25.0", "@esbuild/netbsd-arm64": "0.25.0", "@esbuild/netbsd-x64": "0.25.0", "@esbuild/openbsd-arm64": "0.25.0", "@esbuild/openbsd-x64": "0.25.0", "@esbuild/sunos-x64": "0.25.0", "@esbuild/win32-arm64": "0.25.0", "@esbuild/win32-ia32": "0.25.0", "@esbuild/win32-x64": "0.25.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw=="],
 
     "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
 
@@ -2193,7 +2193,7 @@
 
     "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
 
-    "tsup": ["tsup@8.3.6", "", { "dependencies": { "bundle-require": "^5.0.0", "cac": "^6.7.14", "chokidar": "^4.0.1", "consola": "^3.2.3", "debug": "^4.3.7", "esbuild": "^0.24.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.24.0", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.1", "tinyglobby": "^0.2.9", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-XkVtlDV/58S9Ye0JxUUTcrQk4S+EqlOHKzg6Roa62rdjL1nGWNUstG0xgI4vanHdfIpjP448J8vlN0oK6XOJ5g=="],
+    "tsup": ["tsup@8.4.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ=="],
 
     "tsutils": ["tsutils@3.21.0", "", { "dependencies": { "tslib": "^1.8.1" }, "peerDependencies": { "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA=="],
 
@@ -2479,6 +2479,8 @@
 
     "@vue/devtools-core/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
 
+    "astro/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+
     "astro/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 
     "astro/vite": ["vite@6.1.1", "", { "dependencies": { "esbuild": "^0.24.2", "postcss": "^8.5.2", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "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-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA=="],
@@ -2995,6 +2997,10 @@
 
     "yaml-language-server/yaml": ["yaml@2.2.2", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="],
 
+    "@astrojs/react/vite/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+
+    "@astrojs/solid-js/vite/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+
     "@astrojs/starlight-tailwind/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
 
     "@astrojs/starlight-tailwind/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -3005,6 +3011,8 @@
 
     "@astrojs/tailwind/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
 
+    "@astrojs/vue/vite/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+
     "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
 
     "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
@@ -3021,6 +3029,62 @@
 
     "@sv/svg-sprites/tsup/rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="],
 
+    "@tailwindcss/vite/vite/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+
+    "@vitejs/plugin-vue/vite/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
+
+    "astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+
+    "astro/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+
+    "astro/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+
+    "astro/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+
+    "astro/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+
+    "astro/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+
+    "astro/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+
+    "astro/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+
+    "astro/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+
+    "astro/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+
+    "astro/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+
+    "astro/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+
+    "astro/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+
+    "astro/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+
+    "astro/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+
+    "astro/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+
+    "astro/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+
+    "astro/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+
+    "astro/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+
+    "astro/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+
+    "astro/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+
+    "astro/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+
+    "astro/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+
+    "astro/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+
+    "astro/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+
     "boxen/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
 
     "boxen/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
@@ -4779,6 +4843,106 @@
 
     "yaml-language-server/vscode-languageserver/vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.16.0", "", { "dependencies": { "vscode-jsonrpc": "6.0.0", "vscode-languageserver-types": "3.16.0" } }, "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A=="],
 
+    "@astrojs/react/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+
+    "@astrojs/react/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+
+    "@astrojs/solid-js/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+
     "@astrojs/starlight-tailwind/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
 
     "@astrojs/starlight-tailwind/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
@@ -4787,6 +4951,56 @@
 
     "@astrojs/tailwind/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
 
+    "@astrojs/vue/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+
+    "@astrojs/vue/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+
     "@sv/svg-sprites/tsup/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
 
     "@sv/svg-sprites/tsup/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="],
@@ -4837,6 +5051,156 @@
 
     "@sv/svg-sprites/tsup/postcss-load-config/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
 
+    "@tailwindcss/vite/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+
+    "@tailwindcss/vite/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+
+    "@vitejs/plugin-vue-jsx/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
+
+    "@vitejs/plugin-vue/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
+
     "boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
 
     "colorspace/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
diff --git a/docs/src/content/docs/elements/a-track.mdx b/docs/src/content/docs/elements/a-track.mdx
index 965592e9e4ec80fcd26dbaeb44de881c307562d3..fc56023f891e4c64c0e2ac38d514bbead4896876 100644
--- a/docs/src/content/docs/elements/a-track.mdx
+++ b/docs/src/content/docs/elements/a-track.mdx
@@ -65,7 +65,6 @@ Implemented with [Slider](/atrium/components/slider) and [Drawer](/atrium/compon
 
 <TypeDoc source="../packages/elements/packages/track" symbol="Track" />
 <TypeDoc source="../packages/elements/packages/track" symbol="Trait" />
-<TypeDoc source="../packages/elements/packages/track" symbol="PointerTrait" />
 <TypeDoc source="../packages/elements/packages/track" symbol="SnapTrait" />
 
 Different example confugrations of tracks.
@@ -73,7 +72,7 @@ Different example confugrations of tracks.
 ### Free scroll
 
 <div class="not-content box">
-  <a-track overflowscroll class="flex">
+  <a-track class="flex overflow-visible">
     <a href="#" class="block flex-none pr-4">
       <canvas width="260" height="280" class="bg-slate-500" />
     </a>
@@ -110,7 +109,7 @@ Different example confugrations of tracks.
 ### Snap scroll
 
 <div class="not-content box">
-  <a-track snap class="flex">
+  <a-track snap class="flex overflow-visible">
     <a href="#" class="block flex-none pr-4">
       <canvas width="260" height="280" class="bg-slate-500" />
     </a>
@@ -159,7 +158,7 @@ Different example confugrations of tracks.
 ### Align center
 
 <div class="not-content box">
-  <a-track align="center" snap class="flex">
+  <a-track align="center" snap class="flex overflow-visible">
     <a href="#" class="block flex-none pr-4">
       <canvas width="260" height="280" class="bg-slate-500" />
     </a>
@@ -237,31 +236,31 @@ Different example confugrations of tracks.
         class="flex h-[450px] w-[250px] flex-col overflow-hidden"
         vertical align="center">
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
     <div class="flex-none py-2 font-bold">
-      <canvas width="200" height="80" class="bg-slate-500" />
+      <canvas width="200" height="200" class="bg-slate-500" />
     </div>
   </a-track>
 </div>
@@ -291,33 +290,33 @@ track.addEventListener("format", e => {
       <canvas width="260" height="280" class="bg-slate-500" />
     </div>
     <div class="flex-none pr-4">
-      <a-track vertical snap class="flex h-[280px] flex-col overflow-hidden">
+      <a-track align="center" vertical snap class="flex h-[280px] flex-col overflow-hidden">
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
         <div class="flex-none pb-2 font-bold">
-          <canvas width="200" height="64" class="bg-slate-500" />
+          <canvas width="200" height="180" class="bg-slate-500" />
         </div>
       </a-track>
     </div>
diff --git a/docs/src/content/docs/elements/a-track/Track.tsx b/docs/src/content/docs/elements/a-track/Track.tsx
index 79acbf19bdf2fa221a94657b57b3f4eff60b26c7..ac9692e1a62cb76beddec47988eb747ae6f4549e 100644
--- a/docs/src/content/docs/elements/a-track/Track.tsx
+++ b/docs/src/content/docs/elements/a-track/Track.tsx
@@ -52,7 +52,7 @@ export const VariableTrack = defineComponent(() => {
             />
           </div>
         </div>
-        <a-track ref={track} overflowscroll snap class="flex max-w-[100vw]" debug>
+        <a-track ref={track} snap class="flex max-w-[100vw] overflow-visible" debug>
           {new Array(count.value || 1).fill(1).map((_, i) => {
             return (
               <div class="counted flex-none pr-2" key={i}>
diff --git a/docs/src/custom.css b/docs/src/custom.css
index 4548f2a514270fe365602614edf4dad7661dc02b..a912e564312b1e9106e25a40161ef665ea31f45a 100644
--- a/docs/src/custom.css
+++ b/docs/src/custom.css
@@ -82,6 +82,7 @@ table {
 
 .not-content {
   counter-reset: content-counter;
+  overflow: hidden;
 }
 
 .counted {
diff --git a/knope.toml b/knope.toml
index 6e879236b436c368b802af81dd3a915323bcd7b0..31bfa316257f22ad071741c1b0786a24be2ce2b5 100644
--- a/knope.toml
+++ b/knope.toml
@@ -48,11 +48,6 @@ type = "Command"
 command = "task publish-packages"
 shell = true
 
-[[workflows.steps]]
-type = "Command"
-command = "git push origin -o ci.skip && git push --tags"
-shell = true
-
 [[workflows]]
 name = "document-change"
 
diff --git a/package.json b/package.json
index 556691958836e6a8a3fb477c8c8f3c93a81fc559..600774d823bb88f5bd60cb6c43ac36c7a8dc2d82 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
 		"@types/web": "^0.0.123",
 		"fast-glob": "^3.3.2",
 		"npm": "^10.7.0",
-		"tsup": "^8.1.0",
+		"tsup": "^8.4.0",
 		"turbo": "^2.0.1"
 	},
 	"packageManager": "bun@1.1.12",
diff --git a/packages/components/src/vue/Drawer.tsx b/packages/components/src/vue/Drawer.tsx
index 0dc84ac5633522d787fb4a923caac99865964cb2..89f106a83ad360b65c33d3a67d089d47bc7665cc 100644
--- a/packages/components/src/vue/Drawer.tsx
+++ b/packages/components/src/vue/Drawer.tsx
@@ -1,12 +1,6 @@
 /* @jsxImportSource vue */
 import { defineComponent, ref, onMounted, effect, nextTick } from "vue";
-import {
-  PointerTrait,
-  Track,
-  type InputState,
-  type Easing,
-  type Trait,
-} from "@sv/elements/track";
+import { Track, type InputState, type Easing, type Trait } from "@sv/elements/track";
 import { Button } from "./Button";
 import { Icon } from "./Icon";
 
@@ -121,7 +115,6 @@ export const Drawer = defineComponent(
 
 export class DrawerTrack extends Track {
   public traits: Trait[] = [
-    new PointerTrait(),
     {
       id: "drawer",
       input(track: DrawerTrack, inputState: InputState) {
@@ -139,7 +132,7 @@ export class DrawerTrack extends Track {
         if (track.deltaVelocity.y >= 0) return;
         if (track.isStatic) return;
 
-        const vel = Math.round(track.lastVelocity[track.currentAxis] * 10) / 10;
+        const vel = Math.round(track.velocity[track.currentAxis] * 10) / 10;
         const power = Math.round(vel / 15);
 
         if (power < 0) {
diff --git a/packages/components/src/vue/Tabs.tsx b/packages/components/src/vue/Tabs.tsx
index 94631e248c34fcfd89efbfcedec75834facebbe0..800e659053a7e8d828e6af84981c768054022caf 100644
--- a/packages/components/src/vue/Tabs.tsx
+++ b/packages/components/src/vue/Tabs.tsx
@@ -20,7 +20,7 @@ export const Tabs = defineComponent(
 
     return () => (
       <div class="w-full p-1">
-        <a-track overflowscroll>
+        <a-track>
           <ul class="flex list-none gap-1 p-0">
             {slots.default?.()?.map((item, i) => {
               return (
diff --git a/packages/elements/CHANGELOG.md b/packages/elements/CHANGELOG.md
index 035d321656356461b390aef49262d131713280a8..dac9795ed10c4fc317650ddd35900ad5862bb280 100644
--- a/packages/elements/CHANGELOG.md
+++ b/packages/elements/CHANGELOG.md
@@ -1,5 +1,63 @@
 # @sv/elements
 
+## 0.0.1-dev.3 (2025-03-09)
+
+### Fixes
+
+- package version
+
+## 0.0.1-dev.2 (2025-03-09)
+
+### Fixes
+
+- Grab wont allow clicking on items
+
+## 0.0.1-dev.1 (2025-03-09)
+
+### Fixes
+
+- Grab wont allow clicking on items
+
+## 0.0.1-dev.0 (2025-03-09)
+
+### Fixes
+
+- Grab wont allow clicking on items
+
+## 2.4.2-dev.3 (2025-03-08)
+
+### Fixes
+
+- Improve track behaviour on different variable hardware
+- Fix check
+- Bug whre snap with align center did not snap to the last item
+- Grab wont allow clicking on items
+
+## 2.4.2-dev.2 (2025-03-08)
+
+### Fixes
+
+- Improve track behaviour on different variable hardware
+- Fix check
+- Bug whre snap with align center did not snap to the last item
+- Grab wont allow clicking on items
+
+## 2.4.2-dev.1 (2025-03-08)
+
+### Fixes
+
+- Improve track behaviour on different variable hardware
+- Fix check
+- Bug whre snap with align center did not snap to the last item
+
+## 2.4.2-dev.0 (2025-03-08)
+
+### Fixes
+
+- Improve track behaviour on different variable hardware
+- Fix check
+- Bug whre snap with align center did not snap to the last item
+
 ## 2.4.1 (2025-03-04)
 
 ### Fixes
diff --git a/packages/elements/package.json b/packages/elements/package.json
index 1425f81f47dc952add4df1914704da9881204730..1398670110aebe79f1f432cc862efe498c841113 100644
--- a/packages/elements/package.json
+++ b/packages/elements/package.json
@@ -4,12 +4,12 @@
   "contributors": [
     "luckydye"
   ],
-  "version": "2.4.1",
+  "version": "2.4.2-dev.4",
   "description": "Atrium Elements",
   "type": "module",
   "scripts": {
     "check": "tsc --pretty",
-    "test": "bun test --rerun-each 5",
+    "test": "bun test --rerun-each 3 --coverage",
     "publish-package": "task publish"
   },
   "files": [
@@ -132,4 +132,4 @@
       "types": "./packages/box/src/index.ts"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/packages/elements/packages/track/src/Track.ts b/packages/elements/packages/track/src/Track.ts
index dba6ca729230a8dccdcb18fa851a3e9d975c47f9..d70b47886a93dde2aec791a4fcd2079a3a754150 100644
--- a/packages/elements/packages/track/src/Track.ts
+++ b/packages/elements/packages/track/src/Track.ts
@@ -1,6 +1,51 @@
 import { LitElement, type PropertyValues, css, html } from "lit";
 import { property } from "lit/decorators/property.js";
-import { Vec2 } from "./Vec.js";
+// import { DebugTrait } from "./debug.js";
+
+const PI2 = Math.PI * 2;
+
+export class MoveEvent extends CustomEvent<{
+  delta: Vec2;
+  direction: Vec2;
+  velocity: Vec2;
+  position: Vec2;
+}> {
+  constructor(track: Track, delta: Vec2) {
+    super("move", {
+      cancelable: true,
+      detail: {
+        delta: delta,
+        direction: track.direction.clone(),
+        velocity: track.deltaPosition.clone(),
+        position: track.position.clone(),
+      },
+    });
+  }
+}
+
+export type InputState = {
+  grab: {
+    value: boolean;
+  };
+  scroll: {
+    value: boolean;
+  };
+  move: {
+    value: Vec2;
+  };
+  release: {
+    value: boolean;
+  };
+  format: {
+    value: boolean;
+  };
+  leave: {
+    value: boolean;
+  };
+  enter: {
+    value: boolean;
+  };
+};
 
 /**
  * The Track implements a trait system, which can be used to add new behaviours to the track.
@@ -10,11 +55,10 @@ import { Vec2 } from "./Vec.js";
  * Or the Track class can be extended to override add new behaviours entirely.
  * @example
  * ```js
- * import { type InputState, PointerTrait, Track, type Trait } from "@sv/elements/track";
+ * import { type InputState, Track, type Trait } from "@sv/elements/track";
  *
  * export class CustomTrack extends Track {
  *   public traits: Trait[] = [
- *     new PointerTrait(),
  *     // satefies the "Trait" interface
  *     {
  *       id: "custom-trait",
@@ -48,149 +92,9 @@ export interface Trait<T extends Track = Track> {
 
   /** update tick (fixed tickrate) */
   update?(track: T): void;
-}
-
-/**
- * The PointerTrait addes the ability to move the track with the mouse or touch inputs by dragging.
- *
- * @example
- * ```js
- * new PointerTrait({
- *   borderBounce?: number;
- *   borderResistance?: number;
- * })
- * ```
- */
-export class PointerTrait implements Trait {
-  id = "pointer";
-
-  grabbing = false;
-  grabbedStart = new Vec2();
-  grabDelta = new Vec2();
-
-  borderBounce = 0.1;
-  borderResistance = 0.3;
-
-  moveDrag = 0.5;
-
-  constructor(
-    options: {
-      borderBounce?: number;
-      borderResistance?: number;
-    } = {},
-  ) {
-    this.borderBounce = options.borderBounce ?? this.borderBounce;
-    this.borderResistance = options.borderResistance ?? this.borderResistance;
-  }
-
-  moveVelocity = new Vec2();
-
-  input(track: Track, inputState: InputState) {
-    if (inputState.grab.value && !this.grabbing) {
-      this.grabbing = true;
-      this.grabbedStart.set(track.mousePos);
-      track.dispatchEvent(new Event("pointer:grab"));
-      track.setTarget(undefined);
-    }
-
-    if (track.mousePos.abs()) {
-      this.grabDelta.set(track.mousePos).sub(this.grabbedStart);
-    }
-
-    // TODO: might want to give every trait a "inputForce", so I dont have to mutate the tracks fields.
 
-    if (this.grabbing) {
-      if (inputState.move.value.abs()) {
-        this.moveVelocity.add(inputState.move.value);
-        track.inputForce.set(inputState.move.value.clone().mul(-1));
-      } else {
-        track.inputForce.mul(0);
-      }
-    }
-
-    if (inputState.release.value) {
-      this.grabbing = false;
-      track.dispatchEvent(new Event("pointer:release"));
-
-      track.inputForce.set(this.moveVelocity.clone().mul(-1));
-    }
-
-    // prevent moving in wrong direction
-    if (track.vertical) {
-      track.inputForce.x = 0;
-    } else {
-      track.inputForce.y = 0;
-    }
-
-    if (track.slotElement) {
-      track.slotElement.inert = this.grabbing;
-    }
-    if (!isTouch()) track.style.cursor = this.grabbing ? "grabbing" : "";
-  }
-
-  update(track: Track) {
-    this.moveVelocity.mul(this.moveDrag);
-
-    if (this.grabbing) {
-      track.drag = 0;
-    } else {
-      track.drag = 0.95;
-    }
-
-    // clamp input force
-    const pos = Vec2.add(track.position, track.inputForce);
-    const clamped = this.getClapmedPosition(track, pos);
-    const diff = Vec2.sub(pos, clamped);
-
-    if (!track.loop && diff.abs() && this.grabbing) {
-      const resitance = this.borderResistance * (1 - diff.abs() / 200);
-
-      if (track.vertical) {
-        track.inputForce.mul(resitance);
-      } else {
-        track.inputForce.mul(resitance);
-      }
-    }
-
-    const bounce = this.borderBounce;
-    if (!track.loop && bounce && diff.abs() && !this.grabbing) {
-      if ((track.vertical && Math.abs(diff.y)) || Math.abs(diff.x)) {
-        track.inputForce.sub(diff.mul(bounce));
-        track.acceleration.mul(0);
-      }
-    }
-  }
-
-  getClapmedPosition(e: Track, pos: Vec2) {
-    let clampedPos = pos;
-
-    const bounds = e.scrollBounds;
-
-    switch (e.align) {
-      case "center":
-        clampedPos = new Vec2(
-          Math.min(bounds.right, clampedPos.x),
-          Math.min(bounds.bottom, clampedPos.y),
-        );
-        clampedPos = new Vec2(
-          Math.max(bounds.left, clampedPos.x),
-          Math.max(bounds.top, clampedPos.y),
-        );
-        break;
-      default:
-        clampedPos = new Vec2(
-          Math.min(bounds.right, clampedPos.x),
-          Math.min(bounds.bottom, clampedPos.y),
-        );
-        clampedPos = new Vec2(
-          Math.max(bounds.left, clampedPos.x),
-          Math.max(bounds.top, clampedPos.y),
-        );
-        break;
-    }
-
-    return clampedPos;
-  }
+  /** draw (variable tickrate) */
+  draw?(track: T): void;
 }
 
 /**
@@ -215,10 +119,11 @@ export class SnapTrait implements Trait {
   }
 
   input(track: Track) {
-    if (track.grabbing || track.target) return;
+    if (track.interacting || track.target) return;
 
-    // Only when decelerating
-    if (track.deltaVelocity[track.currentAxis] > 0) return;
+    // only when decelerating, but also when not moving
+    const movement = track.velocity.clone().precision(0.1).abs();
+    if (movement !== 0 && movement < 0.1) return;
 
     switch (track.align) {
       case "center":
@@ -235,24 +140,31 @@ export class SnapTrait implements Trait {
     }
 
     // Project the current velocity to determine the target item.
-    // This checks lastVelocity, because I don't know why velocity is 0,0 at random points.
-    const vel = Math.round(track.lastVelocity[track.currentAxis] * 10) / 10;
+    const vel = Math.round(track.velocity[track.currentAxis] * 10) / 10;
     const dir = Math.sign(vel);
-    const power = Math.max(Math.round(track.lastVelocity.abs() / 40), 1) * dir;
+    const power = Math.max(Math.round(track.velocity.abs() / 40), 1) * dir;
 
     if (!track.loop) {
       // disable snap when past maxIndex
-      if (track.maxIndex && power > 0 && track.currentIndex + power >= track.maxIndex)
+      if (track.maxIndex && power > 0 && track.currentIndex + power > track.maxIndex) {
+        // instead, snap to the end
+        track.setTarget(track.getToItemPosition(track.itemCount - 1), "linear");
         return;
+      }
     }
 
     if (Math.abs(vel) > 8) {
+      // apply inertia to snap target
       track.acceleration.mul(0.25);
       track.inputForce.mul(0.125);
       track.setTarget(track.getToItemPosition(track.currentItem + power), "linear");
     } else {
       track.setTarget(track.getToItemPosition(track.currentItem), "linear");
     }
+
+    if (!track.target) {
+      throw new Error("Track target not set, but snap");
+    }
   }
 }
 
@@ -298,24 +210,24 @@ function getCSSChildren(element: Element) {
  *
  */
 export class Track extends LitElement {
-  static Vec2 = Vec2;
-
   static get styles() {
     return css`
       :host {
         display: flex;
         outline: none;
         overflow: hidden;
+        touch-action: pan-y;
+        overscroll-behavior: none;
+        scrollbar-width: none;
       }
-
-      .debug {
-        position: absolute;
-        z-index: 100;
-        background: rgba(0, 0, 0, 0.5);
-        backdrop-filter: blur(4px);
+      :host([vertical]) {
+        flex-direction: column;
+      }
+      :host([vertical]) {
+        touch-action: pan-x;
       }
-
       slot {
+        will-change: transform;
         display: inherit;
         flex-direction: inherit;
         flex-flow: inherit;
@@ -324,33 +236,84 @@ export class Track extends LitElement {
         will-change: transform;
         min-width: 100%;
       }
-
-      :host([vertical]) {
-        flex-direction: column;
-      }
-
-      :host {
-        touch-action: pan-y;
-      }
-
-      :host([vertical]) {
-        touch-action: pan-x;
-      }
-
-      :host {
-        overscroll-behavior: none;
-        scrollbar-width: none;
-      }
-
       ::-webkit-scrollbar {
         width: 0px;
         height: 0px;
         background: transparent;
         display: none;
       }
+      .debug {
+        position: absolute;
+        z-index: 10;
+        pointer-events: none;
+      }
     `;
   }
 
+  constructor() {
+    super();
+
+    // TODO: put this in a trait so it can be disabled
+    // TODO: sync it to screen?
+    this.addEventListener("wheel", this.onWheel, { passive: false });
+
+    this.addEventListener("focusin", this.onFocusIn);
+
+    this.addEventListener("keydown", this.onKeyDown);
+
+    this.addEventListener("pointerdown", this.onPointerDown);
+
+    this.addEventListener("pointerleave", () => {
+      this.inputState.leave.value = true;
+    });
+
+    this.addEventListener("pointerenter", () => {
+      this.inputState.enter.value = true;
+    });
+  }
+
+  connectedCallback(): void {
+    super.connectedCallback();
+
+    this.updateItems();
+
+    this.ariaRoleDescription = "carousel";
+    this.role = "region";
+
+    this.listener(window, "pointermove", this.onPointerMove);
+    this.listener(window, ["pointerup", "pointercancel"], this.onPointerUpOrCancel);
+
+    const intersectionObserver = new IntersectionObserver((intersections) => {
+      for (const entry of intersections) {
+        if (entry.isIntersecting) {
+          this.startAnimate();
+        } else {
+          this.stopAnimate();
+        }
+      }
+    });
+
+    this.addController({
+      hostConnected: () => intersectionObserver.observe(this),
+      hostDisconnected: () => intersectionObserver.disconnect(),
+    });
+
+    // TODO: diff the container size and appliy to position of track
+    this.resizeObserver = new ResizeObserver(debounce(() => this.onFormat()));
+
+    this.addController({
+      hostConnected: () => this.resizeObserver?.observe(this),
+      hostDisconnected: () => this.resizeObserver?.disconnect(),
+    });
+
+    this.computeCurrentItem();
+  }
+
+  disconnectedCallback(): void {
+    this.stopAnimate();
+    super.disconnectedCallback();
+  }
+
   render() {
     return html`
       <slot @slotchange=${this.onSlotChange}></slot>
@@ -362,7 +325,9 @@ export class Track extends LitElement {
     return this.shadowRoot?.children?.[0] as HTMLSlotElement | undefined;
   }
 
-  public traits: Trait[] = [new PointerTrait()];
+  public traits: Trait[] = [
+    // new DebugTrait()
+  ];
 
   protected updated(_changedProperties: PropertyValues): void {
     if (_changedProperties.has("current") && this.current !== undefined) {
@@ -379,7 +344,7 @@ export class Track extends LitElement {
     }
 
     if (_changedProperties.has("align")) {
-      this.format();
+      this.onFormat();
     }
   }
 
@@ -391,7 +356,7 @@ export class Track extends LitElement {
 
   private updateItems() {
     this._children = getCSSChildren(this);
-    this.format();
+    this.onFormat();
   }
 
   public get itemCount() {
@@ -408,29 +373,6 @@ export class Track extends LitElement {
       .map((_, i) => new Vec2(this.itemWidths[i], this.itemHeights[i]));
   }
 
-  /**
-   * Get the absolute position of the closest item to the current position.
-   * @argument offset - offset the position by a number
-   */
-  public getClosestItemPosition(offset = 0) {
-    const posPrev = this.getToItemPosition(this.currentItem + -offset);
-    const posCurr = this.getToItemPosition(this.currentItem + offset);
-    const posNext = this.getToItemPosition(this.currentItem + (offset + 1));
-
-    const prev = posPrev.clone().sub(this.position).abs();
-    const curr = posCurr.clone().sub(this.position).abs();
-    const next = posNext.clone().sub(this.position).abs();
-
-    switch (Math.min(curr, next, prev)) {
-      case prev:
-        return posPrev;
-      case next:
-        return posNext;
-      default:
-        return posCurr;
-    }
-  }
-
   private _widths: number[] | undefined = undefined;
   private get itemWidths() {
     if (!this._widths) {
@@ -500,8 +442,6 @@ export class Track extends LitElement {
     return this.overflowWidth > 0 || this.overflowHeight > 0;
   }
 
-  public currentItem = 0;
-
   public get currentIndex() {
     return this.currentItem % this.itemCount;
   }
@@ -511,6 +451,10 @@ export class Track extends LitElement {
   }
 
   public get maxIndex() {
+    if (this.overflow === "ignore") {
+      return this.itemCount - 1;
+    }
+
     if (this.align === "start") {
       // get index of item at the end of the track
       if (this.vertical) {
@@ -535,6 +479,24 @@ export class Track extends LitElement {
     return 0;
   }
 
+  public get trackSize() {
+    return this.vertical ? this.trackHeight : this.trackWidth;
+  }
+
+  public get currentAngle() {
+    return (this.currentPosition / this.trackSize) * Math.PI * 2;
+  }
+
+  public get originAngle() {
+    return (this.origin[this.currentAxis] / this.trackSize || 0) * PI2;
+  }
+
+  public get targetAngle() {
+    return this.target ? (this.target.x / this.trackSize) * PI2 : undefined;
+  }
+
+  public itemAngles: number[] = [];
+
   private getScrollBounds() {
     let stopTop = 0;
     let stopLeft = 0;
@@ -572,39 +534,91 @@ export class Track extends LitElement {
     };
   }
 
-  scrollBounds = {
-    top: 0,
-    left: 0,
-    bottom: 0,
-    right: 0,
-  };
+  private toClapmedPosition(pos: Vec2) {
+    let clampedPos = pos;
+
+    const bounds = this.scrollBounds;
+
+    switch (this.align) {
+      case "center":
+        clampedPos = new Vec2(
+          Math.min(bounds.right, clampedPos.x),
+          Math.min(bounds.bottom, clampedPos.y),
+        );
+        clampedPos = new Vec2(
+          Math.max(bounds.left, clampedPos.x),
+          Math.max(bounds.top, clampedPos.y),
+        );
+        break;
+      default:
+        clampedPos = new Vec2(
+          Math.min(bounds.right, clampedPos.x),
+          Math.min(bounds.bottom, clampedPos.y),
+        );
+        clampedPos = new Vec2(
+          Math.max(bounds.left, clampedPos.x),
+          Math.max(bounds.top, clampedPos.y),
+        );
+        break;
+    }
+
+    return clampedPos;
+  }
+
+  private scrollBounds = {
+    top: 0,
+    left: 0,
+    bottom: 0,
+    right: 0,
+  };
 
   private animation: number | undefined;
-  private tickRate = 1000 / 144;
+  private tickRate = 1000 / 120;
   private lastTick = 0;
   private accumulator = 0;
 
+  public currentItem = 0;
+
   public grabbing = false;
 
+  public mouseDown = false;
   public mousePos = new Vec2();
+
+  // Force applied to the acceleration every frame
   public inputForce = new Vec2();
 
   public drag = 0.95;
   public origin = new Vec2();
   public position = new Vec2();
+
+  public moveDrag = 0.5;
+
+  public borderBounce = 0.1;
+  public borderResistance = 0.3;
+
+  public moveVelocity = new Vec2();
+
+  // Delta of position compared to the last frame
+  public deltaPosition = new Vec2();
+
+  // Average velocity over multiple frames
   public velocity = new Vec2();
+
+  // Delta of velocity compared to the last frame
+  public deltaVelocity = new Vec2();
+
+  // Direction of the inputForce
   public direction = new Vec2();
+
+  // Acceleration that is applied to the position every frame
   public acceleration = new Vec2();
-  public lastVelocity = new Vec2();
-  private lastPosition = new Vec2();
 
   public target?: Vec2;
   public targetEasing: Easing = "linear";
   private targetForce = new Vec2();
   private targetStart = new Vec2();
 
-  public transitionTime = 350;
-
+  public transitionTime = 420;
   private transitionAt = 0;
   private transition = 0;
 
@@ -632,6 +646,30 @@ export class Track extends LitElement {
     },
   };
 
+  /** The index of the current item. */
+  @property({ type: Number, reflect: true }) public current: number | undefined;
+
+  /** Whether the track should scroll vertically, instead of horizontally. */
+  @property({ type: Boolean, reflect: true }) public vertical = false;
+
+  /** Whether the track should loop back to the start when reaching the end. */
+  @property({ type: Boolean, reflect: true }) public loop = false;
+
+  /** Whether the track should snap to the closest child element. */
+  @property({ type: Boolean, reflect: true }) public snap = false;
+
+  /** Item alignment in the track. "start" (left/top) or "center" */
+  @property({ type: String }) public align: "start" | "center" = "start";
+
+  /** Change the overflow behavior.
+   * - "auto" - Only scrollable when necessary.
+   * - "scroll" - Always scrollable.
+   * - "ignore" - Ignore any overflow.
+   */
+  @property({ type: String }) public overflow: "auto" | "scroll" | "ignore" = "auto";
+
+  @property({ type: Boolean }) public debug = false;
+
   private trait(callback: (t: Trait) => void) {
     for (const t of this.traits) {
       try {
@@ -662,29 +700,6 @@ export class Track extends LitElement {
     return undefined;
   }
 
-  /** The index of the current item. */
-  @property({ type: Number, reflect: true }) current: number | undefined;
-
-  /** Whether the track should scroll vertically, instead of horizontally. */
-  @property({ type: Boolean, reflect: true }) vertical = false;
-
-  /** Whether the track should loop back to the start when reaching the end. */
-  @property({ type: Boolean, reflect: true }) loop = false;
-
-  /** Whether the track should snap to the closest child element. */
-  @property({ type: Boolean, reflect: true }) snap = false;
-
-  /** Item alignment in the track. "start" (left/top) or "center" */
-  @property({ type: String }) align: "start" | "center" = "start";
-
-  /** Change the overflow behavior.
-   * - "auto" - Only scrollable when necessary.
-   * - "scroll" - Always scrollable.
-   */
-  @property({ type: String }) overflow: "auto" | "scroll" = "auto";
-
-  @property({ type: Boolean }) debug = false;
-
   private observedChildren = new Set<Node>();
 
   private onSlotChange = () => {
@@ -709,43 +724,28 @@ export class Track extends LitElement {
     }
   };
 
-  private format = () => {
-    this.inputState.format.value = true;
-    this._width = undefined;
-    this._height = undefined;
-    this._widths = undefined;
-    this._heights = undefined;
-
-    // apply align prop
-    switch (this.align) {
-      case "start":
-        this.origin.set([0, 0]);
-        break;
-      case "center":
-        if (this.vertical) {
-          this.origin.set([0, -this.height / 2]);
-        } else {
-          this.origin.set([-this.width / 2, 0]);
-        }
-        break;
-    }
-
-    this.scrollBounds = this.getScrollBounds();
-
-    const formatEvent = new CustomEvent("format", {
-      bubbles: true,
-      cancelable: true,
-    });
-    this.dispatchEvent(formatEvent);
+  /**
+   * Get the absolute position of the closest item to the current position.
+   * @argument offset - offset the position by a number
+   */
+  public getClosestItemPosition(offset = 0) {
+    const posPrev = this.getToItemPosition(this.currentItem + -offset);
+    const posCurr = this.getToItemPosition(this.currentItem + offset);
+    const posNext = this.getToItemPosition(this.currentItem + (offset + 1));
 
-    if (!formatEvent.defaultPrevented) {
-      this.trait((t) => t.format?.(this));
+    const prev = posPrev.clone().sub(this.position).abs();
+    const curr = posCurr.clone().sub(this.position).abs();
+    const next = posNext.clone().sub(this.position).abs();
 
-      if (this.position.x > this.overflowWidth || this.position.y > this.overflowHeight) {
-        this.setTarget(undefined);
-      }
+    switch (Math.min(curr, next, prev)) {
+      case prev:
+        return posPrev;
+      case next:
+        return posNext;
+      default:
+        return posCurr;
     }
-  };
+  }
 
   /**
    * Get the index of the item that contains given element. Returns -1 if it is not in any item.
@@ -871,12 +871,11 @@ export class Track extends LitElement {
 
     const deltaTick = ms - this.lastTick;
     this.lastTick = ms;
-
     this.accumulator += deltaTick;
 
-    const lastPosition = this.position.clone();
+    this.trait((t) => t.draw?.(this));
 
-    this.updateInputs();
+    const lastPosition = this.position.clone();
 
     let ticks = 0;
     const maxTicks = 10;
@@ -887,6 +886,8 @@ export class Track extends LitElement {
       this.updateTick();
     }
 
+    this.updateInputs();
+
     const deltaPosition = Vec2.sub(this.position, lastPosition);
 
     const currItem = this.getCurrentItem();
@@ -928,30 +929,102 @@ export class Track extends LitElement {
   private updateInputs() {
     this.trait((t) => t.input?.(this, this.inputState));
 
+    if (this.inputState.grab.value === true) {
+      // grab change
+      this.grabbing = true;
+      this.dispatchEvent(new Event("pointer:grab"));
+      this.setTarget(undefined);
+    }
+
+    if (this.grabbing) {
+      if (this.inputState.move.value.abs()) {
+        this.moveVelocity.add(this.inputState.move.value);
+        this.inputForce.set(this.inputState.move.value.clone().mul(-1));
+      } else {
+        this.inputForce.mul(0);
+      }
+    }
+
+    if (this.inputState.release.value) {
+      this.grabbing = false;
+      this.dispatchEvent(new Event("pointer:release"));
+
+      this.inputForce.set(this.moveVelocity.clone().mul(-1));
+    }
+
+    // prevent moving in wrong direction
+    if (this.vertical) {
+      this.inputForce.x = 0;
+    } else {
+      this.inputForce.y = 0;
+    }
+
+    if (this.slotElement) {
+      this.slotElement.inert = this.grabbing;
+    }
+    if (!isTouch()) this.style.cursor = this.grabbing ? "grabbing" : "";
+
     this.direction.add(this.inputForce).sign();
 
     // clear
     const state = this.inputState;
     state.move.value.mul(0);
     state.grab.value = false;
+    state.scroll.value = false;
     state.format.value = false;
     state.leave.value = false;
     state.enter.value = false;
     state.release.value = false;
   }
 
-  public get deltaVelocity() {
-    return Vec2.sub(this.velocity.abs(), this.lastVelocity.abs());
+  public get interacting() {
+    return this.inputForce.abs() > 0.5 || this.mouseDown;
   }
 
   private updateTick() {
-    this.lastPosition = this.position.clone();
-    this.lastVelocity = this.velocity.clone();
+    const lastPosition = this.position.clone();
+    const lastVelocity = this.velocity.clone();
 
-    this.acceleration.mul(this.drag);
+    this.velocity = Vec2.add(this.velocity.clone().mul(0.5), this.deltaPosition);
+    this.deltaVelocity = Vec2.sub(this.velocity, lastVelocity);
 
     this.trait((t) => t.update?.(this));
 
+    const interacting = this.target || this.interacting;
+
+    this.moveVelocity.mul(this.moveDrag);
+
+    // clamp input force
+    const pos = Vec2.add(this.position, this.inputForce);
+    const clamped = this.toClapmedPosition(pos);
+    const diff = Vec2.sub(pos, clamped);
+
+    if (interacting) {
+      this.drag = 0;
+
+      if (!this.loop && diff.abs()) {
+        const resitance = this.borderResistance * (1 - diff.abs() / 200);
+
+        if (this.vertical) {
+          this.inputForce.mul(resitance);
+        } else {
+          this.inputForce.mul(resitance);
+        }
+      }
+    } else {
+      this.drag = 0.95;
+
+      const bounce = this.borderBounce;
+      if (!this.loop && bounce && diff.abs()) {
+        if ((this.vertical && Math.abs(diff.y)) || Math.abs(diff.x)) {
+          this.inputForce.sub(diff.mul(bounce));
+          this.acceleration.mul(0);
+        }
+      }
+    }
+
+    this.acceleration.mul(this.drag);
+
     this.acceleration.add(this.inputForce);
     this.inputForce.mul(0);
 
@@ -1029,34 +1102,21 @@ export class Track extends LitElement {
     this.position.add(this.targetForce);
     this.targetForce.set(0);
 
-    this.velocity = Vec2.sub(this.position, this.lastPosition);
+    this.deltaPosition = Vec2.sub(this.position, lastPosition);
   }
 
-  debugCanvas = document.createElement("canvas");
+  public debugCanvas = document.createElement("canvas");
 
   private getCurrentItem() {
-    let ctx: CanvasRenderingContext2D | null = null;
-    if (this.debug) {
-      ctx = this.debugCanvas.getContext("2d");
-      ctx?.translate(0.5, 0.5);
-      this.debugCanvas.width = 200;
-      this.debugCanvas.height = 200;
-      this.debugCanvas.style.width = "100px";
-      this.debugCanvas.style.height = "100px";
-    }
-
-    const trackSize = this.vertical ? this.trackHeight : this.trackWidth;
-    const currentAngle = (this.currentPosition / trackSize) * Math.PI * 2;
+    const trackSize = this.trackSize;
+    const currentAngle = this.currentAngle;
 
     let minDist = Number.POSITIVE_INFINITY;
-    let closestAngle = 0;
     let closestIndex = 0;
 
     const rects = this.getItemRects();
 
-    const PI2 = Math.PI * 2;
-
-    const originAngle = (this.origin[this.currentAxis] / trackSize || 0) * PI2;
+    const originAngle = this.originAngle;
 
     const axes = this.vertical ? 1 : 0;
     // all items angles
@@ -1065,6 +1125,8 @@ export class Track extends LitElement {
       return acc;
     }, [] as number[]);
 
+    this.itemAngles = [];
+
     for (let i = -1; i < this.itemCount + 1; i++) {
       const itemIndex = i % this.itemCount;
       const rect = rects[itemIndex];
@@ -1087,15 +1149,7 @@ export class Track extends LitElement {
         itemAngle += (angles[Math.min(lastIndex + 1, this.currentIndex)] || 0) / 2;
       }
 
-      if (this.debug && ctx) {
-        // draw a line from the center to the current position with angle
-        ctx.strokeStyle = `hsl(0, 0%, ${(i / this.itemCount) * 100}%)`;
-        ctx.beginPath();
-        ctx.moveTo(100, 100);
-        ctx.lineTo(100 + Math.cos(itemAngle) * 69, 100 + Math.sin(itemAngle) * 69);
-        ctx.lineWidth = 3;
-        ctx.stroke();
-      }
+      this.itemAngles[itemIndex] = itemAngle;
 
       const deltaAngle = angleDist(itemAngle, currentAngle);
 
@@ -1107,79 +1161,9 @@ export class Track extends LitElement {
         if (currentAngle > PI2 / 2) {
           closestIndex += offset;
         }
-
-        closestAngle = itemAngle;
       }
     }
 
-    if (this.debug && ctx) {
-      // draw a line from the center to the current position with angle
-      ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
-      ctx.beginPath();
-      ctx.moveTo(100, 100);
-      ctx.lineTo(
-        100 + Math.cos(currentAngle + originAngle) * 69,
-        100 + Math.sin(currentAngle + originAngle) * 69,
-      );
-      ctx.arc(
-        100,
-        100,
-        69,
-        currentAngle + originAngle,
-        currentAngle + originAngle + (this.width / trackSize) * PI2,
-      );
-      ctx.lineTo(100, 100);
-      ctx.fill();
-
-      // print current position
-      ctx.font = "24px sans-serif";
-      ctx.fillStyle = "#fff";
-      ctx.textAlign = "left";
-      ctx.textBaseline = "middle";
-      ctx.fillText(
-        `${this.currentPosition.toFixed(1)} / ${trackSize.toFixed(1)}`,
-        42,
-        18,
-      );
-
-      // print current position index
-      ctx.font = "24px sans-serif";
-      ctx.fillStyle = "#fff";
-      ctx.textAlign = "left";
-      ctx.textBaseline = "middle";
-      ctx.fillText(closestIndex.toString(), 6, 18);
-
-      // draw a line from the center to the current position with angle
-      ctx.strokeStyle = "yellow";
-      ctx.beginPath();
-      ctx.moveTo(100, 100);
-      ctx.lineTo(100 + Math.cos(closestAngle) * 69, 100 + Math.sin(closestAngle) * 69);
-      ctx.lineWidth = 3;
-      ctx.stroke();
-
-      const targetAngle = this.target ? (this.target.x / trackSize) * PI2 : undefined;
-
-      if (targetAngle) {
-        // draw a line from the center to the current position with angle
-        ctx.strokeStyle = "blue";
-        ctx.beginPath();
-        ctx.moveTo(100, 100);
-        ctx.lineTo(100 + Math.cos(closestAngle) * 50, 100 + Math.sin(closestAngle) * 50);
-        ctx.lineWidth = 3;
-        ctx.stroke();
-      }
-    }
-
-    if (this.debug && ctx) {
-      // draw a line from the center to the current position with angle
-      ctx.strokeStyle = "lime";
-      ctx.beginPath();
-      ctx.moveTo(100, 100);
-      ctx.lineTo(100 + Math.cos(originAngle) * 69, 100 + Math.sin(originAngle) * 69);
-      ctx.lineWidth = 3;
-      ctx.stroke();
-    }
-
     return closestIndex;
   }
 
@@ -1281,6 +1265,15 @@ export class Track extends LitElement {
 
   private resizeObserver?: ResizeObserver;
 
+  private canMove(delta: Vec2) {
+    if (this.overflow === "auto" && !this.hasOverflow) {
+      // respect overflowscroll
+      return false;
+    }
+
+    return this.dispatchEvent(new MoveEvent(this, delta));
+  }
+
   listener<T extends Event>(
     host: HTMLElement | typeof globalThis,
     events: string | string[],
@@ -1298,247 +1291,184 @@ export class Track extends LitElement {
     }
   }
 
-  private canMove(delta: Vec2) {
-    if (this.overflow === "auto" && !this.hasOverflow) {
-      // respect overflowscroll
-      return false;
-    }
-
-    return this.dispatchEvent(new MoveEvent(this, delta));
-  }
-
-  connectedCallback(): void {
-    super.connectedCallback();
-
-    this.updateItems();
-
-    this.ariaRoleDescription = "carousel";
-    this.role = "region";
+  private onFormat = () => {
+    this.inputState.format.value = true;
+    this._width = undefined;
+    this._height = undefined;
+    this._widths = undefined;
+    this._heights = undefined;
 
-    this.listener(this, "focusin", (e: FocusEvent) => {
-      const item = this.elementItemIndex(e.target as HTMLElement);
-      const dist = Vec2.dist2(this.getToItemPosition(item), this.position);
-      const rect = this.getItemRects()[item];
+    // apply align prop
+    switch (this.align) {
+      case "start":
+        this.origin.set([0, 0]);
+        break;
+      case "center":
+        if (this.vertical) {
+          this.origin.set([0, -this.height / 2]);
+        } else {
+          this.origin.set([-this.width / 2, 0]);
+        }
+        break;
+    }
 
-      if (!rect) return;
+    this.scrollBounds = this.getScrollBounds();
 
-      if (
-        dist.x + rect.x > this.width ||
-        dist.x < 0 ||
-        dist.y + rect.y > this.height ||
-        dist.y < 0
-      ) {
-        this.moveTo(item);
-      }
+    const formatEvent = new CustomEvent("format", {
+      bubbles: true,
+      cancelable: true,
     });
+    this.dispatchEvent(formatEvent);
 
-    this.listener(this, "keydown", (e: KeyboardEvent) => {
-      const Key = {
-        prev: this.vertical ? "ArrowUp" : "ArrowLeft",
-        next: this.vertical ? "ArrowDown" : "ArrowRight",
-      };
+    if (!formatEvent.defaultPrevented) {
+      this.trait((t) => t.format?.(this));
 
-      if (e.key === Key.prev) {
-        this.moveBy(-1, "linear");
-        e.preventDefault();
-      }
-      if (e.key === Key.next) {
-        this.moveBy(1, "linear");
-        e.preventDefault();
+      if (this.position.x > this.overflowWidth || this.position.y > this.overflowHeight) {
+        this.setTarget(undefined);
       }
-    });
+    }
+  };
 
-    this.listener(this, "pointerdown", (e: PointerEvent) => {
-      if (e.button !== 0) return; // only left click
+  public scrollDebounce?: Timer;
 
-      // Try to focus this element when clicked on for arrow key navigation,
-      // will only work when tabindex=0.
-      this.focus();
+  private onWheel = (wheelEvent: WheelEvent) => {
+    // TODO: if there is a tick without a scroll event, it will jitter
+    if (wheelEvent.ctrlKey === true) {
+      // its a pinch zoom gesture
+      return;
+    }
 
-      this.mousePos.x = e.x;
-      this.mousePos.y = e.y;
+    const delta = new Vec2(wheelEvent.deltaX, wheelEvent.deltaY);
 
-      this.setTarget(undefined);
-      this.acceleration.set(0);
+    if (!this.canMove(delta)) return;
 
-      e.preventDefault();
-      e.stopPropagation();
-    });
+    const deltaThreshold = Vec2.abs(delta);
+    const axisThreshold = this.vertical
+      ? Math.abs(delta.x) < Math.abs(delta.y)
+      : Math.abs(delta.x) > Math.abs(delta.y);
 
-    this.listener(this, "pointerleave", () => {
-      this.inputState.leave.value = true;
-    });
+    if (axisThreshold) {
+      wheelEvent.preventDefault();
+    }
 
-    this.listener(this, "pointerenter", () => {
-      this.inputState.enter.value = true;
-    });
+    if (axisThreshold && deltaThreshold > 2) {
+      this.setTarget(undefined);
 
-    this.listener(window, "pointermove", (pointerEvent: PointerEvent) => {
-      const pos = new Vec2(pointerEvent.x, pointerEvent.y);
-      const delta = Vec2.sub(pos, this.mousePos);
+      this.inputState.scroll.value = true;
 
-      if (!this.canMove(delta)) return;
+      if (
+        this.position.x < this.scrollBounds.left - 20 ||
+        this.position.y < this.scrollBounds.top - 20 ||
+        this.position.x > this.scrollBounds.right + 20 ||
+        this.position.y > this.scrollBounds.bottom + 20 ||
+        this.scrollDebounce
+      ) {
+        clearTimeout(this.scrollDebounce);
+        this.scrollDebounce = setTimeout(() => {
+          this.scrollDebounce = undefined;
+        }, 32);
 
-      if (!this.grabbing && delta.abs() > 3) {
-        if (this.vertical && this.mousePos.y && Math.abs(delta.x) < Math.abs(delta.y)) {
-          this.grabbing = true;
-          this.inputState.grab.value = true;
-        } else if (this.mousePos.x && Math.abs(delta.y) < Math.abs(delta.x)) {
-          this.grabbing = true;
-          this.inputState.grab.value = true;
-        }
+        return;
       }
 
-      if (this.grabbing) {
-        this.inputState.move.value.add(delta.clone());
-        this.mousePos.set(pos);
-
-        pointerEvent.preventDefault();
-        pointerEvent.stopPropagation();
+      if (this.vertical) {
+        this.inputForce.y = delta.y;
+      } else {
+        this.inputForce.x = delta.x;
       }
-    });
-
-    // TODO: put this in a trait so it can be disabled
-    this.listener(
-      this,
-      "wheel",
-      (wheelEvent: WheelEvent) => {
-        if (wheelEvent.ctrlKey === true) {
-          // its a pinch zoom gesture
-          return;
-        }
-
-        const delta = new Vec2(wheelEvent.deltaX, wheelEvent.deltaY);
-
-        if (!this.canMove(delta)) return;
-
-        const deltaThreshold = Vec2.abs(delta);
-        const axisThreshold = this.vertical
-          ? Math.abs(delta.x) < Math.abs(delta.y)
-          : Math.abs(delta.x) > Math.abs(delta.y);
+    }
+  };
 
-        if (axisThreshold) {
-          wheelEvent.preventDefault();
-        }
+  private onKeyDown = (e: KeyboardEvent) => {
+    const Key = {
+      prev: this.vertical ? "ArrowUp" : "ArrowLeft",
+      next: this.vertical ? "ArrowDown" : "ArrowRight",
+    };
 
-        if (axisThreshold && deltaThreshold > 2) {
-          this.setTarget(undefined);
+    if (e.key === Key.prev) {
+      this.moveBy(-1, "linear");
+      e.preventDefault();
+    }
+    if (e.key === Key.next) {
+      this.moveBy(1, "linear");
+      e.preventDefault();
+    }
+  };
 
-          this.grabbing = true;
-          this.inputState.scroll.value = true;
+  private onFocusIn = (e: FocusEvent) => {
+    const item = this.elementItemIndex(e.target as HTMLElement);
+    const dist = Vec2.dist2(this.getToItemPosition(item), this.position);
+    const rect = this.getItemRects()[item];
 
-          this.acceleration.mul(0);
+    if (!rect) return;
 
-          if (this.vertical) {
-            this.inputForce.y = wheelEvent.deltaY;
-          } else {
-            this.inputForce.x = wheelEvent.deltaX;
-          }
-        } else {
-          this.grabbing = false;
-        }
-      },
-      { passive: false },
-    );
+    if (
+      dist.x + rect.x > this.width ||
+      dist.x < 0 ||
+      dist.y + rect.y > this.height ||
+      dist.y < 0
+    ) {
+      this.moveTo(item);
+    }
+  };
 
-    this.listener(window, ["pointerup", "pointercancel"], (e: PointerEvent) => {
-      this.mousePos.mul(0);
+  private onPointerDown = (pointerEvent: PointerEvent) => {
+    if (pointerEvent.button !== 0) return; // only left click
 
-      if (this.grabbing) {
-        this.grabbing = false;
-        e.preventDefault();
-        e.stopPropagation();
+    // Try to focus this element when clicked on for arrow key navigation,
+    // will only work when tabindex=0.
+    this.focus();
 
-        this.inputState.release.value = true;
-      }
-    });
+    this.mouseDown = true;
+    this.mousePos.x = pointerEvent.x;
+    this.mousePos.y = pointerEvent.y;
 
-    const intersectionObserver = new IntersectionObserver((intersections) => {
-      for (const entry of intersections) {
-        if (entry.isIntersecting) {
-          this.startAnimate();
-        } else {
-          this.stopAnimate();
-        }
-      }
-    });
+    // stop moving when grabbing
+    this.setTarget(undefined);
+    this.acceleration.set(0);
 
-    this.addController({
-      hostConnected: () => intersectionObserver.observe(this),
-      hostDisconnected: () => intersectionObserver.disconnect(),
-    });
+    pointerEvent.preventDefault();
+    pointerEvent.stopPropagation();
+  };
 
-    // TODO: diff the container size and appliy to position of track
-    this.resizeObserver = new ResizeObserver(debounce(() => this.format()));
+  private onPointerUpOrCancel = (pointerEvent: PointerEvent) => {
+    this.mouseDown = false;
+    this.mousePos.mul(0);
 
-    this.addController({
-      hostConnected: () => this.resizeObserver?.observe(this),
-      hostDisconnected: () => this.resizeObserver?.disconnect(),
-    });
+    if (this.grabbing) {
+      this.grabbing = false;
+      this.inputState.release.value = true;
 
-    this.computeCurrentItem();
-  }
+      pointerEvent.preventDefault();
+      pointerEvent.stopPropagation();
+    }
+  };
 
-  disconnectedCallback(): void {
-    this.stopAnimate();
-    super.disconnectedCallback();
-  }
-}
+  private onPointerMove = (pointerEvent: PointerEvent) => {
+    const pos = new Vec2(pointerEvent.x, pointerEvent.y);
+    const delta = Vec2.sub(pos, this.mousePos);
 
-export class MoveEvent extends CustomEvent<{
-  delta: Vec2;
-  direction: Vec2;
-  velocity: Vec2;
-  position: Vec2;
-}> {
-  constructor(track: Track, delta: Vec2) {
-    super("move", {
-      cancelable: true,
-      detail: {
-        delta: delta,
-        direction: track.direction.clone(),
-        velocity: track.velocity.clone(),
-        position: track.position.clone(),
-      },
-    });
-  }
-}
+    if (!this.canMove(delta)) return;
 
-function mod(a: number, n: number) {
-  return a - Math.floor(a / n) * n;
-}
-
-function angleDist(a: number, b: number) {
-  return mod(b - a + 180, 360) - 180;
-}
+    if (!this.grabbing && delta.abs() > 3) {
+      if (this.vertical && this.mousePos.y && Math.abs(delta.x) < Math.abs(delta.y)) {
+        this.grabbing = true;
+        this.inputState.grab.value = true;
+      } else if (this.mousePos.x && Math.abs(delta.y) < Math.abs(delta.x)) {
+        this.grabbing = true;
+        this.inputState.grab.value = true;
+      }
+    }
 
-export function timer(start: number, time: number) {
-  return Math.min((Date.now() - start) / time, 1);
-}
+    if (this.grabbing) {
+      this.inputState.move.value.add(delta.clone());
+      this.mousePos.set(pos);
 
-export type InputState = {
-  grab: {
-    value: boolean;
-  };
-  scroll: {
-    value: boolean;
-  };
-  move: {
-    value: Vec2;
-  };
-  release: {
-    value: boolean;
-  };
-  format: {
-    value: boolean;
-  };
-  leave: {
-    value: boolean;
-  };
-  enter: {
-    value: boolean;
+      pointerEvent.preventDefault();
+      pointerEvent.stopPropagation();
+    }
   };
-};
+}
 
 export type Easing = "ease" | "linear" | "none";
 
@@ -1555,7 +1485,7 @@ export function isTouch() {
   return !!navigator.maxTouchPoints || "ontouchstart" in window;
 }
 
-function debounce<T>(callback: (arg: T) => void) {
+export function debounce<T>(callback: (arg: T) => void) {
   let timeout: Timer;
 
   return (arg: T) => {
@@ -1563,3 +1493,180 @@ function debounce<T>(callback: (arg: T) => void) {
     timeout = setTimeout(() => callback(arg), 80);
   };
 }
+
+function mod(a: number, n: number) {
+  return a - Math.floor(a / n) * n;
+}
+
+export function angleDist(a: number, b: number) {
+  return mod(b - a + 180, 360) - 180;
+}
+
+export function timer(start: number, time: number) {
+  return Math.min((Date.now() - start) / time, 1);
+}
+
+type VecOrNumber = Vec2 | number[] | number;
+
+export class Vec2 extends Array {
+  constructor(x: VecOrNumber = 0, y = 0) {
+    super();
+
+    if (Vec2.isVec(x)) {
+      this[0] = x[0];
+      this[1] = x[1];
+    } else {
+      this[0] = x;
+      this[1] = y;
+    }
+  }
+
+  get x() {
+    return this[0];
+  }
+
+  set x(x: number) {
+    this[0] = x;
+  }
+
+  get y() {
+    return this[1];
+  }
+
+  set y(y: number) {
+    this[1] = y;
+  }
+
+  get xy() {
+    return [this[0], this[1]];
+  }
+
+  set xy(xy: number[]) {
+    this[0] = xy[0];
+    this[1] = xy[1];
+  }
+
+  add(vec: VecOrNumber) {
+    if (Vec2.isVec(vec)) {
+      this[0] += vec[0];
+      this[1] += vec[1];
+    } else {
+      this[0] += vec;
+      this[1] += vec;
+    }
+    return this;
+  }
+
+  sub(vec: VecOrNumber) {
+    if (Vec2.isVec(vec)) {
+      this[0] -= vec[0];
+      this[1] -= vec[1];
+    } else {
+      this[0] -= vec;
+      this[1] -= vec;
+    }
+    return this;
+  }
+
+  mul(vec: VecOrNumber) {
+    if (Vec2.isVec(vec)) {
+      this[0] *= vec[0];
+      this[1] *= vec[1];
+    } else {
+      this[0] *= vec;
+      this[1] *= vec;
+    }
+    return this;
+  }
+
+  set(vec: VecOrNumber) {
+    if (Vec2.isVec(vec)) {
+      this[0] = vec[0];
+      this[1] = vec[1];
+    } else {
+      this[0] = vec;
+      this[1] = vec;
+    }
+    return this;
+  }
+
+  mod(vec: VecOrNumber) {
+    if (Vec2.isVec(vec)) {
+      this[0] = this[0] % vec[0];
+      this[1] = this[1] % vec[1];
+    } else {
+      this[0] = this[0] % vec;
+      this[1] = this[1] % vec;
+    }
+    return this;
+  }
+
+  sign() {
+    this[0] = Math.sign(this[0]);
+    this[1] = Math.sign(this[1]);
+    return this;
+  }
+
+  dist(vec: Vec2) {
+    return Math.sqrt((vec[0] - this[0]) ** 2 + (vec[1] - this[1]) ** 2);
+  }
+
+  abs() {
+    return Math.sqrt(this[0] ** 2 + this[1] ** 2);
+  }
+
+  precision(precision: number) {
+    this[0] = Math.floor(this[0] / precision) * precision;
+    this[1] = Math.floor(this[1] / precision) * precision;
+    return this;
+  }
+
+  floor() {
+    this[0] = Math.floor(this[0]);
+    this[1] = Math.floor(this[1]);
+    return this;
+  }
+
+  clone() {
+    return new Vec2(this);
+  }
+
+  isNaN() {
+    return Number.isNaN(this[0]) || Number.isNaN(this[1]);
+  }
+
+  toString(): string {
+    return `Vec{${this.map((v) => v.toFixed(2)).join(",")}}`;
+  }
+
+  static add(vec1: VecOrNumber, vec2: VecOrNumber) {
+    if (Vec2.isVec(vec1)) {
+      return new Vec2(vec1[0], vec1[1]).add(vec2);
+    }
+    return new Vec2(vec1, vec1).add(vec2);
+  }
+
+  static sub(vec1: VecOrNumber, vec2: VecOrNumber) {
+    if (Vec2.isVec(vec1)) {
+      return new Vec2(vec1[0], vec1[1]).sub(vec2);
+    }
+    return new Vec2(vec1, vec1).sub(vec2);
+  }
+
+  static mul(vec1: VecOrNumber, vec2: VecOrNumber) {
+    if (Vec2.isVec(vec1)) {
+      return new Vec2(vec1[0], vec1[1]).mul(vec2);
+    }
+    return new Vec2(vec1, vec1).mul(vec2);
+  }
+
+  static abs(vec: Vec2) {
+    return new Vec2(vec.x, vec.y).abs();
+  }
+
+  static dist2(vec1: Vec2, vec2: Vec2) {
+    return new Vec2(vec1[0] - vec2[0], vec1[1] - vec2[1]);
+  }
+
+  static isVec = Array.isArray;
+}
diff --git a/packages/elements/packages/track/src/Vec.ts b/packages/elements/packages/track/src/Vec.ts
deleted file mode 100644
index 521769793c712fca92a7d0263a9382f04192ee0c..0000000000000000000000000000000000000000
--- a/packages/elements/packages/track/src/Vec.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-type VecOrNumber = Vec2 | number[] | number;
-
-export class Vec2 extends Array {
-  constructor(x: VecOrNumber = 0, y = 0) {
-    super();
-
-    if (Vec2.isVec(x)) {
-      this[0] = x[0];
-      this[1] = x[1];
-    } else {
-      this[0] = x;
-      this[1] = y;
-    }
-  }
-
-  get x() {
-    return this[0];
-  }
-
-  set x(x: number) {
-    this[0] = x;
-  }
-
-  get y() {
-    return this[1];
-  }
-
-  set y(y: number) {
-    this[1] = y;
-  }
-
-  get xy() {
-    return [this[0], this[1]];
-  }
-
-  set xy(xy: number[]) {
-    this[0] = xy[0];
-    this[1] = xy[1];
-  }
-
-  add(vec: VecOrNumber) {
-    if (Vec2.isVec(vec)) {
-      this[0] += vec[0];
-      this[1] += vec[1];
-    } else {
-      this[0] += vec;
-      this[1] += vec;
-    }
-    return this;
-  }
-
-  sub(vec: VecOrNumber) {
-    if (Vec2.isVec(vec)) {
-      this[0] -= vec[0];
-      this[1] -= vec[1];
-    } else {
-      this[0] -= vec;
-      this[1] -= vec;
-    }
-    return this;
-  }
-
-  mul(vec: VecOrNumber) {
-    if (Vec2.isVec(vec)) {
-      this[0] *= vec[0];
-      this[1] *= vec[1];
-    } else {
-      this[0] *= vec;
-      this[1] *= vec;
-    }
-    return this;
-  }
-
-  set(vec: VecOrNumber) {
-    if (Vec2.isVec(vec)) {
-      this[0] = vec[0];
-      this[1] = vec[1];
-    } else {
-      this[0] = vec;
-      this[1] = vec;
-    }
-    return this;
-  }
-
-  mod(vec: VecOrNumber) {
-    if (Vec2.isVec(vec)) {
-      this[0] = this[0] % vec[0];
-      this[1] = this[1] % vec[1];
-    } else {
-      this[0] = this[0] % vec;
-      this[1] = this[1] % vec;
-    }
-    return this;
-  }
-
-  sign() {
-    this[0] = Math.sign(this[0]);
-    this[1] = Math.sign(this[1]);
-    return this;
-  }
-
-  dist(vec: Vec2) {
-    return Math.sqrt((vec[0] - this[0]) ** 2 + (vec[1] - this[1]) ** 2);
-  }
-
-  abs() {
-    return Math.sqrt(this[0] ** 2 + this[1] ** 2);
-  }
-
-  abs2() {
-    this[0] = Math.abs(this[0]);
-    this[1] = Math.abs(this[1]);
-    return this;
-  }
-
-  precision(precision: number) {
-    this[0] = Math.floor(this[0] / precision) * precision;
-    this[1] = Math.floor(this[1] / precision) * precision;
-  }
-
-  floor() {
-    this[0] = Math.floor(this[0]);
-    this[1] = Math.floor(this[1]);
-    return this;
-  }
-
-  clone() {
-    return new Vec2(this);
-  }
-
-  isNaN() {
-    return Number.isNaN(this[0]) || Number.isNaN(this[1]);
-  }
-
-  toString(): string {
-    return `Vec{${this.join(",")}}`;
-  }
-
-  static add(vec1: VecOrNumber, vec2: VecOrNumber) {
-    if (Vec2.isVec(vec1)) {
-      return new Vec2(vec1[0], vec1[1]).add(vec2);
-    }
-    return new Vec2(vec1, vec1).add(vec2);
-  }
-
-  static sub(vec1: VecOrNumber, vec2: VecOrNumber) {
-    if (Vec2.isVec(vec1)) {
-      return new Vec2(vec1[0], vec1[1]).sub(vec2);
-    }
-    return new Vec2(vec1, vec1).sub(vec2);
-  }
-
-  static mul(vec1: VecOrNumber, vec2: VecOrNumber) {
-    if (Vec2.isVec(vec1)) {
-      return new Vec2(vec1[0], vec1[1]).mul(vec2);
-    }
-    return new Vec2(vec1, vec1).mul(vec2);
-  }
-
-  static abs(vec: Vec2) {
-    return new Vec2(vec.x, vec.y).abs();
-  }
-
-  static dist2(vec1: Vec2, vec2: Vec2) {
-    return new Vec2(vec1[0] - vec2[0], vec1[1] - vec2[1]);
-  }
-
-  static isVec = Array.isArray;
-}
diff --git a/packages/elements/packages/track/src/debug.ts b/packages/elements/packages/track/src/debug.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb9ede44b584dd44d584e99e04833ba22e76022f
--- /dev/null
+++ b/packages/elements/packages/track/src/debug.ts
@@ -0,0 +1,119 @@
+import type { Track, Trait } from "./Track";
+
+const PI2 = Math.PI * 2;
+
+/**
+ * Debug trait for debugging hud.
+ */
+export class DebugTrait implements Trait {
+  id = "debug";
+
+  update(track: Track): void {
+    if (!track.debug) return;
+
+    const meta = {
+      mouseDown: track.mouseDown,
+      grabbing: track.grabbing,
+      acceleration: track.acceleration,
+      velocity: track.velocity,
+      drag: track.drag,
+      moveVelocity: track.moveVelocity,
+      inputForce: track.inputForce,
+      scrollDebounce: track.scrollDebounce,
+      mousePos: track.mousePos,
+      target: track.target,
+    };
+
+    const trackSize = track.trackSize;
+    const currentAngle = track.currentAngle;
+
+    const canvas = track.debugCanvas;
+    const ctx = canvas.getContext("2d");
+    if (!ctx) return;
+
+    ctx.translate(0.5, 0.5);
+    canvas.width = track.width * 2;
+    canvas.height = track.height * 2;
+    canvas.style.width = `${track.width}px`;
+    canvas.style.height = `${track.height}px`;
+
+    ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
+    ctx.fillRect(0, 0, 400, 600);
+
+    const originAngle = track.originAngle;
+
+    for (let i = -1; i < track.itemCount + 1; i++) {
+      const itemAngle = track.itemAngles[i] || 0;
+
+      // draw a line from the center to the current position with angle
+      ctx.strokeStyle = `hsl(0, 0%, ${(i / track.itemCount) * 100}%)`;
+      ctx.beginPath();
+      ctx.moveTo(100, 100);
+      ctx.lineTo(100 + Math.cos(itemAngle) * 69, 100 + Math.sin(itemAngle) * 69);
+      ctx.lineWidth = 3;
+      ctx.stroke();
+    }
+
+    // draw a line from the center to the current position with angle
+    ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
+    ctx.beginPath();
+    ctx.moveTo(100, 100);
+    ctx.lineTo(
+      100 + Math.cos(currentAngle + originAngle) * 69,
+      100 + Math.sin(currentAngle + originAngle) * 69,
+    );
+    ctx.arc(
+      100,
+      100,
+      69,
+      currentAngle + originAngle,
+      currentAngle + originAngle + (track.width / trackSize) * PI2,
+    );
+    ctx.lineTo(100, 100);
+    ctx.fill();
+
+    // print current position
+    ctx.font = "24px sans-serif";
+    ctx.fillStyle = "#fff";
+    ctx.textAlign = "left";
+    ctx.textBaseline = "middle";
+    ctx.fillText(`${track.currentPosition.toFixed(1)} / ${trackSize.toFixed(1)}`, 42, 18);
+
+    // print current position index
+    ctx.font = "24px sans-serif";
+    ctx.fillStyle = "#fff";
+    ctx.textAlign = "left";
+    ctx.textBaseline = "middle";
+    ctx.fillText(track.currentItem.toString(), 6, 18);
+
+    if (track.targetAngle) {
+      // draw a line from the center to the current position with angle
+      ctx.strokeStyle = "blue";
+      ctx.beginPath();
+      ctx.moveTo(100, 100);
+      ctx.lineTo(
+        100 + Math.cos(track.targetAngle) * 50,
+        100 + Math.sin(track.targetAngle) * 50,
+      );
+      ctx.lineWidth = 3;
+      ctx.stroke();
+    }
+
+    // draw a line from the center to the current position with angle
+    ctx.strokeStyle = "lime";
+    ctx.beginPath();
+    ctx.moveTo(100, 100);
+    ctx.lineTo(100 + Math.cos(originAngle) * 69, 100 + Math.sin(originAngle) * 69);
+    ctx.lineWidth = 3;
+    ctx.stroke();
+
+    let line = 6;
+    ctx.font = "24px sans-serif";
+    ctx.fillStyle = "#fff";
+    ctx.textAlign = "left";
+    for (const key in meta) {
+      ctx.fillText(`${key}: ${meta[key]}`, 6, 24 * 1.5 * line);
+      line++;
+    }
+  }
+}
diff --git a/packages/elements/packages/track/tests/test-utils.ts b/packages/elements/packages/track/tests/test-utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..76c4583072faf5126150f2d24e5720511bd04ac0
--- /dev/null
+++ b/packages/elements/packages/track/tests/test-utils.ts
@@ -0,0 +1,155 @@
+import Rand from "rand-seed";
+import { expect } from "bun:test";
+import type { Vec2 } from "../src";
+import type { Track as TrackElement } from "../src/Track";
+
+export function enviroment() {
+  globalThis.seed = process.env.TEST_SEED || crypto.randomUUID();
+}
+
+export function setup() {
+  globalThis.rand = new Rand(globalThis.seed);
+}
+
+export function random() {
+  return globalThis.rand.next();
+}
+
+export function label(str: string) {
+  return `[TEST_SEED=${globalThis.seed} bun test track -t "${str}"]`;
+}
+
+export function press(ele: Element, key: string) {
+  ele.dispatchEvent(
+    new KeyboardEvent("keydown", {
+      key: key,
+    }),
+  );
+}
+
+export async function fixElementSizes(ele: Element, width: number, height: number) {
+  Object.defineProperty(ele, "offsetWidth", {
+    writable: true,
+  });
+  Object.defineProperty(ele, "offsetHeight", {
+    writable: true,
+  });
+
+  // @ts-ignore
+  ele.offsetWidth = width;
+  // @ts-ignore
+  ele.offsetHeight = height;
+}
+
+export async function sleep(ms = 16) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+export class FakePointerEvent extends PointerEvent {
+  constructor(
+    type: string,
+    public x: number,
+    public y: number,
+    init?: PointerEventInit,
+  ) {
+    super(type, {
+      button: 0,
+      bubbles: true,
+      ...init,
+    });
+  }
+}
+
+export async function drag<
+  T extends HTMLElement & {
+    position: Vec2;
+    target?: Vec2;
+  },
+>(track: T, dist: [number, number]) {
+  const multiplier = 1 + random();
+  const pos = [10, 10] as [number, number];
+  const start = [...track.position];
+  const step = [
+    Math.abs(dist[0]) > 0 ? multiplier * dist[0] : 0,
+    Math.abs(dist[1]) > 0 ? multiplier * dist[1] : 0,
+  ] as [number, number];
+
+  console.info("drag", multiplier, "dist", dist, "step", step, "start", start);
+
+  // start moving
+  track.dispatchEvent(new FakePointerEvent("pointerdown", ...pos));
+  console.info("down");
+
+  await sleep();
+
+  expect(track.target).toBeUndefined();
+
+  for (let i = 0; i < 10; i++) {
+    window.dispatchEvent(new FakePointerEvent("pointermove", ...pos));
+
+    await sleep();
+
+    pos[0] -= step[0];
+    pos[1] -= step[1];
+  }
+  // has moved at all?
+  expect(track.position[0] !== start[0] || track.position[1] !== start[1]).toBeTrue();
+
+  window.dispatchEvent(new FakePointerEvent("pointerup", ...pos));
+  console.info("up");
+
+  await sleep();
+}
+
+export async function trackWithChildren(
+  itemCount = 10,
+  attributes: Record<string, string | boolean | number> = {},
+) {
+  await import("../src/index.js");
+
+  const widths = new Array<number>(itemCount)
+    .fill(0)
+    .map(() => Math.floor(random() * 500 + 150));
+
+  console.info("items", widths);
+
+  const div = document.createElement("div");
+  const markup = `
+    <a-track ${Object.entries(attributes)
+      .map(([key, value]) => `${key}="${value}"`)
+      .join(" ")}>
+      ${widths.map((w) => `<canvas width="${w}" height="800"></canvas>`).join("")}
+    </a-track>
+  `;
+  div.innerHTML = markup;
+
+  const track = div.children[0] as TrackElement;
+  fixElementSizes(track, random() * 1000 + 200, random() * 800 + 200);
+
+  // increase animation speed for testing
+  track.transitionTime = 100;
+
+  for (let i = 0; i < itemCount; i++) {
+    const child = track.children[i] as HTMLCanvasElement;
+    fixElementSizes(
+      child,
+      Number.parseInt(child.getAttribute("width") || "0"),
+      Number.parseInt(child.getAttribute("height") || "0"),
+    );
+  }
+
+  document.body.append(div);
+
+  if (track.vertical) {
+    track.position.y = random() * track.overflowWidth;
+  } else {
+    track.position.x = random() * track.overflowWidth;
+  }
+
+  console.info("track", track.width, track.height, track.trackWidth, track.position);
+
+  // @ts-ignore
+  track.onFormat();
+
+  return track;
+}
diff --git a/packages/elements/packages/track/tests/track.spec.ts b/packages/elements/packages/track/tests/track.spec.ts
index 32d2507af9d1c142b21308523446b2a47e9fd497..fa59875695b725fd96ceeba7a2f787f7bd2e118a 100644
--- a/packages/elements/packages/track/tests/track.spec.ts
+++ b/packages/elements/packages/track/tests/track.spec.ts
@@ -1,292 +1,353 @@
-import { test, expect } from "bun:test";
-import type { MoveEvent } from "../src/Track";
-import type { Track as TrackElement } from "../src/Track";
-import Rand from "rand-seed";
+import { beforeEach, test, expect, afterEach, describe } from "bun:test";
+import type { MoveEvent, Track } from "../src/Track";
+import {
+  drag,
+  enviroment,
+  FakePointerEvent,
+  label,
+  press,
+  setup,
+  sleep,
+  trackWithChildren,
+} from "./test-utils";
+
+let int: Timer;
+function logRun(track: Track) {
+  int = setInterval(() => {
+    console.info(track.position, track.velocity, track.target);
+  }, 16);
+}
 
-const seed = process.env.TEST_SEED || crypto.randomUUID();
-const rand = new Rand(seed);
+window.requestAnimationFrame = (callback: () => void) => {
+  const timer = setTimeout(() => callback(Date.now()), 16.6666666667);
+  return timer;
+};
+window.cancelAnimationFrame = (timer: Timer) => {
+  clearTimeout(timer);
+};
 
-function random() {
-  return rand.next();
-}
+describe("Track", () => {
+  enviroment();
 
-console.info("\nTest run seed:", seed, "\n");
+  afterEach(() => {
+    clearInterval(int);
+  });
 
-const NODE_NAME = "a-track";
+  beforeEach(() => {
+    setup();
+  });
 
-test("import track element", async () => {
-  const { Track } = await import("@sv/elements/track");
-  expect(Track).toBeDefined();
+  test(label("import track element"), async () => {
+    const { Track } = await import("@sv/elements/track");
+    expect(Track).toBeDefined();
 
-  // is defined in custom element registry
-  expect(customElements.get(NODE_NAME)).toBeDefined();
-});
+    // is defined in custom element registry
+    expect(customElements.get("a-track")).toBeDefined();
+  });
 
-test("construct track element", async () => {
-  const { Track } = await import("@sv/elements/track");
+  test("construct track element", async () => {
+    const { Track } = await import("@sv/elements/track");
 
-  // is constructable
-  expect(new Track()).toBeInstanceOf(Track);
+    // is constructable
+    expect(new Track()).toBeInstanceOf(Track);
 
-  const html = `<${NODE_NAME} />`;
-  const ele = document.createElement("div");
-  ele.innerHTML = html;
+    const html = "<a-track></a-track>";
+    const ele = document.createElement("div");
+    ele.innerHTML = html;
 
-  expect(ele.children[0]).toBeInstanceOf(Track);
-});
+    expect(ele.children[0]).toBeInstanceOf(Track);
+  });
 
-test("deconstruct track element", async () => {
-  const track = await trackWithChildren();
-  track.remove();
-  // @ts-ignore
-  expect(track.animation).toBeUndefined();
-});
+  test(label("deconstruct track element"), async () => {
+    const track = await trackWithChildren();
+    track.remove();
+    // @ts-ignore
+    expect(track.animation).toBeUndefined();
+  });
 
-test("check default traits", async () => {
-  const track = await trackWithChildren();
-  expect(track.findTrait("pointer")).toBeDefined();
-});
+  test(label("item count"), async () => {
+    const track = await trackWithChildren(10);
+    expect(track.itemCount).toBe(10);
+  });
 
-test("item count", async () => {
-  const track = await trackWithChildren(10);
-  expect(track.itemCount).toBe(10);
-});
+  test(label("custom trait"), async () => {
+    const track = await trackWithChildren();
+
+    let input = false;
+    let update = false;
+    let format = false;
+    let start = false;
+    let stop = false;
+
+    track.addTrait({
+      id: "test",
 
-test("custom trait", async () => {
-  const track = await trackWithChildren();
+      input() {
+        input = true;
+      },
 
-  let input = false;
-  let update = false;
-  let format = false;
-  let start = false;
-  let stop = false;
+      update() {
+        update = true;
+      },
 
-  track.addTrait({
-    id: "test",
+      stop() {
+        stop = true;
+      },
 
-    input() {
-      input = true;
-    },
+      start() {
+        start = true;
+      },
 
-    update() {
-      update = true;
-    },
+      format() {
+        format = true;
+      },
+    });
 
-    stop() {
-      stop = true;
-    },
+    // @ts-ignore
+    track.onFormat();
+    track.startAnimate();
+    // @ts-ignore Force update tick
+    track.updateTick();
+
+    expect(start).toBeTrue();
+    expect(input).toBeTrue();
+    expect(update).toBeTrue();
+    expect(format).toBeTrue();
 
-    start() {
-      start = true;
-    },
+    track.remove();
 
-    format() {
-      format = true;
-    },
+    expect(stop).toBeTrue();
   });
 
-  // @ts-ignore
-  track.format();
-  track.startAnimate();
-  // @ts-ignore Force update tick
-  track.updateTick();
+  test(label("check snap trait"), async () => {
+    const track = await trackWithChildren();
+    track.snap = true;
+    await track.updateComplete;
+    expect(track.findTrait("snap")).toBeDefined();
+  });
 
-  expect(start).toBeTrue();
-  expect(input).toBeTrue();
-  expect(update).toBeTrue();
-  expect(format).toBeTrue();
+  test(label("arrow key navigation"), async () => {
+    const track = await trackWithChildren();
+    track.tabIndex = 0;
+    track.focus();
 
-  track.remove();
+    press(track, "ArrowRight");
 
-  expect(stop).toBeTrue();
-});
+    await sleep(track.transitionTime + 100);
 
-test("check snap trait", async () => {
-  const track = await trackWithChildren();
-  track.snap = true;
-  await track.updateComplete;
-  expect(track.findTrait("snap")).toBeDefined();
-});
+    expect(document.activeElement).toBe(track);
+    expect(track.currentItem).toBe(1);
+  });
 
-test("arrow key navigation", async () => {
-  const track = await trackWithChildren();
-  track.tabIndex = 0;
-  track.focus();
+  test(label("move event details"), async () => {
+    const track = await trackWithChildren();
+    track.addEventListener("move", ((e: MoveEvent) => {
+      expect(e.detail.direction).toBeDefined();
+      expect(e.detail.velocity).toBeDefined();
+      expect(e.detail.position).toBeDefined();
 
-  press(track, "ArrowRight");
+      e.preventDefault();
+    }) as EventListener);
+
+    // @ts-ignore
+    expect(track.canMove()).toBeFalse();
+  });
 
-  await sleep(track.transitionTime + 100);
+  test(label("moveBy"), async () => {
+    const track = await trackWithChildren();
 
-  expect(document.activeElement).toBe(track);
-  expect(track.currentItem).toBe(1);
-});
+    track.moveBy(2);
 
-test("move event details", async () => {
-  const track = await trackWithChildren();
-  track.addEventListener("move", ((e: MoveEvent) => {
-    expect(e.detail.direction).toBeDefined();
-    expect(e.detail.velocity).toBeDefined();
-    expect(e.detail.position).toBeDefined();
+    const positions: Array<number> = [];
 
-    e.preventDefault();
-  }) as EventListener);
+    const int = setInterval(() => {
+      positions.push(track.currentPosition);
+    }, track.transitionTime / 10);
 
-  // @ts-ignore
-  expect(track.canMove()).toBeFalse();
-});
+    await sleep(track.transitionTime + 100);
 
-test("moveBy", async () => {
-  const track = await trackWithChildren();
+    expect(track.currentItem).toBe(2);
 
-  track.moveBy(2);
+    clearInterval(int);
 
-  const positions: Array<number> = [];
+    expect(positions.length > 5).toBeTrue();
+  });
 
-  const int = setInterval(() => {
-    positions.push(track.currentPosition);
-  }, track.transitionTime / 10);
+  test(label("centered index 1"), async () => {
+    const track = await trackWithChildren(10, {
+      align: "center",
+    });
+    logRun(track);
 
-  await sleep(track.transitionTime + 100);
+    expect(track.align).toBe("center");
 
-  expect(track.currentItem).toBe(2);
+    track.moveTo(1);
+    await sleep(300);
 
-  clearInterval(int);
+    expect(track.currentItem).toBe(1);
+    expect(Math.abs(track.currentPosition)).toBeGreaterThan(0);
 
-  expect(positions.length > 5).toBeTrue();
-});
+    let offset = 0;
+    for (let i = 0; i < track.currentItem; i++) {
+      // @ts-ignore
+      offset += track.itemWidths[i];
+    }
+    // @ts-ignore
+    offset += track.itemWidths[track.currentItem] / 2;
 
-test("centered index 1", async () => {
-  const track = await trackWithChildren(10, {
-    align: "center",
+    expect(track.currentPosition).toBeCloseTo(offset - track.width / 2);
   });
 
-  expect(track.align).toBe("center");
+  test(label("centered index 0"), async () => {
+    const track = await trackWithChildren(10, {
+      align: "center",
+    });
+
+    expect(track.align).toBe("center");
+
+    track.moveTo(0);
+    await sleep(track.transitionTime + 100);
 
-  track.moveTo(1);
-  await sleep(track.transitionTime + 100);
+    expect(track.currentItem).toBe(0);
 
-  expect(track.currentItem).toBe(1);
-  expect(track.currentPosition !== 0).toBeTrue();
+    // @ts-ignore
+    const offset = track.itemWidths[track.currentItem] / 2;
+    expect(track.currentPosition).toBeCloseTo(offset - track.width / 2);
+  });
+
+  test(label("snap"), async () => {
+    const track = await trackWithChildren(10, { snap: true });
+
+    expect(track.snap).toBe(true);
 
-  let offset = 0;
-  for (let i = 0; i < track.currentItem; i++) {
     // @ts-ignore
-    offset += track.itemWidths[i];
-  }
-  // @ts-ignore
-  offset += track.itemWidths[track.currentItem] / 2;
+    const widths = track.itemWidths;
 
-  expect(track.currentPosition).toBe(offset - track.width / 2);
-});
+    track.setTarget([widths[0] + widths[1] + 10, 0]);
+    await sleep(track.transitionTime + 100);
+    track.setTarget(undefined);
+    await sleep(track.transitionTime + 100);
+
+    expect(Math.floor(track.currentPosition)).toBeGreaterThanOrEqual(
+      widths[0] + widths[1],
+    );
 
-test("centered index 0", async () => {
-  const track = await trackWithChildren(10, {
-    align: "center",
+    expect(track.currentIndex).toBeGreaterThanOrEqual(2);
   });
 
-  expect(track.align).toBe("center");
+  test(label("drag with snap"), async () => {
+    const track = await trackWithChildren(10, { snap: true, current: 2 });
+    logRun(track);
 
-  track.moveTo(0);
-  await sleep(track.transitionTime + 100);
+    track.moveTo(4, "none");
+    await sleep(200);
 
-  expect(track.currentItem).toBe(0);
+    console.info(track.position, track.overflowWidth, track.target);
 
-  // @ts-ignore
-  const offset = track.itemWidths[track.currentItem] / 2;
-  expect(track.currentPosition).toBe(offset - track.width / 2);
-});
+    await drag(track, [200, 0]);
+    await sleep(300);
 
-test("snap", async () => {
-  const track = await trackWithChildren(10, { snap: true });
+    // target should be set by snap
+    expect(track.target).toBeDefined();
+    expect(track.position[0]).toBeCloseTo(track.target?.[0], -2);
+  });
+
+  test(label("drag with snap negative"), async () => {
+    const track = await trackWithChildren(10, { snap: true, current: 2 });
+    logRun(track);
 
-  expect(track.snap).toBe(true);
+    track.moveTo(6, "linear");
+    await sleep(300);
 
-  // @ts-ignore
-  const widths = track.itemWidths;
+    await drag(track, [-100, 0]);
+    await sleep(300);
 
-  track.setTarget([widths[0] + widths[1] + 10, 0]);
-  await sleep(track.transitionTime + 100);
-  track.setTarget(undefined);
-  await sleep(track.transitionTime + 100);
+    // target should be set by snap
+    expect(track.target).toBeDefined();
+    expect(track.position[0]).toBeCloseTo(track.target?.[0], -2);
+  });
 
-  expect(Math.floor(track.currentPosition)).toBeGreaterThanOrEqual(widths[0] + widths[1]);
+  test(label("drag vertical with snap"), async () => {
+    const track = await trackWithChildren(10, { snap: true, current: 3, vertical: true });
+    logRun(track);
 
-  expect(track.currentIndex).toBeGreaterThanOrEqual(2);
-});
+    await drag(track, [0, 100]);
+    await sleep(200);
 
-// TODO: Test actual mouse movement with events
+    // target should be set by snap
+    expect(track.target).toBeDefined();
+    expect(track.position[0]).toBeCloseTo(track.target?.[0], -2);
+  });
 
-// TODO: snap with inertia to the correct position
-//
-// TODO: loop
+  test(label("stop when grabbing"), async () => {
+    const track = await trackWithChildren(10, { snap: true });
+    logRun(track);
 
-async function sleep(ms = 0) {
-  return new Promise((resolve) => setTimeout(resolve, ms));
-}
+    track.transitionTime = 1000;
+    track.moveTo(8, "ease");
+    await sleep(track.transitionTime / 2);
+
+    // grab it bevore transition ends
+    track.dispatchEvent(new FakePointerEvent("pointerdown", 0, 0));
+    console.info("Grabbed track");
+
+    await sleep();
+    const pos = track.position[0];
 
-async function fixElementSizes(ele: Element, width: number, height: number) {
-  Object.defineProperty(ele, "offsetWidth", {
-    writable: true,
+    // wait
+    await sleep(200);
+    // pos should not have changed
+    expect(track.position[0]).toBe(pos);
   });
-  Object.defineProperty(ele, "offsetHeight", {
-    writable: true,
+
+  test(label("click without move should not result in a cancled click"), async () => {
+    const track = await trackWithChildren(10, { snap: true, align: "center" });
+
+    track.dispatchEvent(new FakePointerEvent("pointerdown", 100, 100));
+    console.info("down");
+
+    await sleep();
+
+    const ev = new FakePointerEvent("pointerup", 100, 100);
+    track.dispatchEvent(ev);
+    console.info("up");
+
+    expect(ev.defaultPrevented).toBe(false);
+
+    track.dispatchEvent(new FakePointerEvent("pointerdown", 100, 100));
+    console.info("down");
+
+    await sleep(100);
+    window.dispatchEvent(new FakePointerEvent("pointermove", 150, 110));
+    await sleep(100);
+
+    const ev2 = new FakePointerEvent("pointerup", 100, 100);
+    track.dispatchEvent(ev2);
+    console.info("up");
+
+    expect(ev2.defaultPrevented).toBe(true);
   });
 
-  // @ts-ignore
-  ele.offsetWidth = width;
-  // @ts-ignore
-  ele.offsetHeight = height;
-}
+  test(label("wheel event works"), async () => {
+    const track = await trackWithChildren(10, {});
+    track.moveTo(8, "none");
+    await sleep(200);
 
-async function trackWithChildren(
-  itemCount = 10,
-  attributes: Record<string, string | boolean | number> = {},
-) {
-  await import("@sv/elements/track");
-
-  const widths = new Array<number>(itemCount)
-    .fill(0)
-    .map(() => Math.floor(random() * 500 + 150));
-
-  console.info(widths);
-
-  const div = document.createElement("div");
-  const markup = `
-    <a-track ${Object.entries(attributes)
-      .map(([key, value]) => `${key}="${value}"`)
-      .join(" ")}>
-      ${widths.map((w) => `<canvas width="${w}" height="800"></canvas>`).join("")}
-    </a-track>
-  `;
-  div.innerHTML = markup;
-
-  const track = div.children[0] as TrackElement;
-  fixElementSizes(track, 800, 800);
-
-  // increase animation speed for testing
-  track.transitionTime = 150;
-
-  for (let i = 0; i < itemCount; i++) {
-    const child = track.children[i] as HTMLCanvasElement;
-    fixElementSizes(
-      child,
-      Number.parseInt(child.getAttribute("width") || "0"),
-      Number.parseInt(child.getAttribute("height") || "0"),
-    );
-  }
+    const start = [...track.position];
 
-  document.body.append(div);
+    const ev = new WheelEvent("wheel", {
+      deltaX: -200,
+      deltaY: 0,
+    });
+    track.dispatchEvent(ev);
+    console.info("fired");
 
-  // @ts-ignore
-  track.format();
+    expect(ev.defaultPrevented).toBe(true);
 
-  return track;
-}
+    await sleep(100);
 
-function press(ele: Element, key: string) {
-  ele.dispatchEvent(
-    new KeyboardEvent("keydown", {
-      key: key,
-    }),
-  );
-}
+    expect(track.position[0]).not.toBe(start[0]);
+  });
+
+  // TODO: loop
+});