[READ-ONLY] Mirror of https://github.com/flo-bit/room. tiny 3d rooms saved locally or in your bluesky account, svelte/threlte flo-bit.dev/room/
0

Configure Feed

Select the types of activity you want to include in your feed.

first commit

author
Florian
date (Mar 6, 2025, 6:42 PM +0100) commit 2f86eca0
+16987
+50
.github/workflows/deploy_gh_pages.yml
··· 1 + name: Deploy to GitHub Pages 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + 8 + jobs: 9 + build_site: 10 + runs-on: ubuntu-latest 11 + steps: 12 + - name: Checkout 13 + uses: actions/checkout@v4 14 + 15 + - name: Install Node.js 16 + uses: actions/setup-node@v4 17 + with: 18 + node-version: 22 19 + cache: npm 20 + 21 + - name: Install dependencies 22 + run: npm ci 23 + 24 + - name: build 25 + env: 26 + BASE_PATH: '/${{ github.event.repository.name }}' 27 + run: | 28 + npm run build 29 + 30 + - name: Upload Artifacts 31 + uses: actions/upload-pages-artifact@v3 32 + with: 33 + path: 'build/' 34 + 35 + deploy: 36 + needs: build_site 37 + runs-on: ubuntu-latest 38 + 39 + permissions: 40 + pages: write 41 + id-token: write 42 + 43 + environment: 44 + name: github-pages 45 + url: ${{ steps.deployment.outputs.page_url }} 46 + 47 + steps: 48 + - name: Deploy 49 + id: deployment 50 + uses: actions/deploy-pages@v4
+23
.gitignore
··· 1 + node_modules 2 + 3 + # Output 4 + .output 5 + .vercel 6 + .netlify 7 + .wrangler 8 + /.svelte-kit 9 + /build 10 + 11 + # OS 12 + .DS_Store 13 + Thumbs.db 14 + 15 + # Env 16 + .env 17 + .env.* 18 + !.env.example 19 + !.env.test 20 + 21 + # Vite 22 + vite.config.js.timestamp-* 23 + vite.config.ts.timestamp-*
+1
.npmrc
··· 1 + engine-strict=true
+4
.prettierignore
··· 1 + # Package Managers 2 + package-lock.json 3 + pnpm-lock.yaml 4 + yarn.lock
+15
.prettierrc
··· 1 + { 2 + "useTabs": true, 3 + "singleQuote": true, 4 + "trailingComma": "none", 5 + "printWidth": 100, 6 + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 + "overrides": [ 8 + { 9 + "files": "*.svelte", 10 + "options": { 11 + "parser": "svelte" 12 + } 13 + } 14 + ] 15 + }
+17
README.md
··· 1 + # todo 2 + 3 + - show error message when room is not found 4 + - save edited room to local storage 5 + - show info modal 6 + - show success modal when room is saved (+ share button) 7 + - show television with youtube video 8 + - allow placing objects on walls 9 + - add more objects 10 + - add animations 11 + - allow adding images 12 + - add a day/night cycle? 13 + - allow placing objects on other objects 14 + - allow uploading custom models 15 + - add env map 16 + - add shortcut to move object (-> make it placingObject) 17 + - add submenu for objects
+39
eslint.config.js
··· 1 + import prettier from 'eslint-config-prettier'; 2 + import js from '@eslint/js'; 3 + import { includeIgnoreFile } from '@eslint/compat'; 4 + import svelte from 'eslint-plugin-svelte'; 5 + import globals from 'globals'; 6 + import { fileURLToPath } from 'node:url'; 7 + import ts from 'typescript-eslint'; 8 + import svelteConfig from './svelte.config.js'; 9 + const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 10 + 11 + export default ts.config( 12 + includeIgnoreFile(gitignorePath), 13 + js.configs.recommended, 14 + ...ts.configs.recommended, 15 + ...svelte.configs.recommended, 16 + prettier, 17 + ...svelte.configs.prettier, 18 + { 19 + languageOptions: { 20 + globals: { 21 + ...globals.browser, 22 + ...globals.node 23 + } 24 + } 25 + }, 26 + { 27 + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], 28 + ignores: ['eslint.config.js', 'svelte.config.js'], 29 + 30 + languageOptions: { 31 + parserOptions: { 32 + projectService: true, 33 + extraFileExtensions: ['.svelte'], 34 + parser: ts.parser, 35 + svelteConfig 36 + } 37 + } 38 + } 39 + );
+5723
package-lock.json
··· 1 + { 2 + "name": "bluesky-house", 3 + "version": "0.0.1", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "bluesky-house", 9 + "version": "0.0.1", 10 + "dependencies": { 11 + "@atcute/client": "^2.0.8", 12 + "@atcute/oauth-browser-client": "^1.0.15", 13 + "@atproto/api": "^0.14.7", 14 + "@number-flow/svelte": "^0.3.3", 15 + "@sveltejs/adapter-static": "^3.0.8", 16 + "@texel/color": "^1.1.9", 17 + "@threlte/core": "^8.0.1", 18 + "@threlte/extras": "^9.0.1", 19 + "@threlte/studio": "^0.1.3", 20 + "@types/three": "^0.174.0", 21 + "@zerodevx/svelte-img": "^2.1.2", 22 + "bits-ui": "^1.3.6", 23 + "postprocessing": "^6.37.0", 24 + "svelte-tweakpane-ui": "^1.5.5", 25 + "tailwind-variants": "^0.3.1", 26 + "three": "^0.174.0" 27 + }, 28 + "devDependencies": { 29 + "@eslint/compat": "^1.2.5", 30 + "@eslint/js": "^9.18.0", 31 + "@sveltejs/adapter-auto": "^4.0.0", 32 + "@sveltejs/kit": "^2.16.0", 33 + "@sveltejs/vite-plugin-svelte": "^5.0.0", 34 + "@tailwindcss/forms": "^0.5.10", 35 + "@tailwindcss/typography": "^0.5.16", 36 + "@tailwindcss/vite": "^4.0.0", 37 + "eslint": "^9.18.0", 38 + "eslint-config-prettier": "^10.0.1", 39 + "eslint-plugin-svelte": "^3.0.0", 40 + "globals": "^16.0.0", 41 + "prettier": "^3.4.2", 42 + "prettier-plugin-svelte": "^3.3.3", 43 + "prettier-plugin-tailwindcss": "^0.6.11", 44 + "svelte": "^5.0.0", 45 + "svelte-check": "^4.0.0", 46 + "tailwindcss": "^4.0.0", 47 + "typescript": "^5.0.0", 48 + "typescript-eslint": "^8.20.0", 49 + "vite": "^6.0.0", 50 + "vite-plugin-glsl": "^1.3.3" 51 + } 52 + }, 53 + "node_modules/@ampproject/remapping": { 54 + "version": "2.3.0", 55 + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 56 + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 57 + "license": "Apache-2.0", 58 + "dependencies": { 59 + "@jridgewell/gen-mapping": "^0.3.5", 60 + "@jridgewell/trace-mapping": "^0.3.24" 61 + }, 62 + "engines": { 63 + "node": ">=6.0.0" 64 + } 65 + }, 66 + "node_modules/@atcute/client": { 67 + "version": "2.0.8", 68 + "resolved": "https://registry.npmjs.org/@atcute/client/-/client-2.0.8.tgz", 69 + "integrity": "sha512-OTfiWwjB4mOTlp2InGStvoQ+PIA5lvih9cTYU8BvOhzNcCBUpt4l860MKZExHjvQ9Tt1kjq/ED9zRiUjsAgIxw==", 70 + "license": "MIT" 71 + }, 72 + "node_modules/@atcute/oauth-browser-client": { 73 + "version": "1.0.15", 74 + "resolved": "https://registry.npmjs.org/@atcute/oauth-browser-client/-/oauth-browser-client-1.0.15.tgz", 75 + "integrity": "sha512-ipty/8buSYF7RlLeO0rcVMjwFk0AqHfC/IeR9RPmzX8GfDiG3FooEifg3zcabaLBdnzfh2nfPNinbq1QFGFt7Q==", 76 + "license": "MIT", 77 + "dependencies": { 78 + "@atcute/client": "^2.0.8" 79 + } 80 + }, 81 + "node_modules/@atproto/api": { 82 + "version": "0.14.7", 83 + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.14.7.tgz", 84 + "integrity": "sha512-YG2kvAtsgtajLlLrorYuHcxGgepG0c/RUB2/iJyBnwKjGqDLG8joOETf38JSNiGzs6NJbNKa9NHG6BQKourxBA==", 85 + "license": "MIT", 86 + "dependencies": { 87 + "@atproto/common-web": "^0.4.0", 88 + "@atproto/lexicon": "^0.4.7", 89 + "@atproto/syntax": "^0.3.3", 90 + "@atproto/xrpc": "^0.6.9", 91 + "await-lock": "^2.2.2", 92 + "multiformats": "^9.9.0", 93 + "tlds": "^1.234.0", 94 + "zod": "^3.23.8" 95 + } 96 + }, 97 + "node_modules/@atproto/common-web": { 98 + "version": "0.4.0", 99 + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.0.tgz", 100 + "integrity": "sha512-ZYL0P9myHybNgwh/hBY0HaBzqiLR1B5/ie5bJpLQAg0whRzNA28t8/nU2vh99tbsWcAF0LOD29M8++LyENJLNQ==", 101 + "license": "MIT", 102 + "dependencies": { 103 + "graphemer": "^1.4.0", 104 + "multiformats": "^9.9.0", 105 + "uint8arrays": "3.0.0", 106 + "zod": "^3.23.8" 107 + } 108 + }, 109 + "node_modules/@atproto/lexicon": { 110 + "version": "0.4.7", 111 + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.7.tgz", 112 + "integrity": "sha512-/x6h3tAiDNzSi4eXtC8ke65B7UzsagtlGRHmUD95698x5lBRpDnpizj0fZWTZVYed5qnOmz/ZEue+v3wDmO61g==", 113 + "license": "MIT", 114 + "dependencies": { 115 + "@atproto/common-web": "^0.4.0", 116 + "@atproto/syntax": "^0.3.3", 117 + "iso-datestring-validator": "^2.2.2", 118 + "multiformats": "^9.9.0", 119 + "zod": "^3.23.8" 120 + } 121 + }, 122 + "node_modules/@atproto/syntax": { 123 + "version": "0.3.3", 124 + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.3.tgz", 125 + "integrity": "sha512-F1LZweesNYdBbZBXVa72N/cSvchG8Q1tG4/209ZXbIuM3FwQtkgn+zgmmV4P4ORmhOeXPBNXvMBpcqiwx/gEQQ==", 126 + "license": "MIT" 127 + }, 128 + "node_modules/@atproto/xrpc": { 129 + "version": "0.6.9", 130 + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.9.tgz", 131 + "integrity": "sha512-vQGA7++DYMNaHx3C7vEjT+2X6hYYLG7JNbBnDLWu0km1/1KYXgRkAz4h+FfYqg1mvzvIorHU7DAs5wevkJDDlw==", 132 + "license": "MIT", 133 + "dependencies": { 134 + "@atproto/lexicon": "^0.4.7", 135 + "zod": "^3.23.8" 136 + } 137 + }, 138 + "node_modules/@emnapi/runtime": { 139 + "version": "1.3.1", 140 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", 141 + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", 142 + "license": "MIT", 143 + "optional": true, 144 + "dependencies": { 145 + "tslib": "^2.4.0" 146 + } 147 + }, 148 + "node_modules/@esbuild/aix-ppc64": { 149 + "version": "0.25.0", 150 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", 151 + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", 152 + "cpu": [ 153 + "ppc64" 154 + ], 155 + "license": "MIT", 156 + "optional": true, 157 + "os": [ 158 + "aix" 159 + ], 160 + "engines": { 161 + "node": ">=18" 162 + } 163 + }, 164 + "node_modules/@esbuild/android-arm": { 165 + "version": "0.25.0", 166 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", 167 + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", 168 + "cpu": [ 169 + "arm" 170 + ], 171 + "license": "MIT", 172 + "optional": true, 173 + "os": [ 174 + "android" 175 + ], 176 + "engines": { 177 + "node": ">=18" 178 + } 179 + }, 180 + "node_modules/@esbuild/android-arm64": { 181 + "version": "0.25.0", 182 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", 183 + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", 184 + "cpu": [ 185 + "arm64" 186 + ], 187 + "license": "MIT", 188 + "optional": true, 189 + "os": [ 190 + "android" 191 + ], 192 + "engines": { 193 + "node": ">=18" 194 + } 195 + }, 196 + "node_modules/@esbuild/android-x64": { 197 + "version": "0.25.0", 198 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", 199 + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", 200 + "cpu": [ 201 + "x64" 202 + ], 203 + "license": "MIT", 204 + "optional": true, 205 + "os": [ 206 + "android" 207 + ], 208 + "engines": { 209 + "node": ">=18" 210 + } 211 + }, 212 + "node_modules/@esbuild/darwin-arm64": { 213 + "version": "0.25.0", 214 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", 215 + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", 216 + "cpu": [ 217 + "arm64" 218 + ], 219 + "license": "MIT", 220 + "optional": true, 221 + "os": [ 222 + "darwin" 223 + ], 224 + "engines": { 225 + "node": ">=18" 226 + } 227 + }, 228 + "node_modules/@esbuild/darwin-x64": { 229 + "version": "0.25.0", 230 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", 231 + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", 232 + "cpu": [ 233 + "x64" 234 + ], 235 + "license": "MIT", 236 + "optional": true, 237 + "os": [ 238 + "darwin" 239 + ], 240 + "engines": { 241 + "node": ">=18" 242 + } 243 + }, 244 + "node_modules/@esbuild/freebsd-arm64": { 245 + "version": "0.25.0", 246 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", 247 + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", 248 + "cpu": [ 249 + "arm64" 250 + ], 251 + "license": "MIT", 252 + "optional": true, 253 + "os": [ 254 + "freebsd" 255 + ], 256 + "engines": { 257 + "node": ">=18" 258 + } 259 + }, 260 + "node_modules/@esbuild/freebsd-x64": { 261 + "version": "0.25.0", 262 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", 263 + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", 264 + "cpu": [ 265 + "x64" 266 + ], 267 + "license": "MIT", 268 + "optional": true, 269 + "os": [ 270 + "freebsd" 271 + ], 272 + "engines": { 273 + "node": ">=18" 274 + } 275 + }, 276 + "node_modules/@esbuild/linux-arm": { 277 + "version": "0.25.0", 278 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", 279 + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", 280 + "cpu": [ 281 + "arm" 282 + ], 283 + "license": "MIT", 284 + "optional": true, 285 + "os": [ 286 + "linux" 287 + ], 288 + "engines": { 289 + "node": ">=18" 290 + } 291 + }, 292 + "node_modules/@esbuild/linux-arm64": { 293 + "version": "0.25.0", 294 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", 295 + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", 296 + "cpu": [ 297 + "arm64" 298 + ], 299 + "license": "MIT", 300 + "optional": true, 301 + "os": [ 302 + "linux" 303 + ], 304 + "engines": { 305 + "node": ">=18" 306 + } 307 + }, 308 + "node_modules/@esbuild/linux-ia32": { 309 + "version": "0.25.0", 310 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", 311 + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", 312 + "cpu": [ 313 + "ia32" 314 + ], 315 + "license": "MIT", 316 + "optional": true, 317 + "os": [ 318 + "linux" 319 + ], 320 + "engines": { 321 + "node": ">=18" 322 + } 323 + }, 324 + "node_modules/@esbuild/linux-loong64": { 325 + "version": "0.25.0", 326 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", 327 + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", 328 + "cpu": [ 329 + "loong64" 330 + ], 331 + "license": "MIT", 332 + "optional": true, 333 + "os": [ 334 + "linux" 335 + ], 336 + "engines": { 337 + "node": ">=18" 338 + } 339 + }, 340 + "node_modules/@esbuild/linux-mips64el": { 341 + "version": "0.25.0", 342 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", 343 + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", 344 + "cpu": [ 345 + "mips64el" 346 + ], 347 + "license": "MIT", 348 + "optional": true, 349 + "os": [ 350 + "linux" 351 + ], 352 + "engines": { 353 + "node": ">=18" 354 + } 355 + }, 356 + "node_modules/@esbuild/linux-ppc64": { 357 + "version": "0.25.0", 358 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", 359 + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", 360 + "cpu": [ 361 + "ppc64" 362 + ], 363 + "license": "MIT", 364 + "optional": true, 365 + "os": [ 366 + "linux" 367 + ], 368 + "engines": { 369 + "node": ">=18" 370 + } 371 + }, 372 + "node_modules/@esbuild/linux-riscv64": { 373 + "version": "0.25.0", 374 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", 375 + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", 376 + "cpu": [ 377 + "riscv64" 378 + ], 379 + "license": "MIT", 380 + "optional": true, 381 + "os": [ 382 + "linux" 383 + ], 384 + "engines": { 385 + "node": ">=18" 386 + } 387 + }, 388 + "node_modules/@esbuild/linux-s390x": { 389 + "version": "0.25.0", 390 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", 391 + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", 392 + "cpu": [ 393 + "s390x" 394 + ], 395 + "license": "MIT", 396 + "optional": true, 397 + "os": [ 398 + "linux" 399 + ], 400 + "engines": { 401 + "node": ">=18" 402 + } 403 + }, 404 + "node_modules/@esbuild/linux-x64": { 405 + "version": "0.25.0", 406 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", 407 + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", 408 + "cpu": [ 409 + "x64" 410 + ], 411 + "license": "MIT", 412 + "optional": true, 413 + "os": [ 414 + "linux" 415 + ], 416 + "engines": { 417 + "node": ">=18" 418 + } 419 + }, 420 + "node_modules/@esbuild/netbsd-arm64": { 421 + "version": "0.25.0", 422 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", 423 + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", 424 + "cpu": [ 425 + "arm64" 426 + ], 427 + "license": "MIT", 428 + "optional": true, 429 + "os": [ 430 + "netbsd" 431 + ], 432 + "engines": { 433 + "node": ">=18" 434 + } 435 + }, 436 + "node_modules/@esbuild/netbsd-x64": { 437 + "version": "0.25.0", 438 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", 439 + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", 440 + "cpu": [ 441 + "x64" 442 + ], 443 + "license": "MIT", 444 + "optional": true, 445 + "os": [ 446 + "netbsd" 447 + ], 448 + "engines": { 449 + "node": ">=18" 450 + } 451 + }, 452 + "node_modules/@esbuild/openbsd-arm64": { 453 + "version": "0.25.0", 454 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", 455 + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", 456 + "cpu": [ 457 + "arm64" 458 + ], 459 + "license": "MIT", 460 + "optional": true, 461 + "os": [ 462 + "openbsd" 463 + ], 464 + "engines": { 465 + "node": ">=18" 466 + } 467 + }, 468 + "node_modules/@esbuild/openbsd-x64": { 469 + "version": "0.25.0", 470 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", 471 + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", 472 + "cpu": [ 473 + "x64" 474 + ], 475 + "license": "MIT", 476 + "optional": true, 477 + "os": [ 478 + "openbsd" 479 + ], 480 + "engines": { 481 + "node": ">=18" 482 + } 483 + }, 484 + "node_modules/@esbuild/sunos-x64": { 485 + "version": "0.25.0", 486 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", 487 + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", 488 + "cpu": [ 489 + "x64" 490 + ], 491 + "license": "MIT", 492 + "optional": true, 493 + "os": [ 494 + "sunos" 495 + ], 496 + "engines": { 497 + "node": ">=18" 498 + } 499 + }, 500 + "node_modules/@esbuild/win32-arm64": { 501 + "version": "0.25.0", 502 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", 503 + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", 504 + "cpu": [ 505 + "arm64" 506 + ], 507 + "license": "MIT", 508 + "optional": true, 509 + "os": [ 510 + "win32" 511 + ], 512 + "engines": { 513 + "node": ">=18" 514 + } 515 + }, 516 + "node_modules/@esbuild/win32-ia32": { 517 + "version": "0.25.0", 518 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", 519 + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", 520 + "cpu": [ 521 + "ia32" 522 + ], 523 + "license": "MIT", 524 + "optional": true, 525 + "os": [ 526 + "win32" 527 + ], 528 + "engines": { 529 + "node": ">=18" 530 + } 531 + }, 532 + "node_modules/@esbuild/win32-x64": { 533 + "version": "0.25.0", 534 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", 535 + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", 536 + "cpu": [ 537 + "x64" 538 + ], 539 + "license": "MIT", 540 + "optional": true, 541 + "os": [ 542 + "win32" 543 + ], 544 + "engines": { 545 + "node": ">=18" 546 + } 547 + }, 548 + "node_modules/@eslint-community/eslint-utils": { 549 + "version": "4.4.1", 550 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", 551 + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", 552 + "dev": true, 553 + "license": "MIT", 554 + "dependencies": { 555 + "eslint-visitor-keys": "^3.4.3" 556 + }, 557 + "engines": { 558 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 559 + }, 560 + "funding": { 561 + "url": "https://opencollective.com/eslint" 562 + }, 563 + "peerDependencies": { 564 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 565 + } 566 + }, 567 + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 568 + "version": "3.4.3", 569 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 570 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 571 + "dev": true, 572 + "license": "Apache-2.0", 573 + "engines": { 574 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 575 + }, 576 + "funding": { 577 + "url": "https://opencollective.com/eslint" 578 + } 579 + }, 580 + "node_modules/@eslint-community/regexpp": { 581 + "version": "4.12.1", 582 + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 583 + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 584 + "dev": true, 585 + "license": "MIT", 586 + "engines": { 587 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 588 + } 589 + }, 590 + "node_modules/@eslint/compat": { 591 + "version": "1.2.7", 592 + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.7.tgz", 593 + "integrity": "sha512-xvv7hJE32yhegJ8xNAnb62ggiAwTYHBpUCWhRxEj/ksvgDJuSXfoDkBcRYaYNFiJ+jH0IE3K16hd+xXzhBgNbg==", 594 + "dev": true, 595 + "license": "Apache-2.0", 596 + "engines": { 597 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 598 + }, 599 + "peerDependencies": { 600 + "eslint": "^9.10.0" 601 + }, 602 + "peerDependenciesMeta": { 603 + "eslint": { 604 + "optional": true 605 + } 606 + } 607 + }, 608 + "node_modules/@eslint/config-array": { 609 + "version": "0.19.2", 610 + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", 611 + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", 612 + "dev": true, 613 + "license": "Apache-2.0", 614 + "dependencies": { 615 + "@eslint/object-schema": "^2.1.6", 616 + "debug": "^4.3.1", 617 + "minimatch": "^3.1.2" 618 + }, 619 + "engines": { 620 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 621 + } 622 + }, 623 + "node_modules/@eslint/core": { 624 + "version": "0.12.0", 625 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", 626 + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", 627 + "dev": true, 628 + "license": "Apache-2.0", 629 + "dependencies": { 630 + "@types/json-schema": "^7.0.15" 631 + }, 632 + "engines": { 633 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 634 + } 635 + }, 636 + "node_modules/@eslint/eslintrc": { 637 + "version": "3.3.0", 638 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", 639 + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", 640 + "dev": true, 641 + "license": "MIT", 642 + "dependencies": { 643 + "ajv": "^6.12.4", 644 + "debug": "^4.3.2", 645 + "espree": "^10.0.1", 646 + "globals": "^14.0.0", 647 + "ignore": "^5.2.0", 648 + "import-fresh": "^3.2.1", 649 + "js-yaml": "^4.1.0", 650 + "minimatch": "^3.1.2", 651 + "strip-json-comments": "^3.1.1" 652 + }, 653 + "engines": { 654 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 655 + }, 656 + "funding": { 657 + "url": "https://opencollective.com/eslint" 658 + } 659 + }, 660 + "node_modules/@eslint/eslintrc/node_modules/globals": { 661 + "version": "14.0.0", 662 + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 663 + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 664 + "dev": true, 665 + "license": "MIT", 666 + "engines": { 667 + "node": ">=18" 668 + }, 669 + "funding": { 670 + "url": "https://github.com/sponsors/sindresorhus" 671 + } 672 + }, 673 + "node_modules/@eslint/js": { 674 + "version": "9.21.0", 675 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", 676 + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", 677 + "dev": true, 678 + "license": "MIT", 679 + "engines": { 680 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 681 + } 682 + }, 683 + "node_modules/@eslint/object-schema": { 684 + "version": "2.1.6", 685 + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", 686 + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 687 + "dev": true, 688 + "license": "Apache-2.0", 689 + "engines": { 690 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 691 + } 692 + }, 693 + "node_modules/@eslint/plugin-kit": { 694 + "version": "0.2.7", 695 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", 696 + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", 697 + "dev": true, 698 + "license": "Apache-2.0", 699 + "dependencies": { 700 + "@eslint/core": "^0.12.0", 701 + "levn": "^0.4.1" 702 + }, 703 + "engines": { 704 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 705 + } 706 + }, 707 + "node_modules/@floating-ui/core": { 708 + "version": "1.6.9", 709 + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", 710 + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", 711 + "license": "MIT", 712 + "dependencies": { 713 + "@floating-ui/utils": "^0.2.9" 714 + } 715 + }, 716 + "node_modules/@floating-ui/dom": { 717 + "version": "1.6.13", 718 + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", 719 + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", 720 + "license": "MIT", 721 + "dependencies": { 722 + "@floating-ui/core": "^1.6.0", 723 + "@floating-ui/utils": "^0.2.9" 724 + } 725 + }, 726 + "node_modules/@floating-ui/utils": { 727 + "version": "0.2.9", 728 + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", 729 + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", 730 + "license": "MIT" 731 + }, 732 + "node_modules/@humanfs/core": { 733 + "version": "0.19.1", 734 + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 735 + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 736 + "dev": true, 737 + "license": "Apache-2.0", 738 + "engines": { 739 + "node": ">=18.18.0" 740 + } 741 + }, 742 + "node_modules/@humanfs/node": { 743 + "version": "0.16.6", 744 + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", 745 + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", 746 + "dev": true, 747 + "license": "Apache-2.0", 748 + "dependencies": { 749 + "@humanfs/core": "^0.19.1", 750 + "@humanwhocodes/retry": "^0.3.0" 751 + }, 752 + "engines": { 753 + "node": ">=18.18.0" 754 + } 755 + }, 756 + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { 757 + "version": "0.3.1", 758 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", 759 + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", 760 + "dev": true, 761 + "license": "Apache-2.0", 762 + "engines": { 763 + "node": ">=18.18" 764 + }, 765 + "funding": { 766 + "type": "github", 767 + "url": "https://github.com/sponsors/nzakas" 768 + } 769 + }, 770 + "node_modules/@humanwhocodes/module-importer": { 771 + "version": "1.0.1", 772 + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 773 + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 774 + "dev": true, 775 + "license": "Apache-2.0", 776 + "engines": { 777 + "node": ">=12.22" 778 + }, 779 + "funding": { 780 + "type": "github", 781 + "url": "https://github.com/sponsors/nzakas" 782 + } 783 + }, 784 + "node_modules/@humanwhocodes/retry": { 785 + "version": "0.4.2", 786 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", 787 + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", 788 + "dev": true, 789 + "license": "Apache-2.0", 790 + "engines": { 791 + "node": ">=18.18" 792 + }, 793 + "funding": { 794 + "type": "github", 795 + "url": "https://github.com/sponsors/nzakas" 796 + } 797 + }, 798 + "node_modules/@img/sharp-darwin-arm64": { 799 + "version": "0.33.5", 800 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", 801 + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", 802 + "cpu": [ 803 + "arm64" 804 + ], 805 + "license": "Apache-2.0", 806 + "optional": true, 807 + "os": [ 808 + "darwin" 809 + ], 810 + "engines": { 811 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 812 + }, 813 + "funding": { 814 + "url": "https://opencollective.com/libvips" 815 + }, 816 + "optionalDependencies": { 817 + "@img/sharp-libvips-darwin-arm64": "1.0.4" 818 + } 819 + }, 820 + "node_modules/@img/sharp-darwin-x64": { 821 + "version": "0.33.5", 822 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", 823 + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", 824 + "cpu": [ 825 + "x64" 826 + ], 827 + "license": "Apache-2.0", 828 + "optional": true, 829 + "os": [ 830 + "darwin" 831 + ], 832 + "engines": { 833 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 834 + }, 835 + "funding": { 836 + "url": "https://opencollective.com/libvips" 837 + }, 838 + "optionalDependencies": { 839 + "@img/sharp-libvips-darwin-x64": "1.0.4" 840 + } 841 + }, 842 + "node_modules/@img/sharp-libvips-darwin-arm64": { 843 + "version": "1.0.4", 844 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", 845 + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", 846 + "cpu": [ 847 + "arm64" 848 + ], 849 + "license": "LGPL-3.0-or-later", 850 + "optional": true, 851 + "os": [ 852 + "darwin" 853 + ], 854 + "funding": { 855 + "url": "https://opencollective.com/libvips" 856 + } 857 + }, 858 + "node_modules/@img/sharp-libvips-darwin-x64": { 859 + "version": "1.0.4", 860 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", 861 + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", 862 + "cpu": [ 863 + "x64" 864 + ], 865 + "license": "LGPL-3.0-or-later", 866 + "optional": true, 867 + "os": [ 868 + "darwin" 869 + ], 870 + "funding": { 871 + "url": "https://opencollective.com/libvips" 872 + } 873 + }, 874 + "node_modules/@img/sharp-libvips-linux-arm": { 875 + "version": "1.0.5", 876 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", 877 + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", 878 + "cpu": [ 879 + "arm" 880 + ], 881 + "license": "LGPL-3.0-or-later", 882 + "optional": true, 883 + "os": [ 884 + "linux" 885 + ], 886 + "funding": { 887 + "url": "https://opencollective.com/libvips" 888 + } 889 + }, 890 + "node_modules/@img/sharp-libvips-linux-arm64": { 891 + "version": "1.0.4", 892 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", 893 + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", 894 + "cpu": [ 895 + "arm64" 896 + ], 897 + "license": "LGPL-3.0-or-later", 898 + "optional": true, 899 + "os": [ 900 + "linux" 901 + ], 902 + "funding": { 903 + "url": "https://opencollective.com/libvips" 904 + } 905 + }, 906 + "node_modules/@img/sharp-libvips-linux-s390x": { 907 + "version": "1.0.4", 908 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", 909 + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", 910 + "cpu": [ 911 + "s390x" 912 + ], 913 + "license": "LGPL-3.0-or-later", 914 + "optional": true, 915 + "os": [ 916 + "linux" 917 + ], 918 + "funding": { 919 + "url": "https://opencollective.com/libvips" 920 + } 921 + }, 922 + "node_modules/@img/sharp-libvips-linux-x64": { 923 + "version": "1.0.4", 924 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", 925 + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", 926 + "cpu": [ 927 + "x64" 928 + ], 929 + "license": "LGPL-3.0-or-later", 930 + "optional": true, 931 + "os": [ 932 + "linux" 933 + ], 934 + "funding": { 935 + "url": "https://opencollective.com/libvips" 936 + } 937 + }, 938 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 939 + "version": "1.0.4", 940 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", 941 + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", 942 + "cpu": [ 943 + "arm64" 944 + ], 945 + "license": "LGPL-3.0-or-later", 946 + "optional": true, 947 + "os": [ 948 + "linux" 949 + ], 950 + "funding": { 951 + "url": "https://opencollective.com/libvips" 952 + } 953 + }, 954 + "node_modules/@img/sharp-libvips-linuxmusl-x64": { 955 + "version": "1.0.4", 956 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", 957 + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", 958 + "cpu": [ 959 + "x64" 960 + ], 961 + "license": "LGPL-3.0-or-later", 962 + "optional": true, 963 + "os": [ 964 + "linux" 965 + ], 966 + "funding": { 967 + "url": "https://opencollective.com/libvips" 968 + } 969 + }, 970 + "node_modules/@img/sharp-linux-arm": { 971 + "version": "0.33.5", 972 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", 973 + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", 974 + "cpu": [ 975 + "arm" 976 + ], 977 + "license": "Apache-2.0", 978 + "optional": true, 979 + "os": [ 980 + "linux" 981 + ], 982 + "engines": { 983 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 984 + }, 985 + "funding": { 986 + "url": "https://opencollective.com/libvips" 987 + }, 988 + "optionalDependencies": { 989 + "@img/sharp-libvips-linux-arm": "1.0.5" 990 + } 991 + }, 992 + "node_modules/@img/sharp-linux-arm64": { 993 + "version": "0.33.5", 994 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", 995 + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", 996 + "cpu": [ 997 + "arm64" 998 + ], 999 + "license": "Apache-2.0", 1000 + "optional": true, 1001 + "os": [ 1002 + "linux" 1003 + ], 1004 + "engines": { 1005 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1006 + }, 1007 + "funding": { 1008 + "url": "https://opencollective.com/libvips" 1009 + }, 1010 + "optionalDependencies": { 1011 + "@img/sharp-libvips-linux-arm64": "1.0.4" 1012 + } 1013 + }, 1014 + "node_modules/@img/sharp-linux-s390x": { 1015 + "version": "0.33.5", 1016 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", 1017 + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", 1018 + "cpu": [ 1019 + "s390x" 1020 + ], 1021 + "license": "Apache-2.0", 1022 + "optional": true, 1023 + "os": [ 1024 + "linux" 1025 + ], 1026 + "engines": { 1027 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1028 + }, 1029 + "funding": { 1030 + "url": "https://opencollective.com/libvips" 1031 + }, 1032 + "optionalDependencies": { 1033 + "@img/sharp-libvips-linux-s390x": "1.0.4" 1034 + } 1035 + }, 1036 + "node_modules/@img/sharp-linux-x64": { 1037 + "version": "0.33.5", 1038 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", 1039 + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", 1040 + "cpu": [ 1041 + "x64" 1042 + ], 1043 + "license": "Apache-2.0", 1044 + "optional": true, 1045 + "os": [ 1046 + "linux" 1047 + ], 1048 + "engines": { 1049 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1050 + }, 1051 + "funding": { 1052 + "url": "https://opencollective.com/libvips" 1053 + }, 1054 + "optionalDependencies": { 1055 + "@img/sharp-libvips-linux-x64": "1.0.4" 1056 + } 1057 + }, 1058 + "node_modules/@img/sharp-linuxmusl-arm64": { 1059 + "version": "0.33.5", 1060 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", 1061 + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", 1062 + "cpu": [ 1063 + "arm64" 1064 + ], 1065 + "license": "Apache-2.0", 1066 + "optional": true, 1067 + "os": [ 1068 + "linux" 1069 + ], 1070 + "engines": { 1071 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1072 + }, 1073 + "funding": { 1074 + "url": "https://opencollective.com/libvips" 1075 + }, 1076 + "optionalDependencies": { 1077 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" 1078 + } 1079 + }, 1080 + "node_modules/@img/sharp-linuxmusl-x64": { 1081 + "version": "0.33.5", 1082 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", 1083 + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", 1084 + "cpu": [ 1085 + "x64" 1086 + ], 1087 + "license": "Apache-2.0", 1088 + "optional": true, 1089 + "os": [ 1090 + "linux" 1091 + ], 1092 + "engines": { 1093 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1094 + }, 1095 + "funding": { 1096 + "url": "https://opencollective.com/libvips" 1097 + }, 1098 + "optionalDependencies": { 1099 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" 1100 + } 1101 + }, 1102 + "node_modules/@img/sharp-wasm32": { 1103 + "version": "0.33.5", 1104 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", 1105 + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", 1106 + "cpu": [ 1107 + "wasm32" 1108 + ], 1109 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 1110 + "optional": true, 1111 + "dependencies": { 1112 + "@emnapi/runtime": "^1.2.0" 1113 + }, 1114 + "engines": { 1115 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1116 + }, 1117 + "funding": { 1118 + "url": "https://opencollective.com/libvips" 1119 + } 1120 + }, 1121 + "node_modules/@img/sharp-win32-ia32": { 1122 + "version": "0.33.5", 1123 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", 1124 + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", 1125 + "cpu": [ 1126 + "ia32" 1127 + ], 1128 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1129 + "optional": true, 1130 + "os": [ 1131 + "win32" 1132 + ], 1133 + "engines": { 1134 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1135 + }, 1136 + "funding": { 1137 + "url": "https://opencollective.com/libvips" 1138 + } 1139 + }, 1140 + "node_modules/@img/sharp-win32-x64": { 1141 + "version": "0.33.5", 1142 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", 1143 + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", 1144 + "cpu": [ 1145 + "x64" 1146 + ], 1147 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1148 + "optional": true, 1149 + "os": [ 1150 + "win32" 1151 + ], 1152 + "engines": { 1153 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1154 + }, 1155 + "funding": { 1156 + "url": "https://opencollective.com/libvips" 1157 + } 1158 + }, 1159 + "node_modules/@internationalized/date": { 1160 + "version": "3.7.0", 1161 + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz", 1162 + "integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==", 1163 + "license": "Apache-2.0", 1164 + "dependencies": { 1165 + "@swc/helpers": "^0.5.0" 1166 + } 1167 + }, 1168 + "node_modules/@jridgewell/gen-mapping": { 1169 + "version": "0.3.8", 1170 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", 1171 + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", 1172 + "license": "MIT", 1173 + "dependencies": { 1174 + "@jridgewell/set-array": "^1.2.1", 1175 + "@jridgewell/sourcemap-codec": "^1.4.10", 1176 + "@jridgewell/trace-mapping": "^0.3.24" 1177 + }, 1178 + "engines": { 1179 + "node": ">=6.0.0" 1180 + } 1181 + }, 1182 + "node_modules/@jridgewell/resolve-uri": { 1183 + "version": "3.1.2", 1184 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 1185 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 1186 + "license": "MIT", 1187 + "engines": { 1188 + "node": ">=6.0.0" 1189 + } 1190 + }, 1191 + "node_modules/@jridgewell/set-array": { 1192 + "version": "1.2.1", 1193 + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 1194 + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 1195 + "license": "MIT", 1196 + "engines": { 1197 + "node": ">=6.0.0" 1198 + } 1199 + }, 1200 + "node_modules/@jridgewell/sourcemap-codec": { 1201 + "version": "1.5.0", 1202 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 1203 + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 1204 + "license": "MIT" 1205 + }, 1206 + "node_modules/@jridgewell/trace-mapping": { 1207 + "version": "0.3.25", 1208 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 1209 + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 1210 + "license": "MIT", 1211 + "dependencies": { 1212 + "@jridgewell/resolve-uri": "^3.1.0", 1213 + "@jridgewell/sourcemap-codec": "^1.4.14" 1214 + } 1215 + }, 1216 + "node_modules/@kitschpatrol/tweakpane-plugin-camerakit": { 1217 + "version": "0.3.1-beta.3", 1218 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-camerakit/-/tweakpane-plugin-camerakit-0.3.1-beta.3.tgz", 1219 + "integrity": "sha512-Tg9TyaaeG7bfo4d6Bx1qtRBKWzs+8uYpN4FaxtyIcOJES6YjOXNAe7f23G2auSDuhkA8H62nqo4lnqxYOoyGEQ==", 1220 + "license": "MIT", 1221 + "dependencies": { 1222 + "@tweakpane/core": "^2.0.5" 1223 + }, 1224 + "engines": { 1225 + "node": ">=18.0.0" 1226 + }, 1227 + "peerDependencies": { 1228 + "tweakpane": "^4.0.0" 1229 + } 1230 + }, 1231 + "node_modules/@kitschpatrol/tweakpane-plugin-essentials": { 1232 + "version": "0.2.2-beta.3", 1233 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-essentials/-/tweakpane-plugin-essentials-0.2.2-beta.3.tgz", 1234 + "integrity": "sha512-ucqdSQ16G8vCtifuQSr2sCtKCNWzhScOp4g+6p+uXRHIpOC73zHRAlLUndB2ZgjhOyA+0+2zQqBx22QnJkybaw==", 1235 + "license": "MIT", 1236 + "dependencies": { 1237 + "@tweakpane/core": "^2.0.5" 1238 + }, 1239 + "engines": { 1240 + "node": ">=18.0.0" 1241 + }, 1242 + "peerDependencies": { 1243 + "tweakpane": "^4.0.0" 1244 + } 1245 + }, 1246 + "node_modules/@kitschpatrol/tweakpane-plugin-file-import": { 1247 + "version": "1.1.2-beta.2", 1248 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-file-import/-/tweakpane-plugin-file-import-1.1.2-beta.2.tgz", 1249 + "integrity": "sha512-+W5DIOu2B/J0pmwSmxnF93TemJCvpMfvrcx5Opt8DYwsbtmFm0hRaHBO1dHmTeMJaBckOxWxIq22nJ4jcuwB4w==", 1250 + "license": "MIT", 1251 + "dependencies": { 1252 + "@tweakpane/core": "^2.0.5" 1253 + }, 1254 + "engines": { 1255 + "node": ">=18.0.0" 1256 + }, 1257 + "peerDependencies": { 1258 + "tweakpane": "^4.0.0" 1259 + } 1260 + }, 1261 + "node_modules/@kitschpatrol/tweakpane-plugin-image": { 1262 + "version": "2.0.1-beta.6", 1263 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-image/-/tweakpane-plugin-image-2.0.1-beta.6.tgz", 1264 + "integrity": "sha512-eaDYP3qjYHmXvlC9P8XIj+zVb176G96rStL224o+RcU5S4LWPXRXvpucltPbLWVFRi2AFo5LHoEFSkSB1P07Og==", 1265 + "license": "MIT", 1266 + "dependencies": { 1267 + "@tweakpane/core": "^2.0.5" 1268 + }, 1269 + "engines": { 1270 + "node": ">=18.0.0" 1271 + }, 1272 + "peerDependencies": { 1273 + "tweakpane": "^4.0.0" 1274 + } 1275 + }, 1276 + "node_modules/@kitschpatrol/tweakpane-plugin-inputs": { 1277 + "version": "1.0.4-beta.4", 1278 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-inputs/-/tweakpane-plugin-inputs-1.0.4-beta.4.tgz", 1279 + "integrity": "sha512-VRzvTTMCy6wRABJOkPDbguPTzRJytrxx0RUNMSJpx7H5L4XJ8BVzglQqatXLxHr3Nvy3ZovwqX11W7mu7QcX5A==", 1280 + "license": "MIT", 1281 + "dependencies": { 1282 + "@tweakpane/core": "^2.0.5" 1283 + }, 1284 + "engines": { 1285 + "node": ">=18.0.0" 1286 + }, 1287 + "peerDependencies": { 1288 + "tweakpane": "^4.0.0" 1289 + } 1290 + }, 1291 + "node_modules/@kitschpatrol/tweakpane-plugin-profiler": { 1292 + "version": "0.4.2-beta.3", 1293 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-profiler/-/tweakpane-plugin-profiler-0.4.2-beta.3.tgz", 1294 + "integrity": "sha512-9vri0Oew3eO0K/F/jT58VAO3ersaQEfNfEqn5C0Np0zOOuFE+f1d/XgUKfiRPln9p3hrfNjbJ0kGBIfhBw+vzQ==", 1295 + "license": "MIT", 1296 + "dependencies": { 1297 + "@tweakpane/core": "^2.0.5" 1298 + }, 1299 + "engines": { 1300 + "node": ">=18.0.0" 1301 + }, 1302 + "peerDependencies": { 1303 + "tweakpane": "^4.0.0" 1304 + } 1305 + }, 1306 + "node_modules/@kitschpatrol/tweakpane-plugin-rotation": { 1307 + "version": "0.2.1-beta.2", 1308 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-rotation/-/tweakpane-plugin-rotation-0.2.1-beta.2.tgz", 1309 + "integrity": "sha512-chGPIPjdiP18C71xDbO88IkpgaxwBSaFa6W5+XbmxTT5LpHXSyOTtOEv4cjPg85HDUG2Fc+h7tpnTw/3caCFRw==", 1310 + "license": "MIT", 1311 + "dependencies": { 1312 + "@tweakpane/core": "^2.0.5" 1313 + }, 1314 + "engines": { 1315 + "node": ">=18.0.0" 1316 + }, 1317 + "peerDependencies": { 1318 + "tweakpane": "^4.0.0" 1319 + } 1320 + }, 1321 + "node_modules/@kitschpatrol/tweakpane-plugin-textarea": { 1322 + "version": "2.0.1-beta.2", 1323 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-textarea/-/tweakpane-plugin-textarea-2.0.1-beta.2.tgz", 1324 + "integrity": "sha512-VHV6dJRlceyELapEd38Cwqtu+aG7VPX4e1anPxxVESQmb1uzr4LvsDWPYY6s+9XLzT7O1CZHg63Rd7rRn3bsDQ==", 1325 + "license": "MIT", 1326 + "dependencies": { 1327 + "@tweakpane/core": "^2.0.5" 1328 + }, 1329 + "engines": { 1330 + "node": ">=18.0.0" 1331 + }, 1332 + "peerDependencies": { 1333 + "tweakpane": "^4.0.0" 1334 + } 1335 + }, 1336 + "node_modules/@kitschpatrol/tweakpane-plugin-waveform": { 1337 + "version": "1.0.4-beta.3", 1338 + "resolved": "https://registry.npmjs.org/@kitschpatrol/tweakpane-plugin-waveform/-/tweakpane-plugin-waveform-1.0.4-beta.3.tgz", 1339 + "integrity": "sha512-UVzLSoLQNnyvG3xwno+Mxl/O2rED0b7yz3UIl8gbU3eggJOsHMltywHpSuF7oIJYbbtQtjns6um/fhYyvzXnRQ==", 1340 + "license": "MIT", 1341 + "dependencies": { 1342 + "@tweakpane/core": "^2.0.5" 1343 + }, 1344 + "engines": { 1345 + "node": ">=18.0.0" 1346 + }, 1347 + "peerDependencies": { 1348 + "tweakpane": "^4.0.0" 1349 + } 1350 + }, 1351 + "node_modules/@mdi/js": { 1352 + "version": "7.4.47", 1353 + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", 1354 + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", 1355 + "license": "Apache-2.0" 1356 + }, 1357 + "node_modules/@nodelib/fs.scandir": { 1358 + "version": "2.1.5", 1359 + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 1360 + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 1361 + "dev": true, 1362 + "license": "MIT", 1363 + "dependencies": { 1364 + "@nodelib/fs.stat": "2.0.5", 1365 + "run-parallel": "^1.1.9" 1366 + }, 1367 + "engines": { 1368 + "node": ">= 8" 1369 + } 1370 + }, 1371 + "node_modules/@nodelib/fs.stat": { 1372 + "version": "2.0.5", 1373 + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 1374 + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 1375 + "dev": true, 1376 + "license": "MIT", 1377 + "engines": { 1378 + "node": ">= 8" 1379 + } 1380 + }, 1381 + "node_modules/@nodelib/fs.walk": { 1382 + "version": "1.2.8", 1383 + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 1384 + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 1385 + "dev": true, 1386 + "license": "MIT", 1387 + "dependencies": { 1388 + "@nodelib/fs.scandir": "2.1.5", 1389 + "fastq": "^1.6.0" 1390 + }, 1391 + "engines": { 1392 + "node": ">= 8" 1393 + } 1394 + }, 1395 + "node_modules/@number-flow/svelte": { 1396 + "version": "0.3.3", 1397 + "resolved": "https://registry.npmjs.org/@number-flow/svelte/-/svelte-0.3.3.tgz", 1398 + "integrity": "sha512-wo5tiU1qKc74k61sNx8GwvnmZ7yKdbfj1onjzaAa67HRXFClRcrfLlXXatBSQ/uK2XTz9156ILs3VmpuSG1sFA==", 1399 + "license": "MIT", 1400 + "dependencies": { 1401 + "esm-env": "^1.1.4", 1402 + "number-flow": "0.5.3" 1403 + }, 1404 + "peerDependencies": { 1405 + "svelte": "^4 || ^5" 1406 + } 1407 + }, 1408 + "node_modules/@parcel/watcher": { 1409 + "version": "2.5.1", 1410 + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", 1411 + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", 1412 + "hasInstallScript": true, 1413 + "license": "MIT", 1414 + "optional": true, 1415 + "dependencies": { 1416 + "detect-libc": "^1.0.3", 1417 + "is-glob": "^4.0.3", 1418 + "micromatch": "^4.0.5", 1419 + "node-addon-api": "^7.0.0" 1420 + }, 1421 + "engines": { 1422 + "node": ">= 10.0.0" 1423 + }, 1424 + "funding": { 1425 + "type": "opencollective", 1426 + "url": "https://opencollective.com/parcel" 1427 + }, 1428 + "optionalDependencies": { 1429 + "@parcel/watcher-android-arm64": "2.5.1", 1430 + "@parcel/watcher-darwin-arm64": "2.5.1", 1431 + "@parcel/watcher-darwin-x64": "2.5.1", 1432 + "@parcel/watcher-freebsd-x64": "2.5.1", 1433 + "@parcel/watcher-linux-arm-glibc": "2.5.1", 1434 + "@parcel/watcher-linux-arm-musl": "2.5.1", 1435 + "@parcel/watcher-linux-arm64-glibc": "2.5.1", 1436 + "@parcel/watcher-linux-arm64-musl": "2.5.1", 1437 + "@parcel/watcher-linux-x64-glibc": "2.5.1", 1438 + "@parcel/watcher-linux-x64-musl": "2.5.1", 1439 + "@parcel/watcher-win32-arm64": "2.5.1", 1440 + "@parcel/watcher-win32-ia32": "2.5.1", 1441 + "@parcel/watcher-win32-x64": "2.5.1" 1442 + } 1443 + }, 1444 + "node_modules/@parcel/watcher-android-arm64": { 1445 + "version": "2.5.1", 1446 + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", 1447 + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", 1448 + "cpu": [ 1449 + "arm64" 1450 + ], 1451 + "license": "MIT", 1452 + "optional": true, 1453 + "os": [ 1454 + "android" 1455 + ], 1456 + "engines": { 1457 + "node": ">= 10.0.0" 1458 + }, 1459 + "funding": { 1460 + "type": "opencollective", 1461 + "url": "https://opencollective.com/parcel" 1462 + } 1463 + }, 1464 + "node_modules/@parcel/watcher-darwin-arm64": { 1465 + "version": "2.5.1", 1466 + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", 1467 + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", 1468 + "cpu": [ 1469 + "arm64" 1470 + ], 1471 + "license": "MIT", 1472 + "optional": true, 1473 + "os": [ 1474 + "darwin" 1475 + ], 1476 + "engines": { 1477 + "node": ">= 10.0.0" 1478 + }, 1479 + "funding": { 1480 + "type": "opencollective", 1481 + "url": "https://opencollective.com/parcel" 1482 + } 1483 + }, 1484 + "node_modules/@parcel/watcher-darwin-x64": { 1485 + "version": "2.5.1", 1486 + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", 1487 + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", 1488 + "cpu": [ 1489 + "x64" 1490 + ], 1491 + "license": "MIT", 1492 + "optional": true, 1493 + "os": [ 1494 + "darwin" 1495 + ], 1496 + "engines": { 1497 + "node": ">= 10.0.0" 1498 + }, 1499 + "funding": { 1500 + "type": "opencollective", 1501 + "url": "https://opencollective.com/parcel" 1502 + } 1503 + }, 1504 + "node_modules/@parcel/watcher-freebsd-x64": { 1505 + "version": "2.5.1", 1506 + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", 1507 + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", 1508 + "cpu": [ 1509 + "x64" 1510 + ], 1511 + "license": "MIT", 1512 + "optional": true, 1513 + "os": [ 1514 + "freebsd" 1515 + ], 1516 + "engines": { 1517 + "node": ">= 10.0.0" 1518 + }, 1519 + "funding": { 1520 + "type": "opencollective", 1521 + "url": "https://opencollective.com/parcel" 1522 + } 1523 + }, 1524 + "node_modules/@parcel/watcher-linux-arm-glibc": { 1525 + "version": "2.5.1", 1526 + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", 1527 + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", 1528 + "cpu": [ 1529 + "arm" 1530 + ], 1531 + "license": "MIT", 1532 + "optional": true, 1533 + "os": [ 1534 + "linux" 1535 + ], 1536 + "engines": { 1537 + "node": ">= 10.0.0" 1538 + }, 1539 + "funding": { 1540 + "type": "opencollective", 1541 + "url": "https://opencollective.com/parcel" 1542 + } 1543 + }, 1544 + "node_modules/@parcel/watcher-linux-arm-musl": { 1545 + "version": "2.5.1", 1546 + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", 1547 + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", 1548 + "cpu": [ 1549 + "arm" 1550 + ], 1551 + "license": "MIT", 1552 + "optional": true, 1553 + "os": [ 1554 + "linux" 1555 + ], 1556 + "engines": { 1557 + "node": ">= 10.0.0" 1558 + }, 1559 + "funding": { 1560 + "type": "opencollective", 1561 + "url": "https://opencollective.com/parcel" 1562 + } 1563 + }, 1564 + "node_modules/@parcel/watcher-linux-arm64-glibc": { 1565 + "version": "2.5.1", 1566 + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", 1567 + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", 1568 + "cpu": [ 1569 + "arm64" 1570 + ], 1571 + "license": "MIT", 1572 + "optional": true, 1573 + "os": [ 1574 + "linux" 1575 + ], 1576 + "engines": { 1577 + "node": ">= 10.0.0" 1578 + }, 1579 + "funding": { 1580 + "type": "opencollective", 1581 + "url": "https://opencollective.com/parcel" 1582 + } 1583 + }, 1584 + "node_modules/@parcel/watcher-linux-arm64-musl": { 1585 + "version": "2.5.1", 1586 + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", 1587 + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", 1588 + "cpu": [ 1589 + "arm64" 1590 + ], 1591 + "license": "MIT", 1592 + "optional": true, 1593 + "os": [ 1594 + "linux" 1595 + ], 1596 + "engines": { 1597 + "node": ">= 10.0.0" 1598 + }, 1599 + "funding": { 1600 + "type": "opencollective", 1601 + "url": "https://opencollective.com/parcel" 1602 + } 1603 + }, 1604 + "node_modules/@parcel/watcher-linux-x64-glibc": { 1605 + "version": "2.5.1", 1606 + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", 1607 + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", 1608 + "cpu": [ 1609 + "x64" 1610 + ], 1611 + "license": "MIT", 1612 + "optional": true, 1613 + "os": [ 1614 + "linux" 1615 + ], 1616 + "engines": { 1617 + "node": ">= 10.0.0" 1618 + }, 1619 + "funding": { 1620 + "type": "opencollective", 1621 + "url": "https://opencollective.com/parcel" 1622 + } 1623 + }, 1624 + "node_modules/@parcel/watcher-linux-x64-musl": { 1625 + "version": "2.5.1", 1626 + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", 1627 + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", 1628 + "cpu": [ 1629 + "x64" 1630 + ], 1631 + "license": "MIT", 1632 + "optional": true, 1633 + "os": [ 1634 + "linux" 1635 + ], 1636 + "engines": { 1637 + "node": ">= 10.0.0" 1638 + }, 1639 + "funding": { 1640 + "type": "opencollective", 1641 + "url": "https://opencollective.com/parcel" 1642 + } 1643 + }, 1644 + "node_modules/@parcel/watcher-win32-arm64": { 1645 + "version": "2.5.1", 1646 + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", 1647 + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", 1648 + "cpu": [ 1649 + "arm64" 1650 + ], 1651 + "license": "MIT", 1652 + "optional": true, 1653 + "os": [ 1654 + "win32" 1655 + ], 1656 + "engines": { 1657 + "node": ">= 10.0.0" 1658 + }, 1659 + "funding": { 1660 + "type": "opencollective", 1661 + "url": "https://opencollective.com/parcel" 1662 + } 1663 + }, 1664 + "node_modules/@parcel/watcher-win32-ia32": { 1665 + "version": "2.5.1", 1666 + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", 1667 + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", 1668 + "cpu": [ 1669 + "ia32" 1670 + ], 1671 + "license": "MIT", 1672 + "optional": true, 1673 + "os": [ 1674 + "win32" 1675 + ], 1676 + "engines": { 1677 + "node": ">= 10.0.0" 1678 + }, 1679 + "funding": { 1680 + "type": "opencollective", 1681 + "url": "https://opencollective.com/parcel" 1682 + } 1683 + }, 1684 + "node_modules/@parcel/watcher-win32-x64": { 1685 + "version": "2.5.1", 1686 + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", 1687 + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", 1688 + "cpu": [ 1689 + "x64" 1690 + ], 1691 + "license": "MIT", 1692 + "optional": true, 1693 + "os": [ 1694 + "win32" 1695 + ], 1696 + "engines": { 1697 + "node": ">= 10.0.0" 1698 + }, 1699 + "funding": { 1700 + "type": "opencollective", 1701 + "url": "https://opencollective.com/parcel" 1702 + } 1703 + }, 1704 + "node_modules/@polka/url": { 1705 + "version": "1.0.0-next.28", 1706 + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", 1707 + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", 1708 + "license": "MIT" 1709 + }, 1710 + "node_modules/@rollup/pluginutils": { 1711 + "version": "5.1.4", 1712 + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", 1713 + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", 1714 + "license": "MIT", 1715 + "dependencies": { 1716 + "@types/estree": "^1.0.0", 1717 + "estree-walker": "^2.0.2", 1718 + "picomatch": "^4.0.2" 1719 + }, 1720 + "engines": { 1721 + "node": ">=14.0.0" 1722 + }, 1723 + "peerDependencies": { 1724 + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 1725 + }, 1726 + "peerDependenciesMeta": { 1727 + "rollup": { 1728 + "optional": true 1729 + } 1730 + } 1731 + }, 1732 + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { 1733 + "version": "2.0.2", 1734 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1735 + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 1736 + "license": "MIT" 1737 + }, 1738 + "node_modules/@rollup/rollup-android-arm-eabi": { 1739 + "version": "4.34.9", 1740 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz", 1741 + "integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==", 1742 + "cpu": [ 1743 + "arm" 1744 + ], 1745 + "license": "MIT", 1746 + "optional": true, 1747 + "os": [ 1748 + "android" 1749 + ] 1750 + }, 1751 + "node_modules/@rollup/rollup-android-arm64": { 1752 + "version": "4.34.9", 1753 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz", 1754 + "integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==", 1755 + "cpu": [ 1756 + "arm64" 1757 + ], 1758 + "license": "MIT", 1759 + "optional": true, 1760 + "os": [ 1761 + "android" 1762 + ] 1763 + }, 1764 + "node_modules/@rollup/rollup-darwin-arm64": { 1765 + "version": "4.34.9", 1766 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", 1767 + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", 1768 + "cpu": [ 1769 + "arm64" 1770 + ], 1771 + "license": "MIT", 1772 + "optional": true, 1773 + "os": [ 1774 + "darwin" 1775 + ] 1776 + }, 1777 + "node_modules/@rollup/rollup-darwin-x64": { 1778 + "version": "4.34.9", 1779 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", 1780 + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", 1781 + "cpu": [ 1782 + "x64" 1783 + ], 1784 + "license": "MIT", 1785 + "optional": true, 1786 + "os": [ 1787 + "darwin" 1788 + ] 1789 + }, 1790 + "node_modules/@rollup/rollup-freebsd-arm64": { 1791 + "version": "4.34.9", 1792 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz", 1793 + "integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==", 1794 + "cpu": [ 1795 + "arm64" 1796 + ], 1797 + "license": "MIT", 1798 + "optional": true, 1799 + "os": [ 1800 + "freebsd" 1801 + ] 1802 + }, 1803 + "node_modules/@rollup/rollup-freebsd-x64": { 1804 + "version": "4.34.9", 1805 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz", 1806 + "integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==", 1807 + "cpu": [ 1808 + "x64" 1809 + ], 1810 + "license": "MIT", 1811 + "optional": true, 1812 + "os": [ 1813 + "freebsd" 1814 + ] 1815 + }, 1816 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1817 + "version": "4.34.9", 1818 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz", 1819 + "integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==", 1820 + "cpu": [ 1821 + "arm" 1822 + ], 1823 + "license": "MIT", 1824 + "optional": true, 1825 + "os": [ 1826 + "linux" 1827 + ] 1828 + }, 1829 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1830 + "version": "4.34.9", 1831 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz", 1832 + "integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==", 1833 + "cpu": [ 1834 + "arm" 1835 + ], 1836 + "license": "MIT", 1837 + "optional": true, 1838 + "os": [ 1839 + "linux" 1840 + ] 1841 + }, 1842 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1843 + "version": "4.34.9", 1844 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", 1845 + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", 1846 + "cpu": [ 1847 + "arm64" 1848 + ], 1849 + "license": "MIT", 1850 + "optional": true, 1851 + "os": [ 1852 + "linux" 1853 + ] 1854 + }, 1855 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1856 + "version": "4.34.9", 1857 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", 1858 + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", 1859 + "cpu": [ 1860 + "arm64" 1861 + ], 1862 + "license": "MIT", 1863 + "optional": true, 1864 + "os": [ 1865 + "linux" 1866 + ] 1867 + }, 1868 + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 1869 + "version": "4.34.9", 1870 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz", 1871 + "integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==", 1872 + "cpu": [ 1873 + "loong64" 1874 + ], 1875 + "license": "MIT", 1876 + "optional": true, 1877 + "os": [ 1878 + "linux" 1879 + ] 1880 + }, 1881 + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 1882 + "version": "4.34.9", 1883 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz", 1884 + "integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==", 1885 + "cpu": [ 1886 + "ppc64" 1887 + ], 1888 + "license": "MIT", 1889 + "optional": true, 1890 + "os": [ 1891 + "linux" 1892 + ] 1893 + }, 1894 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1895 + "version": "4.34.9", 1896 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz", 1897 + "integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==", 1898 + "cpu": [ 1899 + "riscv64" 1900 + ], 1901 + "license": "MIT", 1902 + "optional": true, 1903 + "os": [ 1904 + "linux" 1905 + ] 1906 + }, 1907 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1908 + "version": "4.34.9", 1909 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz", 1910 + "integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==", 1911 + "cpu": [ 1912 + "s390x" 1913 + ], 1914 + "license": "MIT", 1915 + "optional": true, 1916 + "os": [ 1917 + "linux" 1918 + ] 1919 + }, 1920 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1921 + "version": "4.34.9", 1922 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", 1923 + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", 1924 + "cpu": [ 1925 + "x64" 1926 + ], 1927 + "license": "MIT", 1928 + "optional": true, 1929 + "os": [ 1930 + "linux" 1931 + ] 1932 + }, 1933 + "node_modules/@rollup/rollup-linux-x64-musl": { 1934 + "version": "4.34.9", 1935 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", 1936 + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", 1937 + "cpu": [ 1938 + "x64" 1939 + ], 1940 + "license": "MIT", 1941 + "optional": true, 1942 + "os": [ 1943 + "linux" 1944 + ] 1945 + }, 1946 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1947 + "version": "4.34.9", 1948 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", 1949 + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", 1950 + "cpu": [ 1951 + "arm64" 1952 + ], 1953 + "license": "MIT", 1954 + "optional": true, 1955 + "os": [ 1956 + "win32" 1957 + ] 1958 + }, 1959 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1960 + "version": "4.34.9", 1961 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz", 1962 + "integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==", 1963 + "cpu": [ 1964 + "ia32" 1965 + ], 1966 + "license": "MIT", 1967 + "optional": true, 1968 + "os": [ 1969 + "win32" 1970 + ] 1971 + }, 1972 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1973 + "version": "4.34.9", 1974 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", 1975 + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", 1976 + "cpu": [ 1977 + "x64" 1978 + ], 1979 + "license": "MIT", 1980 + "optional": true, 1981 + "os": [ 1982 + "win32" 1983 + ] 1984 + }, 1985 + "node_modules/@sveltejs/acorn-typescript": { 1986 + "version": "1.0.5", 1987 + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", 1988 + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", 1989 + "license": "MIT", 1990 + "peerDependencies": { 1991 + "acorn": "^8.9.0" 1992 + } 1993 + }, 1994 + "node_modules/@sveltejs/adapter-auto": { 1995 + "version": "4.0.0", 1996 + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-4.0.0.tgz", 1997 + "integrity": "sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ==", 1998 + "dev": true, 1999 + "license": "MIT", 2000 + "dependencies": { 2001 + "import-meta-resolve": "^4.1.0" 2002 + }, 2003 + "peerDependencies": { 2004 + "@sveltejs/kit": "^2.0.0" 2005 + } 2006 + }, 2007 + "node_modules/@sveltejs/adapter-static": { 2008 + "version": "3.0.8", 2009 + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", 2010 + "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", 2011 + "license": "MIT", 2012 + "peerDependencies": { 2013 + "@sveltejs/kit": "^2.0.0" 2014 + } 2015 + }, 2016 + "node_modules/@sveltejs/kit": { 2017 + "version": "2.18.0", 2018 + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.18.0.tgz", 2019 + "integrity": "sha512-4DGCGiwNzgnPJySlMe/Qi6rKMK3ntphJaV95BTW+aggaTIAVZ5x3Bp+LURVLMxAEAtWAI5U449NafVxTS+kXbQ==", 2020 + "license": "MIT", 2021 + "dependencies": { 2022 + "@types/cookie": "^0.6.0", 2023 + "cookie": "^0.6.0", 2024 + "devalue": "^5.1.0", 2025 + "esm-env": "^1.2.2", 2026 + "import-meta-resolve": "^4.1.0", 2027 + "kleur": "^4.1.5", 2028 + "magic-string": "^0.30.5", 2029 + "mrmime": "^2.0.0", 2030 + "sade": "^1.8.1", 2031 + "set-cookie-parser": "^2.6.0", 2032 + "sirv": "^3.0.0" 2033 + }, 2034 + "bin": { 2035 + "svelte-kit": "svelte-kit.js" 2036 + }, 2037 + "engines": { 2038 + "node": ">=18.13" 2039 + }, 2040 + "peerDependencies": { 2041 + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", 2042 + "svelte": "^4.0.0 || ^5.0.0-next.0", 2043 + "vite": "^5.0.3 || ^6.0.0" 2044 + } 2045 + }, 2046 + "node_modules/@sveltejs/vite-plugin-svelte": { 2047 + "version": "5.0.3", 2048 + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", 2049 + "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", 2050 + "license": "MIT", 2051 + "dependencies": { 2052 + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", 2053 + "debug": "^4.4.0", 2054 + "deepmerge": "^4.3.1", 2055 + "kleur": "^4.1.5", 2056 + "magic-string": "^0.30.15", 2057 + "vitefu": "^1.0.4" 2058 + }, 2059 + "engines": { 2060 + "node": "^18.0.0 || ^20.0.0 || >=22" 2061 + }, 2062 + "peerDependencies": { 2063 + "svelte": "^5.0.0", 2064 + "vite": "^6.0.0" 2065 + } 2066 + }, 2067 + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { 2068 + "version": "4.0.1", 2069 + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", 2070 + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", 2071 + "license": "MIT", 2072 + "dependencies": { 2073 + "debug": "^4.3.7" 2074 + }, 2075 + "engines": { 2076 + "node": "^18.0.0 || ^20.0.0 || >=22" 2077 + }, 2078 + "peerDependencies": { 2079 + "@sveltejs/vite-plugin-svelte": "^5.0.0", 2080 + "svelte": "^5.0.0", 2081 + "vite": "^6.0.0" 2082 + } 2083 + }, 2084 + "node_modules/@swc/helpers": { 2085 + "version": "0.5.15", 2086 + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", 2087 + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", 2088 + "license": "Apache-2.0", 2089 + "dependencies": { 2090 + "tslib": "^2.8.0" 2091 + } 2092 + }, 2093 + "node_modules/@tailwindcss/forms": { 2094 + "version": "0.5.10", 2095 + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", 2096 + "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", 2097 + "dev": true, 2098 + "license": "MIT", 2099 + "dependencies": { 2100 + "mini-svg-data-uri": "^1.2.3" 2101 + }, 2102 + "peerDependencies": { 2103 + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" 2104 + } 2105 + }, 2106 + "node_modules/@tailwindcss/node": { 2107 + "version": "4.0.9", 2108 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.9.tgz", 2109 + "integrity": "sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ==", 2110 + "dev": true, 2111 + "license": "MIT", 2112 + "dependencies": { 2113 + "enhanced-resolve": "^5.18.1", 2114 + "jiti": "^2.4.2", 2115 + "tailwindcss": "4.0.9" 2116 + } 2117 + }, 2118 + "node_modules/@tailwindcss/oxide": { 2119 + "version": "4.0.9", 2120 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.9.tgz", 2121 + "integrity": "sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA==", 2122 + "dev": true, 2123 + "license": "MIT", 2124 + "engines": { 2125 + "node": ">= 10" 2126 + }, 2127 + "optionalDependencies": { 2128 + "@tailwindcss/oxide-android-arm64": "4.0.9", 2129 + "@tailwindcss/oxide-darwin-arm64": "4.0.9", 2130 + "@tailwindcss/oxide-darwin-x64": "4.0.9", 2131 + "@tailwindcss/oxide-freebsd-x64": "4.0.9", 2132 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.9", 2133 + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.9", 2134 + "@tailwindcss/oxide-linux-arm64-musl": "4.0.9", 2135 + "@tailwindcss/oxide-linux-x64-gnu": "4.0.9", 2136 + "@tailwindcss/oxide-linux-x64-musl": "4.0.9", 2137 + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.9", 2138 + "@tailwindcss/oxide-win32-x64-msvc": "4.0.9" 2139 + } 2140 + }, 2141 + "node_modules/@tailwindcss/oxide-android-arm64": { 2142 + "version": "4.0.9", 2143 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.9.tgz", 2144 + "integrity": "sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q==", 2145 + "cpu": [ 2146 + "arm64" 2147 + ], 2148 + "dev": true, 2149 + "license": "MIT", 2150 + "optional": true, 2151 + "os": [ 2152 + "android" 2153 + ], 2154 + "engines": { 2155 + "node": ">= 10" 2156 + } 2157 + }, 2158 + "node_modules/@tailwindcss/oxide-darwin-arm64": { 2159 + "version": "4.0.9", 2160 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.9.tgz", 2161 + "integrity": "sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw==", 2162 + "cpu": [ 2163 + "arm64" 2164 + ], 2165 + "dev": true, 2166 + "license": "MIT", 2167 + "optional": true, 2168 + "os": [ 2169 + "darwin" 2170 + ], 2171 + "engines": { 2172 + "node": ">= 10" 2173 + } 2174 + }, 2175 + "node_modules/@tailwindcss/oxide-darwin-x64": { 2176 + "version": "4.0.9", 2177 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.9.tgz", 2178 + "integrity": "sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg==", 2179 + "cpu": [ 2180 + "x64" 2181 + ], 2182 + "dev": true, 2183 + "license": "MIT", 2184 + "optional": true, 2185 + "os": [ 2186 + "darwin" 2187 + ], 2188 + "engines": { 2189 + "node": ">= 10" 2190 + } 2191 + }, 2192 + "node_modules/@tailwindcss/oxide-freebsd-x64": { 2193 + "version": "4.0.9", 2194 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.9.tgz", 2195 + "integrity": "sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ==", 2196 + "cpu": [ 2197 + "x64" 2198 + ], 2199 + "dev": true, 2200 + "license": "MIT", 2201 + "optional": true, 2202 + "os": [ 2203 + "freebsd" 2204 + ], 2205 + "engines": { 2206 + "node": ">= 10" 2207 + } 2208 + }, 2209 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 2210 + "version": "4.0.9", 2211 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.9.tgz", 2212 + "integrity": "sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ==", 2213 + "cpu": [ 2214 + "arm" 2215 + ], 2216 + "dev": true, 2217 + "license": "MIT", 2218 + "optional": true, 2219 + "os": [ 2220 + "linux" 2221 + ], 2222 + "engines": { 2223 + "node": ">= 10" 2224 + } 2225 + }, 2226 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 2227 + "version": "4.0.9", 2228 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.9.tgz", 2229 + "integrity": "sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg==", 2230 + "cpu": [ 2231 + "arm64" 2232 + ], 2233 + "dev": true, 2234 + "license": "MIT", 2235 + "optional": true, 2236 + "os": [ 2237 + "linux" 2238 + ], 2239 + "engines": { 2240 + "node": ">= 10" 2241 + } 2242 + }, 2243 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 2244 + "version": "4.0.9", 2245 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.9.tgz", 2246 + "integrity": "sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg==", 2247 + "cpu": [ 2248 + "arm64" 2249 + ], 2250 + "dev": true, 2251 + "license": "MIT", 2252 + "optional": true, 2253 + "os": [ 2254 + "linux" 2255 + ], 2256 + "engines": { 2257 + "node": ">= 10" 2258 + } 2259 + }, 2260 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 2261 + "version": "4.0.9", 2262 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.9.tgz", 2263 + "integrity": "sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ==", 2264 + "cpu": [ 2265 + "x64" 2266 + ], 2267 + "dev": true, 2268 + "license": "MIT", 2269 + "optional": true, 2270 + "os": [ 2271 + "linux" 2272 + ], 2273 + "engines": { 2274 + "node": ">= 10" 2275 + } 2276 + }, 2277 + "node_modules/@tailwindcss/oxide-linux-x64-musl": { 2278 + "version": "4.0.9", 2279 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.9.tgz", 2280 + "integrity": "sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q==", 2281 + "cpu": [ 2282 + "x64" 2283 + ], 2284 + "dev": true, 2285 + "license": "MIT", 2286 + "optional": true, 2287 + "os": [ 2288 + "linux" 2289 + ], 2290 + "engines": { 2291 + "node": ">= 10" 2292 + } 2293 + }, 2294 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 2295 + "version": "4.0.9", 2296 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.9.tgz", 2297 + "integrity": "sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ==", 2298 + "cpu": [ 2299 + "arm64" 2300 + ], 2301 + "dev": true, 2302 + "license": "MIT", 2303 + "optional": true, 2304 + "os": [ 2305 + "win32" 2306 + ], 2307 + "engines": { 2308 + "node": ">= 10" 2309 + } 2310 + }, 2311 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 2312 + "version": "4.0.9", 2313 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.9.tgz", 2314 + "integrity": "sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA==", 2315 + "cpu": [ 2316 + "x64" 2317 + ], 2318 + "dev": true, 2319 + "license": "MIT", 2320 + "optional": true, 2321 + "os": [ 2322 + "win32" 2323 + ], 2324 + "engines": { 2325 + "node": ">= 10" 2326 + } 2327 + }, 2328 + "node_modules/@tailwindcss/typography": { 2329 + "version": "0.5.16", 2330 + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", 2331 + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", 2332 + "dev": true, 2333 + "license": "MIT", 2334 + "dependencies": { 2335 + "lodash.castarray": "^4.4.0", 2336 + "lodash.isplainobject": "^4.0.6", 2337 + "lodash.merge": "^4.6.2", 2338 + "postcss-selector-parser": "6.0.10" 2339 + }, 2340 + "peerDependencies": { 2341 + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" 2342 + } 2343 + }, 2344 + "node_modules/@tailwindcss/vite": { 2345 + "version": "4.0.9", 2346 + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.9.tgz", 2347 + "integrity": "sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g==", 2348 + "dev": true, 2349 + "license": "MIT", 2350 + "dependencies": { 2351 + "@tailwindcss/node": "4.0.9", 2352 + "@tailwindcss/oxide": "4.0.9", 2353 + "lightningcss": "^1.29.1", 2354 + "tailwindcss": "4.0.9" 2355 + }, 2356 + "peerDependencies": { 2357 + "vite": "^5.2.0 || ^6" 2358 + } 2359 + }, 2360 + "node_modules/@texel/color": { 2361 + "version": "1.1.9", 2362 + "resolved": "https://registry.npmjs.org/@texel/color/-/color-1.1.9.tgz", 2363 + "integrity": "sha512-dXGidZoaaHHdc/8L8PBHAEl3F74s/+rOufcL3yYcBjJgv4btRmPr28owjGYmijs5GRAJ10E9rEIoHnrB1eEAGA==", 2364 + "license": "MIT" 2365 + }, 2366 + "node_modules/@threejs-kit/instanced-sprite-mesh": { 2367 + "version": "2.5.0", 2368 + "resolved": "https://registry.npmjs.org/@threejs-kit/instanced-sprite-mesh/-/instanced-sprite-mesh-2.5.0.tgz", 2369 + "integrity": "sha512-W4cRK7f1o15GtacvLi8h1022ySiMM5218eZJQYWHbVGKqpRezmj8hscdzgp4JFSCkRoAIdyIdLZg5U0Mtmcu/w==", 2370 + "dependencies": { 2371 + "diet-sprite": "^0.0.1", 2372 + "earcut": "^2.2.4", 2373 + "maath": "^0.10.7", 2374 + "three-instanced-uniforms-mesh": "^0.52.0", 2375 + "troika-three-utils": "^0.52.0" 2376 + }, 2377 + "peerDependencies": { 2378 + "three": ">=0.170.0" 2379 + } 2380 + }, 2381 + "node_modules/@threlte/core": { 2382 + "version": "8.0.1", 2383 + "resolved": "https://registry.npmjs.org/@threlte/core/-/core-8.0.1.tgz", 2384 + "integrity": "sha512-vy1xRQppJFNmfPTeiRQue+KmYFsbPgVhwuYXRTvVrwPeD2oYz43gxUeOpe1FACeGKxrxZykeKJF5ebVvl7gBxw==", 2385 + "license": "MIT", 2386 + "dependencies": { 2387 + "mitt": "^3.0.1" 2388 + }, 2389 + "peerDependencies": { 2390 + "svelte": ">=5", 2391 + "three": ">=0.155" 2392 + } 2393 + }, 2394 + "node_modules/@threlte/extras": { 2395 + "version": "9.0.1", 2396 + "resolved": "https://registry.npmjs.org/@threlte/extras/-/extras-9.0.1.tgz", 2397 + "integrity": "sha512-xI0s5a/9NdBbUSaYCnTHfnVl44kZQUTuTPfzwv4gmMM2r2HewM5QsMDBBwBTcuVcYqkM9aUKl+5MWa20LnH/mg==", 2398 + "license": "MIT", 2399 + "dependencies": { 2400 + "@threejs-kit/instanced-sprite-mesh": "^2.5.0", 2401 + "camera-controls": "^2.9.0", 2402 + "three-mesh-bvh": "^0.7.4", 2403 + "three-perf": "github:jerzakm/three-perf#three-kit-threlte-fork", 2404 + "three-viewport-gizmo": "^2.0.2", 2405 + "troika-three-text": "^0.50.3" 2406 + }, 2407 + "peerDependencies": { 2408 + "svelte": ">=5", 2409 + "three": ">=0.155" 2410 + } 2411 + }, 2412 + "node_modules/@threlte/studio": { 2413 + "version": "0.1.3", 2414 + "resolved": "https://registry.npmjs.org/@threlte/studio/-/studio-0.1.3.tgz", 2415 + "integrity": "sha512-YOL3lyLAw2uvxFYbjFOVt01VCQVNBYLsScyKJR7IhESf3npzJqK+N1yIoNUtA6+Jkzs9Wgt1WCi7N+U0ri4c/g==", 2416 + "license": "MIT", 2417 + "dependencies": { 2418 + "@floating-ui/dom": "^1.6.4", 2419 + "@mdi/js": "^7.4.47", 2420 + "@tweakpane/plugin-camerakit": "^0.3.0", 2421 + "camera-controls": "^2.9.0", 2422 + "estree-walker": "^3.0.3", 2423 + "flexible-tree": "^0.1.6", 2424 + "index-to-position": "^1.0.0", 2425 + "json5": "^2.2.3", 2426 + "kleur": "^4.1.5", 2427 + "magic-string": "^0.30.10", 2428 + "svelte-tweakpane-ui": "^1.2.3", 2429 + "tweakpane": "4.0.3", 2430 + "vite-dev-rpc": "^0.1.4" 2431 + }, 2432 + "peerDependencies": { 2433 + "svelte": ">=5", 2434 + "three": ">=0.152", 2435 + "vite": ">=5" 2436 + } 2437 + }, 2438 + "node_modules/@tweakpane/core": { 2439 + "version": "2.0.5", 2440 + "resolved": "https://registry.npmjs.org/@tweakpane/core/-/core-2.0.5.tgz", 2441 + "integrity": "sha512-punBgD5rKCF5vcNo6BsSOXiDR/NSs9VM7SG65QSLJIxfRaGgj54ree9zQW6bO3pNFf3AogiGgaNODUVQRk9YqQ==", 2442 + "license": "MIT" 2443 + }, 2444 + "node_modules/@tweakpane/plugin-camerakit": { 2445 + "version": "0.3.0", 2446 + "resolved": "https://registry.npmjs.org/@tweakpane/plugin-camerakit/-/plugin-camerakit-0.3.0.tgz", 2447 + "integrity": "sha512-6UwgwDKU+oaAgXJ2D/pOoIpEAZts0RyeLmVzBJGs+VVNqSfkiHzL0i5XD+XnmSL2PaLXBne0dlz0bYOrjmeELw==", 2448 + "license": "MIT", 2449 + "peerDependencies": { 2450 + "tweakpane": "^4.0.0-beta.2" 2451 + } 2452 + }, 2453 + "node_modules/@tweenjs/tween.js": { 2454 + "version": "23.1.3", 2455 + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", 2456 + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", 2457 + "license": "MIT" 2458 + }, 2459 + "node_modules/@types/cookie": { 2460 + "version": "0.6.0", 2461 + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", 2462 + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", 2463 + "license": "MIT" 2464 + }, 2465 + "node_modules/@types/estree": { 2466 + "version": "1.0.6", 2467 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 2468 + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 2469 + "license": "MIT" 2470 + }, 2471 + "node_modules/@types/json-schema": { 2472 + "version": "7.0.15", 2473 + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 2474 + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 2475 + "dev": true, 2476 + "license": "MIT" 2477 + }, 2478 + "node_modules/@types/stats.js": { 2479 + "version": "0.17.3", 2480 + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", 2481 + "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", 2482 + "license": "MIT" 2483 + }, 2484 + "node_modules/@types/three": { 2485 + "version": "0.174.0", 2486 + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.174.0.tgz", 2487 + "integrity": "sha512-De/+vZnfg2aVWNiuy1Ldu+n2ydgw1osinmiZTAn0necE++eOfsygL8JpZgFjR2uHmAPo89MkxBj3JJ+2BMe+Uw==", 2488 + "license": "MIT", 2489 + "dependencies": { 2490 + "@tweenjs/tween.js": "~23.1.3", 2491 + "@types/stats.js": "*", 2492 + "@types/webxr": "*", 2493 + "@webgpu/types": "*", 2494 + "fflate": "~0.8.2", 2495 + "meshoptimizer": "~0.18.1" 2496 + } 2497 + }, 2498 + "node_modules/@types/webxr": { 2499 + "version": "0.5.21", 2500 + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.21.tgz", 2501 + "integrity": "sha512-geZIAtLzjGmgY2JUi6VxXdCrTb99A7yP49lxLr2Nm/uIK0PkkxcEi4OGhoGDO4pxCf3JwGz2GiJL2Ej4K2bKaA==", 2502 + "license": "MIT" 2503 + }, 2504 + "node_modules/@typescript-eslint/eslint-plugin": { 2505 + "version": "8.26.0", 2506 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", 2507 + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", 2508 + "dev": true, 2509 + "license": "MIT", 2510 + "dependencies": { 2511 + "@eslint-community/regexpp": "^4.10.0", 2512 + "@typescript-eslint/scope-manager": "8.26.0", 2513 + "@typescript-eslint/type-utils": "8.26.0", 2514 + "@typescript-eslint/utils": "8.26.0", 2515 + "@typescript-eslint/visitor-keys": "8.26.0", 2516 + "graphemer": "^1.4.0", 2517 + "ignore": "^5.3.1", 2518 + "natural-compare": "^1.4.0", 2519 + "ts-api-utils": "^2.0.1" 2520 + }, 2521 + "engines": { 2522 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2523 + }, 2524 + "funding": { 2525 + "type": "opencollective", 2526 + "url": "https://opencollective.com/typescript-eslint" 2527 + }, 2528 + "peerDependencies": { 2529 + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", 2530 + "eslint": "^8.57.0 || ^9.0.0", 2531 + "typescript": ">=4.8.4 <5.9.0" 2532 + } 2533 + }, 2534 + "node_modules/@typescript-eslint/parser": { 2535 + "version": "8.26.0", 2536 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", 2537 + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", 2538 + "dev": true, 2539 + "license": "MIT", 2540 + "dependencies": { 2541 + "@typescript-eslint/scope-manager": "8.26.0", 2542 + "@typescript-eslint/types": "8.26.0", 2543 + "@typescript-eslint/typescript-estree": "8.26.0", 2544 + "@typescript-eslint/visitor-keys": "8.26.0", 2545 + "debug": "^4.3.4" 2546 + }, 2547 + "engines": { 2548 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2549 + }, 2550 + "funding": { 2551 + "type": "opencollective", 2552 + "url": "https://opencollective.com/typescript-eslint" 2553 + }, 2554 + "peerDependencies": { 2555 + "eslint": "^8.57.0 || ^9.0.0", 2556 + "typescript": ">=4.8.4 <5.9.0" 2557 + } 2558 + }, 2559 + "node_modules/@typescript-eslint/scope-manager": { 2560 + "version": "8.26.0", 2561 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", 2562 + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", 2563 + "dev": true, 2564 + "license": "MIT", 2565 + "dependencies": { 2566 + "@typescript-eslint/types": "8.26.0", 2567 + "@typescript-eslint/visitor-keys": "8.26.0" 2568 + }, 2569 + "engines": { 2570 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2571 + }, 2572 + "funding": { 2573 + "type": "opencollective", 2574 + "url": "https://opencollective.com/typescript-eslint" 2575 + } 2576 + }, 2577 + "node_modules/@typescript-eslint/type-utils": { 2578 + "version": "8.26.0", 2579 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", 2580 + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", 2581 + "dev": true, 2582 + "license": "MIT", 2583 + "dependencies": { 2584 + "@typescript-eslint/typescript-estree": "8.26.0", 2585 + "@typescript-eslint/utils": "8.26.0", 2586 + "debug": "^4.3.4", 2587 + "ts-api-utils": "^2.0.1" 2588 + }, 2589 + "engines": { 2590 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2591 + }, 2592 + "funding": { 2593 + "type": "opencollective", 2594 + "url": "https://opencollective.com/typescript-eslint" 2595 + }, 2596 + "peerDependencies": { 2597 + "eslint": "^8.57.0 || ^9.0.0", 2598 + "typescript": ">=4.8.4 <5.9.0" 2599 + } 2600 + }, 2601 + "node_modules/@typescript-eslint/types": { 2602 + "version": "8.26.0", 2603 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", 2604 + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", 2605 + "dev": true, 2606 + "license": "MIT", 2607 + "engines": { 2608 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2609 + }, 2610 + "funding": { 2611 + "type": "opencollective", 2612 + "url": "https://opencollective.com/typescript-eslint" 2613 + } 2614 + }, 2615 + "node_modules/@typescript-eslint/typescript-estree": { 2616 + "version": "8.26.0", 2617 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", 2618 + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", 2619 + "dev": true, 2620 + "license": "MIT", 2621 + "dependencies": { 2622 + "@typescript-eslint/types": "8.26.0", 2623 + "@typescript-eslint/visitor-keys": "8.26.0", 2624 + "debug": "^4.3.4", 2625 + "fast-glob": "^3.3.2", 2626 + "is-glob": "^4.0.3", 2627 + "minimatch": "^9.0.4", 2628 + "semver": "^7.6.0", 2629 + "ts-api-utils": "^2.0.1" 2630 + }, 2631 + "engines": { 2632 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2633 + }, 2634 + "funding": { 2635 + "type": "opencollective", 2636 + "url": "https://opencollective.com/typescript-eslint" 2637 + }, 2638 + "peerDependencies": { 2639 + "typescript": ">=4.8.4 <5.9.0" 2640 + } 2641 + }, 2642 + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 2643 + "version": "2.0.1", 2644 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 2645 + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 2646 + "dev": true, 2647 + "license": "MIT", 2648 + "dependencies": { 2649 + "balanced-match": "^1.0.0" 2650 + } 2651 + }, 2652 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 2653 + "version": "9.0.5", 2654 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 2655 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 2656 + "dev": true, 2657 + "license": "ISC", 2658 + "dependencies": { 2659 + "brace-expansion": "^2.0.1" 2660 + }, 2661 + "engines": { 2662 + "node": ">=16 || 14 >=14.17" 2663 + }, 2664 + "funding": { 2665 + "url": "https://github.com/sponsors/isaacs" 2666 + } 2667 + }, 2668 + "node_modules/@typescript-eslint/utils": { 2669 + "version": "8.26.0", 2670 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", 2671 + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", 2672 + "dev": true, 2673 + "license": "MIT", 2674 + "dependencies": { 2675 + "@eslint-community/eslint-utils": "^4.4.0", 2676 + "@typescript-eslint/scope-manager": "8.26.0", 2677 + "@typescript-eslint/types": "8.26.0", 2678 + "@typescript-eslint/typescript-estree": "8.26.0" 2679 + }, 2680 + "engines": { 2681 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2682 + }, 2683 + "funding": { 2684 + "type": "opencollective", 2685 + "url": "https://opencollective.com/typescript-eslint" 2686 + }, 2687 + "peerDependencies": { 2688 + "eslint": "^8.57.0 || ^9.0.0", 2689 + "typescript": ">=4.8.4 <5.9.0" 2690 + } 2691 + }, 2692 + "node_modules/@typescript-eslint/visitor-keys": { 2693 + "version": "8.26.0", 2694 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", 2695 + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", 2696 + "dev": true, 2697 + "license": "MIT", 2698 + "dependencies": { 2699 + "@typescript-eslint/types": "8.26.0", 2700 + "eslint-visitor-keys": "^4.2.0" 2701 + }, 2702 + "engines": { 2703 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2704 + }, 2705 + "funding": { 2706 + "type": "opencollective", 2707 + "url": "https://opencollective.com/typescript-eslint" 2708 + } 2709 + }, 2710 + "node_modules/@webgpu/types": { 2711 + "version": "0.1.55", 2712 + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.55.tgz", 2713 + "integrity": "sha512-p97I8XEC1h04esklFqyIH+UhFrUcj8/1/vBWgc6lAK4jMJc+KbhUy8D4dquHYztFj6pHLqGcp/P1xvBBF4r3DA==", 2714 + "license": "BSD-3-Clause" 2715 + }, 2716 + "node_modules/@zerodevx/svelte-img": { 2717 + "version": "2.1.2", 2718 + "resolved": "https://registry.npmjs.org/@zerodevx/svelte-img/-/svelte-img-2.1.2.tgz", 2719 + "integrity": "sha512-zPyD3U8hzpe+72QzADKvo8n+uUctD7RUCajgJ9ToX4Yqkov+f80/BYA35N4TfIXj0K7Qi00yPHexeTIzj8QqHA==", 2720 + "license": "ISC", 2721 + "dependencies": { 2722 + "vite-imagetools": "^6.2.9" 2723 + }, 2724 + "peerDependencies": { 2725 + "svelte": "^3.55.1 || ^4.0.0 || ^5.0.0" 2726 + } 2727 + }, 2728 + "node_modules/acorn": { 2729 + "version": "8.14.1", 2730 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", 2731 + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", 2732 + "license": "MIT", 2733 + "bin": { 2734 + "acorn": "bin/acorn" 2735 + }, 2736 + "engines": { 2737 + "node": ">=0.4.0" 2738 + } 2739 + }, 2740 + "node_modules/acorn-jsx": { 2741 + "version": "5.3.2", 2742 + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 2743 + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 2744 + "dev": true, 2745 + "license": "MIT", 2746 + "peerDependencies": { 2747 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 2748 + } 2749 + }, 2750 + "node_modules/ajv": { 2751 + "version": "6.12.6", 2752 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 2753 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 2754 + "dev": true, 2755 + "license": "MIT", 2756 + "dependencies": { 2757 + "fast-deep-equal": "^3.1.1", 2758 + "fast-json-stable-stringify": "^2.0.0", 2759 + "json-schema-traverse": "^0.4.1", 2760 + "uri-js": "^4.2.2" 2761 + }, 2762 + "funding": { 2763 + "type": "github", 2764 + "url": "https://github.com/sponsors/epoberezkin" 2765 + } 2766 + }, 2767 + "node_modules/ansi-styles": { 2768 + "version": "4.3.0", 2769 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 2770 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 2771 + "dev": true, 2772 + "license": "MIT", 2773 + "dependencies": { 2774 + "color-convert": "^2.0.1" 2775 + }, 2776 + "engines": { 2777 + "node": ">=8" 2778 + }, 2779 + "funding": { 2780 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2781 + } 2782 + }, 2783 + "node_modules/argparse": { 2784 + "version": "2.0.1", 2785 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 2786 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 2787 + "dev": true, 2788 + "license": "Python-2.0" 2789 + }, 2790 + "node_modules/aria-query": { 2791 + "version": "5.3.2", 2792 + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", 2793 + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", 2794 + "license": "Apache-2.0", 2795 + "engines": { 2796 + "node": ">= 0.4" 2797 + } 2798 + }, 2799 + "node_modules/await-lock": { 2800 + "version": "2.2.2", 2801 + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", 2802 + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", 2803 + "license": "MIT" 2804 + }, 2805 + "node_modules/axobject-query": { 2806 + "version": "4.1.0", 2807 + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", 2808 + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", 2809 + "license": "Apache-2.0", 2810 + "engines": { 2811 + "node": ">= 0.4" 2812 + } 2813 + }, 2814 + "node_modules/balanced-match": { 2815 + "version": "1.0.2", 2816 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 2817 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 2818 + "dev": true, 2819 + "license": "MIT" 2820 + }, 2821 + "node_modules/bidi-js": { 2822 + "version": "1.0.3", 2823 + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", 2824 + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", 2825 + "license": "MIT", 2826 + "dependencies": { 2827 + "require-from-string": "^2.0.2" 2828 + } 2829 + }, 2830 + "node_modules/birpc": { 2831 + "version": "0.2.19", 2832 + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", 2833 + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", 2834 + "license": "MIT", 2835 + "funding": { 2836 + "url": "https://github.com/sponsors/antfu" 2837 + } 2838 + }, 2839 + "node_modules/bits-ui": { 2840 + "version": "1.3.6", 2841 + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.3.6.tgz", 2842 + "integrity": "sha512-0Ee7Ox5KqIBdio/+TG387Xlj6QJ0S7tHVS2K4DYiBHxBRbm6ni13i/pOoNDjeME6NOGUZxbSe4dNZIW3pGlxZA==", 2843 + "license": "MIT", 2844 + "dependencies": { 2845 + "@floating-ui/core": "^1.6.4", 2846 + "@floating-ui/dom": "^1.6.7", 2847 + "@internationalized/date": "^3.5.6", 2848 + "esm-env": "^1.1.2", 2849 + "runed": "^0.23.2", 2850 + "svelte-toolbelt": "^0.7.1", 2851 + "tabbable": "^6.2.0" 2852 + }, 2853 + "engines": { 2854 + "node": ">=18", 2855 + "pnpm": ">=8.7.0" 2856 + }, 2857 + "funding": { 2858 + "url": "https://github.com/sponsors/huntabyte" 2859 + }, 2860 + "peerDependencies": { 2861 + "svelte": "^5.11.0" 2862 + } 2863 + }, 2864 + "node_modules/brace-expansion": { 2865 + "version": "1.1.11", 2866 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 2867 + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 2868 + "dev": true, 2869 + "license": "MIT", 2870 + "dependencies": { 2871 + "balanced-match": "^1.0.0", 2872 + "concat-map": "0.0.1" 2873 + } 2874 + }, 2875 + "node_modules/braces": { 2876 + "version": "3.0.3", 2877 + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 2878 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 2879 + "devOptional": true, 2880 + "license": "MIT", 2881 + "dependencies": { 2882 + "fill-range": "^7.1.1" 2883 + }, 2884 + "engines": { 2885 + "node": ">=8" 2886 + } 2887 + }, 2888 + "node_modules/callsites": { 2889 + "version": "3.1.0", 2890 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 2891 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 2892 + "dev": true, 2893 + "license": "MIT", 2894 + "engines": { 2895 + "node": ">=6" 2896 + } 2897 + }, 2898 + "node_modules/camera-controls": { 2899 + "version": "2.10.0", 2900 + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.10.0.tgz", 2901 + "integrity": "sha512-vBQ5Daxv4KRsn07U/VqkPxoqD8U+S++0oq5NLf4HevMuh/BDta3rg49e/P564AMzFPBePQeXDKOkiIezRgyDwg==", 2902 + "license": "MIT", 2903 + "peerDependencies": { 2904 + "three": ">=0.126.1" 2905 + } 2906 + }, 2907 + "node_modules/chalk": { 2908 + "version": "4.1.2", 2909 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 2910 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 2911 + "dev": true, 2912 + "license": "MIT", 2913 + "dependencies": { 2914 + "ansi-styles": "^4.1.0", 2915 + "supports-color": "^7.1.0" 2916 + }, 2917 + "engines": { 2918 + "node": ">=10" 2919 + }, 2920 + "funding": { 2921 + "url": "https://github.com/chalk/chalk?sponsor=1" 2922 + } 2923 + }, 2924 + "node_modules/chokidar": { 2925 + "version": "4.0.3", 2926 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", 2927 + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", 2928 + "license": "MIT", 2929 + "dependencies": { 2930 + "readdirp": "^4.0.1" 2931 + }, 2932 + "engines": { 2933 + "node": ">= 14.16.0" 2934 + }, 2935 + "funding": { 2936 + "url": "https://paulmillr.com/funding/" 2937 + } 2938 + }, 2939 + "node_modules/clsx": { 2940 + "version": "2.1.1", 2941 + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 2942 + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 2943 + "license": "MIT", 2944 + "engines": { 2945 + "node": ">=6" 2946 + } 2947 + }, 2948 + "node_modules/color": { 2949 + "version": "4.2.3", 2950 + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", 2951 + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 2952 + "license": "MIT", 2953 + "dependencies": { 2954 + "color-convert": "^2.0.1", 2955 + "color-string": "^1.9.0" 2956 + }, 2957 + "engines": { 2958 + "node": ">=12.5.0" 2959 + } 2960 + }, 2961 + "node_modules/color-convert": { 2962 + "version": "2.0.1", 2963 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 2964 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 2965 + "license": "MIT", 2966 + "dependencies": { 2967 + "color-name": "~1.1.4" 2968 + }, 2969 + "engines": { 2970 + "node": ">=7.0.0" 2971 + } 2972 + }, 2973 + "node_modules/color-name": { 2974 + "version": "1.1.4", 2975 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 2976 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 2977 + "license": "MIT" 2978 + }, 2979 + "node_modules/color-string": { 2980 + "version": "1.9.1", 2981 + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 2982 + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 2983 + "license": "MIT", 2984 + "dependencies": { 2985 + "color-name": "^1.0.0", 2986 + "simple-swizzle": "^0.2.2" 2987 + } 2988 + }, 2989 + "node_modules/concat-map": { 2990 + "version": "0.0.1", 2991 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 2992 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 2993 + "dev": true, 2994 + "license": "MIT" 2995 + }, 2996 + "node_modules/cookie": { 2997 + "version": "0.6.0", 2998 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 2999 + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 3000 + "license": "MIT", 3001 + "engines": { 3002 + "node": ">= 0.6" 3003 + } 3004 + }, 3005 + "node_modules/cross-spawn": { 3006 + "version": "7.0.6", 3007 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 3008 + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 3009 + "dev": true, 3010 + "license": "MIT", 3011 + "dependencies": { 3012 + "path-key": "^3.1.0", 3013 + "shebang-command": "^2.0.0", 3014 + "which": "^2.0.1" 3015 + }, 3016 + "engines": { 3017 + "node": ">= 8" 3018 + } 3019 + }, 3020 + "node_modules/cssesc": { 3021 + "version": "3.0.0", 3022 + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 3023 + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", 3024 + "dev": true, 3025 + "license": "MIT", 3026 + "bin": { 3027 + "cssesc": "bin/cssesc" 3028 + }, 3029 + "engines": { 3030 + "node": ">=4" 3031 + } 3032 + }, 3033 + "node_modules/debug": { 3034 + "version": "4.4.0", 3035 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 3036 + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 3037 + "license": "MIT", 3038 + "dependencies": { 3039 + "ms": "^2.1.3" 3040 + }, 3041 + "engines": { 3042 + "node": ">=6.0" 3043 + }, 3044 + "peerDependenciesMeta": { 3045 + "supports-color": { 3046 + "optional": true 3047 + } 3048 + } 3049 + }, 3050 + "node_modules/deep-is": { 3051 + "version": "0.1.4", 3052 + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 3053 + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 3054 + "dev": true, 3055 + "license": "MIT" 3056 + }, 3057 + "node_modules/deepmerge": { 3058 + "version": "4.3.1", 3059 + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 3060 + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 3061 + "license": "MIT", 3062 + "engines": { 3063 + "node": ">=0.10.0" 3064 + } 3065 + }, 3066 + "node_modules/detect-libc": { 3067 + "version": "1.0.3", 3068 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 3069 + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", 3070 + "devOptional": true, 3071 + "license": "Apache-2.0", 3072 + "bin": { 3073 + "detect-libc": "bin/detect-libc.js" 3074 + }, 3075 + "engines": { 3076 + "node": ">=0.10" 3077 + } 3078 + }, 3079 + "node_modules/devalue": { 3080 + "version": "5.1.1", 3081 + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", 3082 + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", 3083 + "license": "MIT" 3084 + }, 3085 + "node_modules/diet-sprite": { 3086 + "version": "0.0.1", 3087 + "resolved": "https://registry.npmjs.org/diet-sprite/-/diet-sprite-0.0.1.tgz", 3088 + "integrity": "sha512-zSHI2WDAn1wJqJYxcmjWfJv3Iw8oL9reQIbEyx2x2/EZ4/qmUTIo8/5qOCurnAcq61EwtJJaZ0XTy2NRYqpB5A==", 3089 + "license": "ISC" 3090 + }, 3091 + "node_modules/earcut": { 3092 + "version": "2.2.4", 3093 + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", 3094 + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", 3095 + "license": "ISC" 3096 + }, 3097 + "node_modules/enhanced-resolve": { 3098 + "version": "5.18.1", 3099 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", 3100 + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", 3101 + "dev": true, 3102 + "license": "MIT", 3103 + "dependencies": { 3104 + "graceful-fs": "^4.2.4", 3105 + "tapable": "^2.2.0" 3106 + }, 3107 + "engines": { 3108 + "node": ">=10.13.0" 3109 + } 3110 + }, 3111 + "node_modules/esbuild": { 3112 + "version": "0.25.0", 3113 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", 3114 + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", 3115 + "hasInstallScript": true, 3116 + "license": "MIT", 3117 + "bin": { 3118 + "esbuild": "bin/esbuild" 3119 + }, 3120 + "engines": { 3121 + "node": ">=18" 3122 + }, 3123 + "optionalDependencies": { 3124 + "@esbuild/aix-ppc64": "0.25.0", 3125 + "@esbuild/android-arm": "0.25.0", 3126 + "@esbuild/android-arm64": "0.25.0", 3127 + "@esbuild/android-x64": "0.25.0", 3128 + "@esbuild/darwin-arm64": "0.25.0", 3129 + "@esbuild/darwin-x64": "0.25.0", 3130 + "@esbuild/freebsd-arm64": "0.25.0", 3131 + "@esbuild/freebsd-x64": "0.25.0", 3132 + "@esbuild/linux-arm": "0.25.0", 3133 + "@esbuild/linux-arm64": "0.25.0", 3134 + "@esbuild/linux-ia32": "0.25.0", 3135 + "@esbuild/linux-loong64": "0.25.0", 3136 + "@esbuild/linux-mips64el": "0.25.0", 3137 + "@esbuild/linux-ppc64": "0.25.0", 3138 + "@esbuild/linux-riscv64": "0.25.0", 3139 + "@esbuild/linux-s390x": "0.25.0", 3140 + "@esbuild/linux-x64": "0.25.0", 3141 + "@esbuild/netbsd-arm64": "0.25.0", 3142 + "@esbuild/netbsd-x64": "0.25.0", 3143 + "@esbuild/openbsd-arm64": "0.25.0", 3144 + "@esbuild/openbsd-x64": "0.25.0", 3145 + "@esbuild/sunos-x64": "0.25.0", 3146 + "@esbuild/win32-arm64": "0.25.0", 3147 + "@esbuild/win32-ia32": "0.25.0", 3148 + "@esbuild/win32-x64": "0.25.0" 3149 + } 3150 + }, 3151 + "node_modules/escape-string-regexp": { 3152 + "version": "4.0.0", 3153 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 3154 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 3155 + "dev": true, 3156 + "license": "MIT", 3157 + "engines": { 3158 + "node": ">=10" 3159 + }, 3160 + "funding": { 3161 + "url": "https://github.com/sponsors/sindresorhus" 3162 + } 3163 + }, 3164 + "node_modules/eslint": { 3165 + "version": "9.21.0", 3166 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", 3167 + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", 3168 + "dev": true, 3169 + "license": "MIT", 3170 + "dependencies": { 3171 + "@eslint-community/eslint-utils": "^4.2.0", 3172 + "@eslint-community/regexpp": "^4.12.1", 3173 + "@eslint/config-array": "^0.19.2", 3174 + "@eslint/core": "^0.12.0", 3175 + "@eslint/eslintrc": "^3.3.0", 3176 + "@eslint/js": "9.21.0", 3177 + "@eslint/plugin-kit": "^0.2.7", 3178 + "@humanfs/node": "^0.16.6", 3179 + "@humanwhocodes/module-importer": "^1.0.1", 3180 + "@humanwhocodes/retry": "^0.4.2", 3181 + "@types/estree": "^1.0.6", 3182 + "@types/json-schema": "^7.0.15", 3183 + "ajv": "^6.12.4", 3184 + "chalk": "^4.0.0", 3185 + "cross-spawn": "^7.0.6", 3186 + "debug": "^4.3.2", 3187 + "escape-string-regexp": "^4.0.0", 3188 + "eslint-scope": "^8.2.0", 3189 + "eslint-visitor-keys": "^4.2.0", 3190 + "espree": "^10.3.0", 3191 + "esquery": "^1.5.0", 3192 + "esutils": "^2.0.2", 3193 + "fast-deep-equal": "^3.1.3", 3194 + "file-entry-cache": "^8.0.0", 3195 + "find-up": "^5.0.0", 3196 + "glob-parent": "^6.0.2", 3197 + "ignore": "^5.2.0", 3198 + "imurmurhash": "^0.1.4", 3199 + "is-glob": "^4.0.0", 3200 + "json-stable-stringify-without-jsonify": "^1.0.1", 3201 + "lodash.merge": "^4.6.2", 3202 + "minimatch": "^3.1.2", 3203 + "natural-compare": "^1.4.0", 3204 + "optionator": "^0.9.3" 3205 + }, 3206 + "bin": { 3207 + "eslint": "bin/eslint.js" 3208 + }, 3209 + "engines": { 3210 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3211 + }, 3212 + "funding": { 3213 + "url": "https://eslint.org/donate" 3214 + }, 3215 + "peerDependencies": { 3216 + "jiti": "*" 3217 + }, 3218 + "peerDependenciesMeta": { 3219 + "jiti": { 3220 + "optional": true 3221 + } 3222 + } 3223 + }, 3224 + "node_modules/eslint-compat-utils": { 3225 + "version": "0.6.4", 3226 + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.4.tgz", 3227 + "integrity": "sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==", 3228 + "dev": true, 3229 + "license": "MIT", 3230 + "dependencies": { 3231 + "semver": "^7.5.4" 3232 + }, 3233 + "engines": { 3234 + "node": ">=12" 3235 + }, 3236 + "peerDependencies": { 3237 + "eslint": ">=6.0.0" 3238 + } 3239 + }, 3240 + "node_modules/eslint-config-prettier": { 3241 + "version": "10.0.2", 3242 + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", 3243 + "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", 3244 + "dev": true, 3245 + "license": "MIT", 3246 + "bin": { 3247 + "eslint-config-prettier": "build/bin/cli.js" 3248 + }, 3249 + "peerDependencies": { 3250 + "eslint": ">=7.0.0" 3251 + } 3252 + }, 3253 + "node_modules/eslint-plugin-svelte": { 3254 + "version": "3.0.3", 3255 + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.0.3.tgz", 3256 + "integrity": "sha512-R7HSKkLN33P6WwYhVbO+5xPT0YIpO+YAZfWxow7I1IvjVgZOxuI7zReqxFL3B7F028u16Megx+hn8SEXDNcDvw==", 3257 + "dev": true, 3258 + "license": "MIT", 3259 + "dependencies": { 3260 + "@eslint-community/eslint-utils": "^4.4.1", 3261 + "@jridgewell/sourcemap-codec": "^1.5.0", 3262 + "eslint-compat-utils": "^0.6.4", 3263 + "esutils": "^2.0.3", 3264 + "known-css-properties": "^0.35.0", 3265 + "postcss": "^8.4.49", 3266 + "postcss-load-config": "^3.1.4", 3267 + "postcss-safe-parser": "^7.0.0", 3268 + "semver": "^7.6.3", 3269 + "svelte-eslint-parser": "^1.0.1" 3270 + }, 3271 + "engines": { 3272 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3273 + }, 3274 + "funding": { 3275 + "url": "https://github.com/sponsors/ota-meshi" 3276 + }, 3277 + "peerDependencies": { 3278 + "eslint": "^8.57.1 || ^9.0.0", 3279 + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" 3280 + }, 3281 + "peerDependenciesMeta": { 3282 + "svelte": { 3283 + "optional": true 3284 + } 3285 + } 3286 + }, 3287 + "node_modules/eslint-scope": { 3288 + "version": "8.2.0", 3289 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", 3290 + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", 3291 + "dev": true, 3292 + "license": "BSD-2-Clause", 3293 + "dependencies": { 3294 + "esrecurse": "^4.3.0", 3295 + "estraverse": "^5.2.0" 3296 + }, 3297 + "engines": { 3298 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3299 + }, 3300 + "funding": { 3301 + "url": "https://opencollective.com/eslint" 3302 + } 3303 + }, 3304 + "node_modules/eslint-visitor-keys": { 3305 + "version": "4.2.0", 3306 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 3307 + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 3308 + "dev": true, 3309 + "license": "Apache-2.0", 3310 + "engines": { 3311 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3312 + }, 3313 + "funding": { 3314 + "url": "https://opencollective.com/eslint" 3315 + } 3316 + }, 3317 + "node_modules/esm-env": { 3318 + "version": "1.2.2", 3319 + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 3320 + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 3321 + "license": "MIT" 3322 + }, 3323 + "node_modules/espree": { 3324 + "version": "10.3.0", 3325 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", 3326 + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", 3327 + "dev": true, 3328 + "license": "BSD-2-Clause", 3329 + "dependencies": { 3330 + "acorn": "^8.14.0", 3331 + "acorn-jsx": "^5.3.2", 3332 + "eslint-visitor-keys": "^4.2.0" 3333 + }, 3334 + "engines": { 3335 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3336 + }, 3337 + "funding": { 3338 + "url": "https://opencollective.com/eslint" 3339 + } 3340 + }, 3341 + "node_modules/esquery": { 3342 + "version": "1.6.0", 3343 + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 3344 + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 3345 + "dev": true, 3346 + "license": "BSD-3-Clause", 3347 + "dependencies": { 3348 + "estraverse": "^5.1.0" 3349 + }, 3350 + "engines": { 3351 + "node": ">=0.10" 3352 + } 3353 + }, 3354 + "node_modules/esrap": { 3355 + "version": "1.4.5", 3356 + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.5.tgz", 3357 + "integrity": "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==", 3358 + "license": "MIT", 3359 + "dependencies": { 3360 + "@jridgewell/sourcemap-codec": "^1.4.15" 3361 + } 3362 + }, 3363 + "node_modules/esrecurse": { 3364 + "version": "4.3.0", 3365 + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 3366 + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 3367 + "dev": true, 3368 + "license": "BSD-2-Clause", 3369 + "dependencies": { 3370 + "estraverse": "^5.2.0" 3371 + }, 3372 + "engines": { 3373 + "node": ">=4.0" 3374 + } 3375 + }, 3376 + "node_modules/estraverse": { 3377 + "version": "5.3.0", 3378 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 3379 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 3380 + "dev": true, 3381 + "license": "BSD-2-Clause", 3382 + "engines": { 3383 + "node": ">=4.0" 3384 + } 3385 + }, 3386 + "node_modules/estree-walker": { 3387 + "version": "3.0.3", 3388 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 3389 + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 3390 + "license": "MIT", 3391 + "dependencies": { 3392 + "@types/estree": "^1.0.0" 3393 + } 3394 + }, 3395 + "node_modules/esutils": { 3396 + "version": "2.0.3", 3397 + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 3398 + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 3399 + "dev": true, 3400 + "license": "BSD-2-Clause", 3401 + "engines": { 3402 + "node": ">=0.10.0" 3403 + } 3404 + }, 3405 + "node_modules/fast-copy": { 3406 + "version": "3.0.2", 3407 + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", 3408 + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", 3409 + "license": "MIT" 3410 + }, 3411 + "node_modules/fast-deep-equal": { 3412 + "version": "3.1.3", 3413 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 3414 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 3415 + "dev": true, 3416 + "license": "MIT" 3417 + }, 3418 + "node_modules/fast-equals": { 3419 + "version": "5.2.2", 3420 + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", 3421 + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", 3422 + "license": "MIT", 3423 + "engines": { 3424 + "node": ">=6.0.0" 3425 + } 3426 + }, 3427 + "node_modules/fast-glob": { 3428 + "version": "3.3.3", 3429 + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 3430 + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 3431 + "dev": true, 3432 + "license": "MIT", 3433 + "dependencies": { 3434 + "@nodelib/fs.stat": "^2.0.2", 3435 + "@nodelib/fs.walk": "^1.2.3", 3436 + "glob-parent": "^5.1.2", 3437 + "merge2": "^1.3.0", 3438 + "micromatch": "^4.0.8" 3439 + }, 3440 + "engines": { 3441 + "node": ">=8.6.0" 3442 + } 3443 + }, 3444 + "node_modules/fast-glob/node_modules/glob-parent": { 3445 + "version": "5.1.2", 3446 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 3447 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 3448 + "dev": true, 3449 + "license": "ISC", 3450 + "dependencies": { 3451 + "is-glob": "^4.0.1" 3452 + }, 3453 + "engines": { 3454 + "node": ">= 6" 3455 + } 3456 + }, 3457 + "node_modules/fast-json-stable-stringify": { 3458 + "version": "2.1.0", 3459 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 3460 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 3461 + "dev": true, 3462 + "license": "MIT" 3463 + }, 3464 + "node_modules/fast-levenshtein": { 3465 + "version": "2.0.6", 3466 + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 3467 + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 3468 + "dev": true, 3469 + "license": "MIT" 3470 + }, 3471 + "node_modules/fastq": { 3472 + "version": "1.19.1", 3473 + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 3474 + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 3475 + "dev": true, 3476 + "license": "ISC", 3477 + "dependencies": { 3478 + "reusify": "^1.0.4" 3479 + } 3480 + }, 3481 + "node_modules/fdir": { 3482 + "version": "6.4.3", 3483 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", 3484 + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", 3485 + "dev": true, 3486 + "license": "MIT", 3487 + "peerDependencies": { 3488 + "picomatch": "^3 || ^4" 3489 + }, 3490 + "peerDependenciesMeta": { 3491 + "picomatch": { 3492 + "optional": true 3493 + } 3494 + } 3495 + }, 3496 + "node_modules/fflate": { 3497 + "version": "0.8.2", 3498 + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", 3499 + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", 3500 + "license": "MIT" 3501 + }, 3502 + "node_modules/file-entry-cache": { 3503 + "version": "8.0.0", 3504 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 3505 + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 3506 + "dev": true, 3507 + "license": "MIT", 3508 + "dependencies": { 3509 + "flat-cache": "^4.0.0" 3510 + }, 3511 + "engines": { 3512 + "node": ">=16.0.0" 3513 + } 3514 + }, 3515 + "node_modules/fill-range": { 3516 + "version": "7.1.1", 3517 + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 3518 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 3519 + "devOptional": true, 3520 + "license": "MIT", 3521 + "dependencies": { 3522 + "to-regex-range": "^5.0.1" 3523 + }, 3524 + "engines": { 3525 + "node": ">=8" 3526 + } 3527 + }, 3528 + "node_modules/find-up": { 3529 + "version": "5.0.0", 3530 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 3531 + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 3532 + "dev": true, 3533 + "license": "MIT", 3534 + "dependencies": { 3535 + "locate-path": "^6.0.0", 3536 + "path-exists": "^4.0.0" 3537 + }, 3538 + "engines": { 3539 + "node": ">=10" 3540 + }, 3541 + "funding": { 3542 + "url": "https://github.com/sponsors/sindresorhus" 3543 + } 3544 + }, 3545 + "node_modules/flat-cache": { 3546 + "version": "4.0.1", 3547 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 3548 + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 3549 + "dev": true, 3550 + "license": "MIT", 3551 + "dependencies": { 3552 + "flatted": "^3.2.9", 3553 + "keyv": "^4.5.4" 3554 + }, 3555 + "engines": { 3556 + "node": ">=16" 3557 + } 3558 + }, 3559 + "node_modules/flatted": { 3560 + "version": "3.3.3", 3561 + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 3562 + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 3563 + "dev": true, 3564 + "license": "ISC" 3565 + }, 3566 + "node_modules/flexible-tree": { 3567 + "version": "0.1.6", 3568 + "resolved": "https://registry.npmjs.org/flexible-tree/-/flexible-tree-0.1.6.tgz", 3569 + "integrity": "sha512-dlYlpajmUhuoCTwOjqF4+2E9vUf9a9lwNdrXqfw7pPo7fULnQMZ6h/bmCk1bB7BwzPP4HtgAvy/ioyQmGhztEA==", 3570 + "license": "MIT", 3571 + "dependencies": { 3572 + "sass": "^1.63.6" 3573 + } 3574 + }, 3575 + "node_modules/fsevents": { 3576 + "version": "2.3.3", 3577 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 3578 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 3579 + "hasInstallScript": true, 3580 + "license": "MIT", 3581 + "optional": true, 3582 + "os": [ 3583 + "darwin" 3584 + ], 3585 + "engines": { 3586 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 3587 + } 3588 + }, 3589 + "node_modules/glob-parent": { 3590 + "version": "6.0.2", 3591 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 3592 + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 3593 + "dev": true, 3594 + "license": "ISC", 3595 + "dependencies": { 3596 + "is-glob": "^4.0.3" 3597 + }, 3598 + "engines": { 3599 + "node": ">=10.13.0" 3600 + } 3601 + }, 3602 + "node_modules/globals": { 3603 + "version": "16.0.0", 3604 + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", 3605 + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", 3606 + "dev": true, 3607 + "license": "MIT", 3608 + "engines": { 3609 + "node": ">=18" 3610 + }, 3611 + "funding": { 3612 + "url": "https://github.com/sponsors/sindresorhus" 3613 + } 3614 + }, 3615 + "node_modules/graceful-fs": { 3616 + "version": "4.2.11", 3617 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 3618 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 3619 + "dev": true, 3620 + "license": "ISC" 3621 + }, 3622 + "node_modules/graphemer": { 3623 + "version": "1.4.0", 3624 + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 3625 + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 3626 + "license": "MIT" 3627 + }, 3628 + "node_modules/has-flag": { 3629 + "version": "4.0.0", 3630 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 3631 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 3632 + "dev": true, 3633 + "license": "MIT", 3634 + "engines": { 3635 + "node": ">=8" 3636 + } 3637 + }, 3638 + "node_modules/ignore": { 3639 + "version": "5.3.2", 3640 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 3641 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 3642 + "dev": true, 3643 + "license": "MIT", 3644 + "engines": { 3645 + "node": ">= 4" 3646 + } 3647 + }, 3648 + "node_modules/imagetools-core": { 3649 + "version": "6.0.4", 3650 + "resolved": "https://registry.npmjs.org/imagetools-core/-/imagetools-core-6.0.4.tgz", 3651 + "integrity": "sha512-N1qs5qn7u9nR3kboISkYuvJm8MohiphCfBa+wx1UOropVaFis9/mh6wuDPLHJNhl6/64C7q2Pch5NASVKAaSrg==", 3652 + "license": "MIT", 3653 + "dependencies": { 3654 + "sharp": "^0.33.1" 3655 + }, 3656 + "engines": { 3657 + "node": ">=12.0.0" 3658 + } 3659 + }, 3660 + "node_modules/immutable": { 3661 + "version": "5.0.3", 3662 + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", 3663 + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", 3664 + "license": "MIT" 3665 + }, 3666 + "node_modules/import-fresh": { 3667 + "version": "3.3.1", 3668 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 3669 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 3670 + "dev": true, 3671 + "license": "MIT", 3672 + "dependencies": { 3673 + "parent-module": "^1.0.0", 3674 + "resolve-from": "^4.0.0" 3675 + }, 3676 + "engines": { 3677 + "node": ">=6" 3678 + }, 3679 + "funding": { 3680 + "url": "https://github.com/sponsors/sindresorhus" 3681 + } 3682 + }, 3683 + "node_modules/import-meta-resolve": { 3684 + "version": "4.1.0", 3685 + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", 3686 + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", 3687 + "license": "MIT", 3688 + "funding": { 3689 + "type": "github", 3690 + "url": "https://github.com/sponsors/wooorm" 3691 + } 3692 + }, 3693 + "node_modules/imurmurhash": { 3694 + "version": "0.1.4", 3695 + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 3696 + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 3697 + "dev": true, 3698 + "license": "MIT", 3699 + "engines": { 3700 + "node": ">=0.8.19" 3701 + } 3702 + }, 3703 + "node_modules/index-to-position": { 3704 + "version": "1.0.0", 3705 + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.0.0.tgz", 3706 + "integrity": "sha512-sCO7uaLVhRJ25vz1o8s9IFM3nVS4DkuQnyjMwiQPKvQuBYBDmb8H7zx8ki7nVh4HJQOdVWebyvLE0qt+clruxA==", 3707 + "license": "MIT", 3708 + "engines": { 3709 + "node": ">=18" 3710 + }, 3711 + "funding": { 3712 + "url": "https://github.com/sponsors/sindresorhus" 3713 + } 3714 + }, 3715 + "node_modules/inline-style-parser": { 3716 + "version": "0.2.4", 3717 + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", 3718 + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", 3719 + "license": "MIT" 3720 + }, 3721 + "node_modules/is-arrayish": { 3722 + "version": "0.3.2", 3723 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 3724 + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", 3725 + "license": "MIT" 3726 + }, 3727 + "node_modules/is-extglob": { 3728 + "version": "2.1.1", 3729 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 3730 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 3731 + "devOptional": true, 3732 + "license": "MIT", 3733 + "engines": { 3734 + "node": ">=0.10.0" 3735 + } 3736 + }, 3737 + "node_modules/is-glob": { 3738 + "version": "4.0.3", 3739 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 3740 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 3741 + "devOptional": true, 3742 + "license": "MIT", 3743 + "dependencies": { 3744 + "is-extglob": "^2.1.1" 3745 + }, 3746 + "engines": { 3747 + "node": ">=0.10.0" 3748 + } 3749 + }, 3750 + "node_modules/is-number": { 3751 + "version": "7.0.0", 3752 + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 3753 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 3754 + "devOptional": true, 3755 + "license": "MIT", 3756 + "engines": { 3757 + "node": ">=0.12.0" 3758 + } 3759 + }, 3760 + "node_modules/is-reference": { 3761 + "version": "3.0.3", 3762 + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", 3763 + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", 3764 + "license": "MIT", 3765 + "dependencies": { 3766 + "@types/estree": "^1.0.6" 3767 + } 3768 + }, 3769 + "node_modules/isexe": { 3770 + "version": "2.0.0", 3771 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 3772 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 3773 + "dev": true, 3774 + "license": "ISC" 3775 + }, 3776 + "node_modules/iso-datestring-validator": { 3777 + "version": "2.2.2", 3778 + "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 3779 + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 3780 + "license": "MIT" 3781 + }, 3782 + "node_modules/jiti": { 3783 + "version": "2.4.2", 3784 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", 3785 + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", 3786 + "devOptional": true, 3787 + "license": "MIT", 3788 + "bin": { 3789 + "jiti": "lib/jiti-cli.mjs" 3790 + } 3791 + }, 3792 + "node_modules/js-yaml": { 3793 + "version": "4.1.0", 3794 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 3795 + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 3796 + "dev": true, 3797 + "license": "MIT", 3798 + "dependencies": { 3799 + "argparse": "^2.0.1" 3800 + }, 3801 + "bin": { 3802 + "js-yaml": "bin/js-yaml.js" 3803 + } 3804 + }, 3805 + "node_modules/json-buffer": { 3806 + "version": "3.0.1", 3807 + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 3808 + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 3809 + "dev": true, 3810 + "license": "MIT" 3811 + }, 3812 + "node_modules/json-schema-traverse": { 3813 + "version": "0.4.1", 3814 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 3815 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 3816 + "dev": true, 3817 + "license": "MIT" 3818 + }, 3819 + "node_modules/json-stable-stringify-without-jsonify": { 3820 + "version": "1.0.1", 3821 + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 3822 + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 3823 + "dev": true, 3824 + "license": "MIT" 3825 + }, 3826 + "node_modules/json5": { 3827 + "version": "2.2.3", 3828 + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 3829 + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 3830 + "license": "MIT", 3831 + "bin": { 3832 + "json5": "lib/cli.js" 3833 + }, 3834 + "engines": { 3835 + "node": ">=6" 3836 + } 3837 + }, 3838 + "node_modules/keyv": { 3839 + "version": "4.5.4", 3840 + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 3841 + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 3842 + "dev": true, 3843 + "license": "MIT", 3844 + "dependencies": { 3845 + "json-buffer": "3.0.1" 3846 + } 3847 + }, 3848 + "node_modules/kleur": { 3849 + "version": "4.1.5", 3850 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 3851 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 3852 + "license": "MIT", 3853 + "engines": { 3854 + "node": ">=6" 3855 + } 3856 + }, 3857 + "node_modules/known-css-properties": { 3858 + "version": "0.35.0", 3859 + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", 3860 + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", 3861 + "dev": true, 3862 + "license": "MIT" 3863 + }, 3864 + "node_modules/levn": { 3865 + "version": "0.4.1", 3866 + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 3867 + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 3868 + "dev": true, 3869 + "license": "MIT", 3870 + "dependencies": { 3871 + "prelude-ls": "^1.2.1", 3872 + "type-check": "~0.4.0" 3873 + }, 3874 + "engines": { 3875 + "node": ">= 0.8.0" 3876 + } 3877 + }, 3878 + "node_modules/lightningcss": { 3879 + "version": "1.29.1", 3880 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz", 3881 + "integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==", 3882 + "devOptional": true, 3883 + "license": "MPL-2.0", 3884 + "dependencies": { 3885 + "detect-libc": "^1.0.3" 3886 + }, 3887 + "engines": { 3888 + "node": ">= 12.0.0" 3889 + }, 3890 + "funding": { 3891 + "type": "opencollective", 3892 + "url": "https://opencollective.com/parcel" 3893 + }, 3894 + "optionalDependencies": { 3895 + "lightningcss-darwin-arm64": "1.29.1", 3896 + "lightningcss-darwin-x64": "1.29.1", 3897 + "lightningcss-freebsd-x64": "1.29.1", 3898 + "lightningcss-linux-arm-gnueabihf": "1.29.1", 3899 + "lightningcss-linux-arm64-gnu": "1.29.1", 3900 + "lightningcss-linux-arm64-musl": "1.29.1", 3901 + "lightningcss-linux-x64-gnu": "1.29.1", 3902 + "lightningcss-linux-x64-musl": "1.29.1", 3903 + "lightningcss-win32-arm64-msvc": "1.29.1", 3904 + "lightningcss-win32-x64-msvc": "1.29.1" 3905 + } 3906 + }, 3907 + "node_modules/lightningcss-darwin-arm64": { 3908 + "version": "1.29.1", 3909 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.1.tgz", 3910 + "integrity": "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==", 3911 + "cpu": [ 3912 + "arm64" 3913 + ], 3914 + "license": "MPL-2.0", 3915 + "optional": true, 3916 + "os": [ 3917 + "darwin" 3918 + ], 3919 + "engines": { 3920 + "node": ">= 12.0.0" 3921 + }, 3922 + "funding": { 3923 + "type": "opencollective", 3924 + "url": "https://opencollective.com/parcel" 3925 + } 3926 + }, 3927 + "node_modules/lightningcss-darwin-x64": { 3928 + "version": "1.29.1", 3929 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.1.tgz", 3930 + "integrity": "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==", 3931 + "cpu": [ 3932 + "x64" 3933 + ], 3934 + "license": "MPL-2.0", 3935 + "optional": true, 3936 + "os": [ 3937 + "darwin" 3938 + ], 3939 + "engines": { 3940 + "node": ">= 12.0.0" 3941 + }, 3942 + "funding": { 3943 + "type": "opencollective", 3944 + "url": "https://opencollective.com/parcel" 3945 + } 3946 + }, 3947 + "node_modules/lightningcss-freebsd-x64": { 3948 + "version": "1.29.1", 3949 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.1.tgz", 3950 + "integrity": "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==", 3951 + "cpu": [ 3952 + "x64" 3953 + ], 3954 + "license": "MPL-2.0", 3955 + "optional": true, 3956 + "os": [ 3957 + "freebsd" 3958 + ], 3959 + "engines": { 3960 + "node": ">= 12.0.0" 3961 + }, 3962 + "funding": { 3963 + "type": "opencollective", 3964 + "url": "https://opencollective.com/parcel" 3965 + } 3966 + }, 3967 + "node_modules/lightningcss-linux-arm-gnueabihf": { 3968 + "version": "1.29.1", 3969 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.1.tgz", 3970 + "integrity": "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==", 3971 + "cpu": [ 3972 + "arm" 3973 + ], 3974 + "license": "MPL-2.0", 3975 + "optional": true, 3976 + "os": [ 3977 + "linux" 3978 + ], 3979 + "engines": { 3980 + "node": ">= 12.0.0" 3981 + }, 3982 + "funding": { 3983 + "type": "opencollective", 3984 + "url": "https://opencollective.com/parcel" 3985 + } 3986 + }, 3987 + "node_modules/lightningcss-linux-arm64-gnu": { 3988 + "version": "1.29.1", 3989 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.1.tgz", 3990 + "integrity": "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==", 3991 + "cpu": [ 3992 + "arm64" 3993 + ], 3994 + "license": "MPL-2.0", 3995 + "optional": true, 3996 + "os": [ 3997 + "linux" 3998 + ], 3999 + "engines": { 4000 + "node": ">= 12.0.0" 4001 + }, 4002 + "funding": { 4003 + "type": "opencollective", 4004 + "url": "https://opencollective.com/parcel" 4005 + } 4006 + }, 4007 + "node_modules/lightningcss-linux-arm64-musl": { 4008 + "version": "1.29.1", 4009 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.1.tgz", 4010 + "integrity": "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==", 4011 + "cpu": [ 4012 + "arm64" 4013 + ], 4014 + "license": "MPL-2.0", 4015 + "optional": true, 4016 + "os": [ 4017 + "linux" 4018 + ], 4019 + "engines": { 4020 + "node": ">= 12.0.0" 4021 + }, 4022 + "funding": { 4023 + "type": "opencollective", 4024 + "url": "https://opencollective.com/parcel" 4025 + } 4026 + }, 4027 + "node_modules/lightningcss-linux-x64-gnu": { 4028 + "version": "1.29.1", 4029 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.1.tgz", 4030 + "integrity": "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==", 4031 + "cpu": [ 4032 + "x64" 4033 + ], 4034 + "license": "MPL-2.0", 4035 + "optional": true, 4036 + "os": [ 4037 + "linux" 4038 + ], 4039 + "engines": { 4040 + "node": ">= 12.0.0" 4041 + }, 4042 + "funding": { 4043 + "type": "opencollective", 4044 + "url": "https://opencollective.com/parcel" 4045 + } 4046 + }, 4047 + "node_modules/lightningcss-linux-x64-musl": { 4048 + "version": "1.29.1", 4049 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.1.tgz", 4050 + "integrity": "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==", 4051 + "cpu": [ 4052 + "x64" 4053 + ], 4054 + "license": "MPL-2.0", 4055 + "optional": true, 4056 + "os": [ 4057 + "linux" 4058 + ], 4059 + "engines": { 4060 + "node": ">= 12.0.0" 4061 + }, 4062 + "funding": { 4063 + "type": "opencollective", 4064 + "url": "https://opencollective.com/parcel" 4065 + } 4066 + }, 4067 + "node_modules/lightningcss-win32-arm64-msvc": { 4068 + "version": "1.29.1", 4069 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.1.tgz", 4070 + "integrity": "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==", 4071 + "cpu": [ 4072 + "arm64" 4073 + ], 4074 + "license": "MPL-2.0", 4075 + "optional": true, 4076 + "os": [ 4077 + "win32" 4078 + ], 4079 + "engines": { 4080 + "node": ">= 12.0.0" 4081 + }, 4082 + "funding": { 4083 + "type": "opencollective", 4084 + "url": "https://opencollective.com/parcel" 4085 + } 4086 + }, 4087 + "node_modules/lightningcss-win32-x64-msvc": { 4088 + "version": "1.29.1", 4089 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.1.tgz", 4090 + "integrity": "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==", 4091 + "cpu": [ 4092 + "x64" 4093 + ], 4094 + "license": "MPL-2.0", 4095 + "optional": true, 4096 + "os": [ 4097 + "win32" 4098 + ], 4099 + "engines": { 4100 + "node": ">= 12.0.0" 4101 + }, 4102 + "funding": { 4103 + "type": "opencollective", 4104 + "url": "https://opencollective.com/parcel" 4105 + } 4106 + }, 4107 + "node_modules/lilconfig": { 4108 + "version": "2.1.0", 4109 + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", 4110 + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", 4111 + "dev": true, 4112 + "license": "MIT", 4113 + "engines": { 4114 + "node": ">=10" 4115 + } 4116 + }, 4117 + "node_modules/locate-character": { 4118 + "version": "3.0.0", 4119 + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 4120 + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", 4121 + "license": "MIT" 4122 + }, 4123 + "node_modules/locate-path": { 4124 + "version": "6.0.0", 4125 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 4126 + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 4127 + "dev": true, 4128 + "license": "MIT", 4129 + "dependencies": { 4130 + "p-locate": "^5.0.0" 4131 + }, 4132 + "engines": { 4133 + "node": ">=10" 4134 + }, 4135 + "funding": { 4136 + "url": "https://github.com/sponsors/sindresorhus" 4137 + } 4138 + }, 4139 + "node_modules/lodash.castarray": { 4140 + "version": "4.4.0", 4141 + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", 4142 + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", 4143 + "dev": true, 4144 + "license": "MIT" 4145 + }, 4146 + "node_modules/lodash.isplainobject": { 4147 + "version": "4.0.6", 4148 + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 4149 + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", 4150 + "dev": true, 4151 + "license": "MIT" 4152 + }, 4153 + "node_modules/lodash.merge": { 4154 + "version": "4.6.2", 4155 + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 4156 + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 4157 + "dev": true, 4158 + "license": "MIT" 4159 + }, 4160 + "node_modules/maath": { 4161 + "version": "0.10.8", 4162 + "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", 4163 + "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==", 4164 + "license": "MIT", 4165 + "peerDependencies": { 4166 + "@types/three": ">=0.134.0", 4167 + "three": ">=0.134.0" 4168 + } 4169 + }, 4170 + "node_modules/magic-string": { 4171 + "version": "0.30.17", 4172 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 4173 + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 4174 + "license": "MIT", 4175 + "dependencies": { 4176 + "@jridgewell/sourcemap-codec": "^1.5.0" 4177 + } 4178 + }, 4179 + "node_modules/merge2": { 4180 + "version": "1.4.1", 4181 + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 4182 + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 4183 + "dev": true, 4184 + "license": "MIT", 4185 + "engines": { 4186 + "node": ">= 8" 4187 + } 4188 + }, 4189 + "node_modules/meshoptimizer": { 4190 + "version": "0.18.1", 4191 + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", 4192 + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", 4193 + "license": "MIT" 4194 + }, 4195 + "node_modules/micromatch": { 4196 + "version": "4.0.8", 4197 + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 4198 + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 4199 + "devOptional": true, 4200 + "license": "MIT", 4201 + "dependencies": { 4202 + "braces": "^3.0.3", 4203 + "picomatch": "^2.3.1" 4204 + }, 4205 + "engines": { 4206 + "node": ">=8.6" 4207 + } 4208 + }, 4209 + "node_modules/micromatch/node_modules/picomatch": { 4210 + "version": "2.3.1", 4211 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 4212 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 4213 + "devOptional": true, 4214 + "license": "MIT", 4215 + "engines": { 4216 + "node": ">=8.6" 4217 + }, 4218 + "funding": { 4219 + "url": "https://github.com/sponsors/jonschlinkert" 4220 + } 4221 + }, 4222 + "node_modules/mini-svg-data-uri": { 4223 + "version": "1.4.4", 4224 + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", 4225 + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", 4226 + "dev": true, 4227 + "license": "MIT", 4228 + "bin": { 4229 + "mini-svg-data-uri": "cli.js" 4230 + } 4231 + }, 4232 + "node_modules/minimatch": { 4233 + "version": "3.1.2", 4234 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 4235 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 4236 + "dev": true, 4237 + "license": "ISC", 4238 + "dependencies": { 4239 + "brace-expansion": "^1.1.7" 4240 + }, 4241 + "engines": { 4242 + "node": "*" 4243 + } 4244 + }, 4245 + "node_modules/mitt": { 4246 + "version": "3.0.1", 4247 + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", 4248 + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", 4249 + "license": "MIT" 4250 + }, 4251 + "node_modules/mri": { 4252 + "version": "1.2.0", 4253 + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", 4254 + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", 4255 + "license": "MIT", 4256 + "engines": { 4257 + "node": ">=4" 4258 + } 4259 + }, 4260 + "node_modules/mrmime": { 4261 + "version": "2.0.1", 4262 + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", 4263 + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", 4264 + "license": "MIT", 4265 + "engines": { 4266 + "node": ">=10" 4267 + } 4268 + }, 4269 + "node_modules/ms": { 4270 + "version": "2.1.3", 4271 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 4272 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 4273 + "license": "MIT" 4274 + }, 4275 + "node_modules/multiformats": { 4276 + "version": "9.9.0", 4277 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 4278 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 4279 + "license": "(Apache-2.0 AND MIT)" 4280 + }, 4281 + "node_modules/nanoid": { 4282 + "version": "3.3.8", 4283 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", 4284 + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 4285 + "funding": [ 4286 + { 4287 + "type": "github", 4288 + "url": "https://github.com/sponsors/ai" 4289 + } 4290 + ], 4291 + "license": "MIT", 4292 + "bin": { 4293 + "nanoid": "bin/nanoid.cjs" 4294 + }, 4295 + "engines": { 4296 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 4297 + } 4298 + }, 4299 + "node_modules/natural-compare": { 4300 + "version": "1.4.0", 4301 + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 4302 + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 4303 + "dev": true, 4304 + "license": "MIT" 4305 + }, 4306 + "node_modules/node-addon-api": { 4307 + "version": "7.1.1", 4308 + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", 4309 + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", 4310 + "license": "MIT", 4311 + "optional": true 4312 + }, 4313 + "node_modules/number-flow": { 4314 + "version": "0.5.3", 4315 + "resolved": "https://registry.npmjs.org/number-flow/-/number-flow-0.5.3.tgz", 4316 + "integrity": "sha512-iLKyssImNWQmJ41rza9K7P5lHRZTyishi/9FarWPLQHYY2Ydtl6eiXINEjZ1fa8dHeY0O7+YOD+Py3ZsJddYkg==", 4317 + "license": "MIT", 4318 + "dependencies": { 4319 + "esm-env": "^1.1.4" 4320 + } 4321 + }, 4322 + "node_modules/optionator": { 4323 + "version": "0.9.4", 4324 + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 4325 + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 4326 + "dev": true, 4327 + "license": "MIT", 4328 + "dependencies": { 4329 + "deep-is": "^0.1.3", 4330 + "fast-levenshtein": "^2.0.6", 4331 + "levn": "^0.4.1", 4332 + "prelude-ls": "^1.2.1", 4333 + "type-check": "^0.4.0", 4334 + "word-wrap": "^1.2.5" 4335 + }, 4336 + "engines": { 4337 + "node": ">= 0.8.0" 4338 + } 4339 + }, 4340 + "node_modules/p-limit": { 4341 + "version": "3.1.0", 4342 + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 4343 + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 4344 + "dev": true, 4345 + "license": "MIT", 4346 + "dependencies": { 4347 + "yocto-queue": "^0.1.0" 4348 + }, 4349 + "engines": { 4350 + "node": ">=10" 4351 + }, 4352 + "funding": { 4353 + "url": "https://github.com/sponsors/sindresorhus" 4354 + } 4355 + }, 4356 + "node_modules/p-locate": { 4357 + "version": "5.0.0", 4358 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 4359 + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 4360 + "dev": true, 4361 + "license": "MIT", 4362 + "dependencies": { 4363 + "p-limit": "^3.0.2" 4364 + }, 4365 + "engines": { 4366 + "node": ">=10" 4367 + }, 4368 + "funding": { 4369 + "url": "https://github.com/sponsors/sindresorhus" 4370 + } 4371 + }, 4372 + "node_modules/parent-module": { 4373 + "version": "1.0.1", 4374 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 4375 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 4376 + "dev": true, 4377 + "license": "MIT", 4378 + "dependencies": { 4379 + "callsites": "^3.0.0" 4380 + }, 4381 + "engines": { 4382 + "node": ">=6" 4383 + } 4384 + }, 4385 + "node_modules/path-exists": { 4386 + "version": "4.0.0", 4387 + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 4388 + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 4389 + "dev": true, 4390 + "license": "MIT", 4391 + "engines": { 4392 + "node": ">=8" 4393 + } 4394 + }, 4395 + "node_modules/path-key": { 4396 + "version": "3.1.1", 4397 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 4398 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 4399 + "dev": true, 4400 + "license": "MIT", 4401 + "engines": { 4402 + "node": ">=8" 4403 + } 4404 + }, 4405 + "node_modules/picocolors": { 4406 + "version": "1.1.1", 4407 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 4408 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 4409 + "license": "ISC" 4410 + }, 4411 + "node_modules/picomatch": { 4412 + "version": "4.0.2", 4413 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 4414 + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 4415 + "license": "MIT", 4416 + "engines": { 4417 + "node": ">=12" 4418 + }, 4419 + "funding": { 4420 + "url": "https://github.com/sponsors/jonschlinkert" 4421 + } 4422 + }, 4423 + "node_modules/postcss": { 4424 + "version": "8.5.3", 4425 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", 4426 + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", 4427 + "funding": [ 4428 + { 4429 + "type": "opencollective", 4430 + "url": "https://opencollective.com/postcss/" 4431 + }, 4432 + { 4433 + "type": "tidelift", 4434 + "url": "https://tidelift.com/funding/github/npm/postcss" 4435 + }, 4436 + { 4437 + "type": "github", 4438 + "url": "https://github.com/sponsors/ai" 4439 + } 4440 + ], 4441 + "license": "MIT", 4442 + "dependencies": { 4443 + "nanoid": "^3.3.8", 4444 + "picocolors": "^1.1.1", 4445 + "source-map-js": "^1.2.1" 4446 + }, 4447 + "engines": { 4448 + "node": "^10 || ^12 || >=14" 4449 + } 4450 + }, 4451 + "node_modules/postcss-load-config": { 4452 + "version": "3.1.4", 4453 + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", 4454 + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", 4455 + "dev": true, 4456 + "license": "MIT", 4457 + "dependencies": { 4458 + "lilconfig": "^2.0.5", 4459 + "yaml": "^1.10.2" 4460 + }, 4461 + "engines": { 4462 + "node": ">= 10" 4463 + }, 4464 + "funding": { 4465 + "type": "opencollective", 4466 + "url": "https://opencollective.com/postcss/" 4467 + }, 4468 + "peerDependencies": { 4469 + "postcss": ">=8.0.9", 4470 + "ts-node": ">=9.0.0" 4471 + }, 4472 + "peerDependenciesMeta": { 4473 + "postcss": { 4474 + "optional": true 4475 + }, 4476 + "ts-node": { 4477 + "optional": true 4478 + } 4479 + } 4480 + }, 4481 + "node_modules/postcss-load-config/node_modules/yaml": { 4482 + "version": "1.10.2", 4483 + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 4484 + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", 4485 + "dev": true, 4486 + "license": "ISC", 4487 + "engines": { 4488 + "node": ">= 6" 4489 + } 4490 + }, 4491 + "node_modules/postcss-safe-parser": { 4492 + "version": "7.0.1", 4493 + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", 4494 + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", 4495 + "dev": true, 4496 + "funding": [ 4497 + { 4498 + "type": "opencollective", 4499 + "url": "https://opencollective.com/postcss/" 4500 + }, 4501 + { 4502 + "type": "tidelift", 4503 + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" 4504 + }, 4505 + { 4506 + "type": "github", 4507 + "url": "https://github.com/sponsors/ai" 4508 + } 4509 + ], 4510 + "license": "MIT", 4511 + "engines": { 4512 + "node": ">=18.0" 4513 + }, 4514 + "peerDependencies": { 4515 + "postcss": "^8.4.31" 4516 + } 4517 + }, 4518 + "node_modules/postcss-scss": { 4519 + "version": "4.0.9", 4520 + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", 4521 + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", 4522 + "dev": true, 4523 + "funding": [ 4524 + { 4525 + "type": "opencollective", 4526 + "url": "https://opencollective.com/postcss/" 4527 + }, 4528 + { 4529 + "type": "tidelift", 4530 + "url": "https://tidelift.com/funding/github/npm/postcss-scss" 4531 + }, 4532 + { 4533 + "type": "github", 4534 + "url": "https://github.com/sponsors/ai" 4535 + } 4536 + ], 4537 + "license": "MIT", 4538 + "engines": { 4539 + "node": ">=12.0" 4540 + }, 4541 + "peerDependencies": { 4542 + "postcss": "^8.4.29" 4543 + } 4544 + }, 4545 + "node_modules/postcss-selector-parser": { 4546 + "version": "6.0.10", 4547 + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", 4548 + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", 4549 + "dev": true, 4550 + "license": "MIT", 4551 + "dependencies": { 4552 + "cssesc": "^3.0.0", 4553 + "util-deprecate": "^1.0.2" 4554 + }, 4555 + "engines": { 4556 + "node": ">=4" 4557 + } 4558 + }, 4559 + "node_modules/postprocessing": { 4560 + "version": "6.37.0", 4561 + "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.37.0.tgz", 4562 + "integrity": "sha512-BEPKNugVMUhi/pDQvVCcDzYfDRvKi7x8JN+tgf1nNVjl0/s1ZdazOwpHiBiWbMcLcNB8l6tDb4nLa1RwSWjCgA==", 4563 + "license": "Zlib", 4564 + "peerDependencies": { 4565 + "three": ">= 0.157.0 < 0.175.0" 4566 + } 4567 + }, 4568 + "node_modules/prelude-ls": { 4569 + "version": "1.2.1", 4570 + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 4571 + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 4572 + "dev": true, 4573 + "license": "MIT", 4574 + "engines": { 4575 + "node": ">= 0.8.0" 4576 + } 4577 + }, 4578 + "node_modules/prettier": { 4579 + "version": "3.5.3", 4580 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", 4581 + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", 4582 + "dev": true, 4583 + "license": "MIT", 4584 + "bin": { 4585 + "prettier": "bin/prettier.cjs" 4586 + }, 4587 + "engines": { 4588 + "node": ">=14" 4589 + }, 4590 + "funding": { 4591 + "url": "https://github.com/prettier/prettier?sponsor=1" 4592 + } 4593 + }, 4594 + "node_modules/prettier-plugin-svelte": { 4595 + "version": "3.3.3", 4596 + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", 4597 + "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", 4598 + "dev": true, 4599 + "license": "MIT", 4600 + "peerDependencies": { 4601 + "prettier": "^3.0.0", 4602 + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" 4603 + } 4604 + }, 4605 + "node_modules/prettier-plugin-tailwindcss": { 4606 + "version": "0.6.11", 4607 + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", 4608 + "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", 4609 + "dev": true, 4610 + "license": "MIT", 4611 + "engines": { 4612 + "node": ">=14.21.3" 4613 + }, 4614 + "peerDependencies": { 4615 + "@ianvs/prettier-plugin-sort-imports": "*", 4616 + "@prettier/plugin-pug": "*", 4617 + "@shopify/prettier-plugin-liquid": "*", 4618 + "@trivago/prettier-plugin-sort-imports": "*", 4619 + "@zackad/prettier-plugin-twig": "*", 4620 + "prettier": "^3.0", 4621 + "prettier-plugin-astro": "*", 4622 + "prettier-plugin-css-order": "*", 4623 + "prettier-plugin-import-sort": "*", 4624 + "prettier-plugin-jsdoc": "*", 4625 + "prettier-plugin-marko": "*", 4626 + "prettier-plugin-multiline-arrays": "*", 4627 + "prettier-plugin-organize-attributes": "*", 4628 + "prettier-plugin-organize-imports": "*", 4629 + "prettier-plugin-sort-imports": "*", 4630 + "prettier-plugin-style-order": "*", 4631 + "prettier-plugin-svelte": "*" 4632 + }, 4633 + "peerDependenciesMeta": { 4634 + "@ianvs/prettier-plugin-sort-imports": { 4635 + "optional": true 4636 + }, 4637 + "@prettier/plugin-pug": { 4638 + "optional": true 4639 + }, 4640 + "@shopify/prettier-plugin-liquid": { 4641 + "optional": true 4642 + }, 4643 + "@trivago/prettier-plugin-sort-imports": { 4644 + "optional": true 4645 + }, 4646 + "@zackad/prettier-plugin-twig": { 4647 + "optional": true 4648 + }, 4649 + "prettier-plugin-astro": { 4650 + "optional": true 4651 + }, 4652 + "prettier-plugin-css-order": { 4653 + "optional": true 4654 + }, 4655 + "prettier-plugin-import-sort": { 4656 + "optional": true 4657 + }, 4658 + "prettier-plugin-jsdoc": { 4659 + "optional": true 4660 + }, 4661 + "prettier-plugin-marko": { 4662 + "optional": true 4663 + }, 4664 + "prettier-plugin-multiline-arrays": { 4665 + "optional": true 4666 + }, 4667 + "prettier-plugin-organize-attributes": { 4668 + "optional": true 4669 + }, 4670 + "prettier-plugin-organize-imports": { 4671 + "optional": true 4672 + }, 4673 + "prettier-plugin-sort-imports": { 4674 + "optional": true 4675 + }, 4676 + "prettier-plugin-style-order": { 4677 + "optional": true 4678 + }, 4679 + "prettier-plugin-svelte": { 4680 + "optional": true 4681 + } 4682 + } 4683 + }, 4684 + "node_modules/punycode": { 4685 + "version": "2.3.1", 4686 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 4687 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 4688 + "dev": true, 4689 + "license": "MIT", 4690 + "engines": { 4691 + "node": ">=6" 4692 + } 4693 + }, 4694 + "node_modules/queue-microtask": { 4695 + "version": "1.2.3", 4696 + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 4697 + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 4698 + "dev": true, 4699 + "funding": [ 4700 + { 4701 + "type": "github", 4702 + "url": "https://github.com/sponsors/feross" 4703 + }, 4704 + { 4705 + "type": "patreon", 4706 + "url": "https://www.patreon.com/feross" 4707 + }, 4708 + { 4709 + "type": "consulting", 4710 + "url": "https://feross.org/support" 4711 + } 4712 + ], 4713 + "license": "MIT" 4714 + }, 4715 + "node_modules/readdirp": { 4716 + "version": "4.1.2", 4717 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", 4718 + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", 4719 + "license": "MIT", 4720 + "engines": { 4721 + "node": ">= 14.18.0" 4722 + }, 4723 + "funding": { 4724 + "type": "individual", 4725 + "url": "https://paulmillr.com/funding/" 4726 + } 4727 + }, 4728 + "node_modules/require-from-string": { 4729 + "version": "2.0.2", 4730 + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 4731 + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 4732 + "license": "MIT", 4733 + "engines": { 4734 + "node": ">=0.10.0" 4735 + } 4736 + }, 4737 + "node_modules/resolve-from": { 4738 + "version": "4.0.0", 4739 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 4740 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 4741 + "dev": true, 4742 + "license": "MIT", 4743 + "engines": { 4744 + "node": ">=4" 4745 + } 4746 + }, 4747 + "node_modules/reusify": { 4748 + "version": "1.1.0", 4749 + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 4750 + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 4751 + "dev": true, 4752 + "license": "MIT", 4753 + "engines": { 4754 + "iojs": ">=1.0.0", 4755 + "node": ">=0.10.0" 4756 + } 4757 + }, 4758 + "node_modules/rollup": { 4759 + "version": "4.34.9", 4760 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", 4761 + "integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", 4762 + "license": "MIT", 4763 + "dependencies": { 4764 + "@types/estree": "1.0.6" 4765 + }, 4766 + "bin": { 4767 + "rollup": "dist/bin/rollup" 4768 + }, 4769 + "engines": { 4770 + "node": ">=18.0.0", 4771 + "npm": ">=8.0.0" 4772 + }, 4773 + "optionalDependencies": { 4774 + "@rollup/rollup-android-arm-eabi": "4.34.9", 4775 + "@rollup/rollup-android-arm64": "4.34.9", 4776 + "@rollup/rollup-darwin-arm64": "4.34.9", 4777 + "@rollup/rollup-darwin-x64": "4.34.9", 4778 + "@rollup/rollup-freebsd-arm64": "4.34.9", 4779 + "@rollup/rollup-freebsd-x64": "4.34.9", 4780 + "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", 4781 + "@rollup/rollup-linux-arm-musleabihf": "4.34.9", 4782 + "@rollup/rollup-linux-arm64-gnu": "4.34.9", 4783 + "@rollup/rollup-linux-arm64-musl": "4.34.9", 4784 + "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", 4785 + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", 4786 + "@rollup/rollup-linux-riscv64-gnu": "4.34.9", 4787 + "@rollup/rollup-linux-s390x-gnu": "4.34.9", 4788 + "@rollup/rollup-linux-x64-gnu": "4.34.9", 4789 + "@rollup/rollup-linux-x64-musl": "4.34.9", 4790 + "@rollup/rollup-win32-arm64-msvc": "4.34.9", 4791 + "@rollup/rollup-win32-ia32-msvc": "4.34.9", 4792 + "@rollup/rollup-win32-x64-msvc": "4.34.9", 4793 + "fsevents": "~2.3.2" 4794 + } 4795 + }, 4796 + "node_modules/run-parallel": { 4797 + "version": "1.2.0", 4798 + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 4799 + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 4800 + "dev": true, 4801 + "funding": [ 4802 + { 4803 + "type": "github", 4804 + "url": "https://github.com/sponsors/feross" 4805 + }, 4806 + { 4807 + "type": "patreon", 4808 + "url": "https://www.patreon.com/feross" 4809 + }, 4810 + { 4811 + "type": "consulting", 4812 + "url": "https://feross.org/support" 4813 + } 4814 + ], 4815 + "license": "MIT", 4816 + "dependencies": { 4817 + "queue-microtask": "^1.2.2" 4818 + } 4819 + }, 4820 + "node_modules/runed": { 4821 + "version": "0.23.4", 4822 + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", 4823 + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", 4824 + "funding": [ 4825 + "https://github.com/sponsors/huntabyte", 4826 + "https://github.com/sponsors/tglide" 4827 + ], 4828 + "dependencies": { 4829 + "esm-env": "^1.0.0" 4830 + }, 4831 + "peerDependencies": { 4832 + "svelte": "^5.7.0" 4833 + } 4834 + }, 4835 + "node_modules/sade": { 4836 + "version": "1.8.1", 4837 + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", 4838 + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", 4839 + "license": "MIT", 4840 + "dependencies": { 4841 + "mri": "^1.1.0" 4842 + }, 4843 + "engines": { 4844 + "node": ">=6" 4845 + } 4846 + }, 4847 + "node_modules/sass": { 4848 + "version": "1.85.1", 4849 + "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz", 4850 + "integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==", 4851 + "license": "MIT", 4852 + "dependencies": { 4853 + "chokidar": "^4.0.0", 4854 + "immutable": "^5.0.2", 4855 + "source-map-js": ">=0.6.2 <2.0.0" 4856 + }, 4857 + "bin": { 4858 + "sass": "sass.js" 4859 + }, 4860 + "engines": { 4861 + "node": ">=14.0.0" 4862 + }, 4863 + "optionalDependencies": { 4864 + "@parcel/watcher": "^2.4.1" 4865 + } 4866 + }, 4867 + "node_modules/semver": { 4868 + "version": "7.7.1", 4869 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", 4870 + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", 4871 + "license": "ISC", 4872 + "bin": { 4873 + "semver": "bin/semver.js" 4874 + }, 4875 + "engines": { 4876 + "node": ">=10" 4877 + } 4878 + }, 4879 + "node_modules/set-cookie-parser": { 4880 + "version": "2.7.1", 4881 + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", 4882 + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", 4883 + "license": "MIT" 4884 + }, 4885 + "node_modules/sharp": { 4886 + "version": "0.33.5", 4887 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", 4888 + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", 4889 + "hasInstallScript": true, 4890 + "license": "Apache-2.0", 4891 + "dependencies": { 4892 + "color": "^4.2.3", 4893 + "detect-libc": "^2.0.3", 4894 + "semver": "^7.6.3" 4895 + }, 4896 + "engines": { 4897 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 4898 + }, 4899 + "funding": { 4900 + "url": "https://opencollective.com/libvips" 4901 + }, 4902 + "optionalDependencies": { 4903 + "@img/sharp-darwin-arm64": "0.33.5", 4904 + "@img/sharp-darwin-x64": "0.33.5", 4905 + "@img/sharp-libvips-darwin-arm64": "1.0.4", 4906 + "@img/sharp-libvips-darwin-x64": "1.0.4", 4907 + "@img/sharp-libvips-linux-arm": "1.0.5", 4908 + "@img/sharp-libvips-linux-arm64": "1.0.4", 4909 + "@img/sharp-libvips-linux-s390x": "1.0.4", 4910 + "@img/sharp-libvips-linux-x64": "1.0.4", 4911 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", 4912 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", 4913 + "@img/sharp-linux-arm": "0.33.5", 4914 + "@img/sharp-linux-arm64": "0.33.5", 4915 + "@img/sharp-linux-s390x": "0.33.5", 4916 + "@img/sharp-linux-x64": "0.33.5", 4917 + "@img/sharp-linuxmusl-arm64": "0.33.5", 4918 + "@img/sharp-linuxmusl-x64": "0.33.5", 4919 + "@img/sharp-wasm32": "0.33.5", 4920 + "@img/sharp-win32-ia32": "0.33.5", 4921 + "@img/sharp-win32-x64": "0.33.5" 4922 + } 4923 + }, 4924 + "node_modules/sharp/node_modules/detect-libc": { 4925 + "version": "2.0.3", 4926 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", 4927 + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", 4928 + "license": "Apache-2.0", 4929 + "engines": { 4930 + "node": ">=8" 4931 + } 4932 + }, 4933 + "node_modules/shebang-command": { 4934 + "version": "2.0.0", 4935 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 4936 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 4937 + "dev": true, 4938 + "license": "MIT", 4939 + "dependencies": { 4940 + "shebang-regex": "^3.0.0" 4941 + }, 4942 + "engines": { 4943 + "node": ">=8" 4944 + } 4945 + }, 4946 + "node_modules/shebang-regex": { 4947 + "version": "3.0.0", 4948 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 4949 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 4950 + "dev": true, 4951 + "license": "MIT", 4952 + "engines": { 4953 + "node": ">=8" 4954 + } 4955 + }, 4956 + "node_modules/simple-swizzle": { 4957 + "version": "0.2.2", 4958 + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 4959 + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 4960 + "license": "MIT", 4961 + "dependencies": { 4962 + "is-arrayish": "^0.3.1" 4963 + } 4964 + }, 4965 + "node_modules/sirv": { 4966 + "version": "3.0.1", 4967 + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", 4968 + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", 4969 + "license": "MIT", 4970 + "dependencies": { 4971 + "@polka/url": "^1.0.0-next.24", 4972 + "mrmime": "^2.0.0", 4973 + "totalist": "^3.0.0" 4974 + }, 4975 + "engines": { 4976 + "node": ">=18" 4977 + } 4978 + }, 4979 + "node_modules/source-map-js": { 4980 + "version": "1.2.1", 4981 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 4982 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 4983 + "license": "BSD-3-Clause", 4984 + "engines": { 4985 + "node": ">=0.10.0" 4986 + } 4987 + }, 4988 + "node_modules/strip-json-comments": { 4989 + "version": "3.1.1", 4990 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 4991 + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 4992 + "dev": true, 4993 + "license": "MIT", 4994 + "engines": { 4995 + "node": ">=8" 4996 + }, 4997 + "funding": { 4998 + "url": "https://github.com/sponsors/sindresorhus" 4999 + } 5000 + }, 5001 + "node_modules/style-to-object": { 5002 + "version": "1.0.8", 5003 + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", 5004 + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", 5005 + "license": "MIT", 5006 + "dependencies": { 5007 + "inline-style-parser": "0.2.4" 5008 + } 5009 + }, 5010 + "node_modules/supports-color": { 5011 + "version": "7.2.0", 5012 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 5013 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 5014 + "dev": true, 5015 + "license": "MIT", 5016 + "dependencies": { 5017 + "has-flag": "^4.0.0" 5018 + }, 5019 + "engines": { 5020 + "node": ">=8" 5021 + } 5022 + }, 5023 + "node_modules/svelte": { 5024 + "version": "5.22.3", 5025 + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.22.3.tgz", 5026 + "integrity": "sha512-CNaAriQ75Kqw0Pv+Ey2cgbHv+RUkzXySW/g6nK4QbgoWQIVDYHQNv1+GeVPb6cIDshJ58Ub5dD1S31MWpuf/nw==", 5027 + "license": "MIT", 5028 + "dependencies": { 5029 + "@ampproject/remapping": "^2.3.0", 5030 + "@jridgewell/sourcemap-codec": "^1.5.0", 5031 + "@sveltejs/acorn-typescript": "^1.0.5", 5032 + "@types/estree": "^1.0.5", 5033 + "acorn": "^8.12.1", 5034 + "aria-query": "^5.3.1", 5035 + "axobject-query": "^4.1.0", 5036 + "clsx": "^2.1.1", 5037 + "esm-env": "^1.2.1", 5038 + "esrap": "^1.4.3", 5039 + "is-reference": "^3.0.3", 5040 + "locate-character": "^3.0.0", 5041 + "magic-string": "^0.30.11", 5042 + "zimmerframe": "^1.1.2" 5043 + }, 5044 + "engines": { 5045 + "node": ">=18" 5046 + } 5047 + }, 5048 + "node_modules/svelte-check": { 5049 + "version": "4.1.4", 5050 + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.4.tgz", 5051 + "integrity": "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==", 5052 + "dev": true, 5053 + "license": "MIT", 5054 + "dependencies": { 5055 + "@jridgewell/trace-mapping": "^0.3.25", 5056 + "chokidar": "^4.0.1", 5057 + "fdir": "^6.2.0", 5058 + "picocolors": "^1.0.0", 5059 + "sade": "^1.7.4" 5060 + }, 5061 + "bin": { 5062 + "svelte-check": "bin/svelte-check" 5063 + }, 5064 + "engines": { 5065 + "node": ">= 18.0.0" 5066 + }, 5067 + "peerDependencies": { 5068 + "svelte": "^4.0.0 || ^5.0.0-next.0", 5069 + "typescript": ">=5.0.0" 5070 + } 5071 + }, 5072 + "node_modules/svelte-eslint-parser": { 5073 + "version": "1.0.1", 5074 + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.0.1.tgz", 5075 + "integrity": "sha512-JjdEMXOJqy+dxeaElxbN+meTOtVpHfLnq9VGpiTAOLgM0uHO+ogmUsA3IFgx0x3Wl15pqTZWycCikcD7cAQN/g==", 5076 + "dev": true, 5077 + "license": "MIT", 5078 + "dependencies": { 5079 + "eslint-scope": "^8.2.0", 5080 + "eslint-visitor-keys": "^4.0.0", 5081 + "espree": "^10.0.0", 5082 + "postcss": "^8.4.49", 5083 + "postcss-scss": "^4.0.9", 5084 + "postcss-selector-parser": "^7.0.0" 5085 + }, 5086 + "engines": { 5087 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 5088 + }, 5089 + "funding": { 5090 + "url": "https://github.com/sponsors/ota-meshi" 5091 + }, 5092 + "peerDependencies": { 5093 + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" 5094 + }, 5095 + "peerDependenciesMeta": { 5096 + "svelte": { 5097 + "optional": true 5098 + } 5099 + } 5100 + }, 5101 + "node_modules/svelte-eslint-parser/node_modules/postcss-selector-parser": { 5102 + "version": "7.1.0", 5103 + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", 5104 + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", 5105 + "dev": true, 5106 + "license": "MIT", 5107 + "dependencies": { 5108 + "cssesc": "^3.0.0", 5109 + "util-deprecate": "^1.0.2" 5110 + }, 5111 + "engines": { 5112 + "node": ">=4" 5113 + } 5114 + }, 5115 + "node_modules/svelte-persisted-store": { 5116 + "version": "0.12.0", 5117 + "resolved": "https://registry.npmjs.org/svelte-persisted-store/-/svelte-persisted-store-0.12.0.tgz", 5118 + "integrity": "sha512-BdBQr2SGSJ+rDWH8/aEV5GthBJDapVP0GP3fuUCA7TjYG5ctcB+O9Mj9ZC0+Jo1oJMfZUd1y9H68NFRR5MyIJA==", 5119 + "license": "MIT", 5120 + "engines": { 5121 + "node": ">=0.14" 5122 + }, 5123 + "peerDependencies": { 5124 + "svelte": "^3.48.0 || ^4 || ^5" 5125 + } 5126 + }, 5127 + "node_modules/svelte-toolbelt": { 5128 + "version": "0.7.1", 5129 + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz", 5130 + "integrity": "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==", 5131 + "funding": [ 5132 + "https://github.com/sponsors/huntabyte" 5133 + ], 5134 + "dependencies": { 5135 + "clsx": "^2.1.1", 5136 + "runed": "^0.23.2", 5137 + "style-to-object": "^1.0.8" 5138 + }, 5139 + "engines": { 5140 + "node": ">=18", 5141 + "pnpm": ">=8.7.0" 5142 + }, 5143 + "peerDependencies": { 5144 + "svelte": "^5.0.0" 5145 + } 5146 + }, 5147 + "node_modules/svelte-tweakpane-ui": { 5148 + "version": "1.5.5", 5149 + "resolved": "https://registry.npmjs.org/svelte-tweakpane-ui/-/svelte-tweakpane-ui-1.5.5.tgz", 5150 + "integrity": "sha512-sSnRcbwisoZme4N0vguF7hUXCEIbY+SG0maOjVdjmCBBiKx9ktJWGTP4vSG7KmC+0Vk9yS6ba9N3PCmE7KLYQQ==", 5151 + "license": "MIT", 5152 + "dependencies": { 5153 + "@kitschpatrol/tweakpane-plugin-camerakit": "0.3.1-beta.3", 5154 + "@kitschpatrol/tweakpane-plugin-essentials": "0.2.2-beta.3", 5155 + "@kitschpatrol/tweakpane-plugin-file-import": "1.1.2-beta.2", 5156 + "@kitschpatrol/tweakpane-plugin-image": "2.0.1-beta.6", 5157 + "@kitschpatrol/tweakpane-plugin-inputs": "1.0.4-beta.4", 5158 + "@kitschpatrol/tweakpane-plugin-profiler": "0.4.2-beta.3", 5159 + "@kitschpatrol/tweakpane-plugin-rotation": "0.2.1-beta.2", 5160 + "@kitschpatrol/tweakpane-plugin-textarea": "2.0.1-beta.2", 5161 + "@kitschpatrol/tweakpane-plugin-waveform": "1.0.4-beta.3", 5162 + "@tweakpane/core": "2.0.5", 5163 + "esm-env": "^1.2.2", 5164 + "fast-copy": "^3.0.2", 5165 + "fast-equals": "^5.2.2", 5166 + "nanoid": "^5.1.2", 5167 + "svelte-persisted-store": "0.12.0", 5168 + "tweakpane": "4.0.5" 5169 + }, 5170 + "peerDependencies": { 5171 + "svelte": "^4.0.0 || ^5.0.0" 5172 + } 5173 + }, 5174 + "node_modules/svelte-tweakpane-ui/node_modules/nanoid": { 5175 + "version": "5.1.2", 5176 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.2.tgz", 5177 + "integrity": "sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g==", 5178 + "funding": [ 5179 + { 5180 + "type": "github", 5181 + "url": "https://github.com/sponsors/ai" 5182 + } 5183 + ], 5184 + "license": "MIT", 5185 + "bin": { 5186 + "nanoid": "bin/nanoid.js" 5187 + }, 5188 + "engines": { 5189 + "node": "^18 || >=20" 5190 + } 5191 + }, 5192 + "node_modules/svelte-tweakpane-ui/node_modules/tweakpane": { 5193 + "version": "4.0.5", 5194 + "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.5.tgz", 5195 + "integrity": "sha512-rxEXdSI+ArlG1RyO6FghC4ZUX8JkEfz8F3v1JuteXSV0pEtHJzyo07fcDG+NsJfN5L39kSbCYbB9cBGHyuI/tQ==", 5196 + "license": "MIT", 5197 + "funding": { 5198 + "url": "https://github.com/sponsors/cocopon" 5199 + } 5200 + }, 5201 + "node_modules/tabbable": { 5202 + "version": "6.2.0", 5203 + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", 5204 + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", 5205 + "license": "MIT" 5206 + }, 5207 + "node_modules/tailwind-merge": { 5208 + "version": "2.5.4", 5209 + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", 5210 + "integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==", 5211 + "license": "MIT", 5212 + "funding": { 5213 + "type": "github", 5214 + "url": "https://github.com/sponsors/dcastil" 5215 + } 5216 + }, 5217 + "node_modules/tailwind-variants": { 5218 + "version": "0.3.1", 5219 + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.1.tgz", 5220 + "integrity": "sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==", 5221 + "license": "MIT", 5222 + "dependencies": { 5223 + "tailwind-merge": "2.5.4" 5224 + }, 5225 + "engines": { 5226 + "node": ">=16.x", 5227 + "pnpm": ">=7.x" 5228 + }, 5229 + "peerDependencies": { 5230 + "tailwindcss": "*" 5231 + } 5232 + }, 5233 + "node_modules/tailwindcss": { 5234 + "version": "4.0.9", 5235 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.9.tgz", 5236 + "integrity": "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw==", 5237 + "license": "MIT" 5238 + }, 5239 + "node_modules/tapable": { 5240 + "version": "2.2.1", 5241 + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", 5242 + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", 5243 + "dev": true, 5244 + "license": "MIT", 5245 + "engines": { 5246 + "node": ">=6" 5247 + } 5248 + }, 5249 + "node_modules/three": { 5250 + "version": "0.174.0", 5251 + "resolved": "https://registry.npmjs.org/three/-/three-0.174.0.tgz", 5252 + "integrity": "sha512-p+WG3W6Ov74alh3geCMkGK9NWuT62ee21cV3jEnun201zodVF4tCE5aZa2U122/mkLRmhJJUQmLLW1BH00uQJQ==", 5253 + "license": "MIT" 5254 + }, 5255 + "node_modules/three-instanced-uniforms-mesh": { 5256 + "version": "0.52.0", 5257 + "resolved": "https://registry.npmjs.org/three-instanced-uniforms-mesh/-/three-instanced-uniforms-mesh-0.52.0.tgz", 5258 + "integrity": "sha512-YuJigan74qBmt2X8XX4DCxGl42GQXQZWSyy8jos1kqjPyzR7IVl9Jk4HvOGEzb5pI7phVcaArImhaW2FZH3zpA==", 5259 + "license": "MIT", 5260 + "dependencies": { 5261 + "troika-three-utils": "^0.52.0" 5262 + }, 5263 + "peerDependencies": { 5264 + "three": ">=0.125.0" 5265 + } 5266 + }, 5267 + "node_modules/three-mesh-bvh": { 5268 + "version": "0.7.6", 5269 + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.6.tgz", 5270 + "integrity": "sha512-rCjsnxEqR9r1/C/lCqzGLS67NDty/S/eT6rAJfDvsanrIctTWdNoR4ZOGWewCB13h1QkVo2BpmC0wakj1+0m8A==", 5271 + "license": "MIT", 5272 + "peerDependencies": { 5273 + "three": ">= 0.151.0" 5274 + } 5275 + }, 5276 + "node_modules/three-perf": { 5277 + "version": "1.0.10", 5278 + "resolved": "git+ssh://git@github.com/jerzakm/three-perf.git#322d7d38a17069f2b6e9734913edad0fc4a29169", 5279 + "license": "MIT", 5280 + "dependencies": { 5281 + "troika-three-text": "^0.52.0", 5282 + "tweakpane": "^3.1.10" 5283 + }, 5284 + "peerDependencies": { 5285 + "three": ">=0.170" 5286 + } 5287 + }, 5288 + "node_modules/three-perf/node_modules/troika-three-text": { 5289 + "version": "0.52.3", 5290 + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.3.tgz", 5291 + "integrity": "sha512-jLhiwgV8kEkwWjvK12f2fHVpbOC75p7SgPQ0cgcz+IMtN5Bdyg4EuFdwuTOVu9ga8UeYdKBpzd1AxviyixtYTQ==", 5292 + "license": "MIT", 5293 + "dependencies": { 5294 + "bidi-js": "^1.0.2", 5295 + "troika-three-utils": "^0.52.0", 5296 + "troika-worker-utils": "^0.52.0", 5297 + "webgl-sdf-generator": "1.1.1" 5298 + }, 5299 + "peerDependencies": { 5300 + "three": ">=0.125.0" 5301 + } 5302 + }, 5303 + "node_modules/three-perf/node_modules/troika-worker-utils": { 5304 + "version": "0.52.0", 5305 + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", 5306 + "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", 5307 + "license": "MIT" 5308 + }, 5309 + "node_modules/three-perf/node_modules/tweakpane": { 5310 + "version": "3.1.10", 5311 + "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-3.1.10.tgz", 5312 + "integrity": "sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ==", 5313 + "license": "MIT", 5314 + "funding": { 5315 + "url": "https://github.com/sponsors/cocopon" 5316 + } 5317 + }, 5318 + "node_modules/three-viewport-gizmo": { 5319 + "version": "2.1.1", 5320 + "resolved": "https://registry.npmjs.org/three-viewport-gizmo/-/three-viewport-gizmo-2.1.1.tgz", 5321 + "integrity": "sha512-u+tv4DL7O7GqcF2l5xt2fiSZhKifAFOJ1UZI3vPy7W6ZiZaRjNgi/W4v783VK6s+85PP5kCFOrSg1Qn9jm/4zQ==", 5322 + "license": "MIT", 5323 + "peerDependencies": { 5324 + "three": ">=0.162.0 <1.0.0" 5325 + } 5326 + }, 5327 + "node_modules/tlds": { 5328 + "version": "1.256.0", 5329 + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.256.0.tgz", 5330 + "integrity": "sha512-ZmyVB9DAw+FFTmLElGYJgdZFsKLYd/I59Bg9NHkCGPwAbVZNRilFWDMAdX8UG+bHuv7kfursd5XGqo/9wi26lA==", 5331 + "license": "MIT", 5332 + "bin": { 5333 + "tlds": "bin.js" 5334 + } 5335 + }, 5336 + "node_modules/to-regex-range": { 5337 + "version": "5.0.1", 5338 + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 5339 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 5340 + "devOptional": true, 5341 + "license": "MIT", 5342 + "dependencies": { 5343 + "is-number": "^7.0.0" 5344 + }, 5345 + "engines": { 5346 + "node": ">=8.0" 5347 + } 5348 + }, 5349 + "node_modules/totalist": { 5350 + "version": "3.0.1", 5351 + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", 5352 + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", 5353 + "license": "MIT", 5354 + "engines": { 5355 + "node": ">=6" 5356 + } 5357 + }, 5358 + "node_modules/troika-three-text": { 5359 + "version": "0.50.3", 5360 + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.50.3.tgz", 5361 + "integrity": "sha512-UzbIrgV3fOFmFbFfWIBcBEXEyQovs+LLu15E5xGpSQbo1oZH49A+J9bul4H8H0xLtaLkRTO+LqEsmkaeDtZQfg==", 5362 + "license": "MIT", 5363 + "dependencies": { 5364 + "bidi-js": "^1.0.2", 5365 + "troika-three-utils": "^0.50.3", 5366 + "troika-worker-utils": "^0.50.0", 5367 + "webgl-sdf-generator": "1.1.1" 5368 + }, 5369 + "peerDependencies": { 5370 + "three": ">=0.125.0" 5371 + } 5372 + }, 5373 + "node_modules/troika-three-text/node_modules/troika-three-utils": { 5374 + "version": "0.50.3", 5375 + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.50.3.tgz", 5376 + "integrity": "sha512-asQWCESikU58y9cz4OgIjCRlITkDTwf7ds8T9IyWxn7OB2A7XItg2UbHjfexwooTefM+BYbEC4ZKxOUBfbNVLA==", 5377 + "license": "MIT", 5378 + "peerDependencies": { 5379 + "three": ">=0.125.0" 5380 + } 5381 + }, 5382 + "node_modules/troika-three-utils": { 5383 + "version": "0.52.0", 5384 + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.0.tgz", 5385 + "integrity": "sha512-00oxqIIehtEKInOTQekgyknBuRUj1POfOUE2q1OmL+Xlpp4gIu+S0oA0schTyXsDS4d9DkR04iqCdD40rF5R6w==", 5386 + "license": "MIT", 5387 + "peerDependencies": { 5388 + "three": ">=0.125.0" 5389 + } 5390 + }, 5391 + "node_modules/troika-worker-utils": { 5392 + "version": "0.50.0", 5393 + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.50.0.tgz", 5394 + "integrity": "sha512-BxNk0w6+d9NYd7El/3RzlzH/F2vovJI1FX9Zb+D7fArVM1EsUXj6+wVwJSrfucy6/HYhfbZPu/lD6Miiw9Byiw==", 5395 + "license": "MIT" 5396 + }, 5397 + "node_modules/ts-api-utils": { 5398 + "version": "2.0.1", 5399 + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", 5400 + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", 5401 + "dev": true, 5402 + "license": "MIT", 5403 + "engines": { 5404 + "node": ">=18.12" 5405 + }, 5406 + "peerDependencies": { 5407 + "typescript": ">=4.8.4" 5408 + } 5409 + }, 5410 + "node_modules/tslib": { 5411 + "version": "2.8.1", 5412 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 5413 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 5414 + "license": "0BSD" 5415 + }, 5416 + "node_modules/tweakpane": { 5417 + "version": "4.0.3", 5418 + "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.3.tgz", 5419 + "integrity": "sha512-BlcWOAe8oe4c+k9pmLBARGdWB6MVZMszayekkixQXTgkxTaYoTUpHpwVEp+3HkoamZkomodpbBf0CkguIHTgLg==", 5420 + "license": "MIT", 5421 + "funding": { 5422 + "url": "https://github.com/sponsors/cocopon" 5423 + } 5424 + }, 5425 + "node_modules/type-check": { 5426 + "version": "0.4.0", 5427 + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 5428 + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 5429 + "dev": true, 5430 + "license": "MIT", 5431 + "dependencies": { 5432 + "prelude-ls": "^1.2.1" 5433 + }, 5434 + "engines": { 5435 + "node": ">= 0.8.0" 5436 + } 5437 + }, 5438 + "node_modules/typescript": { 5439 + "version": "5.8.2", 5440 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 5441 + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 5442 + "dev": true, 5443 + "license": "Apache-2.0", 5444 + "bin": { 5445 + "tsc": "bin/tsc", 5446 + "tsserver": "bin/tsserver" 5447 + }, 5448 + "engines": { 5449 + "node": ">=14.17" 5450 + } 5451 + }, 5452 + "node_modules/typescript-eslint": { 5453 + "version": "8.26.0", 5454 + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.26.0.tgz", 5455 + "integrity": "sha512-PtVz9nAnuNJuAVeUFvwztjuUgSnJInODAUx47VDwWPXzd5vismPOtPtt83tzNXyOjVQbPRp786D6WFW/M2koIA==", 5456 + "dev": true, 5457 + "license": "MIT", 5458 + "dependencies": { 5459 + "@typescript-eslint/eslint-plugin": "8.26.0", 5460 + "@typescript-eslint/parser": "8.26.0", 5461 + "@typescript-eslint/utils": "8.26.0" 5462 + }, 5463 + "engines": { 5464 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 5465 + }, 5466 + "funding": { 5467 + "type": "opencollective", 5468 + "url": "https://opencollective.com/typescript-eslint" 5469 + }, 5470 + "peerDependencies": { 5471 + "eslint": "^8.57.0 || ^9.0.0", 5472 + "typescript": ">=4.8.4 <5.9.0" 5473 + } 5474 + }, 5475 + "node_modules/uint8arrays": { 5476 + "version": "3.0.0", 5477 + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 5478 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 5479 + "license": "MIT", 5480 + "dependencies": { 5481 + "multiformats": "^9.4.2" 5482 + } 5483 + }, 5484 + "node_modules/uri-js": { 5485 + "version": "4.4.1", 5486 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 5487 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 5488 + "dev": true, 5489 + "license": "BSD-2-Clause", 5490 + "dependencies": { 5491 + "punycode": "^2.1.0" 5492 + } 5493 + }, 5494 + "node_modules/util-deprecate": { 5495 + "version": "1.0.2", 5496 + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 5497 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 5498 + "dev": true, 5499 + "license": "MIT" 5500 + }, 5501 + "node_modules/vite": { 5502 + "version": "6.2.0", 5503 + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", 5504 + "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", 5505 + "license": "MIT", 5506 + "dependencies": { 5507 + "esbuild": "^0.25.0", 5508 + "postcss": "^8.5.3", 5509 + "rollup": "^4.30.1" 5510 + }, 5511 + "bin": { 5512 + "vite": "bin/vite.js" 5513 + }, 5514 + "engines": { 5515 + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 5516 + }, 5517 + "funding": { 5518 + "url": "https://github.com/vitejs/vite?sponsor=1" 5519 + }, 5520 + "optionalDependencies": { 5521 + "fsevents": "~2.3.3" 5522 + }, 5523 + "peerDependencies": { 5524 + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 5525 + "jiti": ">=1.21.0", 5526 + "less": "*", 5527 + "lightningcss": "^1.21.0", 5528 + "sass": "*", 5529 + "sass-embedded": "*", 5530 + "stylus": "*", 5531 + "sugarss": "*", 5532 + "terser": "^5.16.0", 5533 + "tsx": "^4.8.1", 5534 + "yaml": "^2.4.2" 5535 + }, 5536 + "peerDependenciesMeta": { 5537 + "@types/node": { 5538 + "optional": true 5539 + }, 5540 + "jiti": { 5541 + "optional": true 5542 + }, 5543 + "less": { 5544 + "optional": true 5545 + }, 5546 + "lightningcss": { 5547 + "optional": true 5548 + }, 5549 + "sass": { 5550 + "optional": true 5551 + }, 5552 + "sass-embedded": { 5553 + "optional": true 5554 + }, 5555 + "stylus": { 5556 + "optional": true 5557 + }, 5558 + "sugarss": { 5559 + "optional": true 5560 + }, 5561 + "terser": { 5562 + "optional": true 5563 + }, 5564 + "tsx": { 5565 + "optional": true 5566 + }, 5567 + "yaml": { 5568 + "optional": true 5569 + } 5570 + } 5571 + }, 5572 + "node_modules/vite-dev-rpc": { 5573 + "version": "0.1.7", 5574 + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-0.1.7.tgz", 5575 + "integrity": "sha512-e5HCaGI/5cHSB6wzhQyaH/JhUsPLI+TylHYp8UxAypeZaFoEHvBvRPD0nTYQtUEqeZb8qSznbVM/VznKSVC8/Q==", 5576 + "license": "MIT", 5577 + "dependencies": { 5578 + "birpc": "^0.2.19", 5579 + "vite-hot-client": "^0.2.4" 5580 + }, 5581 + "funding": { 5582 + "url": "https://github.com/sponsors/antfu" 5583 + }, 5584 + "peerDependencies": { 5585 + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1" 5586 + } 5587 + }, 5588 + "node_modules/vite-hot-client": { 5589 + "version": "0.2.4", 5590 + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.4.tgz", 5591 + "integrity": "sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==", 5592 + "license": "MIT", 5593 + "funding": { 5594 + "url": "https://github.com/sponsors/antfu" 5595 + }, 5596 + "peerDependencies": { 5597 + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0" 5598 + } 5599 + }, 5600 + "node_modules/vite-imagetools": { 5601 + "version": "6.2.9", 5602 + "resolved": "https://registry.npmjs.org/vite-imagetools/-/vite-imagetools-6.2.9.tgz", 5603 + "integrity": "sha512-C4ZYhgj2vAj43/TpZ06XlDNP0p/7LIeYbgUYr+xG44nM++4HGX6YZBKAYpiBNgiCFUTJ6eXkRppWBrfPMevgmg==", 5604 + "license": "MIT", 5605 + "dependencies": { 5606 + "@rollup/pluginutils": "^5.0.5", 5607 + "imagetools-core": "^6.0.4" 5608 + }, 5609 + "engines": { 5610 + "node": ">=12.0.0" 5611 + } 5612 + }, 5613 + "node_modules/vite-plugin-glsl": { 5614 + "version": "1.3.3", 5615 + "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.3.3.tgz", 5616 + "integrity": "sha512-ZN1PjwPN9MTqt75SAZHcNr9A4IFtxFxZsPwApVuhhnSSeDPk6ezD8LUmcoTQtZwerNT3vWiwv3+zSspT+8yInQ==", 5617 + "dev": true, 5618 + "license": "MIT", 5619 + "dependencies": { 5620 + "@rollup/pluginutils": "^5.1.4" 5621 + }, 5622 + "engines": { 5623 + "node": ">= 20.17.0", 5624 + "npm": ">= 10.8.3" 5625 + }, 5626 + "peerDependencies": { 5627 + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" 5628 + } 5629 + }, 5630 + "node_modules/vitefu": { 5631 + "version": "1.0.6", 5632 + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", 5633 + "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", 5634 + "license": "MIT", 5635 + "workspaces": [ 5636 + "tests/deps/*", 5637 + "tests/projects/*" 5638 + ], 5639 + "peerDependencies": { 5640 + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" 5641 + }, 5642 + "peerDependenciesMeta": { 5643 + "vite": { 5644 + "optional": true 5645 + } 5646 + } 5647 + }, 5648 + "node_modules/webgl-sdf-generator": { 5649 + "version": "1.1.1", 5650 + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", 5651 + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", 5652 + "license": "MIT" 5653 + }, 5654 + "node_modules/which": { 5655 + "version": "2.0.2", 5656 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 5657 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 5658 + "dev": true, 5659 + "license": "ISC", 5660 + "dependencies": { 5661 + "isexe": "^2.0.0" 5662 + }, 5663 + "bin": { 5664 + "node-which": "bin/node-which" 5665 + }, 5666 + "engines": { 5667 + "node": ">= 8" 5668 + } 5669 + }, 5670 + "node_modules/word-wrap": { 5671 + "version": "1.2.5", 5672 + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 5673 + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 5674 + "dev": true, 5675 + "license": "MIT", 5676 + "engines": { 5677 + "node": ">=0.10.0" 5678 + } 5679 + }, 5680 + "node_modules/yaml": { 5681 + "version": "2.7.0", 5682 + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", 5683 + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", 5684 + "license": "ISC", 5685 + "optional": true, 5686 + "peer": true, 5687 + "bin": { 5688 + "yaml": "bin.mjs" 5689 + }, 5690 + "engines": { 5691 + "node": ">= 14" 5692 + } 5693 + }, 5694 + "node_modules/yocto-queue": { 5695 + "version": "0.1.0", 5696 + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 5697 + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 5698 + "dev": true, 5699 + "license": "MIT", 5700 + "engines": { 5701 + "node": ">=10" 5702 + }, 5703 + "funding": { 5704 + "url": "https://github.com/sponsors/sindresorhus" 5705 + } 5706 + }, 5707 + "node_modules/zimmerframe": { 5708 + "version": "1.1.2", 5709 + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", 5710 + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", 5711 + "license": "MIT" 5712 + }, 5713 + "node_modules/zod": { 5714 + "version": "3.24.2", 5715 + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", 5716 + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", 5717 + "license": "MIT", 5718 + "funding": { 5719 + "url": "https://github.com/sponsors/colinhacks" 5720 + } 5721 + } 5722 + } 5723 + }
+58
package.json
··· 1 + { 2 + "name": "bluesky-house", 3 + "private": true, 4 + "version": "0.0.1", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite dev", 8 + "build": "vite build", 9 + "preview": "vite preview", 10 + "prepare": "svelte-kit sync || echo ''", 11 + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 + "format": "prettier --write .", 14 + "lint": "prettier --check . && eslint ." 15 + }, 16 + "devDependencies": { 17 + "@eslint/compat": "^1.2.5", 18 + "@eslint/js": "^9.18.0", 19 + "@sveltejs/adapter-auto": "^4.0.0", 20 + "@sveltejs/kit": "^2.16.0", 21 + "@sveltejs/vite-plugin-svelte": "^5.0.0", 22 + "@tailwindcss/forms": "^0.5.10", 23 + "@tailwindcss/typography": "^0.5.16", 24 + "@tailwindcss/vite": "^4.0.0", 25 + "eslint": "^9.18.0", 26 + "eslint-config-prettier": "^10.0.1", 27 + "eslint-plugin-svelte": "^3.0.0", 28 + "globals": "^16.0.0", 29 + "prettier": "^3.4.2", 30 + "prettier-plugin-svelte": "^3.3.3", 31 + "prettier-plugin-tailwindcss": "^0.6.11", 32 + "svelte": "^5.0.0", 33 + "svelte-check": "^4.0.0", 34 + "tailwindcss": "^4.0.0", 35 + "typescript": "^5.0.0", 36 + "typescript-eslint": "^8.20.0", 37 + "vite": "^6.0.0", 38 + "vite-plugin-glsl": "^1.3.3" 39 + }, 40 + "dependencies": { 41 + "@atcute/client": "^2.0.8", 42 + "@atcute/oauth-browser-client": "^1.0.15", 43 + "@atproto/api": "^0.14.7", 44 + "@number-flow/svelte": "^0.3.3", 45 + "@sveltejs/adapter-static": "^3.0.8", 46 + "@texel/color": "^1.1.9", 47 + "@threlte/core": "^8.0.1", 48 + "@threlte/extras": "^9.0.1", 49 + "@threlte/studio": "^0.1.3", 50 + "@types/three": "^0.174.0", 51 + "@zerodevx/svelte-img": "^2.1.2", 52 + "bits-ui": "^1.3.6", 53 + "postprocessing": "^6.37.0", 54 + "svelte-tweakpane-ui": "^1.5.5", 55 + "tailwind-variants": "^0.3.1", 56 + "three": "^0.174.0" 57 + } 58 + }
+30
src/app.css
··· 1 + @import 'tailwindcss'; 2 + @plugin '@tailwindcss/forms'; 3 + @plugin '@tailwindcss/typography'; 4 + 5 + 6 + @theme { 7 + --color-base-50: var(--color-stone-50); 8 + --color-base-100: var(--color-stone-100); 9 + --color-base-200: var(--color-stone-200); 10 + --color-base-300: var(--color-stone-300); 11 + --color-base-400: var(--color-stone-400); 12 + --color-base-500: var(--color-stone-500); 13 + --color-base-600: var(--color-stone-600); 14 + --color-base-700: var(--color-stone-700); 15 + --color-base-800: var(--color-stone-800); 16 + --color-base-900: var(--color-stone-900); 17 + --color-base-950: var(--color-stone-950); 18 + 19 + --color-accent-50: var(--color-pink-50); 20 + --color-accent-100: var(--color-pink-100); 21 + --color-accent-200: var(--color-pink-200); 22 + --color-accent-300: var(--color-pink-300); 23 + --color-accent-400: var(--color-pink-400); 24 + --color-accent-500: var(--color-pink-500); 25 + --color-accent-600: var(--color-pink-600); 26 + --color-accent-700: var(--color-pink-700); 27 + --color-accent-800: var(--color-pink-800); 28 + --color-accent-900: var(--color-pink-900); 29 + --color-accent-950: var(--color-pink-950); 30 + }
+13
src/app.d.ts
··· 1 + // See https://svelte.dev/docs/kit/types#app.d.ts 2 + // for information about these interfaces 3 + declare global { 4 + namespace App { 5 + // interface Error {} 6 + // interface Locals {} 7 + // interface PageData {} 8 + // interface PageState {} 9 + // interface Platform {} 10 + } 11 + } 12 + 13 + export {};
+11
src/app.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 + %sveltekit.head% 7 + </head> 8 + <body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-950"> 9 + <div style="display: contents">%sveltekit.body%</div> 10 + </body> 11 + </html>
+110
src/lib/HudScene.svelte
··· 1 + <script lang="ts"> 2 + import { T, useThrelte } from '@threlte/core'; 3 + import { interactivity, useCursor, useViewport } from '@threlte/extras'; 4 + import { ACESFilmicToneMapping } from 'three'; 5 + import { 6 + AllObjects, 7 + applyTransformOfSelected, 8 + editorState, 9 + type RoomObjectKind 10 + } from './state.svelte'; 11 + import { onMount } from 'svelte'; 12 + 13 + interactivity(); 14 + 15 + const { onPointerEnter, onPointerLeave } = useCursor(); 16 + 17 + const viewport = useViewport(); 18 + 19 + const { renderer } = useThrelte(); 20 + 21 + onMount(() => { 22 + renderer.toneMapping = ACESFilmicToneMapping; 23 + renderer.toneMappingExposure = 0.2; 24 + 25 + window.addEventListener('resize', (e) => { 26 + updateShownCount(); 27 + }); 28 + 29 + updateShownCount(); 30 + }); 31 + 32 + function updateShownCount() { 33 + editorState.shownCount = Math.max(Math.floor(window.innerWidth / 200), 3); 34 + } 35 + 36 + let hoveredKey: string | null = $state(null); 37 + 38 + let objectsShown = $derived( 39 + Object.values(AllObjects).slice( 40 + editorState.startIndex, 41 + editorState.startIndex + editorState.shownCount 42 + ) 43 + ); 44 + </script> 45 + 46 + <T.OrthographicCamera makeDefault zoom={80} position={[0, 0, 10]} /> 47 + <T.AmbientLight intensity={Math.PI / 2} /> 48 + 49 + <T.DirectionalLight position={[10, 10, 10]} intensity={3} /> 50 + 51 + {#each objectsShown as Kind, i} 52 + <T.Group 53 + position={[ 54 + $viewport.width * (((i + 0.5) / objectsShown.length - 0.5) * 0.8), 55 + -$viewport.height / 2 + 0.3, 56 + 0 57 + ]} 58 + > 59 + <Kind.component rotation={[0.2, 0.5, 0]} /> 60 + 61 + <T.Mesh 62 + position={[0, 0.2, -10]} 63 + onclick={async () => { 64 + if (editorState.placingObject) { 65 + editorState.placingObject = null; 66 + 67 + // wait 10 ms 68 + await new Promise((resolve) => setTimeout(resolve, 10)); 69 + } 70 + 71 + applyTransformOfSelected(); 72 + 73 + setTimeout(() => { 74 + editorState.selectedObject = null; 75 + }, 100); 76 + 77 + console.log(Object.keys(AllObjects)[i + editorState.startIndex], editorState.placingObject); 78 + 79 + editorState.placingObject = { 80 + kind: Object.keys(AllObjects)[i + editorState.startIndex] as RoomObjectKind, 81 + position: [0, 0, 0], 82 + rotation: 0, 83 + colors: ['#f1f1f1', '#f1f1f1', '#f1f1f1', '#f1f1f1', '#f1f1f1'], 84 + placement: 'floor' 85 + }; 86 + }} 87 + onpointerenter={() => { 88 + hoveredKey = Object.keys(AllObjects)[i + editorState.startIndex]; 89 + 90 + onPointerEnter(); 91 + }} 92 + onpointerleave={() => { 93 + hoveredKey = null; 94 + 95 + onPointerLeave(); 96 + }} 97 + > 98 + <T.PlaneGeometry args={[1.5, 2]} /> 99 + <T.MeshBasicMaterial 100 + opacity={editorState.placingObject?.kind === 101 + Object.keys(AllObjects)[i + editorState.startIndex] 102 + ? 0.5 103 + : hoveredKey === Object.keys(AllObjects)[i + editorState.startIndex] 104 + ? 0.15 105 + : 0} 106 + transparent 107 + /> 108 + </T.Mesh> 109 + </T.Group> 110 + {/each}
+85
src/lib/Outline.svelte
··· 1 + <script lang="ts"> 2 + import { Mesh } from 'three'; 3 + import { useTask, useThrelte } from '@threlte/core'; 4 + import { 5 + BlendFunction, 6 + EffectComposer, 7 + EffectPass, 8 + OutlineEffect, 9 + RenderPass 10 + } from 'postprocessing'; 11 + import { editorState } from './state.svelte'; 12 + 13 + const { scene, renderer, camera, size, autoRender, renderStage } = useThrelte(); 14 + 15 + const composer = new EffectComposer(renderer); 16 + 17 + const renderPass = new RenderPass(scene); 18 + composer.addPass(renderPass); 19 + $effect(() => { 20 + composer.setSize($size.width, $size.height); 21 + }); 22 + 23 + export const outlineEffectOptions: ConstructorParameters<typeof OutlineEffect>[2] = { 24 + blendFunction: BlendFunction.ALPHA, 25 + edgeStrength: 100, 26 + pulseSpeed: 0.0, 27 + visibleEdgeColor: 0xf59e0b, 28 + hiddenEdgeColor: 0xf59e0b, 29 + blur: true 30 + }; 31 + 32 + const outlineEffect = new OutlineEffect(scene, undefined, outlineEffectOptions); 33 + $effect(() => { 34 + console.log('changed', editorState.transformControls?.object); 35 + if (!editorState.transformControls?.object) return; 36 + 37 + setTimeout(() => { 38 + outlineEffect.selection.clear(); 39 + 40 + editorState.transformControls?.object.traverse((child) => { 41 + console.log(child); 42 + if (child instanceof Mesh) { 43 + outlineEffect.selection.add(child); 44 + } 45 + }); 46 + }, 100); 47 + 48 + return () => { 49 + outlineEffect.selection.clear(); 50 + }; 51 + }); 52 + 53 + const outlineEffectPass = new EffectPass(undefined, outlineEffect); 54 + composer.addPass(outlineEffectPass); 55 + 56 + $effect(() => { 57 + renderPass.mainCamera = $camera; 58 + outlineEffect.mainCamera = $camera; 59 + outlineEffectPass.mainCamera = $camera; 60 + }); 61 + 62 + $effect(() => { 63 + return () => { 64 + composer.removeAllPasses(); 65 + outlineEffectPass.dispose(); 66 + renderPass.dispose(); 67 + composer.dispose(); 68 + }; 69 + }); 70 + 71 + $effect(() => { 72 + const last = autoRender.current; 73 + autoRender.set(false); 74 + return () => { 75 + autoRender.set(last); 76 + }; 77 + }); 78 + 79 + useTask( 80 + (delta) => { 81 + composer.render(delta); 82 + }, 83 + { stage: renderStage, autoInvalidate: false } 84 + ); 85 + </script>
+131
src/lib/Room.svelte
··· 1 + <script lang="ts"> 2 + import * as THREE from 'three'; 3 + import { T, type Props } from '@threlte/core'; 4 + import Wall from './Wall.svelte'; 5 + import { applyTransformOfSelected, editorState, roomState, rotateObject } from './state.svelte'; 6 + import RoomObject from './RoomObject.svelte'; 7 + import { Grid, TransformControls } from '@threlte/extras'; 8 + import { onMount } from 'svelte'; 9 + 10 + onMount(() => { 11 + window.addEventListener('keydown', (e) => { 12 + if (e.key === 'd' || e.key === 'ArrowRight') { 13 + rotateObject(-rotation); 14 + } else if (e.key === 'a' || e.key === 'ArrowLeft') { 15 + rotateObject(rotation); 16 + } 17 + }); 18 + }); 19 + 20 + let rotation = Math.PI / 6; 21 + let snap = 0.125 * 0.5; 22 + </script> 23 + 24 + <T.Group position.y={editorState.isEditing ? 0 : -1}> 25 + <!-- walls on left and right --> 26 + {#each Array.from({ length: roomState.size.x }, (_, i) => i) as x} 27 + <Wall position={[-roomState.size.z / 2 - 0.05, 0.9, x - roomState.size.x / 2 + 0.5]} /> 28 + {/each} 29 + 30 + <!-- walls on left and right --> 31 + {#each Array.from({ length: roomState.size.z }, (_, i) => i) as z} 32 + <Wall 33 + position={[z - roomState.size.z / 2 + 0.5, 0.9, -roomState.size.x / 2 - 0.05]} 34 + rotation.y={Math.PI / 2} 35 + /> 36 + {/each} 37 + 38 + {#if editorState.isEditing} 39 + <Grid gridSize={[roomState.size.z * 2, roomState.size.x * 2]} scale={0.5} sectionThickness={0} /> 40 + {/if} 41 + 42 + <T.Mesh 43 + position={[0, -0.0502, 0]} 44 + onpointermove={(e: { point: { x: number; z: number } }) => { 45 + let point = e.point; 46 + 47 + // round to snap point 48 + point.x = Math.round(point.x / snap) * snap; 49 + point.z = Math.round(point.z / snap) * snap; 50 + 51 + if (editorState.placingObject) { 52 + editorState.placingObject.position = [point.x, 0, point.z]; 53 + } 54 + }} 55 + onpointerdown={(e: { point: { x: number; z: number } }) => { 56 + let point = e.point; 57 + 58 + // round to snap point 59 + point.x = Math.round(point.x / snap) * snap; 60 + point.z = Math.round(point.z / snap) * snap; 61 + 62 + if (editorState.placingObject) { 63 + roomState.objects.push(editorState.placingObject); 64 + editorState.placingObject.position = [point.x, 0, point.z]; 65 + editorState.selectedObject = editorState.placingObject; 66 + 67 + editorState.placingObject = null; 68 + } 69 + }} 70 + ondblclick={() => { 71 + if (editorState.selectedObject) { 72 + applyTransformOfSelected(); 73 + 74 + editorState.selectedObject = null; 75 + } 76 + }} 77 + receiveShadow 78 + > 79 + <T.BoxGeometry args={[roomState.size.z, 0.1, roomState.size.x]} /> 80 + <T.MeshStandardMaterial color={roomState.floorColor} /> 81 + </T.Mesh> 82 + 83 + {#each roomState.objects as object} 84 + {#if editorState.selectedObject === object} 85 + <TransformControls 86 + bind:controls={editorState.transformControls} 87 + translationSnap={snap} 88 + rotationSnap={Math.PI / 2} 89 + position={object.position} 90 + rotation={[0, object.rotation, 0]} 91 + enableRotate={false} 92 + showY={false} 93 + onchange={(e) => { 94 + if (!e.target || !e.target.object) return; 95 + 96 + e.target.object.position.clamp( 97 + new THREE.Vector3(-roomState.size.z / 2, 0, -roomState.size.x / 2), 98 + new THREE.Vector3(roomState.size.z / 2, 0, roomState.size.x / 2) 99 + ); 100 + }} 101 + > 102 + <RoomObject kind={object.kind} colors={object.colors} /> 103 + </TransformControls> 104 + {:else} 105 + <T.Group 106 + onclick={() => { 107 + if (!editorState.isEditing) return; 108 + console.log('clicked'); 109 + applyTransformOfSelected(); 110 + 111 + editorState.selectedObject = null; 112 + 113 + setTimeout(() => { 114 + editorState.selectedObject = object; 115 + }, 100); 116 + }} 117 + > 118 + <RoomObject {...object} rotation={[0, object.rotation, 0]} /> 119 + </T.Group> 120 + {/if} 121 + {/each} 122 + 123 + {#if editorState.placingObject} 124 + <RoomObject 125 + {...editorState.placingObject} 126 + rotation={[0, editorState.placingObject.rotation, 0]} 127 + colors={[0xf1f1f1, 0xf1f1f1, 0xf1f1f1, 0xf1f1f1, 0xf1f1f1]} 128 + opacity={0.8} 129 + /> 130 + {/if} 131 + </T.Group>
+29
src/lib/RoomObject.svelte
··· 1 + <script lang="ts"> 2 + import { type Props } from '@threlte/core'; 3 + import type * as THREE from 'three'; 4 + import type { RoomObjectKind } from './state.svelte'; 5 + import { AllObjects } from './state.svelte'; 6 + 7 + let { 8 + kind, 9 + selected, 10 + colors, 11 + opacity, 12 + ...restProps 13 + }: { 14 + kind: RoomObjectKind; 15 + selected?: boolean; 16 + colors?: (number | string)[]; 17 + opacity?: number; 18 + } & Props<THREE.Group> = $props(); 19 + 20 + console.log(kind); 21 + 22 + let ObjectComponent = AllObjects[kind]?.component; 23 + </script> 24 + 25 + {@render object()} 26 + 27 + {#snippet object()} 28 + <ObjectComponent {...restProps} colors={colors} opacity={opacity} /> 29 + {/snippet}
+71
src/lib/Scene.svelte
··· 1 + <script lang="ts"> 2 + import { T, useThrelte } from '@threlte/core'; 3 + import { Environment, HUD, OrbitControls } from '@threlte/extras'; 4 + import { interactivity } from '@threlte/extras'; 5 + interactivity(); 6 + 7 + import Room from './Room.svelte'; 8 + import Outline from './Outline.svelte'; 9 + import HudScene from './HudScene.svelte'; 10 + import { editorState, roomState } from './state.svelte'; 11 + import { onMount } from 'svelte'; 12 + import { ACESFilmicToneMapping } from 'three'; 13 + import { base } from '$app/paths'; 14 + 15 + const { renderer, scene } = useThrelte(); 16 + 17 + let lightIntensity = $state(0.4); 18 + 19 + onMount(() => { 20 + renderer.toneMapping = ACESFilmicToneMapping; 21 + renderer.toneMappingExposure = 0.5; 22 + 23 + let lightModeEnvIntensity = 0.4; 24 + let darkModeEnvIntensity = 0.1; 25 + 26 + let lightModeLightIntensity = 2; 27 + let darkModeLightIntensity = 0.3; 28 + 29 + scene.environmentIntensity = window.matchMedia('(prefers-color-scheme: dark)').matches ? darkModeEnvIntensity : lightModeEnvIntensity; 30 + lightIntensity = window.matchMedia('(prefers-color-scheme: dark)').matches ? darkModeLightIntensity : lightModeLightIntensity; 31 + 32 + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { 33 + scene.environmentIntensity = e.matches ? darkModeEnvIntensity : lightModeEnvIntensity; 34 + lightIntensity = e.matches ? darkModeLightIntensity : lightModeLightIntensity; 35 + }); 36 + }); 37 + </script> 38 + 39 + <T.PerspectiveCamera 40 + makeDefault 41 + position={[4, 4, 4]} 42 + oncreate={(ref) => { 43 + ref.lookAt(0, 0, 0); 44 + }} 45 + > 46 + <OrbitControls 47 + maxDistance={20} 48 + maxPolarAngle={Math.PI / 2} 49 + maxAzimuthAngle={2} 50 + minAzimuthAngle={-1} 51 + /> 52 + </T.PerspectiveCamera> 53 + 54 + {#key roomState.id} 55 + <Room /> 56 + {/key} 57 + 58 + <T.DirectionalLight position={[0, 10, 10]} intensity={lightIntensity} castShadow /> 59 + <T.DirectionalLight position={[-10, 5, 0]} intensity={lightIntensity} castShadow /> 60 + <T.AmbientLight intensity={0.1} /> 61 + 62 + <Outline /> 63 + 64 + {#if editorState.isEditing} 65 + <HUD> 66 + <HudScene /> 67 + </HUD> 68 + {/if} 69 + 70 + 71 + <Environment isBackground={false} url={base + '/env/workshop.jpg'} />
+12
src/lib/Wall.svelte
··· 1 + <script lang="ts"> 2 + import type * as THREE from 'three'; 3 + import { T, type Props } from '@threlte/core'; 4 + import { roomState } from './state.svelte'; 5 + 6 + let { ...restProps }: Props<THREE.Mesh> = $props(); 7 + </script> 8 + 9 + <T.Mesh {...restProps} receiveShadow castShadow> 10 + <T.BoxGeometry args={[0.1, 2, 1]} /> 11 + <T.MeshStandardMaterial color={roomState.wallColor} /> 12 + </T.Mesh>
+39
src/lib/components/3d/depth3d/Depth3D.svelte
··· 1 + <script lang="ts"> 2 + import { Canvas } from '@threlte/core'; 3 + import Scene, { type Props } from './Scene.svelte'; 4 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 5 + import type { HTMLAttributes } from 'svelte/elements'; 6 + import { cn } from '$lib/utils'; 7 + 8 + const { 9 + class: className, 10 + sceneProps, 11 + ...restProps 12 + }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 13 + sceneProps: Props; 14 + } = $props(); 15 + </script> 16 + 17 + <div id="depth3d" class={cn('h-80 w-80', className)} {...restProps}> 18 + <Canvas> 19 + <Scene {...sceneProps} /> 20 + </Canvas> 21 + </div> 22 + 23 + <!-- no js fallback --> 24 + <noscript> 25 + <style> 26 + #depth3d { 27 + display: none; 28 + } 29 + </style> 30 + 31 + <img 32 + src={sceneProps.image.image} 33 + alt="" 34 + class={cn( 35 + 'h-80 w-80 rounded-2xl object-cover', 36 + className 37 + )} 38 + /> 39 + </noscript>
+156
src/lib/components/3d/depth3d/Scene.svelte
··· 1 + <script lang="ts" module> 2 + export type Props = { 3 + image: { 4 + image: string; 5 + depth: string; 6 + }; 7 + rounded?: number; 8 + cameraPosition?: [number, number, number]; 9 + rotationScale?: number; 10 + rotationSpeed?: number; 11 + detail?: number; 12 + depthScale?: number; 13 + mouseMovement?: boolean; 14 + }; 15 + </script> 16 + 17 + <script lang="ts"> 18 + import { T, useTask } from '@threlte/core'; 19 + import { useTexture, Align } from '@threlte/extras'; 20 + import { Spring } from 'svelte/motion'; 21 + 22 + import { Vector2, ShaderMaterial, PlaneGeometry, LinearSRGBColorSpace } from 'three'; 23 + 24 + const { 25 + image, 26 + rounded = 0.2, 27 + cameraPosition = [0, 0, 10], 28 + rotationScale = 0.2, 29 + rotationSpeed = 2, 30 + detail = 200, 31 + depthScale = 1.5, 32 + mouseMovement = true 33 + }: Props = $props(); 34 + 35 + const rotation = new Vector2(0.5, 0.5); 36 + 37 + const geometry = new PlaneGeometry(7, 7, detail, detail); 38 + 39 + let rotationX = new Spring(0); 40 + let rotationY = new Spring(0); 41 + 42 + let time = 0; 43 + let mouseMoved = -1; 44 + 45 + const map = useTexture(image.image, { 46 + transform: (texture) => { 47 + texture.colorSpace = LinearSRGBColorSpace; 48 + return texture; 49 + } 50 + }); 51 + const depthMap = useTexture(image.depth, { 52 + transform: (texture) => { 53 + return texture; 54 + } 55 + }); 56 + 57 + const uniforms = { 58 + uTexture: { type: 't', value: map }, 59 + depthMap: { type: 't', value: depthMap } 60 + }; 61 + const material = new ShaderMaterial({ 62 + uniforms: uniforms, 63 + vertexShader: ` 64 + varying vec2 vUv; 65 + uniform sampler2D depthMap; 66 + 67 + void main() { 68 + vUv = uv; 69 + // move z position based on the depth map 70 + float depth = texture2D(depthMap, vUv).r; 71 + vec3 newPosition = position + vec3(0.0, 0.0, depth * ${depthScale.toFixed(1)}); 72 + gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); 73 + }`, 74 + fragmentShader: ` 75 + uniform sampler2D uTexture; 76 + varying vec2 vUv; 77 + 78 + float sdRoundedRect(vec2 p, vec2 b, float r) { 79 + vec2 q = abs(p) - b + vec2(r); 80 + return length(max(q, 0.0)) - r; 81 + } 82 + ${ 83 + rounded > 0.01 84 + ? ` 85 + void main() { 86 + vec2 uv = (vUv * 2.0) - 1.0; 87 + 88 + // Size of the rectangle (half-size) 89 + vec2 rectSize = vec2(1, 1); 90 + 91 + // Calculate distance to the edge of the rounded rectangle 92 + float d = sdRoundedRect(uv, rectSize, ${rounded.toFixed(4)}); 93 + 94 + // Smooth transition for anti-aliasing 95 + float aa = fwidth(d); 96 + float alpha = smoothstep(0.0, aa, -d); 97 + 98 + gl_FragColor = texture2D(uTexture, vUv) * alpha; 99 + } 100 + ` 101 + : ` 102 + 103 + void main() { 104 + vec2 uv = (vUv * 2.0) - 1.0; 105 + gl_FragColor = texture2D(uTexture, vUv); 106 + }` 107 + } 108 + ` 109 + }); 110 + 111 + useTask((dt) => { 112 + if (mouseMoved > 0) { 113 + mouseMoved -= dt; 114 + } else { 115 + time += dt * rotationSpeed; 116 + rotationX.set(Math.sin(time) * 0.5); 117 + rotationY.set(Math.cos(time) * 0.5); 118 + } 119 + }); 120 + 121 + function onDocumentMouseMove(event: MouseEvent) { 122 + if (!mouseMovement) return; 123 + 124 + // convert to [-0.5, 0.5] 125 + rotationX.set((event.clientX / window.innerWidth - 0.5) * 2); 126 + rotationY.set((event.clientY / window.innerHeight - 0.5) * 2); 127 + 128 + mouseMoved = 1; 129 + } 130 + </script> 131 + 132 + <svelte:window onmousemove={onDocumentMouseMove} /> 133 + 134 + <T.PerspectiveCamera makeDefault position={cameraPosition}></T.PerspectiveCamera> 135 + 136 + {#await map then mapValue} 137 + {#await depthMap then depthValue} 138 + <Align> 139 + <T.Mesh 140 + rotation.x={rotationY.current * rotationScale} 141 + rotation.y={rotationX.current * rotationScale} 142 + scale.x={mapValue.image.width / mapValue.image.height} 143 + > 144 + <T is={geometry} /> 145 + <T 146 + is={material} 147 + uniforms={{ 148 + depthMap: { value: depthValue }, 149 + uTexture: { value: mapValue }, 150 + mouse: { value: rotation } 151 + }} 152 + /> 153 + </T.Mesh> 154 + </Align> 155 + {/await} 156 + {/await}
+2
src/lib/components/3d/depth3d/index.ts
··· 1 + export { default as Depth3D } from './Depth3D.svelte'; 2 + export { default } from './Depth3D.svelte';
+180
src/lib/components/3d/voxel-art/VoxelArt.svelte
··· 1 + <script lang="ts"> 2 + // from https://tympanus.net/codrops/2025/03/03/css-meets-voxel-art-building-a-rendering-engine-with-stacked-grids/ 3 + // editor: https://voxels.layoutit.com/ 4 + 5 + interface VoxelArtData { 6 + voxels: { 7 + x: number; 8 + y: number; 9 + z: number; 10 + color: string; 11 + }[]; 12 + } 13 + 14 + let { 15 + rows = undefined, 16 + columns = undefined, 17 + stacks = undefined, 18 + cubeSize = 20, 19 + viewingAngle = 65, 20 + colorMap = { 21 + '050505': 'var(--color-accent-50)', 22 + '101010': 'var(--color-accent-100)', 23 + '202020': 'var(--color-accent-200)', 24 + '303030': 'var(--color-accent-300)', 25 + '404040': 'var(--color-accent-400)', 26 + '505050': 'var(--color-accent-500)', 27 + '606060': 'var(--color-accent-600)', 28 + '707070': 'var(--color-accent-700)', 29 + '808080': 'var(--color-accent-800)', 30 + '909090': 'var(--color-accent-900)', 31 + '959595': 'var(--color-accent-950)' 32 + }, 33 + data 34 + }: { 35 + rows?: number; 36 + columns?: number; 37 + stacks?: number; 38 + cubeSize?: number; 39 + viewingAngle?: number; 40 + colorMap?: Record<string, string>; 41 + data: VoxelArtData; 42 + } = $props(); 43 + 44 + let maxX = Math.max(...data.voxels.map((voxel) => voxel.x)); 45 + let maxY = Math.max(...data.voxels.map((voxel) => voxel.y)); 46 + let maxZ = Math.max(...data.voxels.map((voxel) => voxel.z)); 47 + 48 + if (rows === undefined) { 49 + rows = maxX; 50 + } 51 + if (columns === undefined) { 52 + columns = maxY; 53 + } 54 + if (stacks === undefined) { 55 + stacks = maxZ + 1; 56 + } 57 + if (cubeSize === undefined) { 58 + cubeSize = 20; 59 + } 60 + </script> 61 + 62 + <div 63 + class="scene" 64 + style="--columns: {columns}; --rows: {rows}; --stacks: {stacks}; --cube-size: {cubeSize}px; --viewing-angle: {viewingAngle}deg;" 65 + > 66 + <div class="floor"> 67 + {#each Array(stacks) as _, i} 68 + <div class="z" style="transform: translateZ({i * cubeSize - stacks * cubeSize}px);"> 69 + {#each data.voxels as voxel} 70 + {#if voxel.z === i} 71 + {@render cube( 72 + `grid-area: ${voxel.x} / ${voxel.y} / ${voxel.x + 1} / ${voxel.y + 1}; color: ${colorMap?.[voxel.color] ?? '#' + voxel.color};` 73 + )} 74 + {/if} 75 + {/each} 76 + </div> 77 + {/each} 78 + </div> 79 + </div> 80 + 81 + {#snippet cube(style: string)} 82 + <div class="cube" {style}> 83 + {#if viewingAngle < 90} 84 + <div class="face top"></div> 85 + {/if} 86 + 87 + <div class="face frontRight"></div> 88 + <div class="face frontLeft"></div> 89 + <div class="face backLeft"></div> 90 + <div class="face backRight"></div> 91 + 92 + {#if viewingAngle > 90} 93 + <div class="face bottom"></div> 94 + {/if} 95 + </div> 96 + {/snippet} 97 + 98 + <style> 99 + .scene { 100 + perspective: 8000px; 101 + min-height: calc((var(--stacks) + 5) * var(--cube-size)); 102 + } 103 + 104 + .scene * { 105 + transform-style: preserve-3d; 106 + } 107 + 108 + @keyframes spin { 109 + 0% { 110 + transform: rotateX(var(--viewing-angle)) rotate(0deg); 111 + } 112 + 100% { 113 + transform: rotateX(var(--viewing-angle)) rotate(360deg); 114 + } 115 + } 116 + 117 + .floor { 118 + transform: rotateX(var(--viewing-angle)) rotate(45deg); 119 + width: calc(var(--columns) * var(--cube-size)); 120 + height: calc(var(--rows) * var(--cube-size)); 121 + animation: spin 5s linear infinite; 122 + } 123 + 124 + .cube { 125 + position: relative; 126 + transform: translateZ(calc(var(--cube-size) / 2)); 127 + } 128 + 129 + .face { 130 + position: absolute; 131 + background: currentColor; 132 + inset: 0; 133 + } 134 + .face:after { 135 + content: ''; 136 + display: block; 137 + position: absolute; 138 + inset: 0; 139 + } 140 + .face.frontRight:after { 141 + background: rgba(0, 0, 0, 0.1); 142 + } 143 + .face.frontLeft:after { 144 + background: rgba(0, 0, 0, 0.15); 145 + } 146 + .face.backLeft:after { 147 + background: rgba(0, 0, 0, 0.2); 148 + } 149 + .face.backRight:after { 150 + background: rgba(0, 0, 0, 0.25); 151 + } 152 + 153 + .face.top { 154 + transform: translateZ(calc(var(--cube-size) / 2)); 155 + } 156 + .face.frontRight { 157 + transform: rotateY(90deg) translateZ(calc(var(--cube-size) / 2)); 158 + } 159 + .face.frontLeft { 160 + transform: rotateX(90deg) translateZ(calc(var(--cube-size) / -2)); 161 + } 162 + 163 + .face.bottom { 164 + transform: translateZ(calc(var(--cube-size) / -2)); 165 + } 166 + .face.backLeft { 167 + transform: rotateY(90deg) translateZ(calc(var(--cube-size) / -2)); 168 + } 169 + .face.backRight { 170 + transform: rotateX(90deg) translateZ(calc(var(--cube-size) / 2)); 171 + } 172 + 173 + .z { 174 + display: grid; 175 + grid-template-columns: repeat(var(--columns), var(--cube-size)); 176 + grid-template-rows: repeat(var(--rows), var(--cube-size)); 177 + position: absolute; 178 + inset: 0; 179 + } 180 + </style>
+78
src/lib/components/audio/audio-visualizations/BarVisualizer.svelte
··· 1 + <script lang="ts"> 2 + import { ThemeWatcher } from '$lib/helper/ThemeWatcher.svelte'; 3 + import { cn } from '$lib/utils'; 4 + import { onMount } from 'svelte'; 5 + 6 + let { 7 + values, 8 + barWidth = 0, 9 + barSpacing = 4, 10 + center = true, 11 + class: className, 12 + }: { 13 + values: Float32Array; 14 + barWidth?: number; 15 + barSpacing?: number; 16 + center?: boolean; 17 + class?: string; 18 + } = $props(); 19 + 20 + let canvas: HTMLCanvasElement | undefined = $state(); 21 + let contentRect: DOMRectReadOnly | undefined = $state(); 22 + 23 + onMount(() => {}); 24 + 25 + $effect(() => { 26 + if (!canvas || !contentRect) return; 27 + 28 + const ctx = canvas.getContext('2d'); 29 + 30 + if (!ctx) return; 31 + 32 + 33 + // Update canvas size 34 + canvas.width = Math.round(contentRect.width) * 2; 35 + canvas.height = Math.round(contentRect.height) * 2; 36 + 37 + // Clear canvas 38 + ctx.clearRect(0, 0, canvas.width, canvas.height); 39 + 40 + let width = barWidth; 41 + let dets = values.length; 42 + 43 + if (!dets) { 44 + dets = Math.floor((canvas.width - barSpacing) / (Math.max(barWidth, 1) + barSpacing)); 45 + } 46 + if (!barWidth) { 47 + width = (canvas.width - barSpacing) / dets - barSpacing; 48 + } 49 + 50 + let theme = new ThemeWatcher(); 51 + 52 + const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0); 53 + 54 + gradient.addColorStop(0, theme.getCSSVar('--color-accent-400')); 55 + gradient.addColorStop(0.5, theme.getCSSVar('--color-accent-600')); 56 + gradient.addColorStop(1, theme.getCSSVar('--color-accent-700')); 57 + 58 + ctx.save(); 59 + 60 + ctx.beginPath(); 61 + // Loop over each value/bar 62 + for (let i = 0; i < values.length; i++) { 63 + const amplitude = Math.abs(values[i]); 64 + const height = Math.max(1, amplitude * canvas.height); 65 + const x = barSpacing + i * (width + barSpacing); 66 + const y = center ? (canvas.height - height) / 2 : canvas.height - height; 67 + 68 + ctx.roundRect(x, y, width, height, width); 69 + } 70 + ctx.clip(); 71 + ctx.fillStyle = gradient; 72 + ctx.fillRect(0, 0, canvas.width, canvas.height); 73 + 74 + console.log('redraw'); 75 + }); 76 + </script> 77 + 78 + <canvas bind:this={canvas} bind:contentRect class={cn('w-full h-full', className)}></canvas>
+29
src/lib/components/audio/audio-visualizations/MicrophoneVisualizer.svelte
··· 1 + <script lang="ts"> 2 + let { 3 + value = 0, 4 + class: className, 5 + }: { 6 + value: number; 7 + class?: string; 8 + } = $props(); 9 + </script> 10 + 11 + <svg viewBox="0 0 24 24" class={className} stroke-width="0.5"> 12 + <mask id="microphone-mask-vertical"> 13 + <rect x="0" y="0" width="24" height="24" fill="white" /> 14 + <rect x="0" y="0" width="24" height={(1 - value) * 17} fill="black" /> 15 + </mask> 16 + <path 17 + d="M8.25 4.5a3.75 3.75 0 1 1 7.5 0v8.25a3.75 3.75 0 1 1-7.5 0V4.5Z" 18 + class="fill-accent-200 dark:fill-accent-950 stroke-accent-300 dark:stroke-accent-900" 19 + /> 20 + <path 21 + mask="url(#microphone-mask-vertical)" 22 + d="M8.25 4.5a3.75 3.75 0 1 1 7.5 0v8.25a3.75 3.75 0 1 1-7.5 0V4.5Z" 23 + class="fill-accent-500" 24 + /> 25 + <path 26 + d="M6 10.5a.75.75 0 0 1 .75.75v1.5a5.25 5.25 0 1 0 10.5 0v-1.5a.75.75 0 0 1 1.5 0v1.5a6.751 6.751 0 0 1-6 6.709v2.291h3a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3v-2.291a6.751 6.751 0 0 1-6-6.709v-1.5A.75.75 0 0 1 6 10.5Z" 27 + class="fill-accent-600 dark:fill-accent-700" 28 + /> 29 + </svg>
+38
src/lib/components/audio/audio-visualizations/SpeakerVisualizer.svelte
··· 1 + <script lang="ts"> 2 + let { 3 + value = 0, 4 + class: className, 5 + }: { 6 + value: number; 7 + class?: string; 8 + } = $props(); 9 + </script> 10 + 11 + <svg viewBox="0 0 24 24" class={className} stroke-width="0.5"> 12 + <mask id="speaker-mask-horizontal"> 13 + <rect x="0" y="0" width="24" height="24" fill="white" /> 14 + <rect x={15 + value * 9} y="0" width={12} height={24} fill="black" /> 15 + </mask> 16 + <path 17 + d="M13.5 4.06C13.5 2.724 11.884 2.055 10.94 3L6.44 7.5H4.508C3.367 7.5 2.19 8.164 1.848 9.405C1.61598 10.2504 1.49892 11.1233 1.5 12C1.5 12.898 1.621 13.768 1.85 14.595C2.191 15.835 3.368 16.5 4.509 16.5H6.439L10.939 21C11.884 21.945 13.5 21.276 13.5 19.94V4.06Z" 18 + class="fill-accent-600 dark:fill-accent-700" 19 + /> 20 + <path 21 + d="M15.932 7.757C16.0017 7.6873 16.0844 7.63202 16.1754 7.5943C16.2664 7.55657 16.364 7.53716 16.4625 7.53716C16.561 7.53716 16.6586 7.55657 16.7496 7.5943C16.8406 7.63202 16.9233 7.6873 16.993 7.757C17.5503 8.31416 17.9923 8.97564 18.2939 9.70366C18.5955 10.4317 18.7507 11.212 18.7507 12C18.7507 12.788 18.5955 13.5683 18.2939 14.2963C17.9923 15.0244 17.5503 15.6858 16.993 16.243C16.8515 16.3796 16.662 16.4551 16.4653 16.4533C16.2687 16.4515 16.0806 16.3725 15.9416 16.2334C15.8026 16.0942 15.7239 15.9061 15.7222 15.7095C15.7206 15.5128 15.7963 15.3234 15.933 15.182C16.3509 14.7641 16.6823 14.2681 16.9085 13.7221C17.1346 13.1761 17.251 12.591 17.251 12C17.251 11.409 17.1346 10.8239 16.9085 10.2779C16.6823 9.73194 16.3509 9.23586 15.933 8.818C15.7925 8.67737 15.7137 8.48675 15.7137 8.288C15.7137 8.08925 15.7925 7.89862 15.933 7.758L15.932 7.757Z" 22 + class="fill-accent-200 dark:fill-accent-950 stroke-accent-300 dark:stroke-accent-900" 23 + /> 24 + <path 25 + d="M19.114 4.88666C18.9152 4.88666 18.7246 4.96555 18.584 5.106C18.4435 5.24663 18.3647 5.43725 18.3647 5.636C18.3647 5.83475 18.4435 6.02538 18.584 6.166C19.3502 6.9321 19.958 7.84162 20.3726 8.84262C20.7873 9.84363 21.0007 10.9165 21.0007 12C21.0007 13.0835 20.7873 14.1564 20.3726 15.1574C19.958 16.1584 19.3502 17.0679 18.584 17.834C18.4515 17.9762 18.3794 18.1642 18.3828 18.3585C18.3863 18.5528 18.465 18.7382 18.6024 18.8756C18.7398 19.013 18.9252 19.0917 19.1195 19.0952C19.3138 19.0986 19.5018 19.0265 19.644 18.894C23.452 15.086 23.452 8.913 19.644 5.106C19.5034 4.96555 19.3128 4.88666 19.114 4.88666Z" 26 + class="fill-accent-200 dark:fill-accent-950 stroke-accent-300 dark:stroke-accent-900" 27 + /> 28 + <path 29 + d="M15.932 7.757C16.0017 7.6873 16.0844 7.63202 16.1754 7.5943C16.2664 7.55657 16.364 7.53716 16.4625 7.53716C16.561 7.53716 16.6586 7.55657 16.7496 7.5943C16.8406 7.63202 16.9233 7.6873 16.993 7.757C17.5503 8.31416 17.9923 8.97564 18.2939 9.70366C18.5955 10.4317 18.7507 11.212 18.7507 12C18.7507 12.788 18.5955 13.5683 18.2939 14.2963C17.9923 15.0244 17.5503 15.6858 16.993 16.243C16.8515 16.3796 16.662 16.4551 16.4653 16.4533C16.2687 16.4515 16.0806 16.3725 15.9416 16.2334C15.8026 16.0942 15.7239 15.9061 15.7222 15.7095C15.7206 15.5128 15.7963 15.3234 15.933 15.182C16.3509 14.7641 16.6823 14.2681 16.9085 13.7221C17.1346 13.1761 17.251 12.591 17.251 12C17.251 11.409 17.1346 10.8239 16.9085 10.2779C16.6823 9.73194 16.3509 9.23586 15.933 8.818C15.7925 8.67737 15.7137 8.48675 15.7137 8.288C15.7137 8.08925 15.7925 7.89862 15.933 7.758L15.932 7.757Z" 30 + class="fill-accent-500" 31 + mask="url(#speaker-mask-horizontal)" 32 + /> 33 + <path 34 + d="M19.114 4.88666C18.9152 4.88666 18.7246 4.96555 18.584 5.106C18.4435 5.24663 18.3647 5.43725 18.3647 5.636C18.3647 5.83475 18.4435 6.02538 18.584 6.166C19.3502 6.9321 19.958 7.84162 20.3726 8.84262C20.7873 9.84363 21.0007 10.9165 21.0007 12C21.0007 13.0835 20.7873 14.1564 20.3726 15.1574C19.958 16.1584 19.3502 17.0679 18.584 17.834C18.4515 17.9762 18.3794 18.1642 18.3828 18.3585C18.3863 18.5528 18.465 18.7382 18.6024 18.8756C18.7398 19.013 18.9252 19.0917 19.1195 19.0952C19.3138 19.0986 19.5018 19.0265 19.644 18.894C23.452 15.086 23.452 8.913 19.644 5.106C19.5034 4.96555 19.3128 4.88666 19.114 4.88666Z" 35 + class="fill-accent-500" 36 + mask="url(#speaker-mask-horizontal)" 37 + /> 38 + </svg>
+203
src/lib/components/audio/audio-visualizations/analysis/audio_analysis.js
··· 1 + import { 2 + noteFrequencies, 3 + noteFrequencyLabels, 4 + voiceFrequencies, 5 + voiceFrequencyLabels, 6 + } from './constants.js'; 7 + 8 + /** 9 + * Output of AudioAnalysis for the frequency domain of the audio 10 + * @typedef {Object} AudioAnalysisOutputType 11 + * @property {Float32Array} values Amplitude of this frequency between {0, 1} inclusive 12 + * @property {number[]} frequencies Raw frequency bucket values 13 + * @property {string[]} labels Labels for the frequency bucket values 14 + */ 15 + 16 + /** 17 + * Analyzes audio for visual output 18 + * @class 19 + */ 20 + export class AudioAnalysis { 21 + /** 22 + * Retrieves frequency domain data from an AnalyserNode adjusted to a decibel range 23 + * returns human-readable formatting and labels 24 + * @param {AnalyserNode} analyser 25 + * @param {number} sampleRate 26 + * @param {Float32Array} [fftResult] 27 + * @param {"frequency"|"music"|"voice"} [analysisType] 28 + * @param {number} [minDecibels] default -100 29 + * @param {number} [maxDecibels] default -30 30 + * @returns {AudioAnalysisOutputType} 31 + */ 32 + static getFrequencies( 33 + analyser, 34 + sampleRate, 35 + fftResult, 36 + analysisType = 'frequency', 37 + minDecibels = -100, 38 + maxDecibels = -30, 39 + ) { 40 + if (!fftResult) { 41 + fftResult = new Float32Array(analyser.frequencyBinCount); 42 + analyser.getFloatFrequencyData(fftResult); 43 + } 44 + const nyquistFrequency = sampleRate / 2; 45 + const frequencyStep = (1 / fftResult.length) * nyquistFrequency; 46 + let outputValues; 47 + let frequencies; 48 + let labels; 49 + if (analysisType === 'music' || analysisType === 'voice') { 50 + const useFrequencies = 51 + analysisType === 'voice' ? voiceFrequencies : noteFrequencies; 52 + const aggregateOutput = Array(useFrequencies.length).fill(minDecibels); 53 + for (let i = 0; i < fftResult.length; i++) { 54 + const frequency = i * frequencyStep; 55 + const amplitude = fftResult[i]; 56 + for (let n = useFrequencies.length - 1; n >= 0; n--) { 57 + if (frequency > useFrequencies[n]) { 58 + aggregateOutput[n] = Math.max(aggregateOutput[n], amplitude); 59 + break; 60 + } 61 + } 62 + } 63 + outputValues = aggregateOutput; 64 + frequencies = 65 + analysisType === 'voice' ? voiceFrequencies : noteFrequencies; 66 + labels = 67 + analysisType === 'voice' ? voiceFrequencyLabels : noteFrequencyLabels; 68 + } else { 69 + outputValues = Array.from(fftResult); 70 + frequencies = outputValues.map((_, i) => frequencyStep * i); 71 + labels = frequencies.map((f) => `${f.toFixed(2)} Hz`); 72 + } 73 + // We normalize to {0, 1} 74 + const normalizedOutput = outputValues.map((v) => { 75 + return Math.max( 76 + 0, 77 + Math.min((v - minDecibels) / (maxDecibels - minDecibels), 1), 78 + ); 79 + }); 80 + const values = new Float32Array(normalizedOutput); 81 + return { 82 + values, 83 + frequencies, 84 + labels, 85 + }; 86 + } 87 + 88 + /** 89 + * Creates a new AudioAnalysis instance for an HTMLAudioElement 90 + * @param {HTMLAudioElement} audioElement 91 + * @param {AudioBuffer|null} [audioBuffer] If provided, will cache all frequency domain data from the buffer 92 + * @returns {AudioAnalysis} 93 + */ 94 + constructor(audioElement, audioBuffer = null) { 95 + this.fftResults = []; 96 + if (audioBuffer) { 97 + /** 98 + * Modified from 99 + * https://stackoverflow.com/questions/75063715/using-the-web-audio-api-to-analyze-a-song-without-playing 100 + * 101 + * We do this to populate FFT values for the audio if provided an `audioBuffer` 102 + * The reason to do this is that Safari fails when using `createMediaElementSource` 103 + * This has a non-zero RAM cost so we only opt-in to run it on Safari, Chrome is better 104 + */ 105 + const { length, sampleRate } = audioBuffer; 106 + const offlineAudioContext = new OfflineAudioContext({ 107 + length, 108 + sampleRate, 109 + }); 110 + const source = offlineAudioContext.createBufferSource(); 111 + source.buffer = audioBuffer; 112 + const analyser = offlineAudioContext.createAnalyser(); 113 + analyser.fftSize = 8192; 114 + analyser.smoothingTimeConstant = 0.1; 115 + source.connect(analyser); 116 + // limit is :: 128 / sampleRate; 117 + // but we just want 60fps - cuts ~1s from 6MB to 1MB of RAM 118 + const renderQuantumInSeconds = 1 / 60; 119 + const durationInSeconds = length / sampleRate; 120 + const analyze = (index) => { 121 + const suspendTime = renderQuantumInSeconds * index; 122 + if (suspendTime < durationInSeconds) { 123 + offlineAudioContext.suspend(suspendTime).then(() => { 124 + const fftResult = new Float32Array(analyser.frequencyBinCount); 125 + analyser.getFloatFrequencyData(fftResult); 126 + this.fftResults.push(fftResult); 127 + analyze(index + 1); 128 + }); 129 + } 130 + if (index === 1) { 131 + offlineAudioContext.startRendering(); 132 + } else { 133 + offlineAudioContext.resume(); 134 + } 135 + }; 136 + source.start(0); 137 + analyze(1); 138 + this.audio = audioElement; 139 + this.context = offlineAudioContext; 140 + this.analyser = analyser; 141 + this.sampleRate = sampleRate; 142 + this.audioBuffer = audioBuffer; 143 + } else { 144 + const audioContext = new AudioContext(); 145 + const track = audioContext.createMediaElementSource(audioElement); 146 + const analyser = audioContext.createAnalyser(); 147 + analyser.fftSize = 8192; 148 + analyser.smoothingTimeConstant = 0.1; 149 + track.connect(analyser); 150 + analyser.connect(audioContext.destination); 151 + this.audio = audioElement; 152 + this.context = audioContext; 153 + this.analyser = analyser; 154 + this.sampleRate = this.context.sampleRate; 155 + this.audioBuffer = null; 156 + } 157 + } 158 + 159 + /** 160 + * Gets the current frequency domain data from the playing audio track 161 + * @param {"frequency"|"music"|"voice"} [analysisType] 162 + * @param {number} [minDecibels] default -100 163 + * @param {number} [maxDecibels] default -30 164 + * @returns {AudioAnalysisOutputType} 165 + */ 166 + getFrequencies( 167 + analysisType = 'frequency', 168 + minDecibels = -100, 169 + maxDecibels = -30, 170 + ) { 171 + let fftResult = null; 172 + if (this.audioBuffer && this.fftResults.length) { 173 + const pct = this.audio.currentTime / this.audio.duration; 174 + const index = Math.min( 175 + (pct * this.fftResults.length) | 0, 176 + this.fftResults.length - 1, 177 + ); 178 + fftResult = this.fftResults[index]; 179 + } 180 + return AudioAnalysis.getFrequencies( 181 + this.analyser, 182 + this.sampleRate, 183 + fftResult, 184 + analysisType, 185 + minDecibels, 186 + maxDecibels, 187 + ); 188 + } 189 + 190 + /** 191 + * Resume the internal AudioContext if it was suspended due to the lack of 192 + * user interaction when the AudioAnalysis was instantiated. 193 + * @returns {Promise<true>} 194 + */ 195 + async resumeIfSuspended() { 196 + if (this.context.state === 'suspended') { 197 + await this.context.resume(); 198 + } 199 + return true; 200 + } 201 + } 202 + 203 + globalThis.AudioAnalysis = AudioAnalysis;
+60
src/lib/components/audio/audio-visualizations/analysis/constants.js
··· 1 + /** 2 + * Constants for help with visualization 3 + * Helps map frequency ranges from Fast Fourier Transform 4 + * to human-interpretable ranges, notably music ranges and 5 + * human vocal ranges. 6 + */ 7 + 8 + // Eighth octave frequencies 9 + const octave8Frequencies = [ 10 + 4186.01, 4434.92, 4698.63, 4978.03, 5274.04, 5587.65, 5919.91, 6271.93, 11 + 6644.88, 7040.0, 7458.62, 7902.13, 12 + ]; 13 + 14 + // Labels for each of the above frequencies 15 + const octave8FrequencyLabels = [ 16 + 'C', 17 + 'C#', 18 + 'D', 19 + 'D#', 20 + 'E', 21 + 'F', 22 + 'F#', 23 + 'G', 24 + 'G#', 25 + 'A', 26 + 'A#', 27 + 'B', 28 + ]; 29 + 30 + /** 31 + * All note frequencies from 1st to 8th octave 32 + * in format "A#8" (A#, 8th octave) 33 + */ 34 + export const noteFrequencies = []; 35 + export const noteFrequencyLabels = []; 36 + for (let i = 1; i <= 8; i++) { 37 + for (let f = 0; f < octave8Frequencies.length; f++) { 38 + const freq = octave8Frequencies[f]; 39 + noteFrequencies.push(freq / Math.pow(2, 8 - i)); 40 + noteFrequencyLabels.push(octave8FrequencyLabels[f] + i); 41 + } 42 + } 43 + 44 + /** 45 + * Subset of the note frequencies between 32 and 2000 Hz 46 + * 6 octave range: C1 to B6 47 + */ 48 + const voiceFrequencyRange = [32.0, 2000.0]; 49 + export const voiceFrequencies = noteFrequencies.filter((_, i) => { 50 + return ( 51 + noteFrequencies[i] > voiceFrequencyRange[0] && 52 + noteFrequencies[i] < voiceFrequencyRange[1] 53 + ); 54 + }); 55 + export const voiceFrequencyLabels = noteFrequencyLabels.filter((_, i) => { 56 + return ( 57 + noteFrequencies[i] > voiceFrequencyRange[0] && 58 + noteFrequencies[i] < voiceFrequencyRange[1] 59 + ); 60 + });
+63
src/lib/components/audio/audio-visualizations/analysis/utils.ts
··· 1 + const dataMap = new WeakMap(); 2 + 3 + /** 4 + * Normalizes a Float32Array to Array(m): We use this to draw amplitudes on a graph 5 + * If we're rendering the same audio data, then we'll often be using 6 + * the same (data, m, downsamplePeaks) triplets so we give option to memoize 7 + */ 8 + export const normalizeArray = ( 9 + data: Float32Array, 10 + m: number, 11 + downsamplePeaks: boolean = false, 12 + memoize: boolean = false 13 + ) => { 14 + let cache, mKey, dKey; 15 + if (memoize) { 16 + mKey = m.toString(); 17 + dKey = downsamplePeaks.toString(); 18 + cache = dataMap.has(data) ? dataMap.get(data) : {}; 19 + dataMap.set(data, cache); 20 + cache[mKey] = cache[mKey] || {}; 21 + if (cache[mKey][dKey]) { 22 + return cache[mKey][dKey]; 23 + } 24 + } 25 + const n = data.length; 26 + const result = new Array(m); 27 + if (m <= n) { 28 + // Downsampling 29 + result.fill(0); 30 + const count = new Array(m).fill(0); 31 + for (let i = 0; i < n; i++) { 32 + const index = Math.floor(i * (m / n)); 33 + if (downsamplePeaks) { 34 + // take highest result in the set 35 + result[index] = Math.max(result[index], Math.abs(data[i])); 36 + } else { 37 + result[index] += Math.abs(data[i]); 38 + } 39 + count[index]++; 40 + } 41 + if (!downsamplePeaks) { 42 + for (let i = 0; i < result.length; i++) { 43 + result[i] = result[i] / count[i]; 44 + } 45 + } 46 + } else { 47 + for (let i = 0; i < m; i++) { 48 + const index = (i * (n - 1)) / (m - 1); 49 + const low = Math.floor(index); 50 + const high = Math.ceil(index); 51 + const t = index - low; 52 + if (high >= n) { 53 + result[i] = data[n - 1]; 54 + } else { 55 + result[i] = data[low] * (1 - t) + data[high] * t; 56 + } 57 + } 58 + } 59 + if (memoize) { 60 + cache[mKey as string][dKey as string] = result; 61 + } 62 + return result; 63 + };
+7
src/lib/components/audio/audio-visualizations/index.ts
··· 1 + export { default as BarVisualizer } from './BarVisualizer.svelte'; 2 + export { default as MicrophoneVisualizer } from './MicrophoneVisualizer.svelte'; 3 + export { default as SpeakerVisualizer } from './SpeakerVisualizer.svelte'; 4 + 5 + export { normalizeArray } from './analysis/utils'; 6 + 7 + export { AudioAnalysis } from './analysis/audio_analysis';
+167
src/lib/components/audio/waveform-audio-player/WaveformAudioPlayer.svelte
··· 1 + <script lang="ts"> 2 + import NumberFlow, { NumberFlowGroup } from '@number-flow/svelte'; 3 + import { onMount } from 'svelte'; 4 + import WaveSurfer from 'wavesurfer.js'; 5 + 6 + import { ThemeWatcher } from '$lib/helper/ThemeWatcher.svelte'; 7 + 8 + let wavesurfer: WaveSurfer | null = $state(null); 9 + 10 + let { url, darkMode = 'class' }: { 11 + url: string; 12 + darkMode?: 'class' | 'mediaQuery'; 13 + } = $props(); 14 + 15 + let audioContainer: HTMLElement | null = $state(null); 16 + 17 + let audioDuration = $state(0); 18 + let currentTime = $state(0); 19 + let remainingTime = $derived(Math.round(audioDuration - currentTime)); 20 + let remainingSeconds = $derived(remainingTime % 60); 21 + let remainingMinutes = $derived(Math.floor(remainingTime / 60)); 22 + 23 + let isPlaying = $state(false); 24 + let isLoaded = $state(false); 25 + 26 + let themeWatcher: ThemeWatcher | null = $state(null); 27 + 28 + function isDarkMode() { 29 + return themeWatcher?.isDarkMode() ?? false; 30 + } 31 + 32 + function getCSSVar(variable: string) { 33 + return themeWatcher?.getCSSVar(variable) ?? ''; 34 + } 35 + 36 + onMount(() => { 37 + if (!audioContainer) return; 38 + 39 + themeWatcher = new ThemeWatcher(updateWaveformColors, darkMode); 40 + 41 + wavesurfer = WaveSurfer.create({ 42 + container: audioContainer, 43 + cursorWidth: 2, 44 + height: 16, 45 + barWidth: 2, 46 + barGap: 1, 47 + barRadius: 2, 48 + dragToSeek: { 49 + debounceTime: 100 50 + }, 51 + fillParent: true, 52 + normalize: true, 53 + hideScrollbar: true 54 + }); 55 + 56 + updateWaveformColors(); 57 + 58 + wavesurfer?.load(url); 59 + 60 + wavesurfer?.on('ready', (duration: number) => { 61 + audioDuration = duration; 62 + isLoaded = true; 63 + }); 64 + 65 + wavesurfer?.on('timeupdate', (time: number) => { 66 + currentTime = time; 67 + }); 68 + 69 + wavesurfer?.on('play', () => { 70 + isPlaying = true; 71 + }); 72 + 73 + wavesurfer?.on('pause', () => { 74 + isPlaying = false; 75 + }); 76 + 77 + return () => { 78 + wavesurfer?.pause(); 79 + wavesurfer?.destroy(); 80 + 81 + themeWatcher?.destroy(); 82 + }; 83 + }); 84 + 85 + function updateWaveformColors() { 86 + wavesurfer?.setOptions({ 87 + waveColor: isDarkMode() ? getCSSVar('--color-accent-950') : getCSSVar('--color-accent-200'), 88 + progressColor: isDarkMode() ? getCSSVar('--color-accent-500') : getCSSVar('--color-accent-500') 89 + }); 90 + } 91 + 92 + function playAudio() { 93 + wavesurfer?.play(); 94 + } 95 + 96 + function pauseAudio() { 97 + wavesurfer?.pause(); 98 + } 99 + </script> 100 + 101 + <div class="relative flex h-24 w-full items-center justify-center gap-4 overflow-hidden"> 102 + <button 103 + onclick={() => { 104 + isPlaying ? pauseAudio() : playAudio(); 105 + }} 106 + class="bg-accent-600 hover:bg-accent-500 inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-30 md:size-14" 107 + > 108 + {#if isPlaying} 109 + <svg 110 + xmlns="http://www.w3.org/2000/svg" 111 + viewBox="0 0 24 24" 112 + fill="currentColor" 113 + class="size-6 md:size-9 text-base-50 dark:text-base-950" 114 + > 115 + <path 116 + fill-rule="evenodd" 117 + d="M6.75 5.25a.75.75 0 0 1 .75-.75H9a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V5.25Zm7.5 0A.75.75 0 0 1 15 4.5h1.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H15a.75.75 0 0 1-.75-.75V5.25Z" 118 + clip-rule="evenodd" 119 + /> 120 + </svg> 121 + {:else} 122 + <svg 123 + xmlns="http://www.w3.org/2000/svg" 124 + viewBox="0 0 24 24" 125 + fill="currentColor" 126 + class="ml-1 size-6 md:ml-1.5 md:size-9 text-base-50 dark:text-base-950" 127 + > 128 + <path 129 + fill-rule="evenodd" 130 + d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z" 131 + clip-rule="evenodd" 132 + /> 133 + </svg> 134 + {/if} 135 + <span class="sr-only">{isPlaying ? 'Pause' : 'Play'}</span> 136 + </button> 137 + <div bind:this={audioContainer} class="h-4 w-full grow"> 138 + {#if !isLoaded} 139 + <div class="m-1.5 h-1 w-full dark:bg-accent-950 bg-accent-100 rounded-full"></div> 140 + {/if} 141 + </div> 142 + {#if isLoaded} 143 + <NumberFlowGroup> 144 + <div 145 + class="text-accent-500 flex items-end justify-end font-semibold" 146 + style="font-variant-numeric: tabular-nums;" 147 + > 148 + -<NumberFlow 149 + value={remainingMinutes} 150 + trend={-1} 151 + format={{ minimumIntegerDigits: 2 }} 152 + digits={{ 1: { max: 5 } }} 153 + /> 154 + <NumberFlow 155 + value={remainingSeconds} 156 + trend={-1} 157 + format={{ minimumIntegerDigits: 2 }} 158 + digits={{ 1: { max: 5 } }} 159 + prefix=":" 160 + /> 161 + </div> 162 + </NumberFlowGroup> 163 + {:else} 164 + 165 + <div class="text-accent-500 flex items-end justify-end text-sm font-medium">loading</div> 166 + {/if} 167 + </div>
+24
src/lib/components/base/accordion/accordion-content.svelte
··· 1 + <script lang="ts"> 2 + import { Accordion as AccordionPrimitive, type WithoutChild } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + class: className, 8 + children, 9 + ...restProps 10 + }: WithoutChild<AccordionPrimitive.ContentProps> = $props(); 11 + </script> 12 + 13 + <AccordionPrimitive.Content 14 + bind:ref 15 + class={cn( 16 + 'text-base-800 dark:text-base-200 overflow-hidden text-sm transition-all mt-2', 17 + className 18 + )} 19 + {...restProps} 20 + > 21 + <div class="pt-0 pb-4"> 22 + {@render children?.()} 23 + </div> 24 + </AccordionPrimitive.Content>
+12
src/lib/components/base/accordion/accordion-item.svelte
··· 1 + <script lang="ts"> 2 + import { Accordion as AccordionPrimitive } from "bits-ui"; 3 + import { cn } from "$lib/utils"; 4 + 5 + let { 6 + ref = $bindable(null), 7 + class: className, 8 + ...restProps 9 + }: AccordionPrimitive.ItemProps = $props(); 10 + </script> 11 + 12 + <AccordionPrimitive.Item bind:ref class={cn("border-b border-base-200 dark:border-base-900", className)} {...restProps} />
+40
src/lib/components/base/accordion/accordion-trigger.svelte
··· 1 + <script lang="ts"> 2 + import { Accordion as AccordionPrimitive, type WithoutChild } from 'bits-ui'; 3 + import { cn } from '$lib/utils.js'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + class: className, 8 + level = 3, 9 + children, 10 + ...restProps 11 + }: WithoutChild<AccordionPrimitive.TriggerProps> & { 12 + level?: AccordionPrimitive.HeaderProps['level']; 13 + } = $props(); 14 + </script> 15 + 16 + <AccordionPrimitive.Header {level} class="flex w-full"> 17 + <AccordionPrimitive.Trigger 18 + bind:ref 19 + class={cn( 20 + 'flex flex-1 items-center rounded-2xl focus-visible:outline-2 focus-visible:outline-base-900 dark:focus-visible:outline-base-50 focus-visible:outline-offset-2 justify-between py-2 my-2 w-full text-base-950 dark:text-base-50 gap-2 font-medium transition-all hover:text-accent-600 cursor-pointer dark:hover:text-accent-500 [&[data-state=open]>svg]:rotate-180', 21 + className 22 + )} 23 + {...restProps} 24 + > 25 + {@render children?.()} 26 + 27 + <svg 28 + xmlns="http://www.w3.org/2000/svg" 29 + viewBox="0 0 24 24" 30 + fill="currentColor" 31 + class="size-4 shrink-0 transition-transform duration-200" 32 + > 33 + <path 34 + fill-rule="evenodd" 35 + d="M12.53 16.28a.75.75 0 0 1-1.06 0l-7.5-7.5a.75.75 0 0 1 1.06-1.06L12 14.69l6.97-6.97a.75.75 0 1 1 1.06 1.06l-7.5 7.5Z" 36 + clip-rule="evenodd" 37 + /> 38 + </svg> 39 + </AccordionPrimitive.Trigger> 40 + </AccordionPrimitive.Header>
+17
src/lib/components/base/accordion/index.ts
··· 1 + import { Accordion as AccordionPrimitive } from "bits-ui"; 2 + import Content from "./accordion-content.svelte"; 3 + import Item from "./accordion-item.svelte"; 4 + import Trigger from "./accordion-trigger.svelte"; 5 + const Root = AccordionPrimitive.Root; 6 + 7 + export { 8 + Root, 9 + Content, 10 + Item, 11 + Trigger, 12 + // 13 + Root as Accordion, 14 + Content as AccordionContent, 15 + Item as AccordionItem, 16 + Trigger as AccordionTrigger, 17 + };
+16
src/lib/components/base/alert/alert-description.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils'; 3 + import type { WithElementRef } from 'bits-ui'; 4 + import type { HTMLAttributes } from 'svelte/elements'; 5 + 6 + let { 7 + ref = $bindable(null), 8 + class: className, 9 + children, 10 + ...restProps 11 + }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); 12 + </script> 13 + 14 + <div bind:this={ref} class={cn('text-xs', className)} {...restProps}> 15 + {@render children?.()} 16 + </div>
+25
src/lib/components/base/alert/alert-title.svelte
··· 1 + <script lang="ts"> 2 + import type { HTMLAttributes } from 'svelte/elements'; 3 + import type { WithElementRef } from 'bits-ui'; 4 + import { cn } from '$lib/utils'; 5 + 6 + let { 7 + ref = $bindable(null), 8 + class: className, 9 + level = 5, 10 + children, 11 + ...restProps 12 + }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { 13 + level?: 1 | 2 | 3 | 4 | 5 | 6; 14 + } = $props(); 15 + </script> 16 + 17 + <div 18 + role="heading" 19 + aria-level={level} 20 + bind:this={ref} 21 + class={cn('mb-2 text-sm leading-none font-medium last:mb-0', className)} 22 + {...restProps} 23 + > 24 + {@render children?.()} 25 + </div>
+122
src/lib/components/base/alert/alert.svelte
··· 1 + <script lang="ts" module> 2 + import { type VariantProps, tv } from 'tailwind-variants'; 3 + 4 + export const alertVariants = tv({ 5 + base: 'text-xs relative w-full rounded-2xl border p-4 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:size-4 [&>svg~*]:pl-7 ', 6 + variants: { 7 + variant: { 8 + default: 9 + 'bg-accent-500/10 [&>div:first-of-type]:text-accent-700 [&>svg]:text-accent-600 dark:[&>svg]:text-accent-500 dark:[&>div:first-of-type]:text-accent-500 dark:bg-accent-500/5 text-accent-600 dark:text-accent-400 border-accent-500/20 dark:border-accent-500/10', 10 + secondary: 11 + 'bg-base-500/10 [&>div:first-of-type]:text-base-700 [&>svg]:text-base-600 dark:[&>svg]:text-base-400 dark:[&>div:first-of-type]:text-base-300 dark:bg-base-500/5 text-base-600 dark:text-base-400 border-base-500/20 dark:border-base-500/10', 12 + red: 'bg-red-500/10 [&>div:first-of-type]:text-red-700 [&>svg]:text-red-600 dark:[&>svg]:text-red-500 dark:[&>div:first-of-type]:text-red-500 dark:bg-red-500/5 text-red-600 dark:text-red-400 border-red-500/20 dark:border-red-500/10', 13 + orange: 14 + 'bg-orange-500/10 [&>div:first-of-type]:text-orange-700 [&>svg]:text-orange-600 dark:[&>svg]:text-orange-500 dark:[&>div:first-of-type]:text-orange-500 dark:bg-orange-500/5 text-orange-600 dark:text-orange-400 border-orange-500/20 dark:border-orange-500/10', 15 + amber: 16 + 'bg-amber-500/10 [&>div:first-of-type]:text-amber-700 [&>svg]:text-amber-600 dark:[&>svg]:text-amber-500 dark:[&>div:first-of-type]:text-amber-500 dark:bg-amber-500/5 text-amber-600 dark:text-amber-400 border-amber-500/20 dark:border-amber-500/10', 17 + yellow: 18 + 'bg-yellow-500/10 [&>div:first-of-type]:text-yellow-700 [&>svg]:text-yellow-600 dark:[&>svg]:text-yellow-500 dark:[&>div:first-of-type]:text-yellow-500 dark:bg-yellow-500/5 text-yellow-600 dark:text-yellow-400 border-yellow-500/20 dark:border-yellow-500/10', 19 + lime: 'bg-lime-500/10 [&>div:first-of-type]:text-lime-700 [&>svg]:text-lime-600 dark:[&>svg]:text-lime-500 dark:[&>div:first-of-type]:text-lime-500 dark:bg-lime-500/5 text-lime-600 dark:text-lime-400 border-lime-500/20 dark:border-lime-500/10', 20 + green: 21 + 'bg-green-500/10 [&>div:first-of-type]:text-green-700 [&>svg]:text-green-600 dark:[&>svg]:text-green-500 dark:[&>div:first-of-type]:text-green-500 dark:bg-green-500/5 text-green-600 dark:text-green-400 border-green-500/20 dark:border-green-500/10', 22 + emerald: 23 + 'bg-emerald-500/10 [&>div:first-of-type]:text-emerald-700 [&>svg]:text-emerald-600 dark:[&>svg]:text-emerald-500 dark:[&>div:first-of-type]:text-emerald-500 dark:bg-emerald-500/5 text-emerald-600 dark:text-emerald-400 border-emerald-500/20 dark:border-emerald-500/10', 24 + teal: 'bg-teal-500/10 [&>div:first-of-type]:text-teal-700 [&>svg]:text-teal-600 dark:[&>svg]:text-teal-500 dark:[&>div:first-of-type]:text-teal-500 dark:bg-teal-500/5 text-teal-600 dark:text-teal-400 border-teal-500/20 dark:border-teal-500/10', 25 + cyan: 'bg-cyan-500/10 [&>div:first-of-type]:text-cyan-700 [&>svg]:text-cyan-600 dark:[&>svg]:text-cyan-500 dark:[&>div:first-of-type]:text-cyan-500 dark:bg-cyan-500/5 text-cyan-600 dark:text-cyan-400 border-cyan-500/20 dark:border-cyan-500/10', 26 + sky: 'bg-sky-500/10 [&>div:first-of-type]:text-sky-700 [&>svg]:text-sky-600 dark:[&>svg]:text-sky-500 dark:[&>div:first-of-type]:text-sky-500 dark:bg-sky-500/5 text-sky-600 dark:text-sky-400 border-sky-500/20 dark:border-sky-500/10', 27 + blue: 'bg-blue-500/10 [&>div:first-of-type]:text-blue-700 [&>svg]:text-blue-600 dark:[&>svg]:text-blue-500 dark:[&>div:first-of-type]:text-blue-500 dark:bg-blue-500/5 text-blue-600 dark:text-blue-400 border-blue-500/20 dark:border-blue-500/10', 28 + indigo: 29 + 'bg-indigo-500/10 [&>div:first-of-type]:text-indigo-700 [&>svg]:text-indigo-600 dark:[&>svg]:text-indigo-500 dark:[&>div:first-of-type]:text-indigo-500 dark:bg-indigo-500/5 text-indigo-600 dark:text-indigo-400 border-indigo-500/20 dark:border-indigo-500/10', 30 + violet: 31 + 'bg-violet-500/10 [&>div:first-of-type]:text-violet-700 [&>svg]:text-violet-600 dark:[&>svg]:text-violet-500 dark:[&>div:first-of-type]:text-violet-500 dark:bg-violet-500/5 text-violet-600 dark:text-violet-400 border-violet-500/20 dark:border-violet-500/10', 32 + purple: 33 + 'bg-purple-500/10 [&>div:first-of-type]:text-purple-700 [&>svg]:text-purple-600 dark:[&>svg]:text-purple-500 dark:[&>div:first-of-type]:text-purple-500 dark:bg-purple-500/5 text-purple-600 dark:text-purple-400 border-purple-500/20 dark:border-purple-500/10', 34 + fuchsia: 35 + 'bg-fuchsia-500/10 [&>div:first-of-type]:text-fuchsia-700 [&>svg]:text-fuchsia-600 dark:[&>svg]:text-fuchsia-500 dark:[&>div:first-of-type]:text-fuchsia-500 dark:bg-fuchsia-500/5 text-fuchsia-600 dark:text-fuchsia-400 border-fuchsia-500/20 dark:border-fuchsia-500/10', 36 + pink: 'bg-pink-500/10 [&>div:first-of-type]:text-pink-700 [&>svg]:text-pink-600 dark:[&>svg]:text-pink-500 dark:[&>div:first-of-type]:text-pink-500 dark:bg-pink-500/5 text-pink-600 dark:text-pink-400 border-pink-500/20 dark:border-pink-500/10', 37 + rose: 'bg-rose-500/10 [&>div:first-of-type]:text-rose-700 [&>svg]:text-rose-600 dark:[&>svg]:text-rose-500 dark:[&>div:first-of-type]:text-rose-500 dark:bg-rose-500/5 text-rose-600 dark:text-rose-400 border-rose-500/20 dark:border-rose-500/10' 38 + } 39 + }, 40 + defaultVariants: { 41 + variant: 'default' 42 + } 43 + }); 44 + 45 + export type AlertVariant = VariantProps<typeof alertVariants>['variant']; 46 + </script> 47 + 48 + <script lang="ts"> 49 + import type { HTMLAttributes } from 'svelte/elements'; 50 + import type { WithElementRef } from 'bits-ui'; 51 + import { AlertTitle } from '.'; 52 + import { cn } from '$lib/utils'; 53 + 54 + let { 55 + ref = $bindable(null), 56 + class: className, 57 + variant = undefined, 58 + title, 59 + type, 60 + children, 61 + ...restProps 62 + }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { 63 + variant?: AlertVariant; 64 + title?: string; 65 + type?: 'info' | 'warning' | 'success' | 'error'; 66 + } = $props(); 67 + 68 + if (type === 'warning' && variant === undefined) { 69 + variant = 'amber'; 70 + } else if (type === 'error' && variant === undefined) { 71 + variant = 'red'; 72 + } else if (type === 'success' && variant === undefined) { 73 + variant = 'green'; 74 + } else if (type === 'info' && variant === undefined) { 75 + variant = 'sky'; 76 + } 77 + </script> 78 + 79 + <div 80 + bind:this={ref} 81 + class={cn(alertVariants({ variant: variant ?? 'default' }), className)} 82 + {...restProps} 83 + role="alert" 84 + > 85 + {#if type === 'warning'} 86 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 87 + <path 88 + fill-rule="evenodd" 89 + d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" 90 + clip-rule="evenodd" 91 + /> 92 + </svg> 93 + {:else if type === 'success'} 94 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 95 + <path 96 + fill-rule="evenodd" 97 + d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" 98 + clip-rule="evenodd" 99 + /> 100 + </svg> 101 + {:else if type === 'info'} 102 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 103 + <path 104 + fill-rule="evenodd" 105 + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 0 1 .67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 1 1-.671-1.34l.041-.022ZM12 9a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" 106 + clip-rule="evenodd" 107 + /> 108 + </svg> 109 + {:else if type === 'error'} 110 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6"> 111 + <path 112 + fill-rule="evenodd" 113 + d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.72 6.97a.75.75 0 1 0-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06L12 13.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L13.06 12l1.72-1.72a.75.75 0 1 0-1.06-1.06L12 10.94l-1.72-1.72Z" 114 + clip-rule="evenodd" 115 + /> 116 + </svg> 117 + {/if} 118 + {#if title} 119 + <AlertTitle>{title}</AlertTitle> 120 + {/if} 121 + {@render children?.()} 122 + </div>
+14
src/lib/components/base/alert/index.ts
··· 1 + import Root from './alert.svelte'; 2 + import Description from './alert-description.svelte'; 3 + import Title from './alert-title.svelte'; 4 + export { alertVariants, type AlertVariant } from './alert.svelte'; 5 + 6 + export { 7 + Root, 8 + Description, 9 + Title, 10 + // 11 + Root as Alert, 12 + Description as AlertDescription, 13 + Title as AlertTitle 14 + };
+56
src/lib/components/base/avatar/Avatar.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils'; 3 + import { Avatar as AvatarPrimitive, type WithoutChildrenOrChild } from 'bits-ui'; 4 + 5 + let { 6 + src, 7 + alt, 8 + fallback, 9 + ref = $bindable(null), 10 + 11 + imageRef = $bindable(null), 12 + imageClass, 13 + 14 + fallbackRef = $bindable(null), 15 + fallbackClass, 16 + 17 + class: className, 18 + ...restProps 19 + }: WithoutChildrenOrChild<AvatarPrimitive.RootProps> & { 20 + fallback?: string; 21 + imageRef?: HTMLImageElement | null; 22 + imageClass?: string; 23 + fallbackRef?: HTMLElement | null; 24 + fallbackClass?: string; 25 + 26 + src?: string; 27 + alt?: string; 28 + } = $props(); 29 + </script> 30 + 31 + <AvatarPrimitive.Root 32 + class={cn( 33 + 'border-base-200 bg-base-100 text-base-900 dark:border-base-800 dark:bg-base-900 dark:text-base-50 relative flex size-10 shrink-0 overflow-hidden rounded-full border', 34 + className 35 + )} 36 + {...restProps} 37 + bind:ref 38 + > 39 + {#if src} 40 + <AvatarPrimitive.Image 41 + class={cn('aspect-square size-full', imageClass)} 42 + {src} 43 + {alt} 44 + bind:ref={imageRef} 45 + /> 46 + {/if} 47 + 48 + {#if fallback} 49 + <AvatarPrimitive.Fallback 50 + bind:ref={fallbackRef} 51 + class={cn('flex size-full items-center justify-center', fallbackClass)} 52 + > 53 + {fallback} 54 + </AvatarPrimitive.Fallback> 55 + {/if} 56 + </AvatarPrimitive.Root>
+43
src/lib/components/base/avatar/AvatarGroup.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLAttributes } from 'svelte/elements'; 4 + import { Avatar } from '.'; 5 + import { cn } from '$lib/utils'; 6 + 7 + let { 8 + users, 9 + ref = $bindable(null), 10 + 11 + avatarClass, 12 + imageClass, 13 + fallbackClass, 14 + 15 + class: className, 16 + ...restProps 17 + }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { 18 + avatarClass?: string; 19 + imageClass?: string; 20 + fallbackClass?: string; 21 + 22 + users: { 23 + src?: string; 24 + alt?: string; 25 + fallback?: string; 26 + }[]; 27 + 28 + class?: string; 29 + } = $props(); 30 + </script> 31 + 32 + <div class={cn('flex -space-x-2 overflow-hidden', className)} bind:this={ref} {...restProps}> 33 + {#each users as user} 34 + <Avatar 35 + src={user.src} 36 + alt={user.alt} 37 + fallback={user.fallback} 38 + class={cn('border-base-50 dark:border-base-950 border-2', avatarClass)} 39 + {imageClass} 40 + {fallbackClass} 41 + /> 42 + {/each} 43 + </div>
+4
src/lib/components/base/avatar/index.ts
··· 1 + import Avatar from './Avatar.svelte'; 2 + import AvatarGroup from './AvatarGroup.svelte'; 3 + 4 + export { Avatar, AvatarGroup };
+76
src/lib/components/base/badge/Badge.svelte
··· 1 + <script lang="ts" module> 2 + import { type VariantProps, tv } from 'tailwind-variants'; 3 + import { cn } from '$lib/utils'; 4 + 5 + export const badgeVariants = tv({ 6 + base: 'inline-flex items-center justify-center whitespace-nowrap rounded-2xl text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 duration-300 active:duration-100 [&_svg]:pointer-events-none [&_svg]:shrink-0', 7 + variants: { 8 + variant: { 9 + primary: 10 + 'border border-accent-500/20 dark:border-accent-500/20 bg-accent-500/10 dark:bg-accent-500/10 text-accent-700 dark:text-accent-400', 11 + secondary: 12 + 'bg-base-300/30 dark:bg-base-800/50 text-base-900 dark:text-base-50 border border-base-300/50 dark:border-base-700/30', 13 + 14 + red: 'border border-red-500/20 dark:border-red-500/20 bg-red-500/10 dark:bg-red-500/10 text-red-700 dark:text-red-400', 15 + yellow: 16 + 'border border-yellow-500/20 dark:border-yellow-500/20 bg-yellow-500/10 dark:bg-yellow-500/10 text-yellow-700 dark:text-yellow-400', 17 + green: 18 + 'border border-green-500/20 dark:border-green-500/20 bg-green-500/10 dark:bg-green-500/10 text-green-700 dark:text-green-400', 19 + blue: 'border border-blue-500/20 dark:border-blue-500/20 bg-blue-500/10 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400', 20 + indigo: 21 + 'border border-indigo-500/20 dark:border-indigo-500/20 bg-indigo-500/10 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400', 22 + violet: 23 + 'border border-violet-500/20 dark:border-violet-500/20 bg-violet-500/10 dark:bg-violet-500/10 text-violet-700 dark:text-violet-400', 24 + purple: 25 + 'border border-purple-500/20 dark:border-purple-500/20 bg-purple-500/10 dark:bg-purple-500/10 text-purple-700 dark:text-purple-400', 26 + fuchsia: 27 + 'border border-fuchsia-500/20 dark:border-fuchsia-500/20 bg-fuchsia-500/10 dark:bg-fuchsia-500/10 text-fuchsia-700 dark:text-fuchsia-400', 28 + pink: 'border border-pink-500/20 dark:border-pink-500/20 bg-pink-500/10 dark:bg-pink-500/10 text-pink-700 dark:text-pink-400', 29 + rose: 'border border-rose-500/20 dark:border-rose-500/20 bg-rose-500/10 dark:bg-rose-500/10 text-rose-700 dark:text-rose-400', 30 + orange: 31 + 'border border-orange-500/20 dark:border-orange-500/20 bg-orange-500/10 dark:bg-orange-500/10 text-orange-700 dark:text-orange-400', 32 + amber: 33 + 'border border-amber-500/20 dark:border-amber-500/20 bg-amber-500/10 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400', 34 + lime: 'border border-lime-500/20 dark:border-lime-500/20 bg-lime-500/10 dark:bg-lime-500/10 text-lime-700 dark:text-lime-400', 35 + emerald: 36 + 'border border-emerald-500/20 dark:border-emerald-500/20 bg-emerald-500/10 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400', 37 + teal: 'border border-teal-500/20 dark:border-teal-500/20 bg-teal-500/10 dark:bg-teal-500/10 text-teal-700 dark:text-teal-400', 38 + cyan: 'border border-cyan-500/20 dark:border-cyan-500/20 bg-cyan-500/10 dark:bg-cyan-500/10 text-cyan-700 dark:text-cyan-400', 39 + sky: 'border border-sky-500/20 dark:border-sky-500/20 bg-sky-500/10 dark:bg-sky-500/10 text-sky-700 dark:text-sky-400' 40 + }, 41 + size: { 42 + sm: 'px-2 text-xs py-0.5 [&_svg]:size-3 gap-1.5', 43 + md: 'px-3 py-1 [&_svg]:size-4 gap-2', 44 + lg: 'px-4 text-lg py-1.5 [&_svg]:size-5 gap-2.5' 45 + } 46 + }, 47 + defaultVariants: { 48 + variant: 'primary', 49 + size: 'sm' 50 + } 51 + }); 52 + 53 + export type BadgeVariant = VariantProps<typeof badgeVariants>['variant']; 54 + export type BadgeSize = VariantProps<typeof badgeVariants>['size']; 55 + </script> 56 + 57 + <script lang="ts"> 58 + import type { WithElementRef } from 'bits-ui'; 59 + import type { HTMLAttributes } from 'svelte/elements'; 60 + 61 + let { 62 + ref = $bindable(null), 63 + class: className, 64 + variant = 'primary', 65 + size = 'sm', 66 + children, 67 + ...restProps 68 + }: WithElementRef<HTMLAttributes<HTMLSpanElement>> & { 69 + variant?: BadgeVariant; 70 + size?: BadgeSize; 71 + } = $props(); 72 + </script> 73 + 74 + <span bind:this={ref} class={cn(badgeVariants({ variant, size }), className)} {...restProps}> 75 + {@render children?.()} 76 + </span>
+1
src/lib/components/base/badge/index.ts
··· 1 + export { default as Badge, badgeVariants, type BadgeVariant } from './Badge.svelte';
+23
src/lib/components/base/box/Box.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils'; 3 + import type { WithChildren, WithElementRef } from 'bits-ui'; 4 + import type { HTMLAttributes } from 'svelte/elements'; 5 + 6 + let { 7 + ref, 8 + children, 9 + class: className, 10 + ...restProps 11 + }: WithElementRef<WithChildren<HTMLAttributes<HTMLDivElement>>> = $props(); 12 + </script> 13 + 14 + <div 15 + bind:this={ref} 16 + class={cn( 17 + 'border-base-300/50 bg-base-300/20 dark:border-base-800/50 dark:bg-base-900/30 text-base-900 dark:text-base-100 relative isolate w-full overflow-hidden rounded-2xl border p-4', 18 + className 19 + )} 20 + {...restProps} 21 + > 22 + {@render children?.()} 23 + </div>
+105
src/lib/components/base/button/Button.svelte
··· 1 + <script lang="ts" module> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'; 4 + import { type VariantProps, tv } from 'tailwind-variants'; 5 + import { cn } from '$lib/utils'; 6 + 7 + export const buttonVariants = tv({ 8 + base: 'hover:cursor-pointer pointer-events-auto motion-safe:focus-visible:transition-transform focus-visible:outline-2 outline-offset-2 inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-2xl active:scale-95 text-sm font-medium motion-safe:transition-all disabled:pointer-events-none disabled:opacity-50 duration-300 active:duration-100 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', 9 + variants: { 10 + variant: { 11 + primary: 12 + 'focus-visible:outline-accent-500 border border-accent-500/20 dark:border-accent-500/20 hover:bg-accent-500/20 dark:hover:bg-accent-500/20 bg-accent-500/10 dark:bg-accent-500/10 text-accent-700 dark:text-accent-400', 13 + secondary: 14 + 'focus-visible:outline-base-800 dark:focus-visible:outline-base-200 bg-base-300/30 dark:bg-base-800/50 text-base-900 dark:text-base-50 hover:bg-base-300/50 dark:hover:bg-base-700/50 border border-base-300/50 dark:border-base-700/30', 15 + link: 'focus-visible:outline-base-900 dark:focus-visible:outline-base-50 text-base-800 dark:text-base-200 font-semibold hover:text-accent-600 dark:hover:text-accent-400 data-[current=true]:text-accent-600 dark:data-[current=true]:text-accent-400', 16 + 17 + red: 'focus-visible:outline-red-500 border border-red-500/20 dark:border-red-500/20 hover:bg-red-500/20 dark:hover:bg-red-500/20 bg-red-500/10 dark:bg-red-500/10 text-red-700 dark:text-red-400', 18 + yellow: 19 + 'focus-visible:outline-yellow-500 border border-yellow-500/20 dark:border-yellow-500/20 hover:bg-yellow-500/20 dark:hover:bg-yellow-500/20 bg-yellow-500/10 dark:bg-yellow-500/10 text-yellow-700 dark:text-yellow-400', 20 + green: 21 + 'focus-visible:outline-green-500 border border-green-500/20 dark:border-green-500/20 hover:bg-green-500/20 dark:hover:bg-green-500/20 bg-green-500/10 dark:bg-green-500/10 text-green-700 dark:text-green-400', 22 + blue: 'focus-visible:outline-blue-500 border border-blue-500/20 dark:border-blue-500/20 hover:bg-blue-500/20 dark:hover:bg-blue-500/20 bg-blue-500/10 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400', 23 + indigo: 24 + 'focus-visible:outline-indigo-500 border border-indigo-500/20 dark:border-indigo-500/20 hover:bg-indigo-500/20 dark:hover:bg-indigo-500/20 bg-indigo-500/10 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400', 25 + violet: 26 + 'focus-visible:outline-violet-500 border border-violet-500/20 dark:border-violet-500/20 hover:bg-violet-500/20 dark:hover:bg-violet-500/20 bg-violet-500/10 dark:bg-violet-500/10 text-violet-700 dark:text-violet-400', 27 + purple: 28 + 'focus-visible:outline-purple-500 border border-purple-500/20 dark:border-purple-500/20 hover:bg-purple-500/20 dark:hover:bg-purple-500/20 bg-purple-500/10 dark:bg-purple-500/10 text-purple-700 dark:text-purple-400', 29 + fuchsia: 30 + 'focus-visible:outline-fuchsia-500 border border-fuchsia-500/20 dark:border-fuchsia-500/20 hover:bg-fuchsia-500/20 dark:hover:bg-fuchsia-500/20 bg-fuchsia-500/10 dark:bg-fuchsia-500/10 text-fuchsia-700 dark:text-fuchsia-400', 31 + pink: 'focus-visible:outline-pink-500 border border-pink-500/20 dark:border-pink-500/20 hover:bg-pink-500/20 dark:hover:bg-pink-500/20 bg-pink-500/10 dark:bg-pink-500/10 text-pink-700 dark:text-pink-400', 32 + rose: 'focus-visible:outline-rose-500 border border-rose-500/20 dark:border-rose-500/20 hover:bg-rose-500/20 dark:hover:bg-rose-500/20 bg-rose-500/10 dark:bg-rose-500/10 text-rose-700 dark:text-rose-400', 33 + orange: 34 + 'focus-visible:outline-orange-500 border border-orange-500/20 dark:border-orange-500/20 hover:bg-orange-500/20 dark:hover:bg-orange-500/20 bg-orange-500/10 dark:bg-orange-500/10 text-orange-700 dark:text-orange-400', 35 + amber: 36 + 'focus-visible:outline-amber-500 border border-amber-500/20 dark:border-amber-500/20 hover:bg-amber-500/20 dark:hover:bg-amber-500/20 bg-amber-500/10 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400', 37 + lime: 'focus-visible:outline-lime-500 border border-lime-500/20 dark:border-lime-500/20 hover:bg-lime-500/20 dark:hover:bg-lime-500/20 bg-lime-500/10 dark:bg-lime-500/10 text-lime-700 dark:text-lime-400', 38 + emerald: 39 + 'focus-visible:outline-emerald-500 border border-emerald-500/20 dark:border-emerald-500/20 hover:bg-emerald-500/20 dark:hover:bg-emerald-500/20 bg-emerald-500/10 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400', 40 + teal: 'focus-visible:outline-teal-500 border border-teal-500/20 dark:border-teal-500/20 hover:bg-teal-500/20 dark:hover:bg-teal-500/20 bg-teal-500/10 dark:bg-teal-500/10 text-teal-700 dark:text-teal-400', 41 + cyan: 'focus-visible:outline-cyan-500 border border-cyan-500/20 dark:border-cyan-500/20 hover:bg-cyan-500/20 dark:hover:bg-cyan-500/20 bg-cyan-500/10 dark:bg-cyan-500/10 text-cyan-700 dark:text-cyan-400', 42 + sky: 'focus-visible:outline-sky-500 border border-sky-500/20 dark:border-sky-500/20 hover:bg-sky-500/20 dark:hover:bg-sky-500/20 bg-sky-500/10 dark:bg-sky-500/10 text-sky-700 dark:text-sky-400' 43 + }, 44 + size: { 45 + default: 'px-3 py-1.5', 46 + sm: 'px-2 text-xs py-1 font-base [&_svg]:size-3 gap-1.5', 47 + lg: 'px-4 gap-2.5 text-lg py-2 font-semibold [&_svg]:size-5 gap-2.5', 48 + icon: 'p-2', 49 + iconSm: 'p-1.5 [&_svg]:size-3', 50 + iconLg: 'p-3 [&_svg]:size-5' 51 + } 52 + }, 53 + defaultVariants: { 54 + variant: 'primary', 55 + size: 'default' 56 + } 57 + }); 58 + 59 + export type ButtonVariant = VariantProps<typeof buttonVariants>['variant']; 60 + export type ButtonSize = VariantProps<typeof buttonVariants>['size']; 61 + 62 + export type ButtonProps = WithElementRef<HTMLButtonAttributes> & 63 + WithElementRef<HTMLAnchorAttributes> & { 64 + variant?: ButtonVariant; 65 + size?: ButtonSize; 66 + checkCurrent?: boolean; 67 + }; 68 + </script> 69 + 70 + <script lang="ts"> 71 + import { page } from '$app/state'; 72 + 73 + let { 74 + class: className, 75 + variant = 'primary', 76 + size = 'default', 77 + ref = $bindable(null), 78 + href = undefined, 79 + type = 'button', 80 + children, 81 + checkCurrent = true, 82 + ...restProps 83 + }: ButtonProps = $props(); 84 + </script> 85 + 86 + {#if href} 87 + <a 88 + bind:this={ref} 89 + data-current={variant === 'link' && checkCurrent && page.url.pathname === href} 90 + class={cn(buttonVariants({ variant, size }), className)} 91 + {href} 92 + {...restProps} 93 + > 94 + {@render children?.()} 95 + </a> 96 + {:else} 97 + <button 98 + bind:this={ref} 99 + class={cn(buttonVariants({ variant, size }), className)} 100 + {type} 101 + {...restProps} 102 + > 103 + {@render children?.()} 104 + </button> 105 + {/if}
+16
src/lib/components/base/button/index.ts
··· 1 + import Root, { 2 + type ButtonProps, 3 + type ButtonSize, 4 + type ButtonVariant, 5 + buttonVariants 6 + } from './Button.svelte'; 7 + 8 + export { 9 + Root, 10 + type ButtonProps as Props, 11 + Root as Button, 12 + buttonVariants, 13 + type ButtonProps, 14 + type ButtonSize, 15 + type ButtonVariant 16 + };
+59
src/lib/components/base/card/ImageCard.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils'; 3 + import ImageContainer from '../image-container/ImageContainer.svelte'; 4 + 5 + const { 6 + src, 7 + alt, 8 + title, 9 + description, 10 + href, 11 + onclick 12 + }: { 13 + src: string; 14 + alt: string; 15 + title: string; 16 + description?: string; 17 + href?: string; 18 + onclick?: () => void; 19 + } = $props(); 20 + </script> 21 + 22 + {#snippet titleSnippet()} 23 + <h3 class="text-base-900 dark:text-base-50 line-clamp-2 text-sm font-semibold">{title}</h3> 24 + {/snippet} 25 + 26 + <div 27 + class={cn( 28 + 'group relative flex w-full flex-col gap-5 transition-opacity duration-300 ease-in-out', 29 + href || onclick ? 'hover:opacity-80' : '' 30 + )} 31 + > 32 + <ImageContainer 33 + {src} 34 + {alt} 35 + containerClasses="w-full rounded-2xl overflow-hidden" 36 + class={[ 37 + 'aspect-video w-full object-cover transition-all duration-500 ease-in-out', 38 + href || onclick ? 'group-hover:scale-105' : '' 39 + ]} 40 + ></ImageContainer> 41 + <div class="flex max-w-xl flex-col gap-2"> 42 + {#if href} 43 + <a {href}> 44 + {@render titleSnippet()} 45 + <span aria-hidden="true" class="absolute inset-0"></span> 46 + </a> 47 + {:else if onclick} 48 + <button {onclick} class="inline-flex w-full cursor-pointer items-start"> 49 + {@render titleSnippet()} 50 + <span aria-hidden="true" class="absolute inset-0"></span> 51 + </button> 52 + {:else} 53 + {@render titleSnippet()} 54 + {/if} 55 + {#if description} 56 + <p class="text-base-500 line-clamp-2 text-xs leading-4.5">{description}</p> 57 + {/if} 58 + </div> 59 + </div>
+92
src/lib/components/base/chat-bubble/ChatBubble.svelte
··· 1 + <script lang="ts" module> 2 + import { type VariantProps, tv } from 'tailwind-variants'; 3 + import { cn } from '$lib/utils'; 4 + 5 + export const badgeVariants = tv({ 6 + base: 'inline-flex items-center flex-col justify-start items-start whitespace-nowrap rounded-2xl text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 duration-300 active:duration-100 [&_svg]:pointer-events-none [&_svg]:shrink-0', 7 + variants: { 8 + variant: { 9 + primary: 10 + 'border border-accent-500/20 dark:border-accent-500/20 bg-accent-500/10 dark:bg-accent-500/10 text-accent-700 dark:text-accent-400', 11 + secondary: 12 + 'bg-base-300/30 dark:bg-base-800/50 text-base-900 dark:text-base-50 border border-base-300/50 dark:border-base-700/30', 13 + 14 + red: 'border border-red-500/20 dark:border-red-500/20 bg-red-500/10 dark:bg-red-500/10 text-red-700 dark:text-red-400', 15 + yellow: 16 + 'border border-yellow-500/20 dark:border-yellow-500/20 bg-yellow-500/10 dark:bg-yellow-500/10 text-yellow-700 dark:text-yellow-400', 17 + green: 18 + 'border border-green-500/20 dark:border-green-500/20 bg-green-500/10 dark:bg-green-500/10 text-green-700 dark:text-green-400', 19 + blue: 'border border-blue-500/20 dark:border-blue-500/20 bg-blue-500/10 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400', 20 + indigo: 21 + 'border border-indigo-500/20 dark:border-indigo-500/20 bg-indigo-500/10 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400', 22 + violet: 23 + 'border border-violet-500/20 dark:border-violet-500/20 bg-violet-500/10 dark:bg-violet-500/10 text-violet-700 dark:text-violet-400', 24 + purple: 25 + 'border border-purple-500/20 dark:border-purple-500/20 bg-purple-500/10 dark:bg-purple-500/10 text-purple-700 dark:text-purple-400', 26 + fuchsia: 27 + 'border border-fuchsia-500/20 dark:border-fuchsia-500/20 bg-fuchsia-500/10 dark:bg-fuchsia-500/10 text-fuchsia-700 dark:text-fuchsia-400', 28 + pink: 'border border-pink-500/20 dark:border-pink-500/20 bg-pink-500/10 dark:bg-pink-500/10 text-pink-700 dark:text-pink-400', 29 + rose: 'border border-rose-500/20 dark:border-rose-500/20 bg-rose-500/10 dark:bg-rose-500/10 text-rose-700 dark:text-rose-400', 30 + orange: 31 + 'border border-orange-500/20 dark:border-orange-500/20 bg-orange-500/10 dark:bg-orange-500/10 text-orange-700 dark:text-orange-400', 32 + amber: 33 + 'border border-amber-500/20 dark:border-amber-500/20 bg-amber-500/10 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400', 34 + lime: 'border border-lime-500/20 dark:border-lime-500/20 bg-lime-500/10 dark:bg-lime-500/10 text-lime-700 dark:text-lime-400', 35 + emerald: 36 + 'border border-emerald-500/20 dark:border-emerald-500/20 bg-emerald-500/10 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400', 37 + teal: 'border border-teal-500/20 dark:border-teal-500/20 bg-teal-500/10 dark:bg-teal-500/10 text-teal-700 dark:text-teal-400', 38 + cyan: 'border border-cyan-500/20 dark:border-cyan-500/20 bg-cyan-500/10 dark:bg-cyan-500/10 text-cyan-700 dark:text-cyan-400', 39 + sky: 'border border-sky-500/20 dark:border-sky-500/20 bg-sky-500/10 dark:bg-sky-500/10 text-sky-700 dark:text-sky-400' 40 + }, 41 + size: { 42 + sm: 'px-2 text-xs py-0.5 [&_svg]:size-3 gap-1.5', 43 + md: 'px-3 py-1.5 [&_svg]:size-4 gap-2', 44 + lg: 'px-4 text-lg py-1.5 [&_svg]:size-5 gap-2.5' 45 + } 46 + }, 47 + defaultVariants: { 48 + variant: 'primary', 49 + size: 'md' 50 + } 51 + }); 52 + 53 + export type BadgeVariant = VariantProps<typeof badgeVariants>['variant']; 54 + export type BadgeSize = VariantProps<typeof badgeVariants>['size']; 55 + </script> 56 + 57 + <script lang="ts"> 58 + import type { WithElementRef } from 'bits-ui'; 59 + import type { HTMLAttributes } from 'svelte/elements'; 60 + 61 + let { 62 + ref = $bindable(null), 63 + class: className, 64 + variant = 'primary', 65 + size = 'md', 66 + children, 67 + side = 'left', 68 + ...restProps 69 + }: WithElementRef<HTMLAttributes<HTMLSpanElement>> & { 70 + variant?: BadgeVariant; 71 + size?: BadgeSize; 72 + side?: 'left' | 'right'; 73 + } = $props(); 74 + </script> 75 + 76 + <div class="flex"> 77 + {#if side === 'left'} 78 + <div class="grow"></div> 79 + {/if} 80 + 81 + <span 82 + bind:this={ref} 83 + class={cn(badgeVariants({ variant, size }), 'max-w-2/3 whitespace-normal', className)} 84 + {...restProps} 85 + > 86 + {@render children?.()} 87 + </span> 88 + 89 + {#if side === 'right'} 90 + <div class="grow"></div> 91 + {/if} 92 + </div>
+91
src/lib/components/base/checkbox/checkbox.svelte
··· 1 + <script lang="ts" module> 2 + import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from 'bits-ui'; 3 + import { type VariantProps, tv } from 'tailwind-variants'; 4 + import { cn } from '$lib/utils'; 5 + 6 + export const checkboxVariants = tv({ 7 + base: 'peer cursor-pointer box-content size-4 shrink-0 inline-flex items-center justify-center rounded-2xl border outline-offset-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent-500 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50', 8 + variants: { 9 + variant: { 10 + primary: 11 + 'border-accent-200 bg-accent-100 focus-visible:outline-accent-500 data-[state=checked]:border-accent-300 data-[state=checked]:bg-accent-200 data-[state=checked]:text-accent-600 dark:border-accent-500/20 dark:bg-accent-500/5 dark:text-accent-500 dark:data-[state=checked]:border-accent-500/30 dark:data-[state=checked]:bg-accent-500/20 dark:data-[state=checked]:text-accent-500', 12 + secondary: 13 + 'border-base-300 bg-base-100 focus-visible:outline-base-900 dark:focus-visible:outline-base-100 data-[state=checked]:border-base-400 data-[state=checked]:bg-base-200 data-[state=checked]:text-base-900 dark:border-base-700 dark:bg-base-900 dark:text-base-50 dark:data-[state=checked]:border-base-500/80 dark:data-[state=checked]:bg-base-500/30 dark:data-[state=checked]:text-base-100' 14 + }, 15 + sizeVariant: { 16 + default: 'size-5', 17 + sm: 'size-4', 18 + lg: 'size-6' 19 + } 20 + }, 21 + defaultVariants: { 22 + variant: 'primary', 23 + sizeVariant: 'default' 24 + } 25 + }); 26 + 27 + export type CheckboxVariant = VariantProps<typeof checkboxVariants>['variant']; 28 + export type CheckboxSize = VariantProps<typeof checkboxVariants>['sizeVariant']; 29 + 30 + export type CheckboxProps = WithoutChildrenOrChild<CheckboxPrimitive.RootProps> & { 31 + variant?: CheckboxVariant; 32 + sizeVariant?: CheckboxSize; 33 + }; 34 + </script> 35 + 36 + <script lang="ts"> 37 + let { 38 + ref = $bindable(null), 39 + checked = $bindable(false), 40 + indeterminate = $bindable(false), 41 + variant = $bindable('primary'), 42 + sizeVariant = $bindable('default'), 43 + class: className, 44 + ...restProps 45 + }: CheckboxProps = $props(); 46 + </script> 47 + 48 + <CheckboxPrimitive.Root 49 + bind:ref 50 + class={cn(checkboxVariants({ variant, sizeVariant }), className)} 51 + bind:checked 52 + bind:indeterminate 53 + {...restProps} 54 + > 55 + {#snippet children({ checked, indeterminate })} 56 + <div class="flex size-4 items-center justify-center text-current"> 57 + {#if indeterminate} 58 + <svg 59 + xmlns="http://www.w3.org/2000/svg" 60 + fill="none" 61 + viewBox="0 0 24 24" 62 + stroke-width="2.5" 63 + stroke="currentColor" 64 + class={[ 65 + sizeVariant === 'sm' && 'size-3.5', 66 + sizeVariant === 'default' && 'size-4', 67 + sizeVariant === 'lg' && 'size-4.5' 68 + ]} 69 + > 70 + <path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" /> 71 + </svg> 72 + {:else} 73 + <svg 74 + xmlns="http://www.w3.org/2000/svg" 75 + fill="none" 76 + viewBox="0 0 24 24" 77 + stroke-width="2.5" 78 + stroke="currentColor" 79 + class={[ 80 + sizeVariant === 'sm' && 'size-3.5', 81 + sizeVariant === 'default' && 'size-4', 82 + sizeVariant === 'lg' && 'size-4.5', 83 + !checked && 'text-transparent' 84 + ]} 85 + > 86 + <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /> 87 + </svg> 88 + {/if} 89 + </div> 90 + {/snippet} 91 + </CheckboxPrimitive.Root>
+6
src/lib/components/base/checkbox/index.ts
··· 1 + import Root from './checkbox.svelte'; 2 + export { 3 + Root, 4 + // 5 + Root as Checkbox 6 + };
+11
src/lib/components/base/container/Container.svelte
··· 1 + <script lang="ts"> 2 + import ScrollArea from '$lib/components/base/scroll-area/ScrollArea.svelte'; 3 + 4 + let { children } = $props(); 5 + </script> 6 + 7 + <ScrollArea type="scroll" class="h-[100dvh] w-full" scrollbarYClasses="z-50"> 8 + <div class="mx-auto flex w-full max-w-2xl flex-col items-center justify-center gap-2 px-4 py-24"> 9 + {@render children?.()} 10 + </div> 11 + </ScrollArea>
+62
src/lib/components/base/github-corner/GithubCorner.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils'; 3 + import type { HTMLAnchorAttributes } from 'svelte/elements'; 4 + 5 + type Props = HTMLAnchorAttributes; 6 + let { target = '_blank', class: className, ...restProps }: Props = $props(); 7 + </script> 8 + 9 + <a 10 + {target} 11 + class={cn( 12 + 'github-corner fill-accent-600 text-base-50 dark:fill-accent-500 dark:text-base-950 fixed -top-1 -right-1 z-50', 13 + className 14 + )} 15 + {...restProps} 16 + > 17 + <svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"> 18 + <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> 19 + <path 20 + d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" 21 + fill="currentColor" 22 + style="transform-origin: 130px 106px;" 23 + class="octo-arm" 24 + ></path> 25 + <path 26 + d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" 27 + fill="currentColor" 28 + class="octo-body" 29 + ></path> 30 + </svg> 31 + <span class="sr-only">View source on GitHub</span> 32 + </a> 33 + 34 + <style> 35 + .github-corner:hover .octo-arm { 36 + animation: octocat-wave 560ms ease-in-out; 37 + } 38 + 39 + @keyframes octocat-wave { 40 + 0%, 41 + 100% { 42 + transform: rotate(0); 43 + } 44 + 20%, 45 + 60% { 46 + transform: rotate(-25deg); 47 + } 48 + 40%, 49 + 80% { 50 + transform: rotate(10deg); 51 + } 52 + } 53 + 54 + @media (max-width: 500px) { 55 + .github-corner:hover .octo-arm { 56 + animation: none; 57 + } 58 + .github-corner .octo-arm { 59 + animation: octocat-wave 560ms ease-in-out; 60 + } 61 + } 62 + </style>
+51
src/lib/components/base/head/Head.svelte
··· 1 + <script lang="ts"> 2 + import { page } from '$app/state'; 3 + 4 + const { 5 + title, 6 + description, 7 + image, 8 + url, 9 + emojiFavicon 10 + }: { 11 + title?: string; 12 + description?: string; 13 + image?: string; 14 + url?: string; 15 + emojiFavicon?: string; 16 + } = $props(); 17 + </script> 18 + 19 + <svelte:head> 20 + {#if emojiFavicon} 21 + <link 22 + rel="icon" 23 + href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>{emojiFavicon}</text></svg>" 24 + /> 25 + {/if} 26 + 27 + <meta property="og:type" content="website" /> 28 + 29 + {#if description} 30 + <meta name="description" content={description} /> 31 + <meta property="og:description" content={description} /> 32 + <meta name="twitter:description" content={description} /> 33 + {/if} 34 + 35 + {#if title} 36 + <title>{title}</title> 37 + <meta property="og:title" content={title} /> 38 + <meta name="twitter:title" content={title} /> 39 + {/if} 40 + 41 + {#if image} 42 + <meta property="og:image" content={image} /> 43 + <meta name="twitter:image" content={image} /> 44 + <meta name="twitter:card" content="summary_large_image" /> 45 + {/if} 46 + 47 + {#if url} 48 + <meta property="twitter:domain" content={new URL(url).hostname} /> 49 + <meta property="twitter:url" content={url} /> 50 + {/if} 51 + </svelte:head>
+24
src/lib/components/base/heading/Heading.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLBaseAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + let { 7 + ref = $bindable(null), 8 + class: className, 9 + level = 1, 10 + children, 11 + ...restProps 12 + }: WithElementRef<HTMLBaseAttributes> & { 13 + level?: 1 | 2 | 3 | 4 | 5 | 6; 14 + } = $props(); 15 + </script> 16 + 17 + <svelte:element 18 + this={'h' + level} 19 + bind:this={ref} 20 + class={cn('text-base-900 dark:text-base-50 text-2xl font-semibold', className)} 21 + {...restProps} 22 + > 23 + {@render children?.()} 24 + </svelte:element>
+24
src/lib/components/base/heading/Subheading.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLBaseAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + let { 7 + ref = $bindable(null), 8 + class: className, 9 + level = 2, 10 + children, 11 + ...restProps 12 + }: WithElementRef<HTMLBaseAttributes> & { 13 + level?: 1 | 2 | 3 | 4 | 5 | 6; 14 + } = $props(); 15 + </script> 16 + 17 + <svelte:element 18 + this={'h' + level} 19 + bind:this={ref} 20 + class={cn('text-base-900 dark:text-base-50 text-base font-semibold sm:text-lg', className)} 21 + {...restProps} 22 + > 23 + {@render children?.()} 24 + </svelte:element>
+4
src/lib/components/base/heading/index.ts
··· 1 + import Heading from './Heading.svelte'; 2 + import Subheading from './Subheading.svelte'; 3 + 4 + export { Heading, Subheading };
+15
src/lib/components/base/image-container/ImageContainer.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils'; 3 + import Image, { type ImageProps } from '../image/Image.svelte'; 4 + 5 + let { ref = $bindable(null), containerClasses, ...restProps }: ImageProps = $props(); 6 + </script> 7 + 8 + <Image 9 + bind:ref 10 + {...restProps} 11 + containerClasses={cn( 12 + 'border-base-300 dark:border-base-700 overflow-hidden rounded-2xl border', 13 + containerClasses 14 + )} 15 + />
+94
src/lib/components/base/image/Image.svelte
··· 1 + <script lang="ts" module> 2 + import { cn } from '$lib/utils'; 3 + import type { HTMLImgAttributes } from 'svelte/elements'; 4 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 5 + 6 + export type ImageProps = WithoutChildrenOrChild< 7 + WithElementRef<HTMLImgAttributes, HTMLImageElement> 8 + > & { 9 + useThemeColor?: boolean; 10 + containerClasses?: string; 11 + blur?: boolean; 12 + showNormalOnHover?: boolean; 13 + alt: string; 14 + }; 15 + </script> 16 + 17 + <script lang="ts"> 18 + import Img from '@zerodevx/svelte-img'; 19 + import { onMount } from 'svelte'; 20 + 21 + let loaded = $state(false); 22 + 23 + onMount(() => { 24 + if (ref?.complete) loaded = true; 25 + }); 26 + let { 27 + ref = $bindable(null), 28 + src, 29 + alt, 30 + class: className, 31 + containerClasses, 32 + useThemeColor = false, 33 + showNormalOnHover = false, 34 + blur = true, 35 + ...restProps 36 + }: ImageProps = $props(); 37 + </script> 38 + 39 + <div class={cn('group relative h-fit w-fit', containerClasses)}> 40 + {#if useThemeColor} 41 + <div 42 + class={[ 43 + 'bg-accent-500/30 pointer-events-none absolute inset-0 z-20 size-full', 44 + showNormalOnHover ? 'group-hover:opacity-0 transition-all duration-300 ease-in-out' : '' 45 + ]} 46 + ></div> 47 + {/if} 48 + 49 + {#if typeof src === 'string'} 50 + <img 51 + {src} 52 + bind:this={ref} 53 + {alt} 54 + onload={() => (loaded = true)} 55 + class={cn( 56 + useThemeColor ? 'brightness-125 grayscale' : '', 57 + showNormalOnHover ? 'group-hover:filter-none transition-all duration-300 ease-in-out' : '', 58 + className 59 + )} 60 + {...restProps} 61 + /> 62 + {:else} 63 + <Img 64 + {src} 65 + bind:ref 66 + {alt} 67 + onload={() => (loaded = true)} 68 + class={cn( 69 + useThemeColor ? 'brightness-125 grayscale' : '', 70 + showNormalOnHover ? 'group-hover:filter-none transition-all duration-300 ease-in-out' : '', 71 + className 72 + )} 73 + width={restProps.width ? Number(restProps.width) : undefined} 74 + height={restProps.height ? Number(restProps.height) : undefined} 75 + loading={restProps.loading ?? 'lazy'} 76 + decoding={restProps.decoding ?? 'async'} 77 + sizes={restProps.sizes ?? undefined} 78 + /> 79 + {/if} 80 + {#if !(typeof src === 'string') && blur} 81 + <div 82 + class="image-blur pointer-events-none absolute inset-0 rounded-2xl backdrop-blur-md data-[loaded=true]:hidden" 83 + data-loaded={loaded} 84 + ></div> 85 + {/if} 86 + </div> 87 + 88 + <noscript> 89 + <style> 90 + .image-blur { 91 + display: none; 92 + } 93 + </style> 94 + </noscript>
+60
src/lib/components/base/input/Input.svelte
··· 1 + <script lang="ts" module> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import { type VariantProps, tv } from 'tailwind-variants'; 4 + import { cn } from '$lib/utils'; 5 + 6 + import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements'; 7 + 8 + export const inputVariants = tv({ 9 + base: 'focus:ring-2 ring-1 ring-inset border-0 focus:transition-transform rounded-2xl text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed duration-300 active:duration-100', 10 + variants: { 11 + variant: { 12 + primary: 13 + 'focus:ring-accent-500 dark:focus:ring-accent-500 ring-accent-500/30 dark:ring-accent-500/20 bg-accent-400/5 dark:bg-accent-600/5 text-accent-700 dark:text-accent-400 placeholder:text-accent-700/50 dark:placeholder:text-accent-400/50', 14 + secondary: 15 + 'focus:ring-base-800 dark:focus:ring-base-200 bg-base-100/50 dark:bg-base-900/50 text-base-900 dark:text-base-50 ring-base-200 dark:ring-base-800 placeholder:text-base-900/50 dark:placeholder:text-base-50/50' 16 + }, 17 + sizeVariant: { 18 + default: 'px-3 py-1.5 text-base', 19 + sm: 'px-3 text-xs py-1.5 font-base', 20 + lg: 'px-4 text-lg py-2 font-semibold' 21 + } 22 + }, 23 + defaultVariants: { 24 + variant: 'primary', 25 + sizeVariant: 'default' 26 + } 27 + }); 28 + 29 + export type InputVariant = VariantProps<typeof inputVariants>['variant']; 30 + export type InputSize = VariantProps<typeof inputVariants>['sizeVariant']; 31 + 32 + type InputType = Exclude<HTMLInputTypeAttribute, 'file'>; 33 + 34 + export type InputProps = WithElementRef< 35 + Omit<HTMLInputAttributes, 'type'> & { type?: InputType } 36 + > & { 37 + variant?: InputVariant; 38 + sizeVariant?: InputSize; 39 + }; 40 + </script> 41 + 42 + <script lang="ts"> 43 + let { 44 + ref = $bindable(null), 45 + value = $bindable(), 46 + type, 47 + class: className, 48 + variant = 'primary', 49 + sizeVariant = 'default', 50 + ...restProps 51 + }: InputProps = $props(); 52 + </script> 53 + 54 + <input 55 + bind:this={ref} 56 + class={cn(inputVariants({ variant, sizeVariant }), className)} 57 + {type} 58 + bind:value 59 + {...restProps} 60 + />
+16
src/lib/components/base/input/index.ts
··· 1 + import Root, { 2 + type InputProps, 3 + type InputSize, 4 + type InputVariant, 5 + inputVariants 6 + } from './Input.svelte'; 7 + 8 + export { 9 + Root, 10 + type InputProps as Props, 11 + Root as Input, 12 + inputVariants, 13 + type InputProps, 14 + type InputSize, 15 + type InputVariant 16 + };
+19
src/lib/components/base/label/Label.svelte
··· 1 + <script lang="ts"> 2 + import { Label as LabelPrimitive } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + class: className, 8 + ...restProps 9 + }: LabelPrimitive.RootProps = $props(); 10 + </script> 11 + 12 + <LabelPrimitive.Root 13 + bind:ref 14 + class={cn( 15 + 'text-base-900 dark:text-base-50 text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 16 + className 17 + )} 18 + {...restProps} 19 + />
+7
src/lib/components/base/label/index.ts
··· 1 + import Root from './Label.svelte'; 2 + 3 + export { 4 + Root, 5 + // 6 + Root as Label 7 + };
+105
src/lib/components/base/modal/BlueskyLoginModal.svelte
··· 1 + <script lang="ts" module> 2 + export const blueskyLoginModalState = $state({ 3 + open: false, 4 + show: () => (blueskyLoginModalState.open = true), 5 + hide: () => (blueskyLoginModalState.open = false) 6 + }); 7 + </script> 8 + 9 + <script lang="ts"> 10 + import { Button } from '../button'; 11 + import Modal from './Modal.svelte'; 12 + import { Subheading } from '../heading'; 13 + import { Label } from '../label'; 14 + import Input from '../input/Input.svelte'; 15 + 16 + let value = $state(''); 17 + let error: string | null = $state(null); 18 + let loading = $state(false); 19 + 20 + let { 21 + login 22 + }: { 23 + login: (handle: string) => Promise<void>; 24 + } = $props(); 25 + 26 + async function onSubmit(evt: Event) { 27 + evt.preventDefault(); 28 + if (loading) return; 29 + 30 + error = null; 31 + loading = true; 32 + 33 + try { 34 + await login(value); 35 + } catch (err) { 36 + error = err instanceof Error ? err.message : String(err); 37 + } finally { 38 + loading = false; 39 + } 40 + } 41 + 42 + let input: HTMLInputElement | null = $state(null); 43 + </script> 44 + 45 + <Modal 46 + bind:open={blueskyLoginModalState.open} 47 + class="max-w-sm gap-2 p-4 sm:p-6" 48 + onOpenAutoFocus={(e) => { 49 + e.preventDefault(); 50 + input?.focus(); 51 + }} 52 + > 53 + <form onsubmit={onSubmit} class="flex flex-col gap-2"> 54 + <Subheading class="mb-1 inline-flex items-start gap-2 text-xl font-bold"> 55 + <svg 56 + fill="currentColor" 57 + xmlns="http://www.w3.org/2000/svg" 58 + viewBox="-40 -40 680 620" 59 + version="1.1" 60 + class={['text-accent-600 dark:text-accent-400 size-9 -mt-1']} 61 + aria-hidden="true" 62 + > 63 + <path 64 + d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" 65 + /> 66 + </svg> 67 + Login with Bluesky to save & share your room</Subheading 68 + > 69 + 70 + <div class="text-base-600 dark:text-base-400 text-xs leading-5"> 71 + Don't have an account? 72 + <br /> 73 + <a 74 + href="https://bsky.app" 75 + target="_blank" 76 + class="text-accent-600 dark:text-accent-400 dark:hover:text-accent-500 hover:text-accent-500 font-medium transition-colors" 77 + > 78 + Sign up on bluesky 79 + </a>, then come back here. 80 + </div> 81 + 82 + <div class="mt-4 w-full"> 83 + <Label for="bluesky-handle" class="text-sm">Your handle</Label> 84 + <div class="mt-2"> 85 + <Input 86 + bind:ref={input} 87 + type="text" 88 + name="bluesky-handle" 89 + id="bluesky-handle" 90 + placeholder="yourname.bsky.social" 91 + class="w-full" 92 + bind:value 93 + /> 94 + </div> 95 + </div> 96 + 97 + {#if error} 98 + <p class="text-accent-500 mt-2 text-sm font-medium">{error}</p> 99 + {/if} 100 + 101 + <Button type="submit" class="mt-2 ml-auto w-full md:w-auto" disabled={loading} 102 + >{loading ? 'Loading...' : 'Login'}</Button 103 + > 104 + </form> 105 + </Modal>
+164
src/lib/components/base/modal/Modal.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from 'svelte'; 3 + import { Dialog, type WithoutChild } from 'bits-ui'; 4 + import { Button, type ButtonProps } from '../button'; 5 + import { cn } from '$lib/utils'; 6 + 7 + type Props = Dialog.RootProps & { 8 + title?: string; 9 + titleSnippet?: Snippet; 10 + titleClass?: string; 11 + description?: string; 12 + descriptionSnippet?: Snippet; 13 + descriptionClass?: string; 14 + interactOutsideBehavior?: 'close' | 'ignore'; 15 + closeButton?: boolean; 16 + contentProps?: WithoutChild<Dialog.ContentProps>; 17 + 18 + yesButton?: 19 + | boolean 20 + | { 21 + label?: string; 22 + onclick?: () => void; 23 + variant?: ButtonProps['variant']; 24 + disabled?: boolean; 25 + class?: string; 26 + }; 27 + 28 + noButton?: 29 + | boolean 30 + | { 31 + label?: string; 32 + onclick?: () => void; 33 + variant?: ButtonProps['variant']; 34 + disabled?: boolean; 35 + class?: string; 36 + }; 37 + 38 + class?: string; 39 + 40 + onOpenAutoFocus?: (event: Event) => void; 41 + }; 42 + 43 + let { 44 + open = $bindable(false), 45 + children, 46 + contentProps, 47 + title, 48 + titleSnippet, 49 + titleClass, 50 + description, 51 + descriptionSnippet, 52 + descriptionClass, 53 + interactOutsideBehavior = 'close', 54 + closeButton = true, 55 + yesButton, 56 + noButton, 57 + class: className, 58 + onOpenAutoFocus, 59 + ...restProps 60 + }: Props = $props(); 61 + 62 + let yesButtonRef = $state<HTMLButtonElement | null>(null); 63 + </script> 64 + 65 + <Dialog.Root bind:open {...restProps}> 66 + <Dialog.Portal> 67 + <Dialog.Overlay 68 + class="motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-base-200/10 dark:bg-base-900/10 fixed inset-0 z-50 backdrop-blur-sm" 69 + /> 70 + <Dialog.Content 71 + {onOpenAutoFocus} 72 + {interactOutsideBehavior} 73 + {...contentProps} 74 + class={cn( 75 + 'motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-bottom-1/2 data-[state=open]:slide-in-from-bottom-1/2', 76 + 'fixed left-[50%] z-50 grid w-[calc(100%-1rem)] max-w-lg translate-x-[-50%] top-[50%] translate-y-[-50%]', 77 + 'bg-base-50 dark:bg-base-900 border-base-200/80 dark:border-base-800 gap-4 rounded-2xl border p-6', 78 + className 79 + )} 80 + > 81 + {#if title} 82 + <Dialog.Title class="text-base-900 dark:text-base-100 text-lg font-semibold" 83 + >{title}</Dialog.Title 84 + > 85 + {/if} 86 + {#if titleSnippet} 87 + <Dialog.Title class={titleClass}> 88 + {@render titleSnippet()} 89 + </Dialog.Title> 90 + {/if} 91 + {#if description} 92 + <Dialog.Description class="text-base-600 dark:text-base-400 text-sm" 93 + >{description}</Dialog.Description 94 + > 95 + {/if} 96 + {#if descriptionSnippet} 97 + <Dialog.Description class={descriptionClass}> 98 + {@render descriptionSnippet?.()} 99 + </Dialog.Description> 100 + {/if} 101 + 102 + {#if yesButton || noButton} 103 + <div class="flex flex-col items-stretch justify-end gap-2 md:flex-row md:items-center"> 104 + {#if yesButton} 105 + <Button 106 + bind:ref={yesButtonRef} 107 + onclick={() => { 108 + open = false; 109 + if (typeof yesButton === 'object') { 110 + yesButton.onclick?.(); 111 + } 112 + }} 113 + class={cn('order-last', typeof yesButton === 'object' ? yesButton.class : '')} 114 + variant={typeof yesButton === 'object' ? yesButton?.variant || 'primary' : 'primary'} 115 + disabled={typeof yesButton === 'object' ? yesButton?.disabled : false} 116 + > 117 + {typeof yesButton === 'object' ? yesButton.label || 'Yes' : 'Yes'} 118 + </Button> 119 + {/if} 120 + {#if noButton} 121 + <Button 122 + onclick={() => { 123 + open = false; 124 + if (typeof noButton === 'object') { 125 + noButton.onclick?.(); 126 + } 127 + }} 128 + class={cn(typeof noButton === 'object' ? noButton.class : '')} 129 + variant={typeof noButton === 'object' 130 + ? noButton?.variant || 'secondary' 131 + : 'secondary'} 132 + disabled={typeof noButton === 'object' ? noButton?.disabled : false} 133 + > 134 + {typeof noButton === 'object' ? noButton.label || 'No' : 'No'} 135 + </Button> 136 + {/if} 137 + </div> 138 + {/if} 139 + 140 + {@render children?.()} 141 + 142 + {#if closeButton} 143 + <Dialog.Close 144 + class="text-base-900 dark:text-base-500 hover:text-base-800 dark:hover:text-base-200 hover:bg-base-200 dark:hover:bg-base-800 focus:outline-base-900 dark:focus:outline-base-50 focus:bg-base-200 dark:focus:bg-base-800 focus:text-base-800 dark:focus:text-base-200 absolute top-2 right-2 cursor-pointer rounded-xl p-1 transition-colors focus:outline-2 focus:outline-offset-2" 145 + > 146 + <svg 147 + xmlns="http://www.w3.org/2000/svg" 148 + viewBox="0 0 24 24" 149 + fill="currentColor" 150 + class="size-4" 151 + > 152 + <path 153 + fill-rule="evenodd" 154 + d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z" 155 + clip-rule="evenodd" 156 + /> 157 + </svg> 158 + 159 + <span class="sr-only">Close</span> 160 + </Dialog.Close> 161 + {/if} 162 + </Dialog.Content> 163 + </Dialog.Portal> 164 + </Dialog.Root>
+63
src/lib/components/base/navbar/Navbar.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + const { 7 + class: className, 8 + children, 9 + hasSidebar = false, 10 + ...restProps 11 + }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { hasSidebar?: boolean } = $props(); 12 + </script> 13 + 14 + <div 15 + class={cn( 16 + 'header fixed top-0 right-0 left-0 z-50 flex h-16 items-center justify-between p-2', 17 + hasSidebar ? 'lg:left-72 xl:left-80' : '', 18 + className 19 + )} 20 + {...restProps} 21 + > 22 + {@render children?.()} 23 + <div 24 + class="backdrop from-base-50/90 via-base-50/0 dark:from-base-950/90 dark:via-base-950/0 pointer-events-none absolute inset-0 -z-10 bg-linear-to-b backdrop-blur-md" 25 + ></div> 26 + 27 + <div 28 + class="backdrop-edge bg-base-900/10 dark:bg-base-500/10 pointer-events-none absolute right-0 bottom-0 left-0 -z-10 translate-y-full backdrop-blur-sm" 29 + ></div> 30 + </div> 31 + 32 + <style> 33 + /** better frosted glass effect adopted from https://www.joshwcomeau.com/css/backdrop-filter/ */ 34 + .header { 35 + --thickness: 1px; 36 + } 37 + 38 + @supports (mask-image: none) or (-webkit-mask-image: none) { 39 + .backdrop { 40 + height: 200%; 41 + -webkit-mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%); 42 + mask-image: linear-gradient(to bottom, black 0% 50%, transparent 50% 100%); 43 + } 44 + .backdrop-edge { 45 + height: 100%; 46 + inset: 0; 47 + -webkit-mask-image: linear-gradient( 48 + to bottom, 49 + black 0, 50 + black var(--thickness), 51 + transparent var(--thickness) 52 + ); 53 + mask-image: linear-gradient( 54 + to bottom, 55 + black 0, 56 + black var(--thickness), 57 + transparent var(--thickness) 58 + ); 59 + 60 + filter: brightness(1.5); 61 + } 62 + } 63 + </style>
+205
src/lib/components/base/number-input/NumberInput.svelte
··· 1 + <script lang="ts" module> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'; 4 + import { type VariantProps, tv } from 'tailwind-variants'; 5 + import { cn } from '$lib/utils'; 6 + 7 + export const numberInputVariants = tv({ 8 + base: 'group flex w-full max-w-44 touch-manipulation items-stretch justify-between rounded-2xl ring focus-within:ring-2', 9 + variants: { 10 + variant: { 11 + primary: 12 + 'bg-accent-500/5 focus-within:ring-accent-400 dark:focus-within:ring-accent-600 text-accent-500 ring-accent-500/20 dark:ring-accent-500/20', 13 + secondary: 14 + '', 15 + }, 16 + size: { 17 + default: 'text-base font-medium max-w-32', 18 + sm: 'text-sm', 19 + lg: 'text-3xl font-semibold max-w-44', 20 + } 21 + }, 22 + defaultVariants: { 23 + variant: 'primary', 24 + size: 'default' 25 + } 26 + }); 27 + 28 + export const numberInputButtonVariants = tv({ 29 + base: 'button-number-input flex cursor-pointer items-center pr-[.5em] pl-[.5em] transition-colors disabled:cursor-not-allowed disabled:opacity-50', 30 + variants: { 31 + variant: { 32 + primary: 'hover:text-accent-600 dark:hover:text-accent-400 disabled:hover:text-accent-500 dark:disabled:hover:text-accent-500', 33 + secondary: 'hover:text-base-600 dark:hover:text-base-400 disabled:hover:text-base-500 dark:disabled:hover:text-base-500', 34 + }, 35 + }, 36 + }); 37 + 38 + export const numberInputInputVariants = tv({ 39 + base: 'number-input w-full [appearance:textfield] border-0 bg-transparent text-center font-[inherit] font-semibold text-transparent outline-none [-moz-appearance:_textfield] focus-visible:ring-0 [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none', 40 + variants: { 41 + variant: { 42 + primary: '', 43 + secondary: '', 44 + }, 45 + size: { 46 + default: 'p-1', 47 + sm: '', 48 + lg: 'p-2', 49 + } 50 + }, 51 + }); 52 + export type NumberInputVariant = VariantProps<typeof numberInputVariants>['variant']; 53 + export type NumberInputSize = VariantProps<typeof numberInputVariants>['size']; 54 + 55 + export type NumberInputProps = WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 56 + variant?: NumberInputVariant; 57 + size?: NumberInputSize; 58 + min?: number; 59 + value?: number; 60 + max?: number; 61 + defaultValue?: number; 62 + class?: string; 63 + step?: number; 64 + inputRef?: HTMLInputElement | null; 65 + }; 66 + </script> 67 + 68 + <script lang="ts"> 69 + import NumberFlow from '@number-flow/svelte'; 70 + import type { WithoutChildrenOrChild } from 'bits-ui'; 71 + import type { HTMLAttributes } from 'svelte/elements'; 72 + 73 + let { 74 + min = 0, 75 + value = $bindable(50), 76 + max = 999, 77 + defaultValue = 0, 78 + step = 1, 79 + class: className, 80 + ref = $bindable(null), 81 + inputRef = $bindable(null), 82 + tabindex = undefined, 83 + variant = 'primary', 84 + size = 'default', 85 + ...restProps 86 + }: NumberInputProps = $props(); 87 + 88 + let input: HTMLInputElement; 89 + let animated = $state(true); 90 + let showCaret = $state(true); 91 + function handleInput() { 92 + if (!inputRef) return; 93 + animated = false; 94 + let next = value; 95 + if (inputRef.value === '') { 96 + next = defaultValue; 97 + } else { 98 + const num = parseInt(inputRef.value); 99 + if (!isNaN(num) && min <= num && num <= max) next = num; 100 + } 101 + inputRef.value = String(next); 102 + value = next; 103 + } 104 + function handlePointerDown(event: PointerEvent, diff: number) { 105 + animated = true; 106 + if (event.pointerType === 'mouse') { 107 + event?.preventDefault(); 108 + inputRef?.focus(); 109 + } 110 + const newVal = Math.min(Math.max(value + diff, min), max); 111 + value = newVal; 112 + } 113 + </script> 114 + 115 + <div 116 + class={cn( 117 + numberInputVariants({ variant, size }), 118 + className 119 + )} 120 + bind:this={ref} 121 + {...restProps} 122 + > 123 + <button 124 + aria-hidden="true" 125 + tabindex={-1} 126 + class={numberInputButtonVariants({ variant })} 127 + disabled={min != null && value <= min} 128 + onpointerdown={(event) => handlePointerDown(event, -step)} 129 + > 130 + <svg 131 + xmlns="http://www.w3.org/2000/svg" 132 + fill="none" 133 + viewBox="0 0 24 24" 134 + stroke-width="3.5" 135 + stroke="currentColor" 136 + class="size-4" 137 + > 138 + <path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" /> 139 + </svg> 140 + </button> 141 + <div 142 + class="w-full relative grid items-center justify-items-center text-center [grid-template-areas:'overlap'] *:[grid-area:overlap]" 143 + > 144 + <input 145 + class={cn( 146 + numberInputInputVariants({ variant, size }), 147 + showCaret ? 'caret-accent-500' : 'caret-transparent', 148 + )} 149 + style="font-kerning: none" 150 + type="number" 151 + {min} 152 + step="1" 153 + autocomplete="off" 154 + inputmode="numeric" 155 + {max} 156 + {value} 157 + oninput={handleInput} 158 + bind:this={inputRef} 159 + tabindex={tabindex} 160 + /> 161 + <NumberFlow 162 + {value} 163 + locales="en-US" 164 + format={{ useGrouping: false }} 165 + aria-hidden="true" 166 + {animated} 167 + on:animationsstart={() => (showCaret = false)} 168 + on:animationsfinish={() => (showCaret = true)} 169 + class="pointer-events-none number-flow" 170 + willChange 171 + /> 172 + </div> 173 + <button 174 + aria-hidden="true" 175 + tabindex="-1" 176 + class={numberInputButtonVariants({variant})} 177 + disabled={max != null && value >= max} 178 + onpointerdown={(event) => handlePointerDown(event, step)} 179 + > 180 + <svg 181 + xmlns="http://www.w3.org/2000/svg" 182 + fill="none" 183 + viewBox="0 0 24 24" 184 + stroke-width="3.5" 185 + stroke="currentColor" 186 + class="size-4" 187 + > 188 + <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 189 + </svg> 190 + </button> 191 + </div> 192 + 193 + <noscript> 194 + <style> 195 + .button-number-input { 196 + display: none; 197 + } 198 + .number-input { 199 + color: var(--color-accent-500); 200 + } 201 + .number-flow { 202 + display: none; 203 + } 204 + </style> 205 + </noscript>
+17
src/lib/components/base/popover/index.ts
··· 1 + import { Popover as PopoverPrimitive } from 'bits-ui'; 2 + import Content from './popover-content.svelte'; 3 + const Root = PopoverPrimitive.Root; 4 + const Trigger = PopoverPrimitive.Trigger; 5 + const Close = PopoverPrimitive.Close; 6 + 7 + export { 8 + Root, 9 + Content, 10 + Trigger, 11 + Close, 12 + // 13 + Root as Popover, 14 + Content as PopoverContent, 15 + Trigger as PopoverTrigger, 16 + Close as PopoverClose 17 + };
+28
src/lib/components/base/popover/popover-content.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils.js'; 3 + import { Popover as PopoverPrimitive } from 'bits-ui'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + class: className, 8 + sideOffset = 4, 9 + align = 'center', 10 + portalProps, 11 + ...restProps 12 + }: PopoverPrimitive.ContentProps & { 13 + portalProps?: PopoverPrimitive.PortalProps; 14 + } = $props(); 15 + </script> 16 + 17 + <PopoverPrimitive.Portal {...portalProps}> 18 + <PopoverPrimitive.Content 19 + bind:ref 20 + {sideOffset} 21 + {align} 22 + class={cn( 23 + 'bg-base-100 dark:bg-base-900 border-base-200 dark:border-base-800 text-base-900 dark:text-base-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-2xl border p-4 shadow-md outline-none', 24 + className 25 + )} 26 + {...restProps} 27 + /> 28 + </PopoverPrimitive.Portal>
+63
src/lib/components/base/scroll-area/ScrollArea.svelte
··· 1 + <script lang="ts"> 2 + import { ScrollArea, type WithoutChild } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + type Props = WithoutChild<ScrollArea.RootProps> & { 6 + orientation?: 'vertical' | 'horizontal' | 'both'; 7 + viewportClasses?: string; 8 + scrollbarYClasses?: string; 9 + scrollbarXClasses?: string; 10 + }; 11 + 12 + let { 13 + ref = $bindable(null), 14 + orientation = 'vertical', 15 + viewportClasses, 16 + class: className, 17 + scrollbarYClasses = '', 18 + scrollbarXClasses = '', 19 + children, 20 + ...restProps 21 + }: Props = $props(); 22 + </script> 23 + 24 + {#snippet Scrollbar({ 25 + orientation, 26 + class: className, 27 + ...restProps 28 + }: { 29 + orientation: 'vertical' | 'horizontal'; 30 + class: string; 31 + [key: string]: any; 32 + })} 33 + <ScrollArea.Scrollbar 34 + {orientation} 35 + class={cn( 36 + 'z-10 flex touch-none transition-colors select-none', 37 + orientation === 'vertical' && 'h-full w-2 border-l border-l-transparent p-px', 38 + orientation === 'horizontal' && 'h-2 w-full border-t border-t-transparent p-px', 39 + className 40 + )} 41 + {...restProps} 42 + > 43 + <ScrollArea.Thumb 44 + class={[ 45 + 'bg-base-300 hover:bg-base-400 dark:bg-base-700 dark:hover:bg-base-600 relative rounded-full transition-colors', 46 + orientation === 'vertical' && 'flex-1' 47 + ]} 48 + /> 49 + </ScrollArea.Scrollbar> 50 + {/snippet} 51 + 52 + <ScrollArea.Root bind:ref {...restProps} class={cn('relative overflow-hidden', className)}> 53 + <ScrollArea.Viewport class={cn('h-full w-full rounded-[inherit]', viewportClasses)}> 54 + {@render children?.()} 55 + </ScrollArea.Viewport> 56 + {#if orientation === 'vertical' || orientation === 'both'} 57 + {@render Scrollbar({ orientation: 'vertical', class: scrollbarYClasses })} 58 + {/if} 59 + {#if orientation === 'horizontal' || orientation === 'both'} 60 + {@render Scrollbar({ orientation: 'horizontal', class: scrollbarXClasses })} 61 + {/if} 62 + <ScrollArea.Corner /> 63 + </ScrollArea.Root>
+45
src/lib/components/base/select/Select.svelte
··· 1 + <script lang="ts"> 2 + import { Toolbar, type WithoutChildrenOrChild } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + type Item = 6 + | { 7 + label: string; 8 + value: string; 9 + } 10 + | string; 11 + 12 + let { 13 + ref = $bindable(null), 14 + 15 + class: className, 16 + items = $bindable([]), 17 + selected = $bindable(), 18 + 19 + ...restProps 20 + }: WithoutChildrenOrChild<Toolbar.RootProps> & { 21 + selected?: string; 22 + items: Item[]; 23 + } = $props(); 24 + </script> 25 + 26 + <Toolbar.Root 27 + bind:ref 28 + class={cn( 29 + 'border-accent-300/10 bg-accent-300/5 dark:border-accent-600/5 dark:bg-accent-700/5 flex min-w-max items-center justify-center gap-4 rounded-full border', 30 + className 31 + )} 32 + {...restProps} 33 + > 34 + <Toolbar.Group bind:value={selected} type="single" class="flex items-center gap-x-1 text-sm"> 35 + {#each items as item} 36 + <Toolbar.GroupItem 37 + aria-label="align left" 38 + value={typeof item === 'string' ? item : item.value} 39 + class="text-base-600 hover:text-accent-600 dark:text-base-300 dark:hover:text-accent-400 outline-accent-200 dark:outline-accent-900/50 data-[state=on]:text-accent-600 dark:data-[state=on]:text-accent-400 dark:data-[state=on]:bg-accent-700/10 data-[state=on]:bg-accent-500/10 inline-flex cursor-pointer items-center justify-center rounded-2xl p-1 px-2 transition-all data-[state=on]:outline" 40 + > 41 + {typeof item === 'string' ? item : item.label} 42 + </Toolbar.GroupItem> 43 + {/each} 44 + </Toolbar.Group> 45 + </Toolbar.Root>
+60
src/lib/components/base/sidebar/Sidebar.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLAttributes } from 'svelte/elements'; 4 + import ScrollArea from '../scroll-area/ScrollArea.svelte'; 5 + import { cn } from '$lib/utils'; 6 + 7 + const { 8 + class: className, 9 + mobileClasses, 10 + children, 11 + ...restProps 12 + }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { 13 + mobileClasses?: string; 14 + } = $props(); 15 + </script> 16 + 17 + <div 18 + popover="auto" 19 + id="mobile-menu" 20 + class={cn( 21 + 'mobileMenuPopover border-base-200 bg-base-50 dark:border-base-300/10 dark:bg-base-950 inset-auto top-0 left-0 block h-[100dvh] w-72 border-r shadow-lg lg:hidden', 22 + className, 23 + mobileClasses 24 + )} 25 + > 26 + <ScrollArea type="hover" class="h-full"> 27 + {@render children?.()} 28 + </ScrollArea> 29 + </div> 30 + 31 + <div 32 + class={cn( 33 + 'border-base-200 bg-base-50 dark:border-base-300/10 dark:bg-base-950 fixed top-0 bottom-0 left-0 z-40 hidden h-full -translate-x-72 overflow-y-auto border-r lg:block lg:w-72 lg:translate-x-0 xl:w-80', 34 + className 35 + )} 36 + > 37 + <ScrollArea type="hover" class="h-full"> 38 + {@render children?.()} 39 + </ScrollArea> 40 + </div> 41 + 42 + <style> 43 + .mobileMenuPopover { 44 + /* Closed state transformed off the screen */ 45 + transform: translateX(-110%); 46 + transition: 47 + transform 0.1s, 48 + overlay 0.1s ease-out; 49 + 50 + /* Styles while the menu is open */ 51 + &:popover-open { 52 + transform: translateX(0); 53 + } 54 + 55 + /* Backdrop that overlays other content */ 56 + &::backdrop { 57 + background-color: rgba(0, 0, 0, 0.5); 58 + } 59 + } 60 + </style>
+7
src/lib/components/base/slider/index.ts
··· 1 + import Root from './slider.svelte'; 2 + 3 + export { 4 + Root, 5 + // 6 + Root as Slider 7 + };
+51
src/lib/components/base/slider/slider.svelte
··· 1 + <script lang="ts"> 2 + import { Slider as SliderPrimitive, type WithoutChildrenOrChild } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + value = $bindable(), 8 + orientation = 'horizontal', 9 + class: className, 10 + tabindex = undefined, 11 + ...restProps 12 + }: WithoutChildrenOrChild<SliderPrimitive.RootProps> = $props(); 13 + </script> 14 + 15 + <!-- 16 + Discriminated Unions + Destructing (required for bindable) do not 17 + get along, so we shut typescript up by casting `value` to `never`. 18 + --> 19 + <SliderPrimitive.Root 20 + bind:ref 21 + bind:value={value as never} 22 + {orientation} 23 + class={cn( 24 + "relative flex touch-none items-center select-none data-[orientation='horizontal']:w-full data-[orientation='vertical']:h-full data-[orientation='vertical']:min-h-44 data-[orientation='vertical']:w-auto data-[orientation='vertical']:flex-col", 25 + className 26 + )} 27 + tabindex={tabindex} 28 + {...restProps} 29 + > 30 + {#snippet children({ thumbs })} 31 + <span 32 + data-orientation={orientation} 33 + class="relative grow overflow-hidden rounded-full data-[orientation='horizontal']:h-2 data-[orientation='horizontal']:w-full data-[orientation='vertical']:h-full data-[orientation='vertical']:w-2" 34 + > 35 + <div 36 + class="bg-base-300/30 dark:bg-base-800 border-base-400/50 dark:border-base-700 absolute inset-0 rounded-full border" 37 + ></div> 38 + 39 + <SliderPrimitive.Range 40 + class="bg-accent-400 dark:bg-accent-600 absolute rounded-full data-[orientation='horizontal']:h-full data-[orientation='vertical']:w-full" 41 + /> 42 + </span> 43 + {#each thumbs as thumb} 44 + <SliderPrimitive.Thumb 45 + index={thumb} 46 + class="border-accent-400 dark:border-accent-600 bg-accent-600 dark:bg-accent-800 focus-visible:outline-accent-700 dark:focus-visible:outline-accent-400 block size-6 rounded-full border-2 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50" 47 + tabindex={tabindex} 48 + /> 49 + {/each} 50 + {/snippet} 51 + </SliderPrimitive.Root>
+73
src/lib/components/base/sonner/Toaster.svelte
··· 1 + <script lang="ts"> 2 + import { Toaster as SonnerToaster } from 'svelte-sonner'; 3 + 4 + const colorClasses = { 5 + blue: 'bg-blue-200/50 dark:bg-blue-950/50 text-blue-600 [&_.title]:text-blue-800 [&_.description]:text-blue-700 dark:text-blue-500 border-blue-700/20 dark:border-blue-500/20 dark:[&_.title]:text-blue-300 dark:[&_.description]:text-blue-400', 6 + red: 'bg-red-200/50 dark:bg-red-950/50 text-red-600 [&_.title]:text-red-800 [&_.description]:text-red-700 dark:text-red-500 border-red-700/20 dark:border-red-500/20 dark:[&_.title]:text-red-300 dark:[&_.description]:text-red-400', 7 + yellow: 8 + 'bg-yellow-200/50 dark:bg-yellow-950/50 text-yellow-600 [&_.title]:text-yellow-800 [&_.description]:text-yellow-700 dark:text-yellow-500 border-yellow-700/20 dark:border-yellow-500/20 dark:[&_.title]:text-yellow-300 dark:[&_.description]:text-yellow-400', 9 + green: 10 + 'bg-green-200/50 dark:bg-green-950/50 text-green-600 [&_.title]:text-green-800 [&_.description]:text-green-700 dark:text-green-500 border-green-700/20 dark:border-green-500/20 dark:[&_.title]:text-green-300 dark:[&_.description]:text-green-400', 11 + indigo: 12 + 'bg-indigo-200/50 dark:bg-indigo-950/50 text-indigo-600 [&_.title]:text-indigo-800 [&_.description]:text-indigo-700 dark:text-indigo-500 border-indigo-700/20 dark:border-indigo-500/20 dark:[&_.title]:text-indigo-300 dark:[&_.description]:text-indigo-400', 13 + purple: 14 + 'bg-purple-200/50 dark:bg-purple-950/50 text-purple-600 [&_.title]:text-purple-800 [&_.description]:text-purple-700 dark:text-purple-500 border-purple-700/20 dark:border-purple-500/20 dark:[&_.title]:text-purple-300 dark:[&_.description]:text-purple-400', 15 + pink: 'bg-pink-200/50 dark:bg-pink-950/50 text-pink-600 [&_.title]:text-pink-800 [&_.description]:text-pink-700 dark:text-pink-500 border-pink-700/20 dark:border-pink-500/20 dark:[&_.title]:text-pink-300 dark:[&_.description]:text-pink-400', 16 + orange: 17 + 'bg-orange-200/50 dark:bg-orange-950/50 text-orange-600 [&_.title]:text-orange-800 [&_.description]:text-orange-700 dark:text-orange-500 border-orange-700/20 dark:border-orange-500/20 dark:[&_.title]:text-orange-300 dark:[&_.description]:text-orange-400', 18 + teal: 'bg-teal-200/50 dark:bg-teal-950/50 text-teal-600 [&_.title]:text-teal-800 [&_.description]:text-teal-700 dark:text-teal-500 border-teal-700/20 dark:border-teal-500/20 dark:[&_.title]:text-teal-300 dark:[&_.description]:text-teal-400', 19 + emerald: 20 + 'bg-emerald-200/50 dark:bg-emerald-950/50 text-emerald-600 [&_.title]:text-emerald-800 [&_.description]:text-emerald-700 dark:text-emerald-500 border-emerald-700/20 dark:border-emerald-500/20 dark:[&_.title]:text-emerald-300 dark:[&_.description]:text-emerald-400', 21 + lime: 'bg-lime-200/50 dark:bg-lime-950/50 text-lime-600 [&_.title]:text-lime-800 [&_.description]:text-lime-700 dark:text-lime-500 border-lime-700/20 dark:border-lime-500/20 dark:[&_.title]:text-lime-300 dark:[&_.description]:text-lime-400', 22 + cyan: 'bg-cyan-200/50 dark:bg-cyan-950/50 text-cyan-600 [&_.title]:text-cyan-800 [&_.description]:text-cyan-700 dark:text-cyan-500 border-cyan-700/20 dark:border-cyan-500/20 dark:[&_.title]:text-cyan-300 dark:[&_.description]:text-cyan-400', 23 + sky: 'bg-sky-200/50 dark:bg-sky-950/50 text-sky-600 [&_.title]:text-sky-800 [&_.description]:text-sky-700 dark:text-sky-500 border-sky-700/20 dark:border-sky-500/20 dark:[&_.title]:text-sky-300 dark:[&_.description]:text-sky-400', 24 + rose: 'bg-rose-200/50 dark:bg-rose-950/50 text-rose-600 [&_.title]:text-rose-800 [&_.description]:text-rose-700 dark:text-rose-500 border-rose-700/20 dark:border-rose-500/20 dark:[&_.title]:text-rose-300 dark:[&_.description]:text-rose-400', 25 + amber: 26 + 'bg-amber-200/50 dark:bg-amber-950/50 text-amber-600 [&_.title]:text-amber-800 [&_.description]:text-amber-700 dark:text-amber-500 border-amber-700/20 dark:border-amber-500/20 dark:[&_.title]:text-amber-300 dark:[&_.description]:text-amber-400', 27 + violet: 28 + 'bg-violet-200/50 dark:bg-violet-950/50 text-violet-600 [&_.title]:text-violet-800 [&_.description]:text-violet-700 dark:text-violet-500 border-violet-700/20 dark:border-violet-500/20 dark:[&_.title]:text-violet-300 dark:[&_.description]:text-violet-400', 29 + fuchsia: 30 + 'bg-fuchsia-200/50 dark:bg-fuchsia-950/50 text-fuchsia-600 [&_.title]:text-fuchsia-800 [&_.description]:text-fuchsia-700 dark:text-fuchsia-500 border-fuchsia-700/20 dark:border-fuchsia-500/20 dark:[&_.title]:text-fuchsia-300 dark:[&_.description]:text-fuchsia-400', 31 + base: 'bg-base-50/80 border-base-200 dark:bg-base-900/50 dark:border-base-800 [&_.title]:text-base-900 dark:[&_.title]:text-base-50 [&_.description]:text-base-800 dark:[&_.description]:text-base-100', 32 + accent: 33 + 'bg-accent-200/50 dark:bg-accent-950/50 text-accent-600 [&_.title]:text-accent-800 [&_.description]:text-accent-700 dark:text-accent-500 border-accent-700/20 dark:border-accent-500/20 dark:[&_.title]:text-accent-300 dark:[&_.description]:text-accent-400' 34 + }; 35 + 36 + type Colors = keyof typeof colorClasses; 37 + 38 + const { 39 + colors = { 40 + default: 'accent', 41 + success: 'green', 42 + error: 'red', 43 + info: 'blue' 44 + } 45 + }: { 46 + colors?: { 47 + default?: Colors; 48 + success?: Colors; 49 + error?: Colors; 50 + info?: Colors; 51 + }; 52 + } = $props(); 53 + </script> 54 + 55 + <SonnerToaster 56 + toastOptions={{ 57 + unstyled: true, 58 + classes: { 59 + toast: 60 + 'group toast min-w-12 w-fit sm:min-w-64 backdrop-blur-lg border rounded-2xl p-4 flex items-center gap-2 sm:fixed sm:top-4 sm:right-0 mx-2', 61 + title: 'text-base title', 62 + description: 'text-xs mt-1 description', 63 + 64 + default: colorClasses[colors?.default ?? 'accent'], 65 + loading: colorClasses[colors?.default ?? 'accent'], 66 + 67 + success: colorClasses[colors?.success ?? 'green'], 68 + error: colorClasses[colors?.error ?? 'red'], 69 + info: colorClasses[colors?.info ?? 'blue'] 70 + } 71 + }} 72 + position="top-right" 73 + />
+1
src/lib/components/base/sonner/index.ts
··· 1 + export { default as Toaster } from './Toaster.svelte';
+7
src/lib/components/base/switch/index.ts
··· 1 + import Root from './switch.svelte'; 2 + 3 + export { 4 + Root, 5 + // 6 + Root as Switch 7 + };
+27
src/lib/components/base/switch/switch.svelte
··· 1 + <script lang="ts"> 2 + import { Switch as SwitchPrimitive, type WithoutChildrenOrChild } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + class: className, 8 + checked = $bindable(false), 9 + ...restProps 10 + }: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props(); 11 + </script> 12 + 13 + <SwitchPrimitive.Root 14 + bind:ref 15 + bind:checked 16 + class={cn( 17 + 'data-[state=checked]:bg-accent-500/30 dark:data-[state=checked]:bg-accent-500/20 data-[state=checked]:border-accent-500/30 data-[state=unchecked]:bg-base-500/20 data-[state=unchecked]:border-base-400/30 peer focus-visible:outline-base-900 dark:focus-visible:outline-base-100 inline-flex h-6 w-10.5 shrink-0 cursor-pointer items-center rounded-full border transition-all hover:scale-105 focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:scale-100', 18 + className 19 + )} 20 + {...restProps} 21 + > 22 + <SwitchPrimitive.Thumb 23 + class={cn( 24 + 'bg-base-500 data-[state=checked]:bg-accent-600 dark:bg-base-600 dark:data-[state=checked]:bg-accent-500 pointer-events-none block size-5 rounded-full shadow-lg ring-0 motion-safe:transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0' 25 + )} 26 + /> 27 + </SwitchPrimitive.Root>
+20
src/lib/components/base/text/Text.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + let { 7 + ref = $bindable(null), 8 + class: className, 9 + children, 10 + ...restProps 11 + }: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props(); 12 + </script> 13 + 14 + <p 15 + bind:this={ref} 16 + class={cn('text-base-700 dark:text-base-200 text-sm font-medium', className)} 17 + {...restProps} 18 + > 19 + {@render children?.()} 20 + </p>
+3
src/lib/components/base/text/index.ts
··· 1 + import Text from './Text.svelte'; 2 + 3 + export { Text };
+28
src/lib/components/base/textarea/index.ts
··· 1 + import Root from './textarea.svelte'; 2 + 3 + type FormTextareaEvent<T extends Event = Event> = T & { 4 + currentTarget: EventTarget & HTMLTextAreaElement; 5 + }; 6 + 7 + type TextareaEvents = { 8 + blur: FormTextareaEvent<FocusEvent>; 9 + change: FormTextareaEvent<Event>; 10 + click: FormTextareaEvent<MouseEvent>; 11 + focus: FormTextareaEvent<FocusEvent>; 12 + keydown: FormTextareaEvent<KeyboardEvent>; 13 + keypress: FormTextareaEvent<KeyboardEvent>; 14 + keyup: FormTextareaEvent<KeyboardEvent>; 15 + mouseover: FormTextareaEvent<MouseEvent>; 16 + mouseenter: FormTextareaEvent<MouseEvent>; 17 + mouseleave: FormTextareaEvent<MouseEvent>; 18 + paste: FormTextareaEvent<ClipboardEvent>; 19 + input: FormTextareaEvent<InputEvent>; 20 + }; 21 + 22 + export { 23 + Root, 24 + // 25 + Root as Textarea, 26 + type TextareaEvents, 27 + type FormTextareaEvent 28 + };
+57
src/lib/components/base/textarea/textarea.svelte
··· 1 + <script lang="ts" module> 2 + import type { WithElementRef, WithoutChildren } from 'bits-ui'; 3 + import { type VariantProps, tv } from 'tailwind-variants'; 4 + import { cn } from '$lib/utils'; 5 + 6 + import type { HTMLTextareaAttributes } from 'svelte/elements'; 7 + 8 + export const inputVariants = tv({ 9 + base: 'focus:ring-2 ring-1 resize-none ring-inset border-0 focus:transition-transform rounded-2xl text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed duration-300 active:duration-100', 10 + variants: { 11 + variant: { 12 + primary: 13 + 'focus:ring-accent-500 dark:focus:ring-accent-500 ring-accent-500/30 dark:ring-accent-500/20 bg-accent-400/5 dark:bg-accent-600/5 text-accent-700 dark:text-accent-400 placeholder:text-accent-700/70 dark:placeholder:text-accent-400/70', 14 + secondary: 15 + 'focus:ring-base-800 dark:focus:ring-base-200 bg-base-100/50 dark:bg-base-900/50 text-base-900 dark:text-base-50 ring-base-200 dark:ring-base-800' 16 + }, 17 + sizeVariant: { 18 + default: 'px-3 py-1.5 text-base min-h-[80px]', 19 + sm: 'px-3 text-xs py-1.5 font-base min-h-[60px]', 20 + lg: 'px-4 text-lg py-2 font-semibold min-h-[100px]' 21 + } 22 + }, 23 + defaultVariants: { 24 + variant: 'primary', 25 + sizeVariant: 'default' 26 + } 27 + }); 28 + 29 + export type InputVariant = VariantProps<typeof inputVariants>['variant']; 30 + export type InputSize = VariantProps<typeof inputVariants>['sizeVariant']; 31 + 32 + export type InputProps = WithElementRef<HTMLTextareaAttributes> & { 33 + variant?: InputVariant; 34 + sizeVariant?: InputSize; 35 + }; 36 + </script> 37 + 38 + <script lang="ts"> 39 + let { 40 + ref = $bindable(null), 41 + value = $bindable(), 42 + class: className, 43 + variant = 'primary', 44 + sizeVariant = 'default', 45 + ...restProps 46 + }: WithoutChildren<WithElementRef<HTMLTextareaAttributes>> & { 47 + variant?: InputVariant; 48 + sizeVariant?: InputSize; 49 + } = $props(); 50 + </script> 51 + 52 + <textarea 53 + bind:this={ref} 54 + class={cn(inputVariants({ variant, sizeVariant }), className)} 55 + bind:value 56 + {...restProps} 57 + ></textarea>
+103
src/lib/components/base/theme-toggle/ThemeToggle.svelte
··· 1 + <script lang="ts" module> 2 + export const theme = $state({ 3 + dark: false 4 + }); 5 + </script> 6 + 7 + <script lang="ts"> 8 + import { onMount } from 'svelte'; 9 + import Button, { type ButtonProps } from '../button/Button.svelte'; 10 + import { cn } from '$lib/utils'; 11 + 12 + onMount(() => { 13 + // load from local storage 14 + const savedDarkMode = localStorage.getItem('darkMode'); 15 + if (savedDarkMode) { 16 + theme.dark = JSON.parse(savedDarkMode); 17 + } else { 18 + // prefers color scheme? 19 + theme.dark = window.matchMedia('(prefers-color-scheme: dark)').matches; 20 + } 21 + 22 + // remove local storage 23 + // localStorage.removeItem("darkMode"); 24 + setTheme(theme.dark); 25 + 26 + // recommended method for newer browsers: specify event-type as first argument 27 + window 28 + .matchMedia('(prefers-color-scheme: dark)') 29 + .addEventListener('change', (e) => e.matches && toggleTheme()); 30 + 31 + window 32 + .matchMedia('(prefers-color-scheme: light)') 33 + .addEventListener('change', (e) => e.matches && toggleTheme()); 34 + }); 35 + 36 + function setTheme(dark: Boolean) { 37 + var root = document.getElementsByTagName('html')[0]; 38 + 39 + if (dark) { 40 + root.classList.add('dark'); 41 + } else { 42 + root.classList.remove('dark'); 43 + } 44 + } 45 + 46 + function toggleTheme() { 47 + theme.dark = !theme.dark; 48 + // save to local storage 49 + localStorage.setItem('darkMode', JSON.stringify(theme.dark)); 50 + setTheme(theme.dark); 51 + } 52 + 53 + const { class: className, ...restProps }: ButtonProps = $props(); 54 + </script> 55 + 56 + <Button 57 + variant="link" 58 + onclick={toggleTheme} 59 + class={cn( 60 + 'theme-toggle focus-visible:outline-base-900 dark:focus-visible:outline-base-100 flex items-center justify-center rounded-lg focus-visible:outline-2', 61 + className 62 + )} 63 + {...restProps} 64 + > 65 + <svg 66 + xmlns="http://www.w3.org/2000/svg" 67 + fill="none" 68 + viewBox="0 0 24 24" 69 + stroke-width="1.5" 70 + stroke="currentColor" 71 + class="block size-6! transition-colors duration-500 dark:hidden" 72 + > 73 + <path 74 + stroke-linecap="round" 75 + stroke-linejoin="round" 76 + d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" 77 + /> 78 + </svg> 79 + <svg 80 + xmlns="http://www.w3.org/2000/svg" 81 + fill="none" 82 + viewBox="0 0 24 24" 83 + stroke-width="1.5" 84 + stroke="currentColor" 85 + class="hidden size-6! transition-colors duration-500 dark:block dark:text-white" 86 + > 87 + <path 88 + stroke-linecap="round" 89 + stroke-linejoin="round" 90 + d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" 91 + /> 92 + </svg> 93 + 94 + <span class="sr-only">Toggle theme</span> 95 + </Button> 96 + 97 + <noscript> 98 + <style> 99 + .theme-toggle { 100 + display: none; 101 + } 102 + </style> 103 + </noscript>
+7
src/lib/components/base/theme-toggle/index.ts
··· 1 + import Root from './ThemeToggle.svelte'; 2 + 3 + export { 4 + Root, 5 + // 6 + Root as ThemeToggle 7 + };
+20
src/lib/components/base/tooltip/index.ts
··· 1 + import { Tooltip as TooltipPrimitive } from 'bits-ui'; 2 + import Content from './tooltip-content.svelte'; 3 + import Tooltip from './tooltip.svelte'; 4 + 5 + const Root = TooltipPrimitive.Root; 6 + const Trigger = TooltipPrimitive.Trigger; 7 + const Provider = TooltipPrimitive.Provider; 8 + 9 + export { 10 + Root, 11 + Trigger, 12 + Content, 13 + Provider, 14 + // 15 + Content as TooltipContent, 16 + Trigger as TooltipTrigger, 17 + Provider as TooltipProvider, 18 + // 19 + Tooltip 20 + };
+21
src/lib/components/base/tooltip/tooltip-content.svelte
··· 1 + <script lang="ts"> 2 + import { Tooltip as TooltipPrimitive } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + class: className, 8 + sideOffset = 4, 9 + ...restProps 10 + }: TooltipPrimitive.ContentProps = $props(); 11 + </script> 12 + 13 + <TooltipPrimitive.Content 14 + bind:ref 15 + {sideOffset} 16 + class={cn( 17 + 'border-base-200 bg-base-100 text-base-900 motion-safe:animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-base-800 dark:bg-base-900 dark:text-base-50 z-50 overflow-hidden rounded-2xl border px-3 py-1.5 text-sm', 18 + className 19 + )} 20 + {...restProps} 21 + />
+64
src/lib/components/base/tooltip/tooltip.svelte
··· 1 + <script lang="ts"> 2 + import { Tooltip } from 'bits-ui'; 3 + import { type Snippet } from 'svelte'; 4 + import { buttonVariants, type ButtonSize, type ButtonVariant } from '../button'; 5 + import { TooltipContent } from '.'; 6 + import { cn } from '$lib/utils'; 7 + 8 + type Props = Tooltip.RootProps & { 9 + trigger?: Snippet; 10 + 11 + triggerText?: string; 12 + triggerVariant?: ButtonVariant; 13 + triggerSize?: ButtonSize; 14 + triggerClass?: string; 15 + 16 + withContext?: boolean; 17 + 18 + triggerProps?: Tooltip.TriggerProps; 19 + }; 20 + 21 + let { 22 + open = $bindable(false), 23 + children, 24 + trigger, 25 + triggerText, 26 + triggerVariant = 'primary', 27 + triggerSize = 'default', 28 + triggerClass, 29 + 30 + withContext = false, 31 + 32 + triggerProps = {}, 33 + ...restProps 34 + }: Props = $props(); 35 + </script> 36 + 37 + {#snippet root()} 38 + <Tooltip.Root bind:open {...restProps}> 39 + {#if trigger} 40 + <Tooltip.Trigger {...triggerProps}> 41 + {@render trigger()} 42 + </Tooltip.Trigger> 43 + {:else} 44 + <Tooltip.Trigger 45 + class={cn(buttonVariants({ variant: triggerVariant, size: triggerSize }), triggerClass)} 46 + > 47 + {triggerText} 48 + </Tooltip.Trigger> 49 + {/if} 50 + <Tooltip.Portal> 51 + <TooltipContent sideOffset={10}> 52 + {@render children?.()} 53 + </TooltipContent> 54 + </Tooltip.Portal> 55 + </Tooltip.Root> 56 + {/snippet} 57 + 58 + {#if withContext} 59 + <Tooltip.Provider> 60 + {@render root()} 61 + </Tooltip.Provider> 62 + {:else} 63 + {@render root()} 64 + {/if}
+25
src/lib/components/charts/heatmap/Heatmap.svelte
··· 1 + <script lang="ts"> 2 + let colors = [ 3 + 'bg-accent-200/50 dark:bg-accent-950/50', 4 + 'bg-accent-300/50 dark:bg-accent-800/70', 5 + 'bg-accent-400/80 dark:bg-accent-700', 6 + 'bg-accent-500/80 dark:bg-accent-600', 7 + 'bg-accent-600 dark:bg-accent-500' 8 + ]; 9 + 10 + const { 11 + data 12 + }: { 13 + data: (0 | 1 | 2 | 3 | 4)[][]; 14 + } = $props(); 15 + </script> 16 + 17 + <div class="flex max-w-full gap-0.5 sm:gap-1 justify-start"> 18 + {#each data as week} 19 + <div class="flex w-full flex-col gap-0.5 sm:gap-1"> 20 + {#each week as day} 21 + <div class="size-2 rounded-xs sm:size-3 {colors[day]}"></div> 22 + {/each} 23 + </div> 24 + {/each} 25 + </div>
+94
src/lib/components/charts/line-graph/LineGraph.svelte
··· 1 + <script lang="ts"> 2 + import { Chart, Svg, Axis, Spline, Tooltip, Highlight } from 'layerchart'; 3 + // @ts-ignore 4 + import { curveCatmullRom } from 'd3-shape'; 5 + // @ts-ignore 6 + import { scaleTime } from 'd3-scale'; 7 + import { formatDate, PeriodType } from '@layerstack/utils'; 8 + import { format } from 'date-fns'; 9 + 10 + const defaultValueFormat = (value: any) => { 11 + return formatDate(value, PeriodType.Month, { variant: 'short' }); 12 + }; 13 + 14 + let { data, showYAxis = false }: { 15 + data: { x: Date; value: number }[]; 16 + showYAxis?: boolean; 17 + } = $props(); 18 + </script> 19 + 20 + <div class="h-44 w-full overflow-visible p-4 line-graph-container"> 21 + <Chart 22 + {data} 23 + xScale={scaleTime()} 24 + x="x" 25 + y="value" 26 + yDomain={[0, null]} 27 + yNice 28 + tooltip={{ mode: 'bisect-x' }} 29 + > 30 + <Svg> 31 + <Axis 32 + placement="left" 33 + grid={{ class: 'stroke-accent-700/10' }} 34 + tickLabelProps={{ 35 + rotate: 315, 36 + textAnchor: 'end', 37 + class: 'fill-accent-700 dark:fill-accent-600 font-medium text-xs ' + (showYAxis ? '' : ' hidden') 38 + }} 39 + ticks={4} 40 + classes={{ 41 + tickLabel: 'pl-4' 42 + }} 43 + /> 44 + 45 + <Axis 46 + placement="bottom" 47 + tickLabelProps={{ 48 + class: 'fill-accent-700 dark:fill-accent-600 font-medium text-xs' 49 + }} 50 + rule 51 + format={defaultValueFormat} 52 + ticks={Math.min(data.length, 10)} 53 + /> 54 + <Spline 55 + class="stroke-accent-500 fill-transparent stroke-4 [stroke-linecap:round]" 56 + curve={curveCatmullRom} 57 + /> 58 + <Highlight 59 + lines 60 + points={{ 61 + class: 62 + 'fill-transparent outline-accent-700 dark:outline-accent-200 outline-4 rounded-full' 63 + }} 64 + /> 65 + </Svg> 66 + 67 + <Tooltip.Root 68 + let:data 69 + class="border-base-300 bg-base-200/80 dark:border-base-800 dark:bg-base-900/80 rounded-2xl border py-2 backdrop-blur-xs" 70 + > 71 + <Tooltip.Header class="text-accent-600 border-b-0 text-xs font-medium" 72 + >{format(data.x, 'MMMM do')}</Tooltip.Header 73 + > 74 + <Tooltip.List> 75 + <Tooltip.Item label="" value={data.value} class="text-accent-500 flex gap-2 font-bold" /> 76 + </Tooltip.List> 77 + </Tooltip.Root> 78 + </Chart> 79 + </div> 80 + 81 + 82 + <noscript> 83 + <div 84 + class="border-base-200 bg-base-100 text-base-700 dark:border-base-700 dark:bg-base-900 dark:text-base-300 w-full rounded-2xl border p-4 text-sm" 85 + > 86 + JavaScript is required to view this content. 87 + </div> 88 + 89 + <style> 90 + .line-graph-container { 91 + display: none; 92 + } 93 + </style> 94 + </noscript>
+106
src/lib/components/charts/ring-chart/RingChart.svelte
··· 1 + <script lang="ts"> 2 + import { PieChart, Tooltip } from 'layerchart'; 3 + import type { HTMLAttributes } from 'svelte/elements'; 4 + import type { WithElementRef } from 'bits-ui'; 5 + import { cn } from '$lib/utils'; 6 + 7 + const defaultColors = [ 8 + { key: 0, color: 'var(--color-accent-800)' }, 9 + { key: 1, color: 'var(--color-accent-600)' }, 10 + { key: 2, color: 'var(--color-accent-400)' }, 11 + { key: 3, color: 'var(--color-accent-200)' } 12 + ]; 13 + 14 + const defaultValueTransform = (value: number) => value + ' %'; 15 + 16 + let { 17 + ref = $bindable(null), 18 + data, 19 + colors = defaultColors, 20 + class: className, 21 + showLegend = true, 22 + pieChartClasses, 23 + height = 'h-44', 24 + thickness = 15, 25 + cornerRadius = 16, 26 + padAngle = 0.05, 27 + valueTransform = defaultValueTransform, 28 + ...restProps 29 + }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { 30 + data: { 31 + name: string; 32 + value: number; 33 + }[]; 34 + 35 + colors?: { 36 + key: number; 37 + color: string; 38 + }[]; 39 + 40 + showLegend?: boolean; 41 + 42 + pieChartClasses?: string; 43 + 44 + valueTransform?: (value: number) => string; 45 + 46 + height?: string; 47 + 48 + thickness?: number; 49 + cornerRadius?: number; 50 + padAngle?: number; 51 + } = $props(); 52 + </script> 53 + 54 + <div 55 + bind:this={ref} 56 + class={cn( 57 + 'flex w-full items-end gap-2 overflow-hidden', 58 + showLegend ? 'justify-between' : 'justify-center', 59 + className 60 + )} 61 + {...restProps} 62 + > 63 + {#if showLegend} 64 + <div class="flex flex-col gap-2"> 65 + {#each data.sort((a, b) => b.value - a.value) as point, index} 66 + <div class="flex items-center justify-start gap-1.5"> 67 + <div class="h-4 w-4 rounded-full" style="background-color: {colors[index].color}"></div> 68 + <span class="text-base-700 dark:text-base-300 text-xs font-bold" 69 + >{valueTransform(point.value)}</span 70 + > 71 + <span class="text-base-700 dark:text-base-300 text-xs">{point.name}</span> 72 + </div> 73 + {/each} 74 + </div> 75 + {/if} 76 + <div class={cn('grow', height, pieChartClasses)}> 77 + <PieChart 78 + {data} 79 + key="name" 80 + value="value" 81 + innerRadius={-thickness} 82 + {cornerRadius} 83 + {padAngle} 84 + series={colors} 85 + > 86 + <div slot="tooltip"> 87 + <Tooltip.Root 88 + let:data 89 + class="border-base-300 bg-base-200/80 dark:border-base-800 dark:bg-base-900/80 rounded-2xl border py-2 backdrop-blur-xs" 90 + > 91 + <Tooltip.Header 92 + class="text-accent-700 dark:text-accent-600 border-b-0 text-xs font-medium" 93 + >{data?.name}</Tooltip.Header 94 + > 95 + <Tooltip.List> 96 + <Tooltip.Item 97 + label="" 98 + value={valueTransform(data?.value)} 99 + class="text-accent-600 dark:text-accent-500 flex gap-2 font-bold" 100 + /> 101 + </Tooltip.List> 102 + </Tooltip.Root> 103 + </div> 104 + </PieChart> 105 + </div> 106 + </div>
+1
src/lib/components/charts/ring-chart/index.ts
··· 1 + export { default as RingChart } from './RingChart.svelte';
+265
src/lib/components/extra/color-picker/ColorPicker.svelte
··· 1 + <script lang="ts"> 2 + import { createEventDispatcher } from 'svelte' 3 + import { 4 + eps, 5 + picker_size, 6 + slider_width, 7 + border_size, 8 + gap_size, 9 + } from './constants' 10 + import { render_main_image, render_slider_image } from './render' 11 + import type { RGB, OKlab, OKhsv } from './color' 12 + import { oklab_to_okhsv, okhsv_to_oklab, rgb_to_okhsv, okhsv_to_rgb } from './color' 13 + 14 + export let rgb: RGB | undefined = undefined 15 + export let oklab: OKlab | undefined = undefined 16 + export let okhsv: OKhsv | undefined = undefined 17 + 18 + const dispatch = createEventDispatcher() 19 + const width = picker_size + slider_width + gap_size + border_size * 2 20 + const height = picker_size + border_size * 2 21 + 22 + $: color = convertToInternal(rgb, oklab, okhsv) 23 + $: uihsv = scale_to_ui(color) 24 + 25 + function scale_to_ui(okhsv: OKhsv): OKhsv { 26 + return { 27 + h: clamp_ui(okhsv.h / 360), 28 + s: clamp_ui(okhsv.s), 29 + v: clamp_ui(1 - okhsv.v), 30 + } 31 + } 32 + 33 + function clamp(x: number) { 34 + return x < eps ? eps : x > 1 - eps ? 1 - eps : x 35 + } 36 + 37 + function clamp_ui(value: number) { 38 + return value < 0 ? 0 : value > 1 ? picker_size : value * picker_size 39 + } 40 + 41 + function convertToInternal(rgb: RGB | undefined, oklab: OKlab | undefined, okhsv: OKhsv | undefined) { 42 + if (okhsv) { 43 + return okhsv 44 + } 45 + 46 + if (oklab) { 47 + return oklab_to_okhsv(oklab) 48 + } 49 + 50 + if (rgb) { 51 + return rgb_to_okhsv(rgb) 52 + } 53 + 54 + throw 'rgb, oklab, or okhsv required' 55 + } 56 + 57 + async function update_input() { 58 + if (okhsv) { 59 + okhsv = color 60 + dispatch('change', { okhsv }) 61 + } 62 + 63 + if (oklab) { 64 + oklab = okhsv_to_oklab(color) 65 + dispatch('change', { oklab }) 66 + } 67 + 68 + if (rgb) { 69 + rgb = okhsv_to_rgb(color) 70 + dispatch('change', { rgb }) 71 + } 72 + } 73 + 74 + function update_sv(x: number, y: number) { 75 + let new_s = clamp(x / picker_size) 76 + let new_v = clamp(1 - y / picker_size) 77 + 78 + color.s = new_s 79 + color.v = new_v 80 + 81 + update_input() 82 + } 83 + 84 + function update_h(x: number, y: number) { 85 + let h = clamp(y / picker_size) 86 + color.h = h * 360 87 + color.s = Math.max(color.s, 0.00001) 88 + color.v = Math.max(color.v, 0.00001) 89 + 90 + update_input() 91 + } 92 + 93 + function pointer( 94 + node: HTMLCanvasElement, 95 + fn: (x: number, y: number) => void 96 + ) { 97 + let active = false 98 + 99 + function update(event: PointerEvent) { 100 + const x = event.offsetX 101 + const y = event.offsetY 102 + fn(x, y) 103 + } 104 + 105 + function onpointerdown(event: PointerEvent) { 106 + event.stopPropagation() 107 + node.setPointerCapture(event.pointerId) 108 + update(event) 109 + active = true 110 + } 111 + 112 + function onpointermove(event: PointerEvent) { 113 + event.stopPropagation() 114 + if (active) { 115 + update(event) 116 + } 117 + } 118 + 119 + function onpointerend(event: PointerEvent) { 120 + event.stopPropagation() 121 + node.releasePointerCapture(event.pointerId) 122 + active = false 123 + } 124 + 125 + node.addEventListener('pointerdown', onpointerdown, { passive: true }) 126 + node.addEventListener('pointermove', onpointermove, { passive: true }) 127 + node.addEventListener('pointerup', onpointerend, { passive: true }) 128 + node.addEventListener('pointercancel', onpointerend, { passive: true }) 129 + 130 + return { 131 + destroy() { 132 + node.removeEventListener('pointerdown', onpointerdown) 133 + node.removeEventListener('pointermove', onpointermove) 134 + node.removeEventListener('pointerup', onpointerend) 135 + node.removeEventListener('pointercancel', onpointerend) 136 + }, 137 + } 138 + } 139 + 140 + function onKeydown(event: KeyboardEvent) { 141 + const keyHandled = () => { 142 + event.preventDefault() 143 + event.stopPropagation() 144 + } 145 + 146 + const step = event.shiftKey ? 10 : 1 147 + 148 + switch (event.key) { 149 + case 'ArrowUp': 150 + if (event.altKey) { 151 + update_h(0, Math.round(uihsv.h! - step)) 152 + } else { 153 + update_sv(uihsv.s, Math.round(uihsv.v - step)) 154 + } 155 + keyHandled() 156 + break 157 + 158 + case 'ArrowDown': 159 + if (event.altKey) { 160 + update_h(0, Math.round(uihsv.h! + step)) 161 + } else { 162 + update_sv(uihsv.s, Math.round(uihsv.v + step)) 163 + } 164 + keyHandled() 165 + break 166 + 167 + case 'ArrowLeft': 168 + update_sv(Math.round(uihsv.s - step), uihsv.v) 169 + keyHandled() 170 + break 171 + 172 + case 'ArrowRight': 173 + update_sv(Math.round(uihsv.s + step), uihsv.v) 174 + keyHandled() 175 + break 176 + } 177 + } 178 + </script> 179 + 180 + <!-- svelte-ignore a11y-no-noninteractive-tabindex --> 181 + <!-- svelte-ignore a11y-no-static-element-interactions --> 182 + <div 183 + class={$$props.class} 184 + tabindex="0" 185 + style:width="{width}px" 186 + style:height="{height}px" 187 + on:keydown={onKeydown} 188 + > 189 + <canvas 190 + id="okhsv_sv_canvas" 191 + width={picker_size} 192 + height={picker_size} 193 + style:top="{border_size}px" 194 + style:left="{border_size}px" 195 + class="rounded-xl" 196 + use:pointer={update_sv} 197 + use:render_main_image={color.h} 198 + ></canvas> 199 + <canvas 200 + width={slider_width} 201 + height={picker_size} 202 + style:top="{border_size}px" 203 + style:left="{picker_size + gap_size}px" 204 + class="rounded-xl" 205 + use:pointer={update_h} 206 + use:render_slider_image 207 + ></canvas> 208 + 209 + <svg {width} {height}> 210 + <g transform="translate({border_size},{border_size})"> 211 + <g transform="translate({uihsv.s},{uihsv.v})"> 212 + <circle 213 + cx="0" 214 + cy="0" 215 + r="5" 216 + fill="none" 217 + stroke-width="1.75" 218 + stroke="#fff" 219 + /> 220 + <circle 221 + cx="0" 222 + cy="0" 223 + r="6" 224 + fill="none" 225 + stroke-width="1.25" 226 + stroke="#000" 227 + /> 228 + </g> 229 + </g> 230 + <g transform="translate({picker_size + gap_size},{border_size})"> 231 + <g transform="translate(0,{uihsv.h})"> 232 + <polygon 233 + points="-7,-4 -1,0 -7,4" 234 + fill="#fff" 235 + stroke-width="0.8" 236 + stroke="#000" 237 + /> 238 + <polygon 239 + points="{slider_width + 7},-4 {slider_width + 1},0 {slider_width + 240 + 7},4" 241 + fill="#fff" 242 + stroke-width="0.8" 243 + stroke="#000" 244 + /> 245 + </g> 246 + </g> 247 + </svg> 248 + </div> 249 + 250 + <style> 251 + div { 252 + position: relative; 253 + outline: 0; 254 + } 255 + 256 + canvas, 257 + svg { 258 + touch-action: none; 259 + position: absolute; 260 + } 261 + 262 + svg { 263 + pointer-events: none; 264 + } 265 + </style>
+69
src/lib/components/extra/color-picker/color.ts
··· 1 + import { convert, OKHSL, OKHSV, OKLab, sRGB } from '@texel/color'; 2 + 3 + export interface RGB { 4 + r: number; 5 + g: number; 6 + b: number; 7 + } 8 + 9 + export interface OKlab { 10 + l: number; 11 + a: number; 12 + b: number; 13 + } 14 + 15 + export interface OKhsv { 16 + h: number; 17 + s: number; 18 + v: number; 19 + } 20 + 21 + export interface OKhsl { 22 + h: number; 23 + s: number; 24 + l: number; 25 + } 26 + 27 + export function oklab_to_okhsv(oklab: OKlab): OKhsv { 28 + const [h, s, v] = convert([oklab.l, oklab.a, oklab.b], OKLab, OKHSV); 29 + return { h: h ?? 0, s, v }; 30 + } 31 + 32 + export function okhsv_to_rgb(okhsv: OKhsv): RGB { 33 + const [r, g, b] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, sRGB); 34 + return { r, g, b }; 35 + } 36 + 37 + export function rgb_to_hex(rgb: RGB): string { 38 + // convert from 0-1 to 0-255 39 + const r = Math.round(rgb.r * 255); 40 + const g = Math.round(rgb.g * 255); 41 + const b = Math.round(rgb.b * 255); 42 + return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; 43 + } 44 + 45 + export function hex_to_rgb(hex: string): RGB { 46 + const r = parseInt(hex.slice(1, 3), 16); 47 + const g = parseInt(hex.slice(3, 5), 16); 48 + const b = parseInt(hex.slice(5, 7), 16); 49 + // convert from 0-255 to 0-1 50 + const r_ = r / 255; 51 + const g_ = g / 255; 52 + const b_ = b / 255; 53 + return { r: r_, g: g_, b: b_ }; 54 + } 55 + 56 + export function okhsv_to_oklab(okhsv: OKhsv): OKlab { 57 + const [l, a, b] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, OKLab); 58 + return { l, a, b }; 59 + } 60 + 61 + export function rgb_to_okhsv(rgb: RGB): OKhsv { 62 + const [h, s, v] = convert([rgb.r, rgb.g, rgb.b], sRGB, OKHSV); 63 + return { h, s, v }; 64 + } 65 + 66 + export function okhsl_to_rgb(okhsl: OKhsl): RGB { 67 + const [r, g, b] = convert([okhsl.h, okhsl.s, okhsl.l], OKHSL, sRGB); 68 + return { r, g, b }; 69 + }
+5
src/lib/components/extra/color-picker/constants.ts
··· 1 + export const picker_size = 190; 2 + export const slider_width = 16; 3 + export const border_size = 10; 4 + export const gap_size = 30; 5 + export const eps = 0.0001;
+3
src/lib/components/extra/color-picker/index.ts
··· 1 + export { default } from './ColorPicker.svelte'; 2 + export { default as ColorPicker } from './ColorPicker.svelte'; 3 + export type { RGB, OKlab, OKhsv } from './color';
+100
src/lib/components/extra/color-picker/render.ts
··· 1 + import { f_shader, v_shader } from './shaders' 2 + import { picker_size, slider_width } from './constants' 3 + import { okhsl_to_rgb } from './color' 4 + 5 + export function render_slider_image(canvas: HTMLCanvasElement) { 6 + const ctx = canvas.getContext('2d')! 7 + const data = new Uint8ClampedArray(picker_size * slider_width * 4) 8 + 9 + for (let i = 0; i < picker_size; i++) { 10 + const a_ = Math.cos(2 * Math.PI * i / picker_size) 11 + const b_ = Math.sin(2 * Math.PI * i / picker_size) 12 + 13 + const rgb = okhsl_to_rgb({ 14 + h: i / picker_size * 360, 15 + s: 0.9, 16 + l: 0.65 + 0.17 * b_ - 0.08 * a_, 17 + }) 18 + 19 + for (let j = 0; j < slider_width; j++) { 20 + const index = 4 * (i * slider_width + j) 21 + data[index + 0] = rgb.r * 255 22 + data[index + 1] = rgb.g * 255 23 + data[index + 2] = rgb.b * 255 24 + data[index + 3] = 255 25 + } 26 + } 27 + 28 + const imageData = new ImageData(data, slider_width) 29 + ctx.putImageData(imageData, 0, 0) 30 + } 31 + 32 + export function render_main_image(canvas: HTMLCanvasElement, hue: number) { 33 + const gl = canvas.getContext('webgl')! 34 + 35 + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) 36 + 37 + const shaderProgram = initShaderProgram(gl, v_shader, f_shader)! 38 + 39 + const position = gl.getAttribLocation(shaderProgram, "position") 40 + const resolution = gl.getUniformLocation(shaderProgram, "resolution") 41 + const hue_rad = gl.getUniformLocation(shaderProgram, "hue_rad") 42 + 43 + gl.useProgram(shaderProgram) 44 + gl.uniform2fv(resolution, [gl.canvas.width, gl.canvas.height]) 45 + 46 + const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0] 47 + const positionBuffer = gl.createBuffer() 48 + 49 + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) 50 + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW) 51 + gl.enableVertexAttribArray(position) 52 + gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0) 53 + 54 + function update(hue: number) { 55 + requestAnimationFrame(() => { 56 + gl.uniform1f(hue_rad, hue / 360) 57 + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) 58 + }) 59 + } 60 + 61 + update(hue) 62 + 63 + return { 64 + update, 65 + destroy() { 66 + gl.deleteProgram(shaderProgram) 67 + } 68 + } 69 + } 70 + 71 + function initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string) { 72 + const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)! 73 + const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)! 74 + 75 + const shaderProgram = gl.createProgram()! 76 + gl.attachShader(shaderProgram, vertexShader) 77 + gl.attachShader(shaderProgram, fragmentShader) 78 + gl.linkProgram(shaderProgram) 79 + 80 + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 81 + throw `unable to init shader: ${gl.getProgramInfoLog(shaderProgram)}` 82 + } 83 + 84 + return shaderProgram 85 + } 86 + 87 + function loadShader(gl: WebGLRenderingContext, type: number, source: string) { 88 + const shader = gl.createShader(type)! 89 + 90 + gl.shaderSource(shader, source) 91 + gl.compileShader(shader) 92 + 93 + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 94 + const info = gl.getShaderInfoLog(shader) 95 + gl.deleteShader(shader) 96 + throw `error compiling shaders: ${info}` 97 + } 98 + 99 + return shader 100 + }
+7
src/lib/components/extra/color-picker/shaders.ts
··· 1 + /// <reference types="vite-plugin-glsl/ext" /> 2 + 3 + import f_shader_src from './shaders/f_shader.glsl'; 4 + import v_shader_src from './shaders/v_shader.glsl'; 5 + 6 + export const f_shader = f_shader_src; 7 + export const v_shader = v_shader_src;
+16
src/lib/components/extra/color-picker/shaders/f_shader.glsl
··· 1 + precision mediump float; 2 + uniform vec2 resolution; 3 + uniform float hue_rad; 4 + 5 + #include library; 6 + 7 + void main() { 8 + vec2 uv = gl_FragCoord.xy / resolution; 9 + 10 + float l = uv.y; 11 + float h = hue_rad; 12 + 13 + vec3 hsl = vec3(h, uv.x, uv.y); 14 + vec3 hsvRGB = okhsv_to_srgb(hsl); 15 + gl_FragColor = vec4(hsvRGB, 1.0); 16 + }
+183
src/lib/components/extra/color-picker/shaders/library.glsl
··· 1 + // Copyright(c) 2021 Björn Ottosson 2 + // 3 + // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 + // this softwareand associated documentation files(the "Software"), to deal in 5 + // the Software without restriction, including without limitation the rights to 6 + // use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies 7 + // of the Software, and to permit persons to whom the Software is furnished to do 8 + // so, subject to the following conditions : 9 + // The above copyright noticeand this permission notice shall be included in all 10 + // copies or substantial portions of the Software. 11 + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 14 + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 + // SOFTWARE. 18 + 19 + precision mediump float; 20 + #define M_PI 3.1415926536 21 + 22 + float cbrt(float x) { 23 + return sign(x)*pow(abs(x), 1.0 / 3.0); 24 + } 25 + 26 + float srgb_transfer_function(float a) { 27 + return .0031308 >= a ? 12.92 * a : 1.055 * pow(a, .4166666666666667) - .055; 28 + } 29 + 30 + vec3 oklab_to_linear_srgb(vec3 c) { 31 + float l_ = c.x + 0.3963377774 * c.y + 0.2158037573 * c.z; 32 + float m_ = c.x - 0.1055613458 * c.y - 0.0638541728 * c.z; 33 + float s_ = c.x - 0.0894841775 * c.y - 1.2914855480 * c.z; 34 + 35 + float l = l_ * l_ * l_; 36 + float m = m_ * m_ * m_; 37 + float s = s_ * s_ * s_; 38 + 39 + return vec3( 40 + +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, 41 + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, 42 + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s 43 + ); 44 + } 45 + 46 + // Finds the maximum saturation possible for a given hue that fits in sRGB 47 + // Saturation here is defined as S = C/L 48 + // a and b must be normalized so a^2 + b^2 == 1 49 + float compute_max_saturation(float a, float b) { 50 + // Max saturation will be when one of r, g or b goes below zero. 51 + 52 + // Select different coefficients depending on which component goes below zero first 53 + float k0, k1, k2, k3, k4, wl, wm, ws; 54 + 55 + if (-1.88170328 * a - 0.80936493 * b > 1.0) 56 + { 57 + // Red component 58 + k0 = +1.19086277; k1 = +1.76576728; k2 = +0.59662641; k3 = +0.75515197; k4 = +0.56771245; 59 + wl = +4.0767416621; wm = -3.3077115913; ws = +0.2309699292; 60 + } 61 + else if (1.81444104 * a - 1.19445276 * b > 1.0) 62 + { 63 + // Green component 64 + k0 = +0.73956515; k1 = -0.45954404; k2 = +0.08285427; k3 = +0.12541070; k4 = +0.14503204; 65 + wl = -1.2684380046; wm = +2.6097574011; ws = -0.3413193965; 66 + } 67 + else 68 + { 69 + // Blue component 70 + k0 = +1.35733652; k1 = -0.00915799; k2 = -1.15130210; k3 = -0.50559606; k4 = +0.00692167; 71 + wl = -0.0041960863; wm = -0.7034186147; ws = +1.7076147010; 72 + } 73 + 74 + // Approximate max saturation using a polynomial: 75 + float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b; 76 + 77 + // Do one step Halley's method to get closer 78 + // this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite 79 + // this should be sufficient for most applications, otherwise do two/three steps 80 + 81 + float k_l = +0.3963377774 * a + 0.2158037573 * b; 82 + float k_m = -0.1055613458 * a - 0.0638541728 * b; 83 + float k_s = -0.0894841775 * a - 1.2914855480 * b; 84 + 85 + { 86 + float l_ = 1.0 + S * k_l; 87 + float m_ = 1.0 + S * k_m; 88 + float s_ = 1.0 + S * k_s; 89 + 90 + float l = l_ * l_ * l_; 91 + float m = m_ * m_ * m_; 92 + float s = s_ * s_ * s_; 93 + 94 + float l_dS = 3.0 * k_l * l_ * l_; 95 + float m_dS = 3.0 * k_m * m_ * m_; 96 + float s_dS = 3.0 * k_s * s_ * s_; 97 + 98 + float l_dS2 = 6.0 * k_l * k_l * l_; 99 + float m_dS2 = 6.0 * k_m * k_m * m_; 100 + float s_dS2 = 6.0 * k_s * k_s * s_; 101 + 102 + float f = wl * l + wm * m + ws * s; 103 + float f1 = wl * l_dS + wm * m_dS + ws * s_dS; 104 + float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2; 105 + 106 + S = S - f * f1 / (f1 * f1 - 0.5 * f * f2); 107 + } 108 + 109 + return S; 110 + } 111 + 112 + // finds L_cusp and C_cusp for a given hue 113 + // a and b must be normalized so a^2 + b^2 == 1 114 + vec2 find_cusp(float a, float b) { 115 + // First, find the maximum saturation (saturation S = C/L) 116 + float S_cusp = compute_max_saturation(a, b); 117 + 118 + // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1: 119 + vec3 rgb_at_max = oklab_to_linear_srgb(vec3( 1, S_cusp * a, S_cusp * b )); 120 + float L_cusp = cbrt(1.0 / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b)); 121 + float C_cusp = L_cusp * S_cusp; 122 + 123 + return vec2( L_cusp , C_cusp ); 124 + } 125 + 126 + float toe_inv(float x) { 127 + float k_1 = 0.206; 128 + float k_2 = 0.03; 129 + float k_3 = (1.0 + k_1) / (1.0 + k_2); 130 + return (x * x + k_1 * x) / (k_3 * (x + k_2)); 131 + } 132 + 133 + vec2 to_ST(vec2 cusp) { 134 + float L = cusp.x; 135 + float C = cusp.y; 136 + return vec2( C / L, C / (1.0 - L) ); 137 + } 138 + 139 + vec3 okhsv_to_srgb(vec3 hsv) { 140 + float h = hsv.x; 141 + float s = hsv.y; 142 + float v = hsv.z; 143 + 144 + float a_ = cos(2.0 * M_PI * h); 145 + float b_ = sin(2.0 * M_PI * h); 146 + 147 + vec2 cusp = find_cusp(a_, b_); 148 + vec2 ST_max = to_ST(cusp); 149 + float S_max = ST_max.x; 150 + float T_max = ST_max.y; 151 + float S_0 = 0.5; 152 + float k = 1.0- S_0 / S_max; 153 + 154 + // first we compute L and V as if the gamut is a perfect triangle: 155 + 156 + // L, C when v==1: 157 + float L_v = 1.0 - s * S_0 / (S_0 + T_max - T_max * k * s); 158 + float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s); 159 + 160 + float L = v * L_v; 161 + float C = v * C_v; 162 + 163 + // then we compensate for both toe and the curved top part of the triangle: 164 + float L_vt = toe_inv(L_v); 165 + float C_vt = C_v * L_vt / L_v; 166 + 167 + float L_new = toe_inv(L); 168 + C = C * L_new / L; 169 + L = L_new; 170 + 171 + vec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt )); 172 + float scale_L = cbrt(1.0 / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.0))); 173 + 174 + L = L * scale_L; 175 + C = C * scale_L; 176 + 177 + vec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ )); 178 + return vec3( 179 + srgb_transfer_function(rgb.r), 180 + srgb_transfer_function(rgb.g), 181 + srgb_transfer_function(rgb.b) 182 + ); 183 + }
+2
src/lib/components/extra/color-picker/shaders/readme.md
··· 1 + shaders adapted from https://github.com/dokozero/okcolor 2 + the source in this folder is minified and included in a ts file to enable svelte-package to be used
+5
src/lib/components/extra/color-picker/shaders/v_shader.glsl
··· 1 + attribute vec4 position; 2 + 3 + void main() { 4 + gl_Position = position; 5 + }
+57
src/lib/components/extra/color-select/ColorSelect.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + type Color = { class?: string; label: string; value?: string }; 7 + 8 + let { 9 + ref = $bindable(null), 10 + 11 + class: className, 12 + colorsClass, 13 + colors = $bindable([]), 14 + selected = $bindable(colors[0]), 15 + 16 + onselected, 17 + ...restProps 18 + }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 19 + colors: Color[]; 20 + 21 + selected?: Color; 22 + 23 + colorsClass?: string; 24 + 25 + onselected?: (color: Color, previous: Color) => void; 26 + } = $props(); 27 + </script> 28 + 29 + <div 30 + class={cn('group flex flex-wrap items-center gap-2.5', className)} 31 + {...restProps} 32 + bind:this={ref} 33 + > 34 + {#each colors as color} 35 + <button 36 + class={cn( 37 + 'peer outline-base-900 focus:outline-base-900 dark:outline-base-100 dark:focus:outline-base-100 cursor-pointer items-center justify-center rounded-full p-0.5 outline-offset-2 transition-all duration-100 focus:outline-2 focus:outline-offset-2', 38 + 'border-base-600 border-opacity-30 dark:border-base-400 dark:border-opacity-30 size-8 rounded-full border transition-all', 39 + selected.label === color.label 40 + ? 'bg-opacity-100 group-focus-within:outline-base-500 focus:group-focus-within:outline-base-900 dark:group-focus-within:outline-base-400 dark:focus:group-focus-within:outline-base-100 outline-2' 41 + : 'opacity-90 outline-0 hover:opacity-100 focus:opacity-100', 42 + colorsClass, 43 + color.class 44 + )} 45 + style={color.value ? `background-color: ${color.value}` : undefined} 46 + onclick={() => { 47 + if (selected === color) return; 48 + let previous = selected; 49 + selected = color; 50 + 51 + if (onselected) onselected(color, previous); 52 + }} 53 + > 54 + <span class="sr-only">{color.label}</span> 55 + </button> 56 + {/each} 57 + </div>
+1
src/lib/components/extra/color-select/index.ts
··· 1 + export { default as ColorSelect } from './ColorSelect.svelte';
+106
src/lib/components/extra/excalidraw/Excalidraw.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import { onMount } from 'svelte'; 4 + import type { HTMLAttributes } from 'svelte/elements'; 5 + import { cn } from '$lib/utils'; 6 + 7 + let { 8 + ref = $bindable(null), 9 + svg, 10 + alt, 11 + caption, 12 + class: className, 13 + captionClass, 14 + ...restProps 15 + }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLElement>>> & { 16 + svg: string; 17 + alt: string; 18 + caption?: string; 19 + captionClass?: string; 20 + } = $props(); 21 + 22 + let modifiedSvg = $state(''); 23 + 24 + // Function to modify the SVG using the DOM API 25 + function modifySvg(svgString: string) { 26 + // Create a DOM parser 27 + const parser = new DOMParser(); 28 + const doc = parser.parseFromString(svgString, 'image/svg+xml'); 29 + const svg = doc.documentElement; 30 + 31 + // Modify the SVG element 32 + svg.setAttribute('width', '100%'); 33 + svg.setAttribute('height', '100%'); 34 + svg.classList.add('w-full', 'h-auto'); 35 + svg.removeAttribute('filter'); 36 + 37 + // Modify text elements 38 + const textElements = svg.querySelectorAll('text'); 39 + textElements.forEach((el) => { 40 + el.removeAttribute('fill'); 41 + el.classList.add('fill-base-800', 'dark:fill-base-100'); 42 + }); 43 + 44 + // Modify rect elements 45 + const rectElements = svg.querySelectorAll('rect'); 46 + rectElements.forEach((el) => { 47 + el.removeAttribute('fill'); 48 + el.classList.add('fill-accent-600', 'dark:fill-accent-500'); 49 + }); 50 + 51 + // Modify path elements 52 + const pathElements = svg.querySelectorAll('path'); 53 + pathElements.forEach((el) => { 54 + el.removeAttribute('stroke'); 55 + el.classList.add('stroke-accent-600', 'dark:stroke-accent-500'); 56 + if (el.getAttribute('fill') !== 'none') { 57 + el.classList.add('fill-accent-600', 'dark:fill-accent-500'); 58 + el.removeAttribute('fill'); 59 + } 60 + }); 61 + 62 + // Modify group elements 63 + const groupElements = svg.querySelectorAll('g'); 64 + groupElements.forEach((el) => { 65 + el.classList.add('excalidraw-element'); 66 + }); 67 + 68 + // Convert back to string 69 + return new XMLSerializer().serializeToString(svg); 70 + } 71 + 72 + onMount(async () => { 73 + modifiedSvg = modifySvg(svg); 74 + }); 75 + </script> 76 + 77 + <figure 78 + bind:this={ref} 79 + class={cn('excalidraw-container mx-auto w-full max-w-full overflow-hidden', className)} 80 + {...restProps} 81 + > 82 + <div class="excalidraw-svg w-full" aria-label={alt}> 83 + {@html modifiedSvg} 84 + </div> 85 + {#if caption} 86 + <figcaption 87 + class={cn('text-base-700 dark:text-base-300 mt-4 text-center text-xs', captionClass)} 88 + > 89 + {caption} 90 + </figcaption> 91 + {/if} 92 + </figure> 93 + 94 + <noscript> 95 + <div 96 + class="border-base-200 bg-base-100 text-base-700 dark:border-base-700 dark:bg-base-900 dark:text-base-300 w-full rounded-2xl border p-4 text-sm" 97 + > 98 + JavaScript is required to view this content. 99 + </div> 100 + 101 + <style> 102 + .excalidraw-container { 103 + display: none; 104 + } 105 + </style> 106 + </noscript>
+104
src/lib/components/extra/following-pointer/FollowingPointer.svelte
··· 1 + <script lang="ts"> 2 + import { cn } from '$lib/utils'; 3 + import { Portal } from 'bits-ui'; 4 + import { onMount, type Snippet } from 'svelte'; 5 + 6 + let { 7 + class: className, 8 + children, 9 + container, 10 + title, 11 + titleClasses, 12 + hideCursor = true 13 + }: { 14 + class?: string; 15 + children?: Snippet; 16 + container?: HTMLElement | string; 17 + title?: string; 18 + titleClasses?: string; 19 + hideCursor?: boolean; 20 + } = $props(); 21 + 22 + let x = $state(-1000); 23 + let y = $state(-1000); 24 + let isInside: boolean = $state(false); 25 + 26 + onMount(() => { 27 + if (typeof container === 'string') { 28 + let containerElement = document.querySelector(container); 29 + 30 + if (containerElement) { 31 + container = containerElement as HTMLElement; 32 + } else { 33 + console.error(`Container ${container} not found, using body`); 34 + } 35 + } 36 + 37 + if (!container) { 38 + container = document.body; 39 + } 40 + 41 + if (container instanceof HTMLElement) { 42 + container.addEventListener('mousemove', handleMouseMove); 43 + container.addEventListener('mouseleave', handleMouseLeave); 44 + container.addEventListener('mouseenter', handleMouseEnter); 45 + window.addEventListener('resize', handleResize); 46 + rect = container.getBoundingClientRect(); 47 + 48 + if (hideCursor) { 49 + // important: 50 + // container.style.cursor = 'none !important;'; 51 + } 52 + } 53 + }); 54 + 55 + let rect: DOMRect | null = null; 56 + 57 + const handleResize = () => { 58 + if (container instanceof HTMLElement) { 59 + rect = container.getBoundingClientRect(); 60 + } 61 + }; 62 + 63 + const handleMouseMove = (e: MouseEvent) => { 64 + const scrollX = window.scrollX; 65 + const scrollY = window.scrollY; 66 + 67 + x = e.clientX - (rect?.left ?? 0) + scrollX; 68 + y = e.clientY - (rect?.top ?? 0) + scrollY; 69 + }; 70 + const handleMouseLeave = () => { 71 + console.log('mouse leave'); 72 + isInside = false; 73 + }; 74 + 75 + const handleMouseEnter = () => { 76 + console.log('mouse enter'); 77 + isInside = true; 78 + }; 79 + </script> 80 + 81 + <Portal to={container}> 82 + {#if isInside} 83 + <div 84 + class={cn( 85 + 'animate-in fade-in-0 pointer-events-none absolute z-50', 86 + className 87 + )} 88 + style={`top: ${y}px; left: ${x}px`} 89 + > 90 + {#if title} 91 + <div 92 + class={cn( 93 + 'bg-accent-500/10 dark:bg-accent-500/10 text-accent-600 dark:text-accent-400 border-accent-500/20 dark:border-accent-500/10 m-3 w-fit rounded-2xl border px-2 py-1 text-sm backdrop-blur-sm', 94 + titleClasses 95 + )} 96 + > 97 + {title} 98 + </div> 99 + {/if} 100 + 101 + {@render children?.()} 102 + </div> 103 + {/if} 104 + </Portal>
+8
src/lib/components/extra/gradient/Gradient.svelte
··· 1 + <div 2 + class="absolute right-0 bottom-0 left-0 flex translate-y-100 items-center justify-center" 3 + aria-hidden="true" 4 + > 5 + <div 6 + class="from-accent-500/50 via-accent-300/0 dark:via-accent-800/0 size-[75rem] shrink-0 translate-y-100 bg-radial" 7 + ></div> 8 + </div>
+22
src/lib/components/extra/phone/Phone.svelte
··· 1 + <script lang="ts"> 2 + const { children } = $props(); 3 + </script> 4 + 5 + <div class="bg-accent-500 relative mx-auto h-[712px] w-[350px] max-w-full rounded-[60px]"> 6 + <div class="bg-accent-500 absolute top-20 -left-0.5 h-8 w-3 rounded-xs"></div> 7 + <div class="bg-accent-500 absolute top-[140px] -left-[3px] h-14 w-3 rounded-xs"></div> 8 + <div class="bg-accent-500 absolute top-[210px] -left-[3px] h-14 w-3 rounded-xs"></div> 9 + <div class="bg-accent-500 absolute top-[180px] -right-[3px] h-20 w-3 rounded-xs"></div> 10 + 11 + <div class="absolute top-[18px] z-10 flex w-full justify-center"> 12 + <div class="h-7 w-24 rounded-full bg-black"></div> 13 + </div> 14 + 15 + <div class="bg-accent-500 absolute top-[180px] -right-[3px] h-20 w-3 rounded-xs"></div> 16 + 17 + <div class="absolute inset-0 h-full w-full p-3"> 18 + <div class="bg-accent-200 relative h-full w-full overflow-hidden rounded-[50px]"> 19 + {@render children?.()} 20 + </div> 21 + </div> 22 + </div>
+262
src/lib/components/extra/post/Post.svelte
··· 1 + 2 + 3 + <script lang="ts"> 4 + import RelativeTime from './relative-time'; 5 + import Embed from './embeds/Embed.svelte'; 6 + import { cn } from '$lib/utils'; 7 + import Avatar from '../../base/avatar/Avatar.svelte'; 8 + import type { WithChildren, WithElementRef } from 'bits-ui'; 9 + import type { HTMLAttributes } from 'svelte/elements'; 10 + import type { PostData } from '.'; 11 + 12 + let { 13 + ref, 14 + data, 15 + class: className, 16 + bookmarked = $bindable(false), 17 + liked = $bindable(false), 18 + children 19 + }: WithElementRef<WithChildren<HTMLAttributes<HTMLDivElement>>> & { 20 + data: PostData; 21 + class?: string; 22 + intersect?: () => void; 23 + bookmarked?: boolean; 24 + liked?: boolean; 25 + } = $props(); 26 + 27 + export function numberToHumanReadable(number: number) { 28 + if (number < 1000) { 29 + return number; 30 + } 31 + if (number < 1000000) { 32 + return `${Math.floor(number / 1000)}k`; 33 + } 34 + return `${Math.floor(number / 1000000)}m`; 35 + } 36 + </script> 37 + 38 + <div 39 + bind:this={ref} 40 + class={cn('text-base-950 dark:text-base-50 transition-colors duration-200', className)} 41 + > 42 + {#if data.reposted} 43 + <div class="mb-3 inline-flex items-center gap-2 pl-4 text-xs font-medium"> 44 + <svg 45 + xmlns="http://www.w3.org/2000/svg" 46 + viewBox="0 0 24 24" 47 + fill="currentColor" 48 + class="size-3" 49 + > 50 + <path 51 + fill-rule="evenodd" 52 + d="M4.755 10.059a7.5 7.5 0 0 1 12.548-3.364l1.903 1.903h-3.183a.75.75 0 1 0 0 1.5h4.992a.75.75 0 0 0 .75-.75V4.356a.75.75 0 0 0-1.5 0v3.18l-1.9-1.9A9 9 0 0 0 3.306 9.67a.75.75 0 1 0 1.45.388Zm15.408 3.352a.75.75 0 0 0-.919.53 7.5 7.5 0 0 1-12.548 3.364l-1.902-1.903h3.183a.75.75 0 0 0 0-1.5H2.984a.75.75 0 0 0-.75.75v4.992a.75.75 0 0 0 1.5 0v-3.18l1.9 1.9a9 9 0 0 0 15.059-4.035.75.75 0 0 0-.53-.918Z" 53 + clip-rule="evenodd" 54 + /> 55 + </svg> 56 + 57 + <div class="inline-flex gap-1"> 58 + reposted by 59 + <a href={data.reposted.href} class="hover:text-accent-400 font-bold"> 60 + @{data.reposted.handle} 61 + </a> 62 + </div> 63 + </div> 64 + {/if} 65 + <div class="flex gap-4"> 66 + <Avatar src={data.author.avatar} /> 67 + 68 + <div class="w-full"> 69 + <div class="mb-1 flex items-start justify-between gap-2"> 70 + <a 71 + class="group hover:bg-accent-900/5 -mx-2 -my-0.5 flex flex-col items-baseline gap-x-2 gap-y-0.5 rounded-xl px-2 py-0.5 sm:flex-row" 72 + href={data.author.href} 73 + > 74 + <div 75 + class="text-base-900 group-hover:text-accent-600 dark:text-base-50 dark:group-hover:text-accent-300 text-sm leading-tight font-semibold" 76 + > 77 + {data.author.displayName} 78 + </div> 79 + <div 80 + class="text-base-600 group-hover:text-accent-600 dark:text-base-400 dark:group-hover:text-accent-400 text-sm" 81 + > 82 + @{data.author.handle} 83 + </div> 84 + </a> 85 + 86 + {#if data.createdAt} 87 + <a 88 + href={'#'} 89 + class="text-base-600 hover:text-accent-700 dark:text-base-400 dark:hover:text-accent-300 block text-sm no-underline" 90 + > 91 + <RelativeTime date={new Date(data.createdAt)} locale="en" /> 92 + </a> 93 + {/if} 94 + </div> 95 + 96 + <div class="prose-base"> 97 + <div 98 + class="prose dark:prose-invert prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-500" 99 + > 100 + {#if data.htmlContent} 101 + {@html data.htmlContent} 102 + {:else} 103 + {@render children?.()} 104 + {/if} 105 + </div> 106 + </div> 107 + 108 + <Embed {data} /> 109 + 110 + <div class="text-base-500 dark:text-base-400 mt-4 flex justify-between gap-2"> 111 + <button class="group inline-flex items-center gap-2 text-sm"> 112 + <svg 113 + xmlns="http://www.w3.org/2000/svg" 114 + fill="none" 115 + viewBox="0 0 24 24" 116 + stroke-width="1.5" 117 + stroke="currentColor" 118 + class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 119 + > 120 + <path 121 + stroke-linecap="round" 122 + stroke-linejoin="round" 123 + d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 0 1-.923 1.785A5.969 5.969 0 0 0 6 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337Z" 124 + /> 125 + </svg> 126 + {#if data.replyCount} 127 + {numberToHumanReadable(data.replyCount)} 128 + {/if} 129 + </button> 130 + 131 + <button class="group inline-flex items-center gap-2 text-sm"> 132 + <svg 133 + xmlns="http://www.w3.org/2000/svg" 134 + fill="none" 135 + viewBox="0 0 24 24" 136 + stroke-width="1.5" 137 + stroke="currentColor" 138 + class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 139 + > 140 + <path 141 + stroke-linecap="round" 142 + stroke-linejoin="round" 143 + d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" 144 + /> 145 + </svg> 146 + {#if data.repostCount} 147 + {numberToHumanReadable(data.repostCount)} 148 + {/if} 149 + </button> 150 + 151 + <button class="group inline-flex items-center gap-2 text-sm" onclick={() => {}}> 152 + {#if liked} 153 + <svg 154 + xmlns="http://www.w3.org/2000/svg" 155 + viewBox="0 0 24 24" 156 + fill="currentColor" 157 + class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 158 + > 159 + <path 160 + d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" 161 + /> 162 + </svg> 163 + {:else} 164 + <svg 165 + xmlns="http://www.w3.org/2000/svg" 166 + fill="none" 167 + viewBox="0 0 24 24" 168 + stroke-width="1.5" 169 + stroke="currentColor" 170 + class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 171 + > 172 + <path 173 + stroke-linecap="round" 174 + stroke-linejoin="round" 175 + d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" 176 + /> 177 + </svg> 178 + {/if} 179 + {#if data.likeCount} 180 + {numberToHumanReadable(data.likeCount)} 181 + {/if} 182 + </button> 183 + 184 + <button onclick={async () => {}} class="group inline-flex items-center gap-2 text-sm"> 185 + <span class="sr-only">Bookmark</span> 186 + 187 + {#if bookmarked} 188 + <svg 189 + xmlns="http://www.w3.org/2000/svg" 190 + viewBox="0 0 24 24" 191 + fill="currentColor" 192 + class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 193 + > 194 + <path 195 + fill-rule="evenodd" 196 + d="M6.32 2.577a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93V21a.75.75 0 0 1-1.085.67L12 18.089l-7.165 3.583A.75.75 0 0 1 3.75 21V5.507c0-1.47 1.073-2.756 2.57-2.93Z" 197 + clip-rule="evenodd" 198 + /> 199 + </svg> 200 + {:else} 201 + <svg 202 + xmlns="http://www.w3.org/2000/svg" 203 + fill="none" 204 + viewBox="0 0 24 24" 205 + stroke-width="1.5" 206 + stroke="currentColor" 207 + class="group-hover:bg-accent-500/10 group-hover:text-accent-700 dark:group-hover:text-accent-400 -m-1.5 size-7 rounded-full p-1.5 transition-all duration-100" 208 + > 209 + <path 210 + stroke-linecap="round" 211 + stroke-linejoin="round" 212 + d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z" 213 + /> 214 + </svg> 215 + {/if} 216 + </button> 217 + </div> 218 + </div> 219 + </div> 220 + </div> 221 + 222 + <style> 223 + .prose-base { 224 + --tw-prose-body: var(--color-base-700); 225 + --tw-prose-headings: var(--color-base-900); 226 + --tw-prose-lead: var(--color-base-600); 227 + --tw-prose-links: var(--color-base-900); 228 + --tw-prose-bold: var(--color-base-900); 229 + --tw-prose-counters: var(--color-base-500); 230 + --tw-prose-bullets: var(--color-base-300); 231 + --tw-prose-hr: var(--color-base-200); 232 + --tw-prose-quotes: var(--color-base-900); 233 + --tw-prose-quote-borders: var(--color-base-200); 234 + --tw-prose-captions: var(--color-base-500); 235 + --tw-prose-kbd: var(--color-base-900); 236 + --tw-prose-kbd-shadows: var(--color-base-900); 237 + --tw-prose-code: var(--color-base-900); 238 + --tw-prose-pre-code: var(--color-base-200); 239 + --tw-prose-pre-bg: var(--color-base-800); 240 + --tw-prose-th-borders: var(--color-base-300); 241 + --tw-prose-td-borders: var(--color-base-200); 242 + 243 + --tw-prose-invert-body: var(--color-base-300); 244 + --tw-prose-invert-headings: var(--color-white); 245 + --tw-prose-invert-lead: var(--color-base-400); 246 + --tw-prose-invert-links: var(--color-white); 247 + --tw-prose-invert-bold: var(--color-white); 248 + --tw-prose-invert-counters: var(--color-base-400); 249 + --tw-prose-invert-bullets: var(--color-base-600); 250 + --tw-prose-invert-hr: var(--color-base-700); 251 + --tw-prose-invert-quotes: var(--color-base-100); 252 + --tw-prose-invert-quote-borders: var(--color-base-700); 253 + --tw-prose-invert-captions: var(--color-base-400); 254 + --tw-prose-invert-kbd: var(--color-white); 255 + --tw-prose-invert-kbd-shadows: var(--color-white); 256 + --tw-prose-invert-code: var(--color-white); 257 + --tw-prose-invert-pre-code: var(--color-base-300); 258 + --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%); 259 + --tw-prose-invert-th-borders: var(--color-base-600); 260 + --tw-prose-invert-td-borders: var(--color-base-700); 261 + } 262 + </style>
+22
src/lib/components/extra/post/embeds/Embed.svelte
··· 1 + <script lang="ts"> 2 + import type { PostData } from '../'; 3 + import External from './External.svelte'; 4 + import Images from './Images.svelte'; 5 + import Video from './Video.svelte'; 6 + 7 + const { data }: { data: PostData } = $props(); 8 + </script> 9 + 10 + {#if data.embed} 11 + <div class="flex flex-col gap-2 pt-3 text-sm"> 12 + {#if data.embed.type === 'images'} 13 + <Images data={data.embed} /> 14 + {:else if data.embed.type === 'external' && data.embed.external} 15 + <External data={data.embed} /> 16 + {:else if data.embed.type === 'video' && data.embed.video} 17 + <Video data={data.embed} /> 18 + {:else if data.embed.type === 'unknown'} 19 + <div class="text-base-700 dark:text-base-300 text-sm p-4 rounded-2xl bg-base-200/50 dark:bg-base-900/50 border border-base-300 dark:border-base-600/30">Unknown embed type</div> 20 + {/if} 21 + </div> 22 + {/if}
+32
src/lib/components/extra/post/embeds/External.svelte
··· 1 + <script lang="ts"> 2 + import type { PostEmbedExternal } from ".."; 3 + 4 + const { data }: {data: PostEmbedExternal} = $props(); 5 + 6 + const domain = new URL(data.external.href).hostname.replace('www.', ''); 7 + </script> 8 + 9 + <article 10 + class={["group dark:bg-base-900 bg-base-200 border-base-300 dark:border-base-600/30 relative isolate flex flex-col justify-end overflow-hidden rounded-2xl border p-4", 11 + data.external.thumb ? 'aspect-[16/9]' : '' 12 + ]} 13 + > 14 + {#if data.external.thumb} 15 + <img 16 + src={data.external.thumb} 17 + alt={data.external.description} 18 + class="absolute inset-0 -z-10 size-full object-cover transition-transform duration-500 group-hover:scale-105 ease-in-out" 19 + /> 20 + {/if} 21 + <div class="dark:from-base-950/90 dark:via-base-950/40 from-base-50/90 via-base-50/40 absolute inset-0 -z-10 bg-gradient-to-t"></div> 22 + 23 + <div class="text-base-700 dark:text-base-300 flex flex-wrap items-center gap-y-1 overflow-hidden text-sm"> 24 + <div>{domain}</div> 25 + </div> 26 + <h3 class="mt-1 text-lg/6 font-semibold dark:text-base-50 text-base-900 group-hover:text-accent-600 dark:group-hover:text-accent-400 transition-colors duration-200"> 27 + <a href={data.external.href} target="_blank" rel="noopener noreferrer nofollow"> 28 + <span class="absolute inset-0"></span> 29 + {data.external.title} 30 + </a> 31 + </h3> 32 + </article>
+38
src/lib/components/extra/post/embeds/Images.svelte
··· 1 + <script lang="ts"> 2 + import type { PostEmbedImage } from '..'; 3 + 4 + const { data }: { data: PostEmbedImage } = $props(); 5 + </script> 6 + 7 + {#snippet imageSnippet( 8 + image: { 9 + alt: string; 10 + thumb: string; 11 + fullsize: string; 12 + aspectRatio?: { width: number; height: number }; 13 + }, 14 + className?: string 15 + )} 16 + <img 17 + loading="lazy" 18 + src={image.thumb} 19 + alt={image.alt} 20 + style={image.aspectRatio 21 + ? `aspect-ratio: ${image.aspectRatio.width} / ${image.aspectRatio.height}` 22 + : 'aspect-ratio: 1 / 1'} 23 + class={[ 24 + 'border-base-500/20 dark:border-base-400/20 w-full rounded-2xl border object-cover', 25 + className 26 + ]} 27 + /> 28 + {/snippet} 29 + 30 + {#if data.images.length === 1} 31 + {@render imageSnippet(data.images[0])} 32 + {:else} 33 + <div class="columns-2 gap-4"> 34 + {#each data.images as image} 35 + {@render imageSnippet(image, 'mb-4')} 36 + {/each} 37 + </div> 38 + {/if}
+53
src/lib/components/extra/post/embeds/Video.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import Hls from 'hls.js'; 4 + import type { PostEmbedVideo } from '..'; 5 + 6 + const { data }: { data: PostEmbedVideo } = $props(); 7 + 8 + onMount(async () => { 9 + const Plyr = (await import('plyr')).default; 10 + if (Hls.isSupported()) { 11 + var hls = new Hls(); 12 + hls.loadSource(data.video.playlist); 13 + hls.attachMedia(element); 14 + } 15 + 16 + new Plyr(element, { 17 + controls: [ 18 + 'play-large', 19 + 'play', 20 + 'progress', 21 + 'current-time', 22 + 'mute', 23 + 'volume', 24 + 'fullscreen', 25 + ], 26 + ratio: data.video.aspectRatio 27 + ? `${data.video.aspectRatio.width}:${data.video.aspectRatio.height}` 28 + : '16:9' 29 + }); 30 + }); 31 + 32 + let element: HTMLMediaElement; 33 + </script> 34 + 35 + <svelte:head> 36 + <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" /> 37 + </svelte:head> 38 + 39 + <div 40 + style={data.video.aspectRatio 41 + ? `aspect-ratio: ${data.video.aspectRatio.width} / ${data.video.aspectRatio.height}` 42 + : 'aspect-ratio: 16 / 9'} 43 + class="border-base-300 dark:border-base-400/40 w-full max-w-full overflow-hidden rounded-2xl border" 44 + > 45 + <!-- svelte-ignore a11y_media_has_caption --> 46 + <video bind:this={element} class="h-full w-full" aria-label={data.video.alt}></video> 47 + </div> 48 + 49 + <style> 50 + * { 51 + --plyr-color-main: var(--color-accent-500); 52 + } 53 + </style>
+158
src/lib/components/extra/post/index.ts
··· 1 + import { RichText } from '@atproto/api'; 2 + 3 + export type PostEmbedImage = { 4 + type: 'images'; 5 + 6 + images: { 7 + alt: string; 8 + thumb: string; 9 + fullsize: string; 10 + aspectRatio?: { 11 + width: number; 12 + height: number; 13 + }; 14 + }[]; 15 + }; 16 + 17 + export type PostEmbedExternal = { 18 + type: 'external'; 19 + 20 + external: { 21 + href: string; 22 + thumb: string; 23 + title: string; 24 + description: string; 25 + }; 26 + }; 27 + 28 + export type PostEmbedVideo = { 29 + type: 'video'; 30 + 31 + video: { 32 + playlist: string; 33 + 34 + thumb: string; 35 + alt: string; 36 + 37 + aspectRatio?: { 38 + width: number; 39 + height: number; 40 + }; 41 + }; 42 + }; 43 + 44 + export type UnknownEmbed = { 45 + type: 'unknown'; 46 + } & Record<string, unknown>; 47 + 48 + function blueskyEmbedTypeToEmbedType(type: string) { 49 + switch (type) { 50 + case 'app.bsky.embed.external#view': 51 + case 'app.bsky.embed.external': 52 + return 'external'; 53 + case 'app.bsky.embed.images#view': 54 + case 'app.bsky.embed.images': 55 + return 'images'; 56 + case 'app.bsky.embed.video#view': 57 + case 'app.bsky.embed.video': 58 + return 'video'; 59 + default: 60 + return 'unknown'; 61 + } 62 + } 63 + 64 + export type PostData = { 65 + reposted?: { handle: string; href: string }; 66 + 67 + author: { 68 + displayName: string; 69 + handle: string; 70 + avatar: string; 71 + href: string; 72 + }; 73 + 74 + replyCount: number; 75 + repostCount: number; 76 + likeCount: number; 77 + 78 + createdAt: string; 79 + 80 + embed?: PostEmbedImage | PostEmbedExternal | PostEmbedVideo | UnknownEmbed; 81 + 82 + htmlContent?: string; 83 + }; 84 + 85 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 86 + export function blueskyPostToPostData(post: any): PostData { 87 + return { 88 + author: { 89 + displayName: post.author.displayName, 90 + handle: post.author.handle, 91 + avatar: post.author.avatar, 92 + href: '#' 93 + }, 94 + replyCount: post.replyCount, 95 + repostCount: post.repostCount, 96 + likeCount: post.likeCount, 97 + createdAt: post.createdAt, 98 + 99 + embed: { 100 + type: blueskyEmbedTypeToEmbedType(post.embed.$type), 101 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 102 + images: post.embed.images?.map((image: any) => ({ 103 + alt: image.alt, 104 + thumb: image.thumb, 105 + aspectRatio: image.aspectRatio, 106 + fullsize: image.fullsize 107 + })), 108 + external: post.embed.external 109 + ? { 110 + href: post.embed.external.uri, 111 + title: post.embed.external.title, 112 + description: post.embed.external.description, 113 + thumb: post.embed.external.thumb 114 + } 115 + : undefined, 116 + video: post.embed 117 + ? { 118 + playlist: post.embed.playlist, 119 + thumb: post.embed.thumbnail, 120 + alt: post.embed.alt, 121 + aspectRatio: post.embed.aspectRatio 122 + } 123 + : undefined 124 + } as PostEmbedExternal | PostEmbedVideo | PostEmbedImage | UnknownEmbed, 125 + 126 + htmlContent: blueskyPostToHTML(post) 127 + }; 128 + } 129 + 130 + // eslint-disable-next-line @typescript-eslint/no-explicit-any 131 + export function blueskyPostToHTML(post: any, baseBskyUrl: string = 'https://bsky.app') { 132 + if (!post?.record) { 133 + return ''; 134 + } 135 + const rt = new RichText(post.record); 136 + let html = ''; 137 + 138 + const createLink = (href: string, text: string) => { 139 + return `<a target="_blank" rel="noopener noreferrer nofollow" href="${encodeURI(href)}">${encodeURI(text)}</a>`; 140 + }; 141 + 142 + for (const segment of rt.segments()) { 143 + if (!segment) continue; 144 + if (segment.isLink() && segment.link?.uri) { 145 + html += createLink(segment.link?.uri, segment.text); 146 + } else if (segment.isMention() && segment.mention?.did) { 147 + html += createLink(`${baseBskyUrl}/profile/${segment.mention?.did}`, segment.text); 148 + } else if (segment.isTag() && segment.tag?.tag) { 149 + html += createLink(`${baseBskyUrl}/hashtag/${segment.tag?.tag}`, segment.text); 150 + } else { 151 + html += segment.text; 152 + } 153 + } 154 + 155 + return html.replace(/\n/g, '<br>'); 156 + } 157 + 158 + export { default as Post } from './Post.svelte';
+17
src/lib/components/extra/post/relative-time/RelativeTime.svelte
··· 1 + <script lang="ts"> 2 + import { onDestroy } from 'svelte'; 3 + import { register, unregister } from './state'; 4 + 5 + export let date: Date | number; 6 + export let locale: string; 7 + export let live = true; 8 + 9 + let instance = new Object(); 10 + let text = ''; 11 + 12 + register(instance, date, locale, live, (value) => ({ text } = value)); 13 + 14 + onDestroy(() => unregister(instance)); 15 + </script> 16 + 17 + <span class={$$props.class}>{text}</span>
+31
src/lib/components/extra/post/relative-time/action.ts
··· 1 + import type { Callback } from './render'; 2 + import { register, unregister } from './state'; 3 + 4 + export interface Options { 5 + date: Date | number; 6 + locale?: string; 7 + live?: boolean; 8 + } 9 + 10 + export function relativeTime(node: HTMLElement, options: Options) { 11 + const callback: Callback = ({ text }) => (node.textContent = text); 12 + 13 + function init(options: Options) { 14 + const date = options.date; 15 + const locale = options.locale ?? navigator.language; 16 + const live = (options.live = true); 17 + 18 + register(node, date, locale, live, callback); 19 + } 20 + 21 + init(options); 22 + 23 + return { 24 + update(options: Options) { 25 + init(options); 26 + }, 27 + destroy() { 28 + unregister(node); 29 + } 30 + }; 31 + }
+12
src/lib/components/extra/post/relative-time/formatter.ts
··· 1 + // keep a cache of formatter per locale, to avoid re-creating them (GC) 2 + const formatters = new Map<string, Intl.RelativeTimeFormat>(); 3 + 4 + // get the Intl.RelativeTimeFormat formatter for the given locale 5 + export function getFormatter(locale: string) { 6 + if (formatters.has(locale)) { 7 + return formatters.get(locale)!; 8 + } 9 + const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'always', style: 'narrow' }); 10 + formatters.set(locale, formatter); 11 + return formatter; 12 + }
+6
src/lib/components/extra/post/relative-time/index.ts
··· 1 + // adapted from https://github.com/CaptainCodeman/svelte-relative-time 2 + 3 + export * from './action'; 4 + export type { Callback } from './render'; 5 + export { register, unregister } from './state'; 6 + export { default } from './RelativeTime.svelte';
+80
src/lib/components/extra/post/relative-time/render.ts
··· 1 + export type Callback = (result: { 2 + seconds: number; 3 + count: number; 4 + units: Intl.RelativeTimeFormatUnit; 5 + text: string; 6 + }) => void; 7 + 8 + export interface RenderState { 9 + date: Date | number; 10 + callback: Callback; 11 + formatter: Intl.RelativeTimeFormat; 12 + } 13 + 14 + // Array reprsenting one minute, hour, day, week, month, etc in seconds 15 + const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity]; 16 + 17 + // Array equivalent to the above but in the string representation of the units 18 + const formatUnits: Intl.RelativeTimeFormatUnit[] = [ 19 + 'seconds', 20 + 'minutes', 21 + 'hours', 22 + 'days', 23 + 'weeks', 24 + 'months', 25 + 'years' 26 + ]; 27 + 28 + // function to render relative time into 29 + export function render(state: RenderState, now: number) { 30 + const { date, callback, formatter } = state; 31 + 32 + // Allow dates or times to be passed 33 + const timeMs = typeof date === 'number' ? date : date.getTime(); 34 + 35 + // Get the amount of seconds between the given date and now 36 + const delta = timeMs - now; 37 + const seconds = Math.round(delta / 1000); 38 + 39 + // Grab the ideal cutoff unit 40 + const unitIndex = cutoffs.findIndex((cutoff) => cutoff > Math.abs(seconds)); 41 + 42 + // units 43 + const units = formatUnits[unitIndex]; 44 + 45 + // Get the divisor to divide from the seconds. E.g. if our unit is 'day' our divisor 46 + // is one day in seconds, so we can divide our seconds by this to get the # of days 47 + const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1; 48 + 49 + // count of units 50 + const count = Math.round(seconds / divisor); 51 + 52 + // Intl.RelativeTimeFormat do its magic 53 + callback({ 54 + seconds: seconds, 55 + count, 56 + units, 57 + text: formatter.format(count, units).replace('ago', '') 58 + }); 59 + 60 + // calculate time to next update, taking account rounding (e.g. it goes from 2 minutes to 1 minute at 90 seconds) 61 + // and also the changeover from units (59 seconds shouldn't show as 1 minute, so at 61 seconds we schedule the next 62 + // update for 1 second time) 63 + 64 + const divisorMs = divisor * 1000; 65 + 66 + let updateIn; 67 + 68 + if (unitIndex) { 69 + updateIn = divisorMs / 2 - (Math.abs(delta) % divisorMs); 70 + if (updateIn < 0) { 71 + updateIn += divisorMs; 72 + } 73 + } else { 74 + updateIn = divisorMs - (Math.abs(delta) % divisorMs); 75 + } 76 + 77 + const updateAt = now + updateIn; 78 + 79 + return updateAt; 80 + }
+60
src/lib/components/extra/post/relative-time/state.ts
··· 1 + import { getFormatter } from './formatter'; 2 + import { render } from './render'; 3 + import type { Callback, RenderState } from './render'; 4 + 5 + interface UpdateState extends RenderState { 6 + update: number; 7 + } 8 + 9 + // keep track of each instance 10 + const instances = new Map<Object, UpdateState>(); 11 + 12 + // we use a single timer for efficiency and to keep updates in sync 13 + let updateInterval: number | NodeJS.Timeout; 14 + 15 + // register or update instance 16 + export function register( 17 + instance: Object, 18 + date: Date | number, 19 + locale: string, 20 + live: boolean, 21 + callback: Callback 22 + ) { 23 + // get the formatter for the given locale, we do this here so we don't keep having to look it up on each tick 24 + const formatter = getFormatter(locale); 25 + 26 + // create state to render 27 + const state = { date, callback, formatter }; 28 + 29 + // initial render is immediate, so works for SSR 30 + const update = render(state, Date.now()); 31 + 32 + // if it's to update live, we keep a track and schedule the next update 33 + if (live) { 34 + instances.set(instance, { ...state, update }); 35 + } else { 36 + instances.delete(instance); 37 + } 38 + 39 + // start the clock ticking if there are any live instances 40 + if (instances.size) { 41 + updateInterval = 42 + updateInterval || 43 + setInterval(() => { 44 + const now = Date.now(); 45 + for (const state of instances.values()) { 46 + if (state.update <= now) { 47 + state.update = render(state, now); 48 + } 49 + } 50 + }, 1000); 51 + } 52 + } 53 + 54 + export function unregister(instance: Object) { 55 + instances.delete(instance); 56 + if (instances.size === 0) { 57 + clearInterval(updateInterval); 58 + updateInterval = 0; 59 + } 60 + }
+52
src/lib/components/extra/progress/Progress.svelte
··· 1 + <script lang="ts"> 2 + import { Progress as ProgressPrimitive, type WithoutChildrenOrChild } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + 5 + let { 6 + ref = $bindable(null), 7 + progress = $bindable(0), 8 + value = $bindable(0), 9 + max = $bindable(100), 10 + 11 + class: className, 12 + backgroundClass, 13 + progressClass, 14 + ...restProps 15 + }: WithoutChildrenOrChild<ProgressPrimitive.RootProps> & { 16 + progress?: number; 17 + 18 + backgroundClass?: string; 19 + 20 + value?: number; 21 + max?: number; 22 + 23 + progressClass?: string; 24 + } = $props(); 25 + </script> 26 + 27 + <ProgressPrimitive.Root 28 + bind:ref 29 + class={cn( 30 + 'bg-base-100 dark:bg-base-900 relative h-2 w-full overflow-hidden rounded-2xl', 31 + className 32 + )} 33 + {value} 34 + {max} 35 + {...restProps} 36 + > 37 + <div 38 + class={cn( 39 + 'border-base-300 dark:border-base-800 absolute h-full w-full rounded-2xl border', 40 + backgroundClass 41 + )} 42 + ></div> 43 + 44 + <div 45 + class={cn( 46 + 'border-accent-600/50 bg-accent-500/50 dark:border-accent-600/50 dark:bg-accent-900/70 absolute h-full w-full rounded-2xl transition-all duration-300 ease-in-out', 47 + progress > 0 ? 'border' : '', 48 + progressClass 49 + )} 50 + style="width: {Math.min((progress / max) * 100, 100)}%" 51 + ></div> 52 + </ProgressPrimitive.Root>
+1
src/lib/components/extra/progress/index.ts
··· 1 + export { default as Progress } from './Progress.svelte';
+20
src/lib/components/extra/pulse-loader/PulseLoader.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + let { 7 + ref = $bindable(null), 8 + 9 + class: className, 10 + ...restProps 11 + }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> = $props(); 12 + </script> 13 + 14 + <div class={cn('relative inline-flex size-3', className)} {...restProps} bind:this={ref}> 15 + <div class="border-accent-600/40 bg-accent-600/40 -z-10 h-full w-full rounded-full border"></div> 16 + <div class="bg-accent-500/50 absolute inset-0 h-full w-full animate-ping rounded-full"></div> 17 + <div 18 + class="border-accent-500/50 bg-accent-500/50 absolute inset-0 z-10 h-full w-full animate-pulse rounded-full border" 19 + ></div> 20 + </div>
+1
src/lib/components/extra/pulse-loader/index.ts
··· 1 + export { default as PulseLoader } from './PulseLoader.svelte';
+89
src/lib/components/extra/quote/Quote.svelte
··· 1 + <script lang="ts"> 2 + import { 3 + Avatar as AvatarPrimitive, 4 + type WithElementRef, 5 + type WithoutChildrenOrChild 6 + } from 'bits-ui'; 7 + import type { HTMLAttributes } from 'svelte/elements'; 8 + import { cn } from '$lib/utils'; 9 + 10 + let { 11 + quote, 12 + author, 13 + role, 14 + 15 + ref = $bindable(null), 16 + 17 + imageUseThemeColor = false, 18 + 19 + class: className, 20 + ...restProps 21 + }: WithoutChildrenOrChild<WithElementRef<HTMLAttributes<HTMLDivElement>>> & { 22 + quote?: string; 23 + author?: { 24 + src?: string; 25 + alt?: string; 26 + fallback?: string; 27 + 28 + role?: string; 29 + name?: string; 30 + 31 + avatarClass?: string; 32 + fallbackClass?: string; 33 + imageClass?: string; 34 + }; 35 + 36 + imageUseThemeColor?: boolean; 37 + } = $props(); 38 + </script> 39 + 40 + <div 41 + class={cn( 42 + 'flex flex-col items-start justify-between gap-4 md:flex-row md:items-center md:gap-6', 43 + className 44 + )} 45 + bind:this={ref} 46 + {...restProps} 47 + > 48 + <div class="h-24 w-24 shrink-0 md:h-32 md:w-32"> 49 + <AvatarPrimitive.Root 50 + class={cn( 51 + 'border-base-200 bg-base-100 text-base-900 dark:border-base-800 dark:bg-base-900 dark:text-base-50 relative flex h-24 w-24 shrink-0 overflow-hidden rounded-2xl border md:h-32 md:w-32', 52 + className 53 + )} 54 + > 55 + {#if imageUseThemeColor} 56 + <div class="bg-accent-500/30 absolute inset-0 z-20 size-full"></div> 57 + {/if} 58 + {#if author?.src} 59 + <AvatarPrimitive.Image 60 + class={cn('aspect-square size-full object-cover', author.imageClass)} 61 + src={author.src} 62 + alt={author.alt} 63 + style={{ filter: imageUseThemeColor ? 'grayscale(100%) brightness(1.2)' : '' }} 64 + /> 65 + {/if} 66 + 67 + {#if author?.fallback} 68 + <AvatarPrimitive.Fallback 69 + class={cn('flex size-full items-center justify-center', author.fallbackClass)} 70 + > 71 + {author.fallback} 72 + </AvatarPrimitive.Fallback> 73 + {/if} 74 + </AvatarPrimitive.Root> 75 + </div> 76 + <div class="flex flex-col gap-2"> 77 + <blockquote class="text-base-900 dark:text-base-50 text-lg font-medium"> 78 + <p> 79 + "{quote}" 80 + </p> 81 + </blockquote> 82 + 83 + <div class="flex items-center gap-2 text-sm"> 84 + <div class="text-accent-600 dark:text-accent-500 font-medium">{author?.name}</div> 85 + 86 + <div class="text-base-600 dark:text-base-400">{author?.role}</div> 87 + </div> 88 + </div> 89 + </div>
+47
src/lib/components/extra/social-icons/All.svelte
··· 1 + <script lang="ts"> 2 + import Discord from './Discord.svelte'; 3 + import Github from './Github.svelte'; 4 + import Twitter from './Twitter.svelte'; 5 + import Youtube from './Youtube.svelte'; 6 + import Bluesky from './Bluesky.svelte'; 7 + import Facebook from './Facebook.svelte'; 8 + 9 + const { 10 + discord, 11 + github, 12 + twitter, 13 + youtube, 14 + bluesky, 15 + facebook, 16 + svgClasses 17 + }: { 18 + discord?: string; 19 + github?: string; 20 + twitter?: string; 21 + youtube?: string; 22 + bluesky?: string; 23 + facebook?: string; 24 + svgClasses?: string; 25 + } = $props(); 26 + </script> 27 + 28 + <div class="flex flex-wrap items-center gap-4"> 29 + {#if discord} 30 + <Discord href={discord} {svgClasses} /> 31 + {/if} 32 + {#if github} 33 + <Github href={github} {svgClasses} /> 34 + {/if} 35 + {#if twitter} 36 + <Twitter href={twitter} {svgClasses} /> 37 + {/if} 38 + {#if youtube} 39 + <Youtube href={youtube} {svgClasses} /> 40 + {/if} 41 + {#if bluesky} 42 + <Bluesky href={bluesky} {svgClasses} /> 43 + {/if} 44 + {#if facebook} 45 + <Facebook href={facebook} {svgClasses} /> 46 + {/if} 47 + </div>
+37
src/lib/components/extra/social-icons/Bluesky.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + const { 7 + class: className, 8 + target = '_blank', 9 + svgClasses, 10 + ...restProps 11 + }: WithoutChildrenOrChild<WithElementRef<HTMLAnchorAttributes>> & { 12 + svgClasses?: string; 13 + } = $props(); 14 + </script> 15 + 16 + <a 17 + {target} 18 + class={cn( 19 + 'text-base-800 hover:text-accent-600 dark:text-base-300 dark:hover:text-accent-400 transition-colors', 20 + className 21 + )} 22 + {...restProps} 23 + > 24 + <span class="sr-only">Bluesky</span> 25 + 26 + <svg 27 + role="img" 28 + viewBox="0 0 24 24" 29 + xmlns="http://www.w3.org/2000/svg" 30 + class={['size-6', svgClasses]} 31 + aria-hidden="true" 32 + fill="currentColor" 33 + ><path 34 + d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z" 35 + /></svg 36 + > 37 + </a>
+37
src/lib/components/extra/social-icons/Discord.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + const { 7 + class: className, 8 + target = '_blank', 9 + svgClasses, 10 + ...restProps 11 + }: WithoutChildrenOrChild<WithElementRef<HTMLAnchorAttributes>> & { 12 + svgClasses?: string; 13 + } = $props(); 14 + </script> 15 + 16 + <a 17 + {target} 18 + class={cn( 19 + 'text-base-800 hover:text-accent-600 dark:text-base-300 dark:hover:text-accent-400 transition-colors', 20 + className 21 + )} 22 + {...restProps} 23 + > 24 + <span class="sr-only">Discord</span> 25 + 26 + <svg 27 + role="img" 28 + viewBox="0 0 24 24" 29 + xmlns="http://www.w3.org/2000/svg" 30 + class={['size-6', svgClasses]} 31 + fill="currentColor" 32 + aria-hidden="true" 33 + ><path 34 + d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" 35 + /></svg 36 + > 37 + </a>
+37
src/lib/components/extra/social-icons/Facebook.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + const { 7 + class: className, 8 + target = '_blank', 9 + svgClasses, 10 + ...restProps 11 + }: WithoutChildrenOrChild<WithElementRef<HTMLAnchorAttributes>> & { 12 + svgClasses?: string; 13 + } = $props(); 14 + </script> 15 + 16 + <a 17 + {target} 18 + class={cn( 19 + 'text-base-800 hover:text-accent-600 dark:text-base-300 dark:hover:text-accent-400 transition-colors', 20 + className 21 + )} 22 + {...restProps} 23 + > 24 + <span class="sr-only">Facebook</span> 25 + 26 + <svg 27 + role="img" 28 + viewBox="0 0 24 24" 29 + xmlns="http://www.w3.org/2000/svg" 30 + class={['size-6', svgClasses]} 31 + fill="currentColor" 32 + aria-hidden="true" 33 + ><path 34 + d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z" 35 + /></svg 36 + > 37 + </a>
+37
src/lib/components/extra/social-icons/Github.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + const { 7 + class: className, 8 + target = '_blank', 9 + svgClasses, 10 + ...restProps 11 + }: WithoutChildrenOrChild<WithElementRef<HTMLAnchorAttributes>> & { 12 + svgClasses?: string; 13 + } = $props(); 14 + </script> 15 + 16 + <a 17 + {target} 18 + class={cn( 19 + 'text-base-800 hover:text-accent-600 dark:text-base-300 dark:hover:text-accent-400 transition-colors', 20 + className 21 + )} 22 + {...restProps} 23 + > 24 + <span class="sr-only">GitHub</span> 25 + 26 + <svg 27 + role="img" 28 + viewBox="0 0 24 24" 29 + xmlns="http://www.w3.org/2000/svg" 30 + fill="currentColor" 31 + aria-hidden="true" 32 + class={['size-6', svgClasses]} 33 + ><path 34 + d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" 35 + /></svg 36 + > 37 + </a>
+37
src/lib/components/extra/social-icons/Twitter.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + const { 7 + class: className, 8 + target = '_blank', 9 + svgClasses, 10 + ...restProps 11 + }: WithoutChildrenOrChild<WithElementRef<HTMLAnchorAttributes>> & { 12 + svgClasses?: string; 13 + } = $props(); 14 + </script> 15 + 16 + <a 17 + {target} 18 + class={cn( 19 + 'text-base-800 hover:text-accent-600 dark:text-base-300 dark:hover:text-accent-400 transition-colors', 20 + className 21 + )} 22 + {...restProps} 23 + > 24 + <span class="sr-only">X</span> 25 + 26 + <svg 27 + role="img" 28 + viewBox="0 0 24 24" 29 + class={['size-6', svgClasses]} 30 + fill="currentColor" 31 + aria-hidden="true" 32 + xmlns="http://www.w3.org/2000/svg" 33 + ><title>X</title><path 34 + d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" 35 + /></svg 36 + > 37 + </a>
+36
src/lib/components/extra/social-icons/Youtube.svelte
··· 1 + <script lang="ts"> 2 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes } from 'svelte/elements'; 4 + import { cn } from '$lib/utils'; 5 + 6 + const { 7 + class: className, 8 + target = '_blank', 9 + svgClasses, 10 + ...restProps 11 + }: WithoutChildrenOrChild<WithElementRef<HTMLAnchorAttributes>> & { 12 + svgClasses?: string; 13 + } = $props(); 14 + </script> 15 + 16 + <a 17 + {target} 18 + class={cn( 19 + 'text-base-800 hover:text-accent-600 dark:text-base-300 dark:hover:text-accent-400 transition-colors', 20 + className 21 + )} 22 + {...restProps} 23 + > 24 + <span class="sr-only">YouTube</span> 25 + <svg 26 + class={['size-7', svgClasses]} 27 + fill="currentColor" 28 + aria-hidden="true" 29 + role="img" 30 + viewBox="0 0 24 24" 31 + xmlns="http://www.w3.org/2000/svg" 32 + ><path 33 + d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" 34 + /></svg 35 + > 36 + </a>
+9
src/lib/components/extra/social-icons/index.ts
··· 1 + // all icons from https://simpleicons.org/ 2 + 3 + export { default as SocialIcons } from './All.svelte'; 4 + export { default as Discord } from './Discord.svelte'; 5 + export { default as Github } from './Github.svelte'; 6 + export { default as Twitter } from './Twitter.svelte'; 7 + export { default as Youtube } from './Youtube.svelte'; 8 + export { default as Bluesky } from './Bluesky.svelte'; 9 + export { default as Facebook } from './Facebook.svelte';
+104
src/lib/components/extra/star-rating/StarRating.svelte
··· 1 + <script lang="ts"> 2 + // todo convert to radio group 3 + import { cn } from '$lib/utils'; 4 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 5 + import type { HTMLAttributes } from 'svelte/elements'; 6 + 7 + let { 8 + rating = $bindable(), 9 + size = 'size-8', 10 + changeable = true, 11 + class: className, 12 + ref = $bindable(null), 13 + strokeWidth = 0.8, 14 + buttonClasses, 15 + svgClasses, 16 + ...restProps 17 + }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 18 + rating: number; 19 + size?: string; 20 + changeable?: boolean; 21 + 22 + strokeWidth?: number; 23 + 24 + buttonClasses?: string; 25 + svgClasses?: string; 26 + } = $props(); 27 + 28 + let hoverRating = $state(rating); 29 + 30 + $effect(() => { 31 + hoverRating = rating; 32 + }); 33 + </script> 34 + 35 + <div class={cn('flex items-center', className)} {...restProps} bind:this={ref}> 36 + {#if changeable} 37 + {#each Array.from({ length: 5 }).map((_, i) => i + 1) as i} 38 + <button 39 + class={cn( 40 + 'group focus-visible:outline-base-900 dark:focus-visible:outline-base-100 cursor-pointer rounded-xl focus-visible:outline-2', 41 + buttonClasses 42 + )} 43 + onclick={() => (rating = i)} 44 + onfocus={() => (hoverRating = i)} 45 + onblur={() => (hoverRating = rating)} 46 + onmouseenter={() => (hoverRating = i)} 47 + onmouseleave={() => (hoverRating = rating)} 48 + > 49 + <svg 50 + class={cn( 51 + size, 52 + 'shrink-0', 53 + i > rating && 54 + i > hoverRating && 55 + 'stroke-base-400 text-base-100 dark:stroke-base-700/70 dark:text-base-900', 56 + i <= rating && 57 + i <= hoverRating && 58 + 'stroke-accent-600 text-accent-400 dark:stroke-accent-500/80 dark:text-accent-500/40', 59 + 60 + ((i > rating && i <= hoverRating) || (i > hoverRating && i <= rating)) && 61 + 'stroke-accent-600/50 text-accent-400/50 dark:stroke-accent-500/40 dark:text-accent-500/20', 62 + svgClasses 63 + )} 64 + viewBox="0 0 24 24" 65 + fill="currentColor" 66 + aria-hidden="true" 67 + stroke-width={strokeWidth} 68 + data-slot="icon" 69 + > 70 + <path 71 + fill-rule="evenodd" 72 + d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z" 73 + clip-rule="evenodd" 74 + /> 75 + </svg> 76 + 77 + <span class="sr-only">rate {i} stars</span> 78 + </button> 79 + {/each} 80 + {:else} 81 + {#each Array.from({ length: 5 }).map((_, i) => i + 1) as i} 82 + <svg 83 + class={cn( 84 + size, 85 + 'stroke-base-400 text-base-100 dark:stroke-base-700 dark:text-base-800 shrink-0', 86 + i <= rating && 87 + 'stroke-accent-600 text-accent-400 dark:stroke-accent-500/80 dark:text-accent-500/50', 88 + svgClasses 89 + )} 90 + viewBox="0 0 24 24" 91 + fill="currentColor" 92 + aria-hidden="true" 93 + stroke-width={strokeWidth} 94 + data-slot="icon" 95 + > 96 + <path 97 + fill-rule="evenodd" 98 + d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z" 99 + clip-rule="evenodd" 100 + /> 101 + </svg> 102 + {/each} 103 + {/if} 104 + </div>
+7
src/lib/components/extra/star-rating/index.ts
··· 1 + import Root from './StarRating.svelte'; 2 + 3 + export { 4 + Root, 5 + // 6 + Root as StarRating 7 + };
+49
src/lib/components/extra/timer/Timer.svelte
··· 1 + <script lang="ts"> 2 + import NumberFlow, { NumberFlowGroup } from '@number-flow/svelte'; 3 + import { TimerState } from './TimerState.svelte'; 4 + import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 5 + import type { HTMLAttributes } from 'svelte/elements'; 6 + import { cn } from '$lib/utils.js'; 7 + 8 + let { 9 + timer = $bindable(new TimerState(1000 * 60 * 60)), 10 + class: className, 11 + ref = $bindable(null), 12 + ...restProps 13 + }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 14 + timer?: TimerState; 15 + } = $props(); 16 + 17 + const hh = $derived(Math.floor(timer.remaining / 3600000)); 18 + const mm = $derived(Math.floor((timer.remaining % 3600000) / 60000)); 19 + const ss = $derived(Math.floor((timer.remaining % 60000) / 1000)); 20 + </script> 21 + 22 + <NumberFlowGroup> 23 + <div 24 + bind:this={ref} 25 + class={cn( 26 + 'text-base-900 dark:text-base-100 flex w-full justify-center text-5xl font-bold sm:text-7xl', 27 + className 28 + )} 29 + style="font-variant-numeric: tabular-nums;" 30 + {...restProps} 31 + > 32 + <NumberFlow value={hh} trend={-1} format={{ minimumIntegerDigits: 2 }} /> 33 + 34 + <NumberFlow 35 + value={mm} 36 + trend={-1} 37 + format={{ minimumIntegerDigits: 2 }} 38 + prefix=":" 39 + digits={{ 1: { max: 5 } }} 40 + /> 41 + <NumberFlow 42 + value={ss} 43 + format={{ minimumIntegerDigits: 2 }} 44 + trend={-1} 45 + prefix=":" 46 + digits={{ 1: { max: 5 } }} 47 + /> 48 + </div> 49 + </NumberFlowGroup>
+104
src/lib/components/extra/timer/TimerState.svelte.ts
··· 1 + // adopted from https://github.com/joshnuss/svelte-reactive-timer 2 + 3 + type Status = 'running' | 'paused' | 'stopped'; 4 + type Options = { 5 + precision?: number; 6 + }; 7 + 8 + export class TimerState { 9 + status = $state<Status>('stopped'); 10 + duration = $state<number>(0); 11 + elapsed = $state<number>(0); 12 + remaining = $derived(this.duration - this.elapsed); 13 + startAt = $state<Date | null>(null); 14 + endAt = $state<Date | null>(null); 15 + time = $state<Date | null>(null); 16 + precision: number; 17 + #interval: number | null = null; 18 + 19 + constructor(duration: number, options: Options = {}) { 20 + this.duration = duration; 21 + this.precision = options.precision ?? 300; 22 + } 23 + 24 + get isRunning() { 25 + return this.status == 'running'; 26 + } 27 + 28 + get isStopped() { 29 + return this.status == 'stopped'; 30 + } 31 + 32 + get isPaused() { 33 + return this.status == 'paused'; 34 + } 35 + 36 + start() { 37 + if (this.isRunning) { 38 + this.stop(); 39 + } 40 + 41 + this.time = new Date(); 42 + this.startAt = this.time; 43 + this.endAt = new Date(this.time.getTime() + this.duration); 44 + this.status = 'running'; 45 + this.elapsed = 0; 46 + this.#schedule(); 47 + } 48 + 49 + stop() { 50 + this.#dispose(); 51 + this.status = 'stopped'; 52 + } 53 + 54 + pause() { 55 + this.#dispose(); 56 + this.status = 'paused'; 57 + } 58 + 59 + resume() { 60 + this.time = new Date(); 61 + this.endAt = new Date(this.time.getTime() + this.remaining); 62 + this.status = 'running'; 63 + this.#schedule(); 64 + } 65 + 66 + reset(duration?: number) { 67 + if (duration) { 68 + this.duration = duration; 69 + } 70 + this.#dispose(); 71 + 72 + if (!this.isStopped) { 73 + this.start(); 74 + } else { 75 + this.time = new Date(); 76 + this.startAt = this.time; 77 + this.endAt = new Date(this.time.getTime() + this.duration); 78 + this.elapsed = 0; 79 + } 80 + } 81 + 82 + #schedule() { 83 + this.time = new Date(); 84 + this.#interval = setInterval(() => this.#onInterval(), this.precision); 85 + } 86 + 87 + #dispose() { 88 + if (this.#interval) { 89 + clearInterval(this.#interval); 90 + } 91 + } 92 + 93 + #onInterval() { 94 + const now = new Date(); 95 + 96 + this.elapsed += now.getTime() - this.time!.getTime(); 97 + this.time = now; 98 + 99 + if (this.time >= this.endAt!) { 100 + this.elapsed = this.duration; 101 + this.stop(); 102 + } 103 + } 104 + }
+159
src/lib/components/extra/video-player/FullscreenVideoPlayer.svelte
··· 1 + <script lang="ts" module> 2 + export const videoPlayer: { 3 + showing: boolean; 4 + id: string | undefined; 5 + 6 + show: (id: string) => void; 7 + hide: () => void; 8 + } = $state({ 9 + showing: false, 10 + id: undefined, 11 + 12 + show: (id: string) => { 13 + videoPlayer.id = id; 14 + videoPlayer.showing = true; 15 + }, 16 + 17 + hide: () => { 18 + videoPlayer.id = undefined; 19 + videoPlayer.showing = false; 20 + } 21 + }); 22 + </script> 23 + 24 + <script lang="ts"> 25 + import { Portal } from 'bits-ui'; 26 + import { onMount } from 'svelte'; 27 + 28 + const { class: className }: { class?: string } = $props(); 29 + 30 + let Plyr: typeof import('plyr') | undefined = $state(); 31 + onMount(async () => { 32 + Plyr = (await import('plyr')).default; 33 + }); 34 + 35 + $effect(() => { 36 + if (!Plyr) return; 37 + 38 + const player = new Plyr('.js-player', { 39 + settings: ['captions', 'quality', 'loop', 'speed'], 40 + controls: [ 41 + 'play-large', 42 + 'play', 43 + 'progress', 44 + 'current-time', 45 + 'volume', 46 + 'download', 47 + 'fullscreen' 48 + ] 49 + }); 50 + 51 + // set the video player to the id 52 + if (videoPlayer.id) { 53 + player.source = { 54 + type: 'video', 55 + sources: [ 56 + { 57 + src: videoPlayer.id, 58 + type: 'video/youtube' 59 + } 60 + ] 61 + }; 62 + } 63 + 64 + // when loaded play the video and go fullscreen 65 + player.on('ready', () => { 66 + player.play(); 67 + //player.fullscreen.enter(); 68 + }); 69 + 70 + return () => { 71 + player.destroy(); 72 + }; 73 + }); 74 + 75 + let glow = 40; 76 + </script> 77 + 78 + <svelte:head> 79 + <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" /> 80 + </svelte:head> 81 + 82 + <svelte:window 83 + onkeydown={(e) => { 84 + if (e.key === 'Escape' && videoPlayer.showing) { 85 + videoPlayer.hide(); 86 + } 87 + }} 88 + /> 89 + 90 + {#key videoPlayer.id} 91 + {#if videoPlayer.showing && videoPlayer.id} 92 + <Portal> 93 + <div class="fixed inset-0 z-100 flex h-[100dvh] w-screen items-center justify-center"> 94 + <button 95 + onclick={() => videoPlayer.hide()} 96 + class="absolute inset-0 cursor-default bg-white/70 backdrop-blur-xs dark:bg-black/70" 97 + > 98 + <span class="sr-only">Close</span> 99 + </button> 100 + 101 + <div 102 + class={[ 103 + 'border-base-400 relative mx-4 aspect-video max-h-screen w-full overflow-hidden rounded-xl border bg-white object-cover sm:mx-20 dark:border-white/10 dark:bg-white/5', 104 + className 105 + ]} 106 + style="filter: url(#blur-sm); width: 100%;" 107 + > 108 + <div 109 + id="player" 110 + class="h-full w-full overflow-hidden rounded-xl object-cover font-semibold text-black dark:text-white" 111 + > 112 + <div 113 + class="js-player plyr__video-embed" 114 + id="player" 115 + data-plyr-provider="youtube" 116 + data-plyr-embed-id={videoPlayer.id} 117 + ></div> 118 + </div> 119 + </div> 120 + 121 + <button 122 + onclick={() => { 123 + videoPlayer.hide(); 124 + }} 125 + class="text-base-900 dark:text-base-50 absolute top-2 right-2 z-20 p-2 backdrop-blur-xs" 126 + > 127 + <svg 128 + xmlns="http://www.w3.org/2000/svg" 129 + viewBox="0 0 24 24" 130 + fill="currentColor" 131 + class="size-6" 132 + > 133 + <path 134 + fill-rule="evenodd" 135 + d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z" 136 + clip-rule="evenodd" 137 + /> 138 + </svg> 139 + 140 + <span class="sr-only">Close</span> 141 + </button> 142 + </div> 143 + </Portal> 144 + {/if} 145 + {/key} 146 + 147 + <svg width="0" height="0"> 148 + <filter id="blur-sm" y="-50%" x="-50%" width="200%" height="200%"> 149 + <feGaussianBlur in="SourceGraphic" stdDeviation={glow} result="blurred" /> 150 + <feColorMatrix type="saturate" in="blurred" values="3" /> 151 + <feComposite in="SourceGraphic" operator="over" /> 152 + </filter> 153 + </svg> 154 + 155 + <style> 156 + * { 157 + --plyr-color-main: var(--color-accent-500); 158 + } 159 + </style>
+89
src/lib/components/extra/video-player/VideoPlayer.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import { cn } from '$lib/utils'; 4 + 5 + const { class: className, id }: { class?: string; id?: string } = $props(); 6 + 7 + let Plyr: typeof import('plyr') | undefined = $state(); 8 + onMount(async () => { 9 + Plyr = (await import('plyr')).default; 10 + }); 11 + 12 + $effect(() => { 13 + if (!Plyr) return; 14 + 15 + const player = new Plyr('.js-player', { 16 + settings: ['captions', 'quality', 'loop', 'speed'], 17 + controls: [ 18 + 'play-large', 19 + 'play', 20 + 'progress', 21 + 'current-time', 22 + 'volume', 23 + 'download', 24 + 'fullscreen' 25 + ] 26 + }); 27 + 28 + // set the video player to the id 29 + if (id) { 30 + player.source = { 31 + type: 'video', 32 + sources: [ 33 + { 34 + src: id, 35 + type: 'video/youtube' 36 + } 37 + ] 38 + }; 39 + } 40 + 41 + return () => { 42 + player.destroy(); 43 + }; 44 + }); 45 + 46 + let glow = 15; 47 + </script> 48 + 49 + <svelte:head> 50 + <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" /> 51 + </svelte:head> 52 + 53 + {#key id} 54 + {#if id} 55 + <div 56 + class={cn( 57 + 'border-base-400 relative aspect-video w-full overflow-hidden rounded-2xl border bg-white object-cover dark:border-white/10 dark:bg-white/5', 58 + className 59 + )} 60 + style="filter: url(#blur-fullscreen); width: 100%;" 61 + > 62 + <div 63 + id="player" 64 + class="h-full w-full overflow-hidden rounded-xl object-cover font-semibold text-black dark:text-white" 65 + > 66 + <div 67 + class="js-player plyr__video-embed" 68 + id="player" 69 + data-plyr-provider="youtube" 70 + data-plyr-embed-id={id} 71 + ></div> 72 + </div> 73 + </div> 74 + {/if} 75 + {/key} 76 + 77 + <svg width="0" height="0"> 78 + <filter id="blur-fullscreen" y="-50%" x="-50%" width="200%" height="200%"> 79 + <feGaussianBlur in="SourceGraphic" stdDeviation={glow} result="blurred" /> 80 + <feColorMatrix type="saturate" in="blurred" values="2" /> 81 + <feComposite in="SourceGraphic" operator="over" /> 82 + </filter> 83 + </svg> 84 + 85 + <style> 86 + * { 87 + --plyr-color-main: var(--color-accent-500); 88 + } 89 + </style>
+1
src/lib/index.ts
··· 1 + // place files you want to import through the `$lib` alias in this folder.
+156
src/lib/models/kenney-furniture-kit/bedBunk.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/bedBunk.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props() 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_bedBunk: THREE.Mesh 34 + Mesh_bedBunk_1: THREE.Mesh 35 + Mesh_cover: THREE.Mesh 36 + Mesh_cover_1: THREE.Mesh 37 + pillowBottom: THREE.Mesh 38 + pillowTop: THREE.Mesh 39 + } 40 + materials: { 41 + wood: THREE.MeshStandardMaterial 42 + carpetWhite: THREE.MeshStandardMaterial 43 + carpet: THREE.MeshStandardMaterial 44 + } 45 + } 46 + 47 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/bedBunk.glb') 48 + </script> 49 + 50 + <T.Group 51 + bind:ref 52 + dispose={false} 53 + {...props} 54 + > 55 + <T.Group position={[-0.3, 0, 0.6]}> 56 + {#await gltf} 57 + {@render fallback?.()} 58 + {:then gltf} 59 + <T.Mesh 60 + castShadow 61 + receiveShadow 62 + geometry={gltf.nodes.Mesh_bedBunk.geometry} 63 + material={gltf.materials.wood.clone()} 64 + 65 + material.color={colors?.[0] ?? gltf.materials.wood.color} 66 + material.opacity={opacity ?? gltf.materials.wood.opacity} 67 + material.transparent={opacity !== undefined} 68 + /> 69 + <T.Mesh 70 + castShadow 71 + receiveShadow 72 + geometry={gltf.nodes.Mesh_bedBunk_1.geometry} 73 + material={gltf.materials.carpetWhite.clone()} 74 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 75 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 76 + material.transparent={opacity !== undefined} 77 + /> 78 + <T.Group 79 + position={[-0.38, 0.2, -0.83]} 80 + rotation={[0, -Math.PI / 2, 0]} 81 + scale={0.5} 82 + > 83 + <T.Mesh 84 + castShadow 85 + receiveShadow 86 + geometry={gltf.nodes.Mesh_cover.geometry} 87 + material={gltf.materials.carpet.clone()} 88 + material.color={colors?.[2] ?? gltf.materials.carpet.color} 89 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 90 + material.transparent={opacity !== undefined} 91 + /> 92 + <T.Mesh 93 + castShadow 94 + receiveShadow 95 + geometry={gltf.nodes.Mesh_cover_1.geometry} 96 + material={gltf.materials.carpetWhite.clone()} 97 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 98 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 99 + material.transparent={opacity !== undefined} 100 + /> 101 + </T.Group> 102 + <T.Group 103 + position={[-0.38, 0.73, -0.83]} 104 + rotation={[0, -Math.PI / 2, 0]} 105 + scale={0.5} 106 + > 107 + <T.Mesh 108 + castShadow 109 + receiveShadow 110 + geometry={gltf.nodes.Mesh_cover.geometry} 111 + material={gltf.materials.carpet.clone()} 112 + material.color={colors?.[2] ?? gltf.materials.carpet.color} 113 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 114 + material.transparent={opacity !== undefined} 115 + /> 116 + <T.Mesh 117 + castShadow 118 + receiveShadow 119 + geometry={gltf.nodes.Mesh_cover_1.geometry} 120 + material={gltf.materials.carpetWhite.clone()} 121 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 122 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 123 + material.transparent={opacity !== undefined} 124 + /> 125 + </T.Group> 126 + <T.Mesh 127 + castShadow 128 + receiveShadow 129 + geometry={gltf.nodes.pillowBottom.geometry} 130 + material={gltf.materials.carpetWhite.clone()} 131 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 132 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 133 + material.transparent={opacity !== undefined} 134 + position={[0.12, 0.2, -1.07]} 135 + rotation={[0.13, -Math.PI / 2, 0]} 136 + scale={[0.5, 0.5, 0.85]} 137 + /> 138 + <T.Mesh 139 + castShadow 140 + receiveShadow 141 + geometry={gltf.nodes.pillowTop.geometry} 142 + material={gltf.materials.carpetWhite.clone()} 143 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 144 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 145 + material.transparent={opacity !== undefined} 146 + position={[0.12, 0.74, -1.07]} 147 + rotation={[0.13, -Math.PI / 2, 0]} 148 + scale={[0.5, 0.5, 0.85]} 149 + /> 150 + {:catch err} 151 + {@render error?.({ error: err })} 152 + {/await} 153 + 154 + {@render children?.({ ref })} 155 + </T.Group> 156 + </T.Group>
+134
src/lib/models/kenney-furniture-kit/bedDouble.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/bedDouble.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_bedDouble: THREE.Mesh; 34 + Mesh_bedDouble_1: THREE.Mesh; 35 + Mesh_bedDouble_2: THREE.Mesh; 36 + Mesh_cover: THREE.Mesh; 37 + Mesh_cover_1: THREE.Mesh; 38 + pillowLeft: THREE.Mesh; 39 + pillowRight: THREE.Mesh; 40 + }; 41 + materials: { 42 + carpetWhite: THREE.MeshStandardMaterial; 43 + wood: THREE.MeshStandardMaterial; 44 + metal: THREE.MeshStandardMaterial; 45 + carpet: THREE.MeshStandardMaterial; 46 + }; 47 + }; 48 + 49 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/bedDouble.glb'); 50 + </script> 51 + 52 + <T.Group bind:ref dispose={false} {...props}> 53 + <T.Group position={[-0.5, 0, 0.5]}> 54 + {#await gltf} 55 + {@render fallback?.()} 56 + {:then gltf} 57 + <T.Mesh 58 + castShadow 59 + receiveShadow 60 + geometry={gltf.nodes.Mesh_bedDouble.geometry} 61 + material={gltf.materials.carpetWhite.clone()} 62 + material.color={colors?.[0] ?? gltf.materials.carpetWhite.color} 63 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 64 + material.transparent={opacity !== undefined} 65 + /> 66 + <T.Mesh 67 + castShadow 68 + receiveShadow 69 + geometry={gltf.nodes.Mesh_bedDouble_1.geometry} 70 + material={gltf.materials.wood.clone()} 71 + material.color={colors?.[1] ?? gltf.materials.wood.color} 72 + material.opacity={opacity ?? gltf.materials.wood.opacity} 73 + material.transparent={opacity !== undefined} 74 + /> 75 + <T.Mesh 76 + castShadow 77 + receiveShadow 78 + geometry={gltf.nodes.Mesh_bedDouble_2.geometry} 79 + material={gltf.materials.metal.clone()} 80 + material.color={colors?.[2] ?? gltf.materials.metal.color} 81 + material.opacity={opacity ?? gltf.materials.metal.opacity} 82 + material.transparent={opacity !== undefined} 83 + /> 84 + <T.Group position={[0.01, 0.21, -0.79]} rotation={[0, -Math.PI / 2, 0]} scale={0.5}> 85 + <T.Mesh 86 + castShadow 87 + receiveShadow 88 + geometry={gltf.nodes.Mesh_cover.geometry} 89 + material={gltf.materials.carpet.clone()} 90 + material.color={colors?.[3] ?? gltf.materials.carpet.color} 91 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 92 + material.transparent={opacity !== undefined} 93 + /> 94 + <T.Mesh 95 + castShadow 96 + receiveShadow 97 + geometry={gltf.nodes.Mesh_cover_1.geometry} 98 + material={gltf.materials.carpetWhite.clone()} 99 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 100 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 101 + material.transparent={opacity !== undefined} 102 + /> 103 + </T.Group> 104 + <T.Mesh 105 + castShadow 106 + receiveShadow 107 + geometry={gltf.nodes.pillowLeft.geometry} 108 + material={gltf.materials.carpetWhite.clone()} 109 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 110 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 111 + material.transparent={opacity !== undefined} 112 + position={[0.11, 0.22, -1.03]} 113 + rotation={[0.13, -Math.PI / 2, 0]} 114 + scale={[0.5, 0.5, 0.85]} 115 + /> 116 + <T.Mesh 117 + castShadow 118 + receiveShadow 119 + geometry={gltf.nodes.pillowRight.geometry} 120 + material={gltf.materials.carpetWhite.clone()} 121 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 122 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 123 + material.transparent={opacity !== undefined} 124 + position={[0.5, 0.22, -1.03]} 125 + rotation={[0.13, -Math.PI / 2, 0]} 126 + scale={[0.5, 0.5, 0.85]} 127 + /> 128 + {:catch err} 129 + {@render error?.({ error: err })} 130 + {/await} 131 + 132 + {@render children?.({ ref })} 133 + </T.Group> 134 + </T.Group>
+121
src/lib/models/kenney-furniture-kit/bedSingle.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/bedSingle.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_bedSingle: THREE.Mesh; 34 + Mesh_bedSingle_1: THREE.Mesh; 35 + Mesh_bedSingle_2: THREE.Mesh; 36 + Mesh_cover: THREE.Mesh; 37 + Mesh_cover_1: THREE.Mesh; 38 + pillow: THREE.Mesh; 39 + }; 40 + materials: { 41 + carpetWhite: THREE.MeshStandardMaterial; 42 + wood: THREE.MeshStandardMaterial; 43 + metal: THREE.MeshStandardMaterial; 44 + carpet: THREE.MeshStandardMaterial; 45 + }; 46 + }; 47 + 48 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/bedSingle.glb'); 49 + </script> 50 + 51 + <T.Group bind:ref dispose={false} {...props}> 52 + <T.Group position={[-0.7, 0, 0.5]}> 53 + {#await gltf} 54 + {@render fallback?.()} 55 + {:then gltf} 56 + <T.Mesh 57 + castShadow 58 + receiveShadow 59 + geometry={gltf.nodes.Mesh_bedSingle.geometry} 60 + material={gltf.materials.carpetWhite.clone()} 61 + material.color={colors?.[0] ?? gltf.materials.carpetWhite.color} 62 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 63 + material.transparent={opacity !== undefined} 64 + /> 65 + <T.Mesh 66 + castShadow 67 + receiveShadow 68 + geometry={gltf.nodes.Mesh_bedSingle_1.geometry} 69 + material={gltf.materials.wood.clone()} 70 + material.color={colors?.[1] ?? gltf.materials.wood.color} 71 + material.opacity={opacity ?? gltf.materials.wood.opacity} 72 + material.transparent={opacity !== undefined} 73 + /> 74 + <T.Mesh 75 + castShadow 76 + receiveShadow 77 + geometry={gltf.nodes.Mesh_bedSingle_2.geometry} 78 + material={gltf.materials.metal.clone()} 79 + material.color={colors?.[2] ?? gltf.materials.metal.color} 80 + material.opacity={opacity ?? gltf.materials.metal.opacity} 81 + material.transparent={opacity !== undefined} 82 + /> 83 + <T.Group position={[0.01, 0.21, -0.79]} rotation={[0, -Math.PI / 2, 0]} scale={0.5}> 84 + <T.Mesh 85 + castShadow 86 + receiveShadow 87 + geometry={gltf.nodes.Mesh_cover.geometry} 88 + material={gltf.materials.carpet.clone()} 89 + material.color={colors?.[3] ?? gltf.materials.carpet.color} 90 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 91 + material.transparent={opacity !== undefined} 92 + /> 93 + <T.Mesh 94 + castShadow 95 + receiveShadow 96 + geometry={gltf.nodes.Mesh_cover_1.geometry} 97 + material={gltf.materials.carpetWhite.clone()} 98 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 99 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 100 + material.transparent={opacity !== undefined} 101 + /> 102 + </T.Group> 103 + <T.Mesh 104 + castShadow 105 + receiveShadow 106 + geometry={gltf.nodes.pillow.geometry} 107 + material={gltf.materials.carpetWhite.clone()} 108 + material.color={colors?.[1] ?? gltf.materials.carpetWhite.color} 109 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 110 + material.transparent={opacity !== undefined} 111 + position={[0.5, 0.22, -1.03]} 112 + rotation={[0.13, -Math.PI / 2, 0]} 113 + scale={[0.5, 0.5, 0.85]} 114 + /> 115 + {:catch err} 116 + {@render error?.({ error: err })} 117 + {/await} 118 + 119 + {@render children?.({ ref })} 120 + </T.Group> 121 + </T.Group>
+66
src/lib/models/kenney-furniture-kit/bench.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/bench.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['bench(Clone)']: THREE.Mesh 34 + } 35 + materials: { 36 + wood: THREE.MeshStandardMaterial 37 + } 38 + } 39 + 40 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/bench.glb'); 41 + </script> 42 + 43 + <T.Group 44 + bind:ref 45 + dispose={false} 46 + {...props} 47 + > 48 + {#await gltf} 49 + {@render fallback?.()} 50 + {:then gltf} 51 + <T.Mesh 52 + castShadow 53 + receiveShadow 54 + geometry={gltf.nodes['bench(Clone)'].geometry} 55 + material={gltf.materials.wood.clone()} 56 + material.color={colors?.[0] ?? gltf.materials.wood.color} 57 + material.opacity={opacity ?? gltf.materials.wood.opacity} 58 + material.transparent={opacity !== undefined} 59 + position={[-0.2, 0, 0.1]} 60 + /> 61 + {:catch err} 62 + {@render error?.({ error: err })} 63 + {/await} 64 + 65 + {@render children?.({ ref })} 66 + </T.Group>
+78
src/lib/models/kenney-furniture-kit/benchCushion.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/benchCushion.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_benchCushion: THREE.Mesh 34 + Mesh_benchCushion_1: THREE.Mesh 35 + } 36 + materials: { 37 + wood: THREE.MeshStandardMaterial 38 + carpet: THREE.MeshStandardMaterial 39 + } 40 + } 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/benchCushion.glb'); 43 + </script> 44 + 45 + <T.Group 46 + bind:ref 47 + dispose={false} 48 + {...props} 49 + > 50 + {#await gltf} 51 + {@render fallback?.()} 52 + {:then gltf} 53 + <T.Mesh 54 + castShadow 55 + receiveShadow 56 + geometry={gltf.nodes.Mesh_benchCushion.geometry} 57 + material={gltf.materials.wood.clone()} 58 + material.color={colors?.[0] ?? gltf.materials.wood.color} 59 + material.opacity={opacity ?? gltf.materials.wood.opacity} 60 + material.transparent={opacity !== undefined} 61 + position={[-0.2, 0, 0.1]} 62 + /> 63 + <T.Mesh 64 + castShadow 65 + receiveShadow 66 + geometry={gltf.nodes.Mesh_benchCushion_1.geometry} 67 + material={gltf.materials.carpet.clone()} 68 + material.color={colors?.[1] ?? gltf.materials.carpet.color} 69 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 70 + material.transparent={opacity !== undefined} 71 + position={[-0.2, 0, 0.1]} 72 + /> 73 + {:catch err} 74 + {@render error?.({ error: err })} 75 + {/await} 76 + 77 + {@render children?.({ ref })} 78 + </T.Group>
+90
src/lib/models/kenney-furniture-kit/benchCushionLow.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/benchCushionLow.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_benchCushionLow: THREE.Mesh 34 + Mesh_benchCushionLow_1: THREE.Mesh 35 + Mesh_benchCushionLow_2: THREE.Mesh 36 + } 37 + materials: { 38 + wood: THREE.MeshStandardMaterial 39 + carpet: THREE.MeshStandardMaterial 40 + _defaultMat: THREE.MeshStandardMaterial 41 + } 42 + } 43 + 44 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/benchCushionLow.glb'); 45 + </script> 46 + 47 + <T.Group 48 + bind:ref 49 + dispose={false} 50 + {...props} 51 + > 52 + {#await gltf} 53 + {@render fallback?.()} 54 + {:then gltf} 55 + <T.Mesh 56 + castShadow 57 + receiveShadow 58 + geometry={gltf.nodes.Mesh_benchCushionLow.geometry} 59 + material={gltf.materials.wood.clone()} 60 + material.color={colors?.[0] ?? gltf.materials.wood.color} 61 + material.opacity={opacity ?? gltf.materials.wood.opacity} 62 + material.transparent={opacity !== undefined} 63 + position={[-0.2, 0, 0.1]} 64 + /> 65 + <T.Mesh 66 + castShadow 67 + receiveShadow 68 + geometry={gltf.nodes.Mesh_benchCushionLow_1.geometry} 69 + material={gltf.materials.carpet.clone()} 70 + material.color={colors?.[1] ?? gltf.materials.carpet.color} 71 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 72 + material.transparent={opacity !== undefined} 73 + position={[-0.2, 0, 0.1]} 74 + /> 75 + <T.Mesh 76 + castShadow 77 + receiveShadow 78 + geometry={gltf.nodes.Mesh_benchCushionLow_2.geometry} 79 + material={gltf.materials._defaultMat.clone()} 80 + material.color={colors?.[2] ?? gltf.materials._defaultMat.color} 81 + material.opacity={opacity ?? gltf.materials._defaultMat.opacity} 82 + material.transparent={opacity !== undefined} 83 + position={[-0.2, 0, 0.1]} 84 + /> 85 + {:catch err} 86 + {@render error?.({ error: err })} 87 + {/await} 88 + 89 + {@render children?.({ ref })} 90 + </T.Group>
+65
src/lib/models/kenney-furniture-kit/bookcaseClosed.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/bookcaseClosed.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['bookcaseClosed(Clone)']: THREE.Mesh 34 + } 35 + materials: { 36 + wood: THREE.MeshStandardMaterial 37 + } 38 + } 39 + 40 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/bookcaseClosed.glb'); 41 + </script> 42 + 43 + <T.Group 44 + bind:ref 45 + dispose={false} 46 + {...props} 47 + > 48 + {#await gltf} 49 + {@render fallback?.()} 50 + {:then gltf} 51 + <T.Mesh 52 + castShadow 53 + receiveShadow 54 + geometry={gltf.nodes['bookcaseClosed(Clone)'].geometry} 55 + material={gltf.materials.wood.clone()} 56 + material.color={colors?.[0] ?? gltf.materials.wood.color} 57 + material.opacity={opacity ?? gltf.materials.wood.opacity} 58 + material.transparent={opacity !== undefined} 59 + /> 60 + {:catch err} 61 + {@render error?.({ error: err })} 62 + {/await} 63 + 64 + {@render children?.({ ref })} 65 + </T.Group>
+90
src/lib/models/kenney-furniture-kit/cabinetBed.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/cabinetBed.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['cabinetBed(Clone)']: THREE.Mesh 34 + Mesh_door: THREE.Mesh 35 + Mesh_door_1: THREE.Mesh 36 + } 37 + materials: { 38 + wood: THREE.MeshStandardMaterial 39 + metal: THREE.MeshStandardMaterial 40 + } 41 + } 42 + 43 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/cabinetBed.glb'); 44 + </script> 45 + 46 + <T.Group 47 + bind:ref 48 + dispose={false} 49 + {...props} 50 + > 51 + {#await gltf} 52 + {@render fallback?.()} 53 + {:then gltf} 54 + <T.Mesh 55 + castShadow 56 + receiveShadow 57 + geometry={gltf.nodes['cabinetBed(Clone)'].geometry} 58 + material={gltf.materials.wood.clone()} 59 + material.color={colors?.[0] ?? gltf.materials.wood.color} 60 + material.opacity={opacity ?? gltf.materials.wood.opacity} 61 + material.transparent={opacity !== undefined} 62 + position={[-0.12, 0.00, 0.1]} 63 + > 64 + <T.Group position={[0.02, 0.02, -0.02]}> 65 + <T.Mesh 66 + castShadow 67 + receiveShadow 68 + geometry={gltf.nodes.Mesh_door.geometry} 69 + material={gltf.materials.wood.clone()} 70 + material.color={colors?.[0] ?? gltf.materials.wood.color} 71 + material.opacity={opacity ?? gltf.materials.wood.opacity} 72 + material.transparent={opacity !== undefined} 73 + /> 74 + <T.Mesh 75 + castShadow 76 + receiveShadow 77 + geometry={gltf.nodes.Mesh_door_1.geometry} 78 + material={gltf.materials.metal.clone()} 79 + material.color={colors?.[1] ?? gltf.materials.metal.color} 80 + material.opacity={opacity ?? gltf.materials.metal.opacity} 81 + material.transparent={opacity !== undefined} 82 + /> 83 + </T.Group> 84 + </T.Mesh> 85 + {:catch err} 86 + {@render error?.({ error: err })} 87 + {/await} 88 + 89 + {@render children?.({ ref })} 90 + </T.Group>
+116
src/lib/models/kenney-furniture-kit/cabinetBedDrawer.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/cabinetBedDrawer.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_cabinetBedDrawer: THREE.Mesh; 34 + Mesh_cabinetBedDrawer_1: THREE.Mesh; 35 + Mesh_door: THREE.Mesh; 36 + Mesh_door_1: THREE.Mesh; 37 + Mesh_drawer: THREE.Mesh; 38 + Mesh_drawer_1: THREE.Mesh; 39 + }; 40 + materials: { 41 + wood: THREE.MeshStandardMaterial; 42 + _defaultMat: THREE.MeshStandardMaterial; 43 + metal: THREE.MeshStandardMaterial; 44 + }; 45 + }; 46 + 47 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/cabinetBedDrawer.glb'); 48 + </script> 49 + 50 + <T.Group bind:ref dispose={false} {...props}> 51 + {#await gltf} 52 + {@render fallback?.()} 53 + {:then gltf} 54 + <T.Group position={[-0.12, 0.0, 0.1]}> 55 + <T.Mesh 56 + castShadow 57 + receiveShadow 58 + geometry={gltf.nodes.Mesh_cabinetBedDrawer.geometry} 59 + material={gltf.materials.wood.clone()} 60 + material.color={colors?.[0] ?? gltf.materials.wood.color} 61 + material.opacity={opacity ?? gltf.materials.wood.opacity} 62 + material.transparent={opacity !== undefined} 63 + /> 64 + <T.Mesh 65 + castShadow 66 + receiveShadow 67 + geometry={gltf.nodes.Mesh_cabinetBedDrawer_1.geometry} 68 + material={gltf.materials._defaultMat} 69 + /> 70 + <T.Group position={[0.02, 0.05, -0.01]}> 71 + <T.Mesh 72 + castShadow 73 + receiveShadow 74 + geometry={gltf.nodes.Mesh_door.geometry} 75 + material={gltf.materials.wood.clone()} 76 + material.color={colors?.[0] ?? gltf.materials.wood.color} 77 + material.opacity={opacity ?? gltf.materials.wood.opacity} 78 + material.transparent={opacity !== undefined} 79 + /> 80 + <T.Mesh 81 + castShadow 82 + receiveShadow 83 + geometry={gltf.nodes.Mesh_door_1.geometry} 84 + material={gltf.materials.metal.clone()} 85 + material.color={colors?.[1] ?? gltf.materials.metal.color} 86 + material.opacity={opacity ?? gltf.materials.metal.opacity} 87 + material.transparent={opacity !== undefined} 88 + /> 89 + </T.Group> 90 + <T.Group position={[0.02, 0.17, -0.16]}> 91 + <T.Mesh 92 + castShadow 93 + receiveShadow 94 + geometry={gltf.nodes.Mesh_drawer.geometry} 95 + material={gltf.materials.wood.clone()} 96 + material.color={colors?.[0] ?? gltf.materials.wood.color} 97 + material.opacity={opacity ?? gltf.materials.wood.opacity} 98 + material.transparent={opacity !== undefined} 99 + /> 100 + <T.Mesh 101 + castShadow 102 + receiveShadow 103 + geometry={gltf.nodes.Mesh_drawer_1.geometry} 104 + material={gltf.materials.metal.clone()} 105 + material.color={colors?.[1] ?? gltf.materials.metal.color} 106 + material.opacity={opacity ?? gltf.materials.metal.opacity} 107 + material.transparent={opacity !== undefined} 108 + /> 109 + </T.Group> 110 + </T.Group> 111 + {:catch err} 112 + {@render error?.({ error: err })} 113 + {/await} 114 + 115 + {@render children?.({ ref })} 116 + </T.Group>
+94
src/lib/models/kenney-furniture-kit/cabinetBedDrawerTable.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/cabinetBedDrawerTable.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_cabinetBedDrawerTable: THREE.Mesh; 34 + Mesh_cabinetBedDrawerTable_1: THREE.Mesh; 35 + Mesh_drawer: THREE.Mesh; 36 + Mesh_drawer_1: THREE.Mesh; 37 + }; 38 + materials: { 39 + wood: THREE.MeshStandardMaterial; 40 + _defaultMat: THREE.MeshStandardMaterial; 41 + metal: THREE.MeshStandardMaterial; 42 + }; 43 + }; 44 + 45 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/cabinetBedDrawerTable.glb'); 46 + </script> 47 + 48 + <T.Group bind:ref dispose={false} {...props}> 49 + {#await gltf} 50 + {@render fallback?.()} 51 + {:then gltf} 52 + <T.Group position={[-0.12, 0.0, 0.1]}> 53 + <T.Mesh 54 + castShadow 55 + receiveShadow 56 + geometry={gltf.nodes.Mesh_cabinetBedDrawerTable.geometry} 57 + material={gltf.materials.wood.clone()} 58 + material.color={colors?.[0] ?? gltf.materials.wood.color} 59 + material.opacity={opacity ?? gltf.materials.wood.opacity} 60 + material.transparent={opacity !== undefined} 61 + /> 62 + <T.Mesh 63 + castShadow 64 + receiveShadow 65 + geometry={gltf.nodes.Mesh_cabinetBedDrawerTable_1.geometry} 66 + material={gltf.materials._defaultMat} 67 + /> 68 + <T.Group position={[0.02, 0.17, -0.16]}> 69 + <T.Mesh 70 + castShadow 71 + receiveShadow 72 + geometry={gltf.nodes.Mesh_drawer.geometry} 73 + material={gltf.materials.wood.clone()} 74 + material.color={colors?.[0] ?? gltf.materials.wood.color} 75 + material.opacity={opacity ?? gltf.materials.wood.opacity} 76 + material.transparent={opacity !== undefined} 77 + /> 78 + <T.Mesh 79 + castShadow 80 + receiveShadow 81 + geometry={gltf.nodes.Mesh_drawer_1.geometry} 82 + material={gltf.materials.metal.clone()} 83 + material.color={colors?.[1] ?? gltf.materials.metal.color} 84 + material.opacity={opacity ?? gltf.materials.metal.opacity} 85 + material.transparent={opacity !== undefined} 86 + /> 87 + </T.Group> 88 + </T.Group> 89 + {:catch err} 90 + {@render error?.({ error: err })} 91 + {/await} 92 + 93 + {@render children?.({ ref })} 94 + </T.Group>
+66
src/lib/models/kenney-furniture-kit/chair.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/chair.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['chair(Clone)']: THREE.Mesh 34 + } 35 + materials: { 36 + wood: THREE.MeshStandardMaterial 37 + } 38 + } 39 + 40 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/chair.glb'); 41 + </script> 42 + 43 + <T.Group 44 + bind:ref 45 + dispose={false} 46 + {...props} 47 + > 48 + {#await gltf} 49 + {@render fallback?.()} 50 + {:then gltf} 51 + <T.Mesh 52 + castShadow 53 + receiveShadow 54 + geometry={gltf.nodes['chair(Clone)'].geometry} 55 + material={gltf.materials.wood.clone()} 56 + material.color={colors?.[0] ?? gltf.materials.wood.color} 57 + material.opacity={opacity ?? gltf.materials.wood.opacity} 58 + material.transparent={opacity !== undefined} 59 + position={[-0.1, 0.0, 0.1]} 60 + /> 61 + {:catch err} 62 + {@render error?.({ error: err })} 63 + {/await} 64 + 65 + {@render children?.({ ref })} 66 + </T.Group>
+74
src/lib/models/kenney-furniture-kit/chairCushion.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/chairCushion.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_chairCushion: THREE.Mesh; 34 + Mesh_chairCushion_1: THREE.Mesh; 35 + }; 36 + materials: { 37 + wood: THREE.MeshStandardMaterial; 38 + carpet: THREE.MeshStandardMaterial; 39 + }; 40 + }; 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/chairCushion.glb'); 43 + </script> 44 + 45 + <T.Group bind:ref dispose={false} {...props}> 46 + {#await gltf} 47 + {@render fallback?.()} 48 + {:then gltf} 49 + <T.Mesh 50 + castShadow 51 + receiveShadow 52 + geometry={gltf.nodes.Mesh_chairCushion.geometry} 53 + material={gltf.materials.wood.clone()} 54 + material.color={colors?.[0] ?? gltf.materials.wood.color} 55 + material.opacity={opacity ?? gltf.materials.wood.opacity} 56 + material.transparent={opacity !== undefined} 57 + position={[-0.1, 0.0, 0.1]} 58 + /> 59 + <T.Mesh 60 + castShadow 61 + receiveShadow 62 + geometry={gltf.nodes.Mesh_chairCushion_1.geometry} 63 + material={gltf.materials.carpet.clone()} 64 + material.color={colors?.[1] ?? gltf.materials.carpet.color} 65 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 66 + material.transparent={opacity !== undefined} 67 + position={[-0.1, 0.0, 0.1]} 68 + /> 69 + {:catch err} 70 + {@render error?.({ error: err })} 71 + {/await} 72 + 73 + {@render children?.({ ref })} 74 + </T.Group>
+91
src/lib/models/kenney-furniture-kit/chairDesk.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/chairDesk.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['chairDesk(Clone)']: THREE.Mesh 34 + Mesh_chair: THREE.Mesh 35 + Mesh_chair_1: THREE.Mesh 36 + } 37 + materials: { 38 + metalMedium: THREE.MeshStandardMaterial 39 + carpet: THREE.MeshStandardMaterial 40 + } 41 + } 42 + 43 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/chairDesk.glb'); 44 + </script> 45 + 46 + <T.Group 47 + bind:ref 48 + dispose={false} 49 + {...props} 50 + > 51 + {#await gltf} 52 + {@render fallback?.()} 53 + {:then gltf} 54 + <T.Mesh 55 + castShadow 56 + receiveShadow 57 + geometry={gltf.nodes['chairDesk(Clone)'].geometry} 58 + material={gltf.materials.metalMedium.clone()} 59 + material.color={colors?.[0] ?? gltf.materials.metalMedium.color} 60 + material.opacity={opacity ?? gltf.materials.metalMedium.opacity} 61 + material.transparent={opacity !== undefined} 62 + position={[-0.16, 0.0, 0.16]} 63 + 64 + > 65 + <T.Group position={[0.17, 0.19, -0.16]}> 66 + <T.Mesh 67 + castShadow 68 + receiveShadow 69 + geometry={gltf.nodes.Mesh_chair.geometry} 70 + material={gltf.materials.carpet.clone()} 71 + material.color={colors?.[1] ?? gltf.materials.carpet.color} 72 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 73 + material.transparent={opacity !== undefined} 74 + /> 75 + <T.Mesh 76 + castShadow 77 + receiveShadow 78 + geometry={gltf.nodes.Mesh_chair_1.geometry} 79 + material={gltf.materials.metalMedium.clone()} 80 + material.color={colors?.[0] ?? gltf.materials.metalMedium.color} 81 + material.opacity={opacity ?? gltf.materials.metalMedium.opacity} 82 + material.transparent={opacity !== undefined} 83 + /> 84 + </T.Group> 85 + </T.Mesh> 86 + {:catch err} 87 + {@render error?.({ error: err })} 88 + {/await} 89 + 90 + {@render children?.({ ref })} 91 + </T.Group>
+80
src/lib/models/kenney-furniture-kit/chairModernCushion.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/chairModernCushion.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_chairModernCushion: THREE.Mesh 34 + Mesh_chairModernCushion_1: THREE.Mesh 35 + } 36 + materials: { 37 + metal: THREE.MeshStandardMaterial 38 + carpetBlue: THREE.MeshStandardMaterial 39 + } 40 + } 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/chairModernCushion.glb'); 43 + </script> 44 + 45 + <T.Group 46 + bind:ref 47 + dispose={false} 48 + {...props} 49 + > 50 + {#await gltf} 51 + {@render fallback?.()} 52 + {:then gltf} 53 + <T.Mesh 54 + castShadow 55 + receiveShadow 56 + geometry={gltf.nodes.Mesh_chairModernCushion.geometry} 57 + material={gltf.materials.metal.clone()} 58 + material.color={colors?.[0] ?? gltf.materials.metal.color} 59 + material.opacity={opacity ?? gltf.materials.metal.opacity} 60 + material.transparent={opacity !== undefined} 61 + position={[-0.1, 0.0, 0.1]} 62 + 63 + /> 64 + <T.Mesh 65 + castShadow 66 + receiveShadow 67 + geometry={gltf.nodes.Mesh_chairModernCushion_1.geometry} 68 + material={gltf.materials.carpetBlue.clone()} 69 + material.color={colors?.[1] ?? gltf.materials.carpetBlue.color} 70 + material.opacity={opacity ?? gltf.materials.carpetBlue.opacity} 71 + material.transparent={opacity !== undefined} 72 + position={[-0.1, 0.0, 0.1]} 73 + 74 + /> 75 + {:catch err} 76 + {@render error?.({ error: err })} 77 + {/await} 78 + 79 + {@render children?.({ ref })} 80 + </T.Group>
+80
src/lib/models/kenney-furniture-kit/chairModernFrameCushion.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/chairModernFrameCushion.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_chairModernFrameCushion: THREE.Mesh 34 + Mesh_chairModernFrameCushion_1: THREE.Mesh 35 + } 36 + materials: { 37 + metal: THREE.MeshStandardMaterial 38 + carpetBlue: THREE.MeshStandardMaterial 39 + } 40 + } 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/chairModernFrameCushion.glb'); 43 + </script> 44 + 45 + <T.Group 46 + bind:ref 47 + dispose={false} 48 + {...props} 49 + > 50 + {#await gltf} 51 + {@render fallback?.()} 52 + {:then gltf} 53 + <T.Mesh 54 + castShadow 55 + receiveShadow 56 + geometry={gltf.nodes.Mesh_chairModernFrameCushion.geometry} 57 + material={gltf.materials.metal.clone()} 58 + material.color={colors?.[0] ?? gltf.materials.metal.color} 59 + material.opacity={opacity ?? gltf.materials.metal.opacity} 60 + material.transparent={opacity !== undefined} 61 + position={[-0.1, 0.0, 0.1]} 62 + 63 + /> 64 + <T.Mesh 65 + castShadow 66 + receiveShadow 67 + geometry={gltf.nodes.Mesh_chairModernFrameCushion_1.geometry} 68 + material={gltf.materials.carpetBlue.clone()} 69 + material.color={colors?.[1] ?? gltf.materials.carpetBlue.color} 70 + material.opacity={opacity ?? gltf.materials.carpetBlue.opacity} 71 + material.transparent={opacity !== undefined} 72 + position={[-0.1, 0.0, 0.1]} 73 + 74 + /> 75 + {:catch err} 76 + {@render error?.({ error: err })} 77 + {/await} 78 + 79 + {@render children?.({ ref })} 80 + </T.Group>
+67
src/lib/models/kenney-furniture-kit/chairRounded.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/chairRounded.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['chairRounded(Clone)']: THREE.Mesh 34 + } 35 + materials: { 36 + wood: THREE.MeshStandardMaterial 37 + } 38 + } 39 + 40 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/chairRounded.glb'); 41 + </script> 42 + 43 + <T.Group 44 + bind:ref 45 + dispose={false} 46 + {...props} 47 + > 48 + {#await gltf} 49 + {@render fallback?.()} 50 + {:then gltf} 51 + <T.Mesh 52 + castShadow 53 + receiveShadow 54 + geometry={gltf.nodes['chairRounded(Clone)'].geometry} 55 + material={gltf.materials.wood.clone()} 56 + material.color={colors?.[0] ?? gltf.materials.wood.color} 57 + material.opacity={opacity ?? gltf.materials.wood.opacity} 58 + material.transparent={opacity !== undefined} 59 + position={[-0.1, 0.0, 0.1]} 60 + 61 + /> 62 + {:catch err} 63 + {@render error?.({ error: err })} 64 + {/await} 65 + 66 + {@render children?.({ ref })} 67 + </T.Group>
+65
src/lib/models/kenney-furniture-kit/coatRackStanding.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/coatRackStanding.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['coatRackStanding(Clone)']: THREE.Mesh 34 + } 35 + materials: { 36 + wood: THREE.MeshStandardMaterial 37 + } 38 + } 39 + 40 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/coatRackStanding.glb'); 41 + </script> 42 + 43 + <T.Group 44 + bind:ref 45 + dispose={false} 46 + {...props} 47 + > 48 + {#await gltf} 49 + {@render fallback?.()} 50 + {:then gltf} 51 + <T.Mesh 52 + castShadow 53 + receiveShadow 54 + geometry={gltf.nodes['coatRackStanding(Clone)'].geometry} 55 + material={gltf.materials.wood.clone()} 56 + material.color={colors?.[0] ?? gltf.materials.wood.color} 57 + material.opacity={opacity ?? gltf.materials.wood.opacity} 58 + material.transparent={opacity !== undefined} 59 + /> 60 + {:catch err} 61 + {@render error?.({ error: err })} 62 + {/await} 63 + 64 + {@render children?.({ ref })} 65 + </T.Group>
+90
src/lib/models/kenney-furniture-kit/desk.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/desk.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['desk(Clone)']: THREE.Mesh 34 + Mesh_drawer: THREE.Mesh 35 + Mesh_drawer_1: THREE.Mesh 36 + } 37 + materials: { 38 + wood: THREE.MeshStandardMaterial 39 + metal: THREE.MeshStandardMaterial 40 + } 41 + } 42 + 43 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/desk.glb'); 44 + </script> 45 + 46 + <T.Group 47 + bind:ref 48 + dispose={false} 49 + {...props} 50 + > 51 + {#await gltf} 52 + {@render fallback?.()} 53 + {:then gltf} 54 + <T.Mesh 55 + castShadow 56 + receiveShadow 57 + geometry={gltf.nodes['desk(Clone)'].geometry} 58 + material={gltf.materials.wood.clone()} 59 + material.color={colors?.[0] ?? gltf.materials.wood.color} 60 + material.opacity={opacity ?? gltf.materials.wood.opacity} 61 + material.transparent={opacity !== undefined} 62 + position={[-0.35, 0.0, 0.17]} 63 + > 64 + <T.Group position={[0.04, 0.27, -0.16]}> 65 + <T.Mesh 66 + castShadow 67 + receiveShadow 68 + geometry={gltf.nodes.Mesh_drawer.geometry} 69 + material={gltf.materials.wood.clone()} 70 + material.color={colors?.[0] ?? gltf.materials.wood.color} 71 + material.opacity={opacity ?? gltf.materials.wood.opacity} 72 + material.transparent={opacity !== undefined} 73 + /> 74 + <T.Mesh 75 + castShadow 76 + receiveShadow 77 + geometry={gltf.nodes.Mesh_drawer_1.geometry} 78 + material={gltf.materials.metal.clone()} 79 + material.color={colors?.[1] ?? gltf.materials.metal.color} 80 + material.opacity={opacity ?? gltf.materials.metal.opacity} 81 + material.transparent={opacity !== undefined} 82 + /> 83 + </T.Group> 84 + </T.Mesh> 85 + {:catch err} 86 + {@render error?.({ error: err })} 87 + {/await} 88 + 89 + {@render children?.({ ref })} 90 + </T.Group>
+90
src/lib/models/kenney-furniture-kit/deskCorner.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/deskCorner.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['deskCorner(Clone)']: THREE.Mesh 34 + Mesh_drawer: THREE.Mesh 35 + Mesh_drawer_1: THREE.Mesh 36 + } 37 + materials: { 38 + wood: THREE.MeshStandardMaterial 39 + metal: THREE.MeshStandardMaterial 40 + } 41 + } 42 + 43 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/deskCorner.glb'); 44 + </script> 45 + 46 + <T.Group 47 + bind:ref 48 + dispose={false} 49 + {...props} 50 + > 51 + {#await gltf} 52 + {@render fallback?.()} 53 + {:then gltf} 54 + <T.Mesh 55 + castShadow 56 + receiveShadow 57 + geometry={gltf.nodes['deskCorner(Clone)'].geometry} 58 + material={gltf.materials.wood.clone()} 59 + material.color={colors?.[0] ?? gltf.materials.wood.color} 60 + material.opacity={opacity ?? gltf.materials.wood.opacity} 61 + material.transparent={opacity !== undefined} 62 + position={[-0.5, 0.0, 0.5]} 63 + > 64 + <T.Group position={[0.05, 0.27, -0.76]}> 65 + <T.Mesh 66 + castShadow 67 + receiveShadow 68 + geometry={gltf.nodes.Mesh_drawer.geometry} 69 + material={gltf.materials.wood.clone()} 70 + material.color={colors?.[0] ?? gltf.materials.wood.color} 71 + material.opacity={opacity ?? gltf.materials.wood.opacity} 72 + material.transparent={opacity !== undefined} 73 + /> 74 + <T.Mesh 75 + castShadow 76 + receiveShadow 77 + geometry={gltf.nodes.Mesh_drawer_1.geometry} 78 + material={gltf.materials.metal.clone()} 79 + material.color={colors?.[1] ?? gltf.materials.metal.color} 80 + material.opacity={opacity ?? gltf.materials.metal.opacity} 81 + material.transparent={opacity !== undefined} 82 + /> 83 + </T.Group> 84 + </T.Mesh> 85 + {:catch err} 86 + {@render error?.({ error: err })} 87 + {/await} 88 + 89 + {@render children?.({ ref })} 90 + </T.Group>
+90
src/lib/models/kenney-furniture-kit/doorway.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/doorway.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['doorway(Clone)']: THREE.Mesh 34 + Mesh_door: THREE.Mesh 35 + Mesh_door_1: THREE.Mesh 36 + } 37 + materials: { 38 + wood: THREE.MeshStandardMaterial 39 + metal: THREE.MeshStandardMaterial 40 + } 41 + } 42 + 43 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/doorway.glb'); 44 + </script> 45 + 46 + <T.Group 47 + bind:ref 48 + dispose={false} 49 + {...props} 50 + > 51 + {#await gltf} 52 + {@render fallback?.()} 53 + {:then gltf} 54 + <T.Mesh 55 + castShadow 56 + receiveShadow 57 + geometry={gltf.nodes['doorway(Clone)'].geometry} 58 + material={gltf.materials.wood.clone()} 59 + material.color={colors?.[1] ?? gltf.materials.wood.color} 60 + material.opacity={opacity ?? gltf.materials.wood.opacity} 61 + material.transparent={opacity !== undefined} 62 + position={[-0.23, 0.0, 0.0]} 63 + > 64 + <T.Group position={[0.03, 0, -0.04]}> 65 + <T.Mesh 66 + castShadow 67 + receiveShadow 68 + geometry={gltf.nodes.Mesh_door.geometry} 69 + material={gltf.materials.wood.clone()} 70 + material.color={colors?.[0] ?? gltf.materials.wood.color} 71 + material.opacity={opacity ?? gltf.materials.wood.opacity} 72 + material.transparent={opacity !== undefined} 73 + /> 74 + <T.Mesh 75 + castShadow 76 + receiveShadow 77 + geometry={gltf.nodes.Mesh_door_1.geometry} 78 + material={gltf.materials.metal.clone()} 79 + material.color={colors?.[2] ?? gltf.materials.metal.color} 80 + material.opacity={opacity ?? gltf.materials.metal.opacity} 81 + material.transparent={opacity !== undefined} 82 + /> 83 + </T.Group> 84 + </T.Mesh> 85 + {:catch err} 86 + {@render error?.({ error: err })} 87 + {/await} 88 + 89 + {@render children?.({ ref })} 90 + </T.Group>
+102
src/lib/models/kenney-furniture-kit/doorwayFront.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/doorwayFront.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['doorwayFront(Clone)']: THREE.Mesh 34 + Mesh_door: THREE.Mesh 35 + Mesh_door_1: THREE.Mesh 36 + Mesh_door_2: THREE.Mesh 37 + } 38 + materials: { 39 + carpetWhite: THREE.MeshStandardMaterial 40 + metal: THREE.MeshStandardMaterial 41 + metalDark: THREE.MeshStandardMaterial 42 + glass: THREE.MeshStandardMaterial 43 + } 44 + } 45 + 46 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/doorwayFront.glb'); 47 + </script> 48 + 49 + <T.Group 50 + bind:ref 51 + dispose={false} 52 + {...props} 53 + > 54 + {#await gltf} 55 + {@render fallback?.()} 56 + {:then gltf} 57 + <T.Mesh 58 + castShadow 59 + receiveShadow 60 + geometry={gltf.nodes['doorwayFront(Clone)'].geometry} 61 + material={gltf.materials.carpetWhite.clone()} 62 + material.color={colors?.[0] ?? gltf.materials.carpetWhite.color} 63 + material.opacity={opacity ?? gltf.materials.carpetWhite.opacity} 64 + material.transparent={opacity !== undefined} 65 + position={[-0.23, 0.0, 0.0]} 66 + > 67 + <T.Group position={[0.03, 0, -0.04]}> 68 + <T.Mesh 69 + castShadow 70 + receiveShadow 71 + geometry={gltf.nodes.Mesh_door.geometry} 72 + material={gltf.materials.metal.clone()} 73 + material.color={colors?.[1] ?? gltf.materials.metal.color} 74 + material.opacity={opacity ?? gltf.materials.metal.opacity} 75 + material.transparent={opacity !== undefined} 76 + /> 77 + <T.Mesh 78 + castShadow 79 + receiveShadow 80 + geometry={gltf.nodes.Mesh_door_1.geometry} 81 + material={gltf.materials.metalDark.clone()} 82 + material.color={colors?.[2] ?? gltf.materials.metalDark.color} 83 + material.opacity={opacity ?? gltf.materials.metalDark.opacity} 84 + material.transparent={opacity !== undefined} 85 + /> 86 + <T.Mesh 87 + castShadow 88 + receiveShadow 89 + geometry={gltf.nodes.Mesh_door_2.geometry} 90 + material={gltf.materials.glass.clone()} 91 + material.color={colors?.[3] ?? gltf.materials.glass.color} 92 + material.opacity={opacity ?? gltf.materials.glass.opacity} 93 + material.transparent={opacity !== undefined} 94 + /> 95 + </T.Group> 96 + </T.Mesh> 97 + {:catch err} 98 + {@render error?.({ error: err })} 99 + {/await} 100 + 101 + {@render children?.({ ref })} 102 + </T.Group>
+100
src/lib/models/kenney-furniture-kit/dryer.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/dryer.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_dryer: THREE.Mesh 34 + Mesh_dryer_1: THREE.Mesh 35 + Mesh_dryer_2: THREE.Mesh 36 + dryerDoor: THREE.Mesh 37 + } 38 + materials: { 39 + metalLight: THREE.MeshStandardMaterial 40 + metalMedium: THREE.MeshStandardMaterial 41 + metalDark: THREE.MeshStandardMaterial 42 + } 43 + } 44 + 45 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/dryer.glb'); 46 + </script> 47 + 48 + <T.Group 49 + bind:ref 50 + dispose={false} 51 + {...props} 52 + > 53 + {#await gltf} 54 + {@render fallback?.()} 55 + {:then gltf} 56 + <T.Group position={[-0.2, 0.0, 0.15]}> 57 + <T.Mesh 58 + castShadow 59 + receiveShadow 60 + geometry={gltf.nodes.Mesh_dryer.geometry} 61 + material={gltf.materials.metalLight.clone()} 62 + material.color={colors?.[0] ?? gltf.materials.metalLight.color} 63 + material.opacity={opacity ?? gltf.materials.metalLight.opacity} 64 + material.transparent={opacity !== undefined} 65 + /> 66 + <T.Mesh 67 + castShadow 68 + receiveShadow 69 + geometry={gltf.nodes.Mesh_dryer_1.geometry} 70 + material={gltf.materials.metalMedium.clone()} 71 + material.color={colors?.[1] ?? gltf.materials.metalMedium.color} 72 + material.opacity={opacity ?? gltf.materials.metalMedium.opacity} 73 + material.transparent={opacity !== undefined} 74 + /> 75 + <T.Mesh 76 + castShadow 77 + receiveShadow 78 + geometry={gltf.nodes.Mesh_dryer_2.geometry} 79 + material={gltf.materials.metalDark.clone()} 80 + material.color={colors?.[2] ?? gltf.materials.metalDark.color} 81 + material.opacity={opacity ?? gltf.materials.metalDark.opacity} 82 + material.transparent={opacity !== undefined} 83 + /> 84 + <T.Mesh 85 + castShadow 86 + receiveShadow 87 + geometry={gltf.nodes.dryerDoor.geometry} 88 + material={gltf.materials.metalLight.clone()} 89 + material.color={colors?.[0] ?? gltf.materials.metalLight.color} 90 + material.opacity={opacity ?? gltf.materials.metalLight.opacity} 91 + material.transparent={opacity !== undefined} 92 + position={[0.07, 0.23, 0]} 93 + /> 94 + </T.Group> 95 + {:catch err} 96 + {@render error?.({ error: err })} 97 + {/await} 98 + 99 + {@render children?.({ ref })} 100 + </T.Group>
+89
src/lib/models/kenney-furniture-kit/kitchenBar.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/kitchenBar.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_kitchenBar: THREE.Mesh 34 + Mesh_kitchenBar_1: THREE.Mesh 35 + Mesh_kitchenBar_2: THREE.Mesh 36 + } 37 + materials: { 38 + wood: THREE.MeshStandardMaterial 39 + metal: THREE.MeshStandardMaterial 40 + woodDark: THREE.MeshStandardMaterial 41 + } 42 + } 43 + 44 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/kitchenBar.glb'); 45 + </script> 46 + 47 + <T.Group 48 + bind:ref 49 + dispose={false} 50 + {...props} 51 + > 52 + {#await gltf} 53 + {@render fallback?.()} 54 + {:then gltf} 55 + <T.Group position={[-0.22, 0.0, 0.1]}> 56 + <T.Mesh 57 + castShadow 58 + receiveShadow 59 + geometry={gltf.nodes.Mesh_kitchenBar.geometry} 60 + material={gltf.materials.wood.clone()} 61 + material.color={colors?.[0] ?? gltf.materials.wood.color} 62 + material.opacity={opacity ?? gltf.materials.wood.opacity} 63 + material.transparent={opacity !== undefined} 64 + /> 65 + <T.Mesh 66 + castShadow 67 + receiveShadow 68 + geometry={gltf.nodes.Mesh_kitchenBar_1.geometry} 69 + material={gltf.materials.metal.clone()} 70 + material.color={colors?.[1] ?? gltf.materials.metal.color} 71 + material.opacity={opacity ?? gltf.materials.metal.opacity} 72 + material.transparent={opacity !== undefined} 73 + /> 74 + <T.Mesh 75 + castShadow 76 + receiveShadow 77 + geometry={gltf.nodes.Mesh_kitchenBar_2.geometry} 78 + material={gltf.materials.woodDark.clone()} 79 + material.color={colors?.[2] ?? gltf.materials.woodDark.color} 80 + material.opacity={opacity ?? gltf.materials.woodDark.opacity} 81 + material.transparent={opacity !== undefined} 82 + /> 83 + </T.Group> 84 + {:catch err} 85 + {@render error?.({ error: err })} 86 + {/await} 87 + 88 + {@render children?.({ ref })} 89 + </T.Group>
+111
src/lib/models/kenney-furniture-kit/kitchenCabinet.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/kitchenCabinet.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three' 8 + 9 + import type { Snippet } from 'svelte' 10 + import { T, type Props } from '@threlte/core' 11 + import { useGltf } from '@threlte/extras' 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group 24 + children?: Snippet<[{ ref: THREE.Group }]> 25 + fallback?: Snippet 26 + error?: Snippet<[{ error: Error }]> 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_kitchenCabinet: THREE.Mesh 34 + Mesh_kitchenCabinet_1: THREE.Mesh 35 + Mesh_door: THREE.Mesh 36 + Mesh_door_1: THREE.Mesh 37 + Mesh_door_2: THREE.Mesh 38 + } 39 + materials: { 40 + metal: THREE.MeshStandardMaterial 41 + wood: THREE.MeshStandardMaterial 42 + woodDark: THREE.MeshStandardMaterial 43 + } 44 + } 45 + 46 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/kitchenCabinet.glb'); 47 + </script> 48 + 49 + <T.Group 50 + bind:ref 51 + dispose={false} 52 + {...props} 53 + > 54 + {#await gltf} 55 + {@render fallback?.()} 56 + {:then gltf} 57 + <T.Group position={[-0.23, 0.0, 0.2]}> 58 + <T.Mesh 59 + castShadow 60 + receiveShadow 61 + geometry={gltf.nodes.Mesh_kitchenCabinet.geometry} 62 + material={gltf.materials.metal.clone()} 63 + material.color={colors?.[0] ?? gltf.materials.metal.color} 64 + material.opacity={opacity ?? gltf.materials.metal.opacity} 65 + material.transparent={opacity !== undefined} 66 + /> 67 + <T.Mesh 68 + castShadow 69 + receiveShadow 70 + geometry={gltf.nodes.Mesh_kitchenCabinet_1.geometry} 71 + material={gltf.materials.wood.clone()} 72 + material.color={colors?.[1] ?? gltf.materials.wood.color} 73 + material.opacity={opacity ?? gltf.materials.wood.opacity} 74 + material.transparent={opacity !== undefined} 75 + /> 76 + <T.Group position={[0.03, 0.09, -0.03]}> 77 + <T.Mesh 78 + castShadow 79 + receiveShadow 80 + geometry={gltf.nodes.Mesh_door.geometry} 81 + material={gltf.materials.wood.clone()} 82 + material.color={colors?.[1] ?? gltf.materials.wood.color} 83 + material.opacity={opacity ?? gltf.materials.wood.opacity} 84 + material.transparent={opacity !== undefined} 85 + /> 86 + <T.Mesh 87 + castShadow 88 + receiveShadow 89 + geometry={gltf.nodes.Mesh_door_1.geometry} 90 + material={gltf.materials.woodDark.clone()} 91 + material.color={colors?.[2] ?? gltf.materials.woodDark.color} 92 + material.opacity={opacity ?? gltf.materials.woodDark.opacity} 93 + material.transparent={opacity !== undefined} 94 + /> 95 + <T.Mesh 96 + castShadow 97 + receiveShadow 98 + geometry={gltf.nodes.Mesh_door_2.geometry} 99 + material={gltf.materials.metal.clone()} 100 + material.color={colors?.[0] ?? gltf.materials.metal.color} 101 + material.opacity={opacity ?? gltf.materials.metal.opacity} 102 + material.transparent={opacity !== undefined} 103 + /> 104 + </T.Group> 105 + </T.Group> 106 + {:catch err} 107 + {@render error?.({ error: err })} 108 + {/await} 109 + 110 + {@render children?.({ ref })} 111 + </T.Group>
+139
src/lib/models/kenney-furniture-kit/kitchenCabinetDrawer.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/kenney-furniture-kit/kitchenCabinetDrawer.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_kitchenCabinetDrawer: THREE.Mesh; 34 + Mesh_kitchenCabinetDrawer_1: THREE.Mesh; 35 + Mesh_door: THREE.Mesh; 36 + Mesh_door_1: THREE.Mesh; 37 + Mesh_door_2: THREE.Mesh; 38 + Mesh_drawer: THREE.Mesh; 39 + Mesh_drawer_1: THREE.Mesh; 40 + Mesh_drawer_2: THREE.Mesh; 41 + }; 42 + materials: { 43 + metal: THREE.MeshStandardMaterial; 44 + wood: THREE.MeshStandardMaterial; 45 + woodDark: THREE.MeshStandardMaterial; 46 + }; 47 + }; 48 + 49 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/kitchenCabinetDrawer.glb'); 50 + </script> 51 + 52 + <T.Group bind:ref dispose={false} {...props}> 53 + {#await gltf} 54 + {@render fallback?.()} 55 + {:then gltf} 56 + <T.Group position={[-0.23, 0.0, 0.2]}> 57 + <T.Mesh 58 + castShadow 59 + receiveShadow 60 + geometry={gltf.nodes.Mesh_kitchenCabinetDrawer.geometry} 61 + material={gltf.materials.metal.clone()} 62 + material.color={colors?.[0] ?? gltf.materials.metal.color} 63 + material.opacity={opacity ?? gltf.materials.metal.opacity} 64 + material.transparent={opacity !== undefined} 65 + /> 66 + <T.Mesh 67 + castShadow 68 + receiveShadow 69 + geometry={gltf.nodes.Mesh_kitchenCabinetDrawer_1.geometry} 70 + material={gltf.materials.wood.clone()} 71 + material.color={colors?.[1] ?? gltf.materials.wood.color} 72 + material.opacity={opacity ?? gltf.materials.wood.opacity} 73 + material.transparent={opacity !== undefined} 74 + /> 75 + <T.Group position={[0.03, 0.09, -0.03]}> 76 + <T.Mesh 77 + castShadow 78 + receiveShadow 79 + geometry={gltf.nodes.Mesh_door.geometry} 80 + material={gltf.materials.wood.clone()} 81 + material.color={colors?.[1] ?? gltf.materials.wood.color} 82 + material.opacity={opacity ?? gltf.materials.wood.opacity} 83 + material.transparent={opacity !== undefined} 84 + /> 85 + <T.Mesh 86 + castShadow 87 + receiveShadow 88 + geometry={gltf.nodes.Mesh_door_1.geometry} 89 + material={gltf.materials.woodDark.clone()} 90 + material.color={colors?.[2] ?? gltf.materials.woodDark.color} 91 + material.opacity={opacity ?? gltf.materials.woodDark.opacity} 92 + material.transparent={opacity !== undefined} 93 + /> 94 + <T.Mesh 95 + castShadow 96 + receiveShadow 97 + geometry={gltf.nodes.Mesh_door_2.geometry} 98 + material={gltf.materials.metal.clone()} 99 + material.color={colors?.[0] ?? gltf.materials.metal.color} 100 + material.opacity={opacity ?? gltf.materials.metal.opacity} 101 + material.transparent={opacity !== undefined} 102 + /> 103 + </T.Group> 104 + <T.Group position={[0.03, 0.28, -0.02]}> 105 + <T.Mesh 106 + castShadow 107 + receiveShadow 108 + geometry={gltf.nodes.Mesh_drawer.geometry} 109 + material={gltf.materials.woodDark.clone()} 110 + material.color={colors?.[2] ?? gltf.materials.woodDark.color} 111 + material.opacity={opacity ?? gltf.materials.woodDark.opacity} 112 + material.transparent={opacity !== undefined} 113 + /> 114 + <T.Mesh 115 + castShadow 116 + receiveShadow 117 + geometry={gltf.nodes.Mesh_drawer_1.geometry} 118 + material={gltf.materials.wood.clone()} 119 + material.color={colors?.[1] ?? gltf.materials.wood.color} 120 + material.opacity={opacity ?? gltf.materials.wood.opacity} 121 + material.transparent={opacity !== undefined} 122 + /> 123 + <T.Mesh 124 + castShadow 125 + receiveShadow 126 + geometry={gltf.nodes.Mesh_drawer_2.geometry} 127 + material={gltf.materials.metal.clone()} 128 + material.color={colors?.[0] ?? gltf.materials.metal.color} 129 + material.opacity={opacity ?? gltf.materials.metal.opacity} 130 + material.transparent={opacity !== undefined} 131 + /> 132 + </T.Group> 133 + </T.Group> 134 + {:catch err} 135 + {@render error?.({ error: err })} 136 + {/await} 137 + 138 + {@render children?.({ ref })} 139 + </T.Group>
+76
src/lib/models/kenney-furniture-kit/lampRoundFloor.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/lampRoundFloor.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_lampRoundFloor: THREE.Mesh; 34 + Mesh_lampRoundFloor_1: THREE.Mesh; 35 + }; 36 + materials: { 37 + metal: THREE.MeshStandardMaterial; 38 + lamp: THREE.MeshStandardMaterial; 39 + }; 40 + }; 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/lampRoundFloor.glb'); 43 + </script> 44 + 45 + <T.Group bind:ref dispose={false} {...props}> 46 + {#await gltf} 47 + {@render fallback?.()} 48 + {:then gltf} 49 + <T.Mesh 50 + castShadow 51 + receiveShadow 52 + geometry={gltf.nodes.Mesh_lampRoundFloor.geometry} 53 + material={gltf.materials.metal.clone()} 54 + material.color={colors?.[0] ?? gltf.materials.metal.color} 55 + material.opacity={opacity ?? gltf.materials.metal.opacity} 56 + material.transparent={opacity !== undefined} 57 + position={[-0.05, 0, 0.05]} 58 + /> 59 + <T.Mesh 60 + castShadow 61 + receiveShadow 62 + geometry={gltf.nodes.Mesh_lampRoundFloor_1.geometry} 63 + material={gltf.materials.lamp.clone()} 64 + material.emissive={colors?.[1] ?? '#ffffff'} 65 + material.opacity={opacity ?? gltf.materials.lamp.opacity} 66 + material.transparent={opacity !== undefined} 67 + position={[-0.05, 0, 0.05]} 68 + /> 69 + 70 + <T.PointLight position={[0, 1, 0]} castShadow color={colors?.[1] ?? '#ffffff'} /> 71 + {:catch err} 72 + {@render error?.({ error: err })} 73 + {/await} 74 + 75 + {@render children?.({ ref })} 76 + </T.Group>
+74
src/lib/models/kenney-furniture-kit/loungeChair.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/loungeChair.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_loungeChair: THREE.Mesh; 34 + Mesh_loungeChair_1: THREE.Mesh; 35 + }; 36 + materials: { 37 + carpet: THREE.MeshStandardMaterial; 38 + wood: THREE.MeshStandardMaterial; 39 + }; 40 + }; 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/loungeChair.glb'); 43 + </script> 44 + 45 + <T.Group bind:ref dispose={false} {...props}> 46 + {#await gltf} 47 + {@render fallback?.()} 48 + {:then gltf} 49 + <T.Mesh 50 + castShadow 51 + receiveShadow 52 + geometry={gltf.nodes.Mesh_loungeChair.geometry} 53 + material={gltf.materials.carpet.clone()} 54 + material.color={colors?.[0] ?? gltf.materials.carpet.color} 55 + position={[-0.25, 0, 0.25]} 56 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 57 + material.transparent={opacity !== undefined} 58 + /> 59 + <T.Mesh 60 + castShadow 61 + receiveShadow 62 + geometry={gltf.nodes.Mesh_loungeChair_1.geometry} 63 + material={gltf.materials.wood.clone()} 64 + material.color={colors?.[1] ?? gltf.materials.wood.color} 65 + position={[-0.25, 0, 0.25]} 66 + material.opacity={opacity ?? gltf.materials.wood.opacity} 67 + material.transparent={opacity !== undefined} 68 + /> 69 + {:catch err} 70 + {@render error?.({ error: err })} 71 + {/await} 72 + 73 + {@render children?.({ ref })} 74 + </T.Group>
+74
src/lib/models/kenney-furniture-kit/loungeSofa.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/loungeSofa.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_loungeSofa: THREE.Mesh; 34 + Mesh_loungeSofa_1: THREE.Mesh; 35 + }; 36 + materials: { 37 + carpet: THREE.MeshStandardMaterial; 38 + wood: THREE.MeshStandardMaterial; 39 + }; 40 + }; 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/loungeSofa.glb'); 43 + </script> 44 + 45 + <T.Group bind:ref dispose={false} {...props}> 46 + {#await gltf} 47 + {@render fallback?.()} 48 + {:then gltf} 49 + <T.Mesh 50 + castShadow 51 + receiveShadow 52 + geometry={gltf.nodes.Mesh_loungeSofa.geometry} 53 + material={gltf.materials.carpet.clone()} 54 + material.color={colors?.[0] ?? gltf.materials.carpet.color} 55 + material.opacity={opacity ?? gltf.materials.carpet.opacity} 56 + material.transparent={opacity !== undefined} 57 + position={[-0.5, 0, 0.25]} 58 + /> 59 + <T.Mesh 60 + castShadow 61 + receiveShadow 62 + geometry={gltf.nodes.Mesh_loungeSofa_1.geometry} 63 + material={gltf.materials.wood.clone()} 64 + material.color={colors?.[1] ?? gltf.materials.wood.color} 65 + material.opacity={opacity ?? gltf.materials.wood.opacity} 66 + material.transparent={opacity !== undefined} 67 + position={[-0.5, 0, 0.25]} 68 + /> 69 + {:catch err} 70 + {@render error?.({ error: err })} 71 + {/await} 72 + 73 + {@render children?.({ ref })} 74 + </T.Group>
+87
src/lib/models/kenney-furniture-kit/pottedPlant.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/pottedPlant.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_pottedPlant: THREE.Mesh; 34 + Mesh_pottedPlant_1: THREE.Mesh; 35 + plant: THREE.Mesh; 36 + }; 37 + materials: { 38 + wood: THREE.MeshStandardMaterial; 39 + woodDark: THREE.MeshStandardMaterial; 40 + plant: THREE.MeshStandardMaterial; 41 + }; 42 + }; 43 + 44 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/pottedPlant.glb'); 45 + </script> 46 + 47 + <T.Group bind:ref dispose={false} {...props}> 48 + {#await gltf} 49 + {@render fallback?.()} 50 + {:then gltf} 51 + <T.Group scale={2}> 52 + <T.Mesh 53 + castShadow 54 + receiveShadow 55 + geometry={gltf.nodes.Mesh_pottedPlant.geometry} 56 + material={gltf.materials.wood.clone()} 57 + material.color={colors?.[0] ?? gltf.materials.wood.color} 58 + material.opacity={opacity ?? gltf.materials.wood.opacity} 59 + material.transparent={opacity !== undefined} 60 + /> 61 + <T.Mesh 62 + castShadow 63 + receiveShadow 64 + geometry={gltf.nodes.Mesh_pottedPlant_1.geometry} 65 + material={gltf.materials.woodDark.clone()} 66 + material.color={colors?.[1] ?? gltf.materials.woodDark.color} 67 + material.opacity={opacity ?? gltf.materials.woodDark.opacity} 68 + material.transparent={opacity !== undefined} 69 + /> 70 + <T.Mesh 71 + castShadow 72 + receiveShadow 73 + geometry={gltf.nodes.plant.geometry} 74 + material={gltf.materials.plant.clone()} 75 + material.color={colors?.[2] ?? gltf.materials.plant.color} 76 + material.opacity={opacity ?? gltf.materials.plant.opacity} 77 + material.transparent={opacity !== undefined} 78 + position={[-0.05, 0.06, 0.06]} 79 + scale={0.5} 80 + /> 81 + </T.Group> 82 + {:catch err} 83 + {@render error?.({ error: err })} 84 + {/await} 85 + 86 + {@render children?.({ ref })} 87 + </T.Group>
+74
src/lib/models/kenney-furniture-kit/sideTable.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/sideTable.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + colors, 19 + opacity, 20 + ref = $bindable(), 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + Mesh_sideTable: THREE.Mesh; 34 + Mesh_sideTable_1: THREE.Mesh; 35 + }; 36 + materials: { 37 + wood: THREE.MeshStandardMaterial; 38 + _defaultMat: THREE.MeshStandardMaterial; 39 + }; 40 + }; 41 + 42 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/sideTable.glb'); 43 + </script> 44 + 45 + <T.Group bind:ref dispose={false} {...props}> 46 + {#await gltf} 47 + {@render fallback?.()} 48 + {:then gltf} 49 + <T.Mesh 50 + castShadow 51 + receiveShadow 52 + geometry={gltf.nodes.Mesh_sideTable.geometry} 53 + material={gltf.materials.wood.clone()} 54 + material.color={colors?.[0] ?? gltf.materials.wood.color} 55 + material.opacity={opacity ?? gltf.materials.wood.opacity} 56 + material.transparent={opacity !== undefined} 57 + position={[-0.25, 0, 0.1]} 58 + /> 59 + <T.Mesh 60 + castShadow 61 + receiveShadow 62 + geometry={gltf.nodes.Mesh_sideTable_1.geometry} 63 + material={gltf.materials._defaultMat.clone()} 64 + material.color={colors?.[1] ?? gltf.materials._defaultMat.color} 65 + material.opacity={opacity ?? gltf.materials._defaultMat.opacity} 66 + material.transparent={opacity !== undefined} 67 + position={[-0.25, 0, 0.1]} 68 + /> 69 + {:catch err} 70 + {@render error?.({ error: err })} 71 + {/await} 72 + 73 + {@render children?.({ ref })} 74 + </T.Group>
+62
src/lib/models/kenney-furniture-kit/table.svelte
··· 1 + <!-- 2 + Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf 3 + Command: npx @threlte/gltf@3.0.0 static/models/table.glb -t -s 4 + --> 5 + 6 + <script lang="ts"> 7 + import type * as THREE from 'three'; 8 + 9 + import type { Snippet } from 'svelte'; 10 + import { T, type Props } from '@threlte/core'; 11 + import { useGltf } from '@threlte/extras'; 12 + import { base } from '$app/paths'; 13 + 14 + let { 15 + fallback, 16 + error, 17 + children, 18 + ref = $bindable(), 19 + colors, 20 + opacity, 21 + ...props 22 + }: Props<THREE.Group> & { 23 + ref?: THREE.Group; 24 + children?: Snippet<[{ ref: THREE.Group }]>; 25 + fallback?: Snippet; 26 + error?: Snippet<[{ error: Error }]>; 27 + colors?: (number | string)[]; 28 + opacity?: number; 29 + } = $props(); 30 + 31 + type GLTFResult = { 32 + nodes: { 33 + ['table(Clone)']: THREE.Mesh; 34 + }; 35 + materials: { 36 + wood: THREE.MeshStandardMaterial; 37 + }; 38 + }; 39 + 40 + const gltf = useGltf<GLTFResult>(base + '/models/kenney-furniture-kit/table.glb'); 41 + </script> 42 + 43 + <T.Group bind:ref dispose={false} {...props}> 44 + {#await gltf} 45 + {@render fallback?.()} 46 + {:then gltf} 47 + <T.Mesh 48 + castShadow 49 + receiveShadow 50 + geometry={gltf.nodes['table(Clone)'].geometry} 51 + material={gltf.materials.wood.clone()} 52 + material.color={colors?.[0] ?? gltf.materials.wood.color} 53 + material.opacity={opacity ?? gltf.materials.wood.opacity} 54 + material.transparent={opacity !== undefined} 55 + position={[-0.4, 0, 0.2]} 56 + /> 57 + {:catch err} 58 + {@render error?.({ error: err })} 59 + {/await} 60 + 61 + {@render children?.({ ref })} 62 + </T.Group>
+258
src/lib/oauth/auth.svelte.ts
··· 1 + import { 2 + configureOAuth, 3 + createAuthorizationUrl, 4 + finalizeAuthorization, 5 + resolveFromIdentity, 6 + type Session, 7 + OAuthUserAgent, 8 + getSession 9 + } from '@atcute/oauth-browser-client'; 10 + import { dev } from '$app/environment'; 11 + import { CredentialManager, XRPC } from '@atcute/client'; 12 + import { metadata } from './const'; 13 + import { AtpBaseClient } from '@atproto/api'; 14 + import type { DidDocument } from '@atcute/client/utils/did'; 15 + 16 + export const client = $state({ 17 + agent: null as OAuthUserAgent | null, 18 + session: null as Session | null, 19 + rpc: null as XRPC | null, 20 + profile: null as { 21 + handle: string; 22 + did: string; 23 + createdAt: string; 24 + description?: string; 25 + displayName?: string; 26 + banner?: string; 27 + avatar?: string; 28 + followersCount?: number; 29 + followsCount?: number; 30 + postsCount?: number; 31 + } | null, 32 + isInitializing: true, 33 + isLoggedIn: false, 34 + justLoggedIn: false 35 + }); 36 + 37 + export async function initClient() { 38 + client.isInitializing = true; 39 + 40 + const clientId = dev 41 + ? `http://localhost` + 42 + `?redirect_uri=${encodeURIComponent('http://127.0.0.1:5179')}` + 43 + `&scope=${encodeURIComponent(metadata.scope)}` 44 + : metadata.client_id; 45 + 46 + configureOAuth({ 47 + metadata: { 48 + client_id: clientId, 49 + redirect_uri: `${dev ? 'http://127.0.0.1:5179' : metadata.redirect_uris[0]}` 50 + } 51 + }); 52 + 53 + const params = new URLSearchParams(location.hash.slice(1)); 54 + 55 + const did = localStorage.getItem('last-login') ?? undefined; 56 + 57 + if (params.size > 0) { 58 + await finalizeLogin(params, did); 59 + } else if (did) { 60 + await resumeSession(did); 61 + } 62 + 63 + client.isInitializing = false; 64 + } 65 + 66 + export async function login(handle: string) { 67 + if (handle.startsWith('did:')) { 68 + if (handle.length > 5) await authorizationFlow(handle); 69 + else throw new Error('DID must be at least 6 characters'); 70 + } else if (handle.includes('.') && handle.length > 3) { 71 + const processed = handle.startsWith('@') ? handle.slice(1) : handle; 72 + if (processed.length > 3) await authorizationFlow(processed); 73 + else throw new Error('Handle must be at least 4 characters'); 74 + } else if (handle.length > 3) { 75 + const processed = (handle.startsWith('@') ? handle.slice(1) : handle) + '.bsky.social'; 76 + await authorizationFlow(processed); 77 + } else { 78 + throw new Error('Please provide a valid handle, DID, or PDS URL'); 79 + } 80 + } 81 + 82 + export async function logout() { 83 + const currentAgent = client.agent; 84 + if (currentAgent) { 85 + const did = currentAgent.session.info.sub; 86 + 87 + localStorage.removeItem('last-login'); 88 + localStorage.removeItem(`profile-${did}`); 89 + 90 + await currentAgent.signOut(); 91 + client.session = null; 92 + client.agent = null; 93 + client.profile = null; 94 + 95 + client.isLoggedIn = false; 96 + } else { 97 + throw new Error('Not signed in'); 98 + } 99 + } 100 + 101 + async function finalizeLogin(params: URLSearchParams, did?: string) { 102 + try { 103 + history.replaceState(null, '', location.pathname + location.search); 104 + 105 + const session = await finalizeAuthorization(params); 106 + client.session = session; 107 + 108 + setAgentAndXRPC(session); 109 + localStorage.setItem('last-login', session.info.sub); 110 + 111 + await loadProfile(session.info.sub); 112 + 113 + client.justLoggedIn = true; 114 + client.isLoggedIn = true; 115 + } catch (error) { 116 + console.error('error finalizing login', error); 117 + if (did) { 118 + await resumeSession(did); 119 + } 120 + } 121 + } 122 + 123 + async function resumeSession(did: string) { 124 + try { 125 + const session = await getSession(did as `did:${string}`, { allowStale: true }); 126 + client.session = session; 127 + 128 + setAgentAndXRPC(session); 129 + 130 + await loadProfile(session.info.sub); 131 + 132 + client.isLoggedIn = true; 133 + } catch (error) { 134 + console.error('error resuming session', error); 135 + } 136 + } 137 + 138 + function setAgentAndXRPC(session: Session) { 139 + client.agent = new OAuthUserAgent(session); 140 + 141 + client.rpc = new XRPC({ handler: client.agent }); 142 + } 143 + 144 + async function loadProfile(actor: string) { 145 + // check if profile is already loaded in local storage 146 + const profile = localStorage.getItem(`profile-${actor}`); 147 + if (profile) { 148 + console.log('loading profile from local storage'); 149 + client.profile = JSON.parse(profile); 150 + return; 151 + } 152 + 153 + console.log('loading profile from server'); 154 + const response = await client.rpc?.request({ 155 + type: 'get', 156 + nsid: 'app.bsky.actor.getProfile', 157 + params: { actor } 158 + }); 159 + 160 + if (response) { 161 + client.profile = response.data; 162 + localStorage.setItem(`profile-${actor}`, JSON.stringify(response.data)); 163 + } 164 + } 165 + 166 + async function authorizationFlow(input: string) { 167 + const { identity, metadata: meta } = await resolveFromIdentity(input); 168 + 169 + const authUrl = await createAuthorizationUrl({ 170 + metadata: meta, 171 + identity: identity, 172 + scope: metadata.scope 173 + }); 174 + 175 + await new Promise((resolve) => setTimeout(resolve, 200)); 176 + 177 + window.location.assign(authUrl); 178 + 179 + await new Promise((_resolve, reject) => { 180 + const listener = () => { 181 + reject(new Error(`user aborted the login request`)); 182 + }; 183 + 184 + window.addEventListener('pageshow', listener, { once: true }); 185 + }); 186 + } 187 + 188 + let agent: AtpBaseClient | null = null; 189 + 190 + const didPDSCache: Record<string, string> = {}; 191 + const didDocCache: Record<string, DidDocument> = {}; 192 + 193 + const getPDS = async (did: string) => { 194 + if (did in didPDSCache) return didPDSCache[did]; 195 + const res = await fetch( 196 + did.startsWith('did:web') 197 + ? `https://${did.split(':')[2]}/.well-known/did.json` 198 + : 'https://plc.directory/' + did 199 + ); 200 + 201 + return res.json().then((doc) => { 202 + if (!doc.service) throw new Error('No PDS found'); 203 + for (const service of doc.service) { 204 + if (service.id === '#atproto_pds') { 205 + didPDSCache[did] = service.serviceEndpoint.toString(); 206 + didDocCache[did] = doc; 207 + } 208 + } 209 + return didPDSCache[did]; 210 + }); 211 + }; 212 + 213 + export async function getRoom({ did }: { did: string }) { 214 + if (!agent) { 215 + agent = new AtpBaseClient({ service: 'https://api.bsky.app' }); 216 + } 217 + 218 + // // get profile 219 + // const { data } = await agent.com.atproto.sync.getRecord({ 220 + // did: did, 221 + // collection: 'app.bsky.actor.profile', 222 + // rkey: 'self' 223 + // }); 224 + 225 + const pds = await getPDS(did); 226 + const rpc = new XRPC({ handler: new CredentialManager({ service: pds }) }); 227 + 228 + const { data } = await rpc.get('com.atproto.repo.getRecord', { 229 + params: { 230 + repo: did, 231 + collection: 'dev.flo-bit.room', 232 + rkey: 'self' 233 + } 234 + }); 235 + 236 + // get profile 237 + const profile = await getProfile({ did }); 238 + 239 + return { room: data.value.roomState, profile: profile }; 240 + } 241 + 242 + export async function getProfile({ did }: { did: string }) { 243 + if (!agent) { 244 + agent = new AtpBaseClient({ service: 'https://public.api.bsky.app' }); 245 + } 246 + 247 + const data = await agent.app.bsky.actor.getProfile({ actor: did }); 248 + return data.data; 249 + } 250 + 251 + export async function resolveHandle({ handle }: { handle: string }) { 252 + if (!agent) { 253 + agent = new AtpBaseClient({ service: 'https://public.api.bsky.app' }); 254 + } 255 + 256 + const data = await agent.com.atproto.identity.resolveHandle({ handle }); 257 + return data.data.did; 258 + }
+16
src/lib/oauth/const.ts
··· 1 + import { base } from '$app/paths'; 2 + 3 + export const SITE_URL = 'https://flo-bit.dev'; 4 + 5 + export const metadata = { 6 + client_id: `${SITE_URL}${base}/client-metadata.json`, 7 + 8 + redirect_uris: [SITE_URL + base], 9 + 10 + scope: 'atproto transition:generic', 11 + grant_types: ['authorization_code', 'refresh_token'], 12 + response_types: ['code'], 13 + token_endpoint_auth_method: 'none', 14 + application_type: 'web', 15 + dpop_bound_access_tokens: true 16 + };
+4
src/lib/oauth/index.ts
··· 1 + import { client, login, logout, initClient } from './auth.svelte'; 2 + import { metadata } from './const'; 3 + 4 + export { metadata, client, login, logout, initClient };
+270
src/lib/state.svelte.ts
··· 1 + import type { TransformControls } from '@threlte/extras'; 2 + import Table from './models/kenney-furniture-kit/table.svelte'; 3 + import LampRoundFloor from './models/kenney-furniture-kit/lampRoundFloor.svelte'; 4 + import LoungeChair from './models/kenney-furniture-kit/loungeChair.svelte'; 5 + import LoungeSofa from './models/kenney-furniture-kit/loungeSofa.svelte'; 6 + import PottedPlant from './models/kenney-furniture-kit/pottedPlant.svelte'; 7 + import SideTable from './models/kenney-furniture-kit/sideTable.svelte'; 8 + import BedBunk from './models/kenney-furniture-kit/bedBunk.svelte'; 9 + import BedDouble from './models/kenney-furniture-kit/bedDouble.svelte'; 10 + import BedSingle from './models/kenney-furniture-kit/bedSingle.svelte'; 11 + import Bench from './models/kenney-furniture-kit/bench.svelte'; 12 + import BenchCushion from './models/kenney-furniture-kit/benchCushion.svelte'; 13 + import BenchCushionLow from './models/kenney-furniture-kit/benchCushionLow.svelte'; 14 + import BookcaseClosed from './models/kenney-furniture-kit/bookcaseClosed.svelte'; 15 + import CabinetBed from './models/kenney-furniture-kit/cabinetBed.svelte'; 16 + import CabinetBedDrawer from './models/kenney-furniture-kit/cabinetBedDrawer.svelte'; 17 + import CabinetBedDrawerTable from './models/kenney-furniture-kit/cabinetBedDrawerTable.svelte'; 18 + import Chair from './models/kenney-furniture-kit/chair.svelte'; 19 + import ChairCushion from './models/kenney-furniture-kit/chairCushion.svelte'; 20 + import ChairDesk from './models/kenney-furniture-kit/chairDesk.svelte'; 21 + import ChairModernCushion from './models/kenney-furniture-kit/chairModernCushion.svelte'; 22 + import ChairModernFrameCushion from './models/kenney-furniture-kit/chairModernFrameCushion.svelte'; 23 + import ChairRounded from './models/kenney-furniture-kit/chairRounded.svelte'; 24 + import CoatRackStanding from './models/kenney-furniture-kit/coatRackStanding.svelte'; 25 + import Desk from './models/kenney-furniture-kit/desk.svelte'; 26 + import DeskCorner from './models/kenney-furniture-kit/deskCorner.svelte'; 27 + import Doorway from './models/kenney-furniture-kit/doorway.svelte'; 28 + import DoorwayFront from './models/kenney-furniture-kit/doorwayFront.svelte'; 29 + import Dryer from './models/kenney-furniture-kit/dryer.svelte'; 30 + import KitchenBar from './models/kenney-furniture-kit/kitchenBar.svelte'; 31 + import KitchenCabinet from './models/kenney-furniture-kit/kitchenCabinet.svelte'; 32 + import KitchenCabinetDrawer from './models/kenney-furniture-kit/kitchenCabinetDrawer.svelte'; 33 + 34 + export const AllObjects = { 35 + 'kenney-furniture-kit-table': { 36 + component: Table, 37 + colors: 1 38 + }, 39 + 'kenney-furniture-kit-lampRoundFloor': { 40 + component: LampRoundFloor, 41 + colors: 2 42 + }, 43 + 'kenney-furniture-kit-loungeChair': { 44 + component: LoungeChair, 45 + colors: 2 46 + }, 47 + 'kenney-furniture-kit-loungeSofa': { 48 + component: LoungeSofa, 49 + colors: 2 50 + }, 51 + 'kenney-furniture-kit-pottedPlant': { 52 + component: PottedPlant, 53 + colors: 3 54 + }, 55 + 'kenney-furniture-kit-sideTable': { 56 + component: SideTable, 57 + colors: 1 58 + }, 59 + 'kenney-furniture-kit-bedBunk': { 60 + component: BedBunk, 61 + colors: 3 62 + }, 63 + 'kenney-furniture-kit-bedDouble': { 64 + component: BedDouble, 65 + colors: 4 66 + }, 67 + 'kenney-furniture-kit-bedSingle': { 68 + component: BedSingle, 69 + colors: 4 70 + }, 71 + 'kenney-furniture-kit-bench': { 72 + component: Bench, 73 + colors: 1 74 + }, 75 + 'kenney-furniture-kit-benchCushion': { 76 + component: BenchCushion, 77 + colors: 2 78 + }, 79 + 'kenney-furniture-kit-benchCushionLow': { 80 + component: BenchCushionLow, 81 + colors: 2 82 + }, 83 + 'kenney-furniture-kit-bookcaseClosed': { 84 + component: BookcaseClosed, 85 + colors: 2 86 + }, 87 + 'kenney-furniture-kit-cabinetBed': { 88 + component: CabinetBed, 89 + colors: 2 90 + }, 91 + 'kenney-furniture-kit-cabinetBedDrawer': { 92 + component: CabinetBedDrawer, 93 + colors: 2 94 + }, 95 + 'kenney-furniture-kit-cabinetBedDrawerTable': { 96 + component: CabinetBedDrawerTable, 97 + colors: 2 98 + }, 99 + 'kenney-furniture-kit-chair': { 100 + component: Chair, 101 + colors: 1 102 + }, 103 + 'kenney-furniture-kit-chairCushion': { 104 + component: ChairCushion, 105 + colors: 2 106 + }, 107 + 'kenney-furniture-kit-chairDesk': { 108 + component: ChairDesk, 109 + colors: 2 110 + }, 111 + 'kenney-furniture-kit-chairModernCushion': { 112 + component: ChairModernCushion, 113 + colors: 2 114 + }, 115 + 'kenney-furniture-kit-chairModernFrameCushion': { 116 + component: ChairModernFrameCushion, 117 + colors: 2 118 + }, 119 + 'kenney-furniture-kit-chairRounded': { 120 + component: ChairRounded, 121 + colors: 1 122 + }, 123 + 'kenney-furniture-kit-coatRackStanding': { 124 + component: CoatRackStanding, 125 + colors: 1 126 + }, 127 + 'kenney-furniture-kit-desk': { 128 + component: Desk, 129 + colors: 2 130 + }, 131 + 'kenney-furniture-kit-deskCorner': { 132 + component: DeskCorner, 133 + colors: 2 134 + }, 135 + 'kenney-furniture-kit-doorway': { 136 + component: Doorway, 137 + colors: 3 138 + }, 139 + 'kenney-furniture-kit-doorwayFront': { 140 + component: DoorwayFront, 141 + colors: 4 142 + }, 143 + 'kenney-furniture-kit-dryer': { 144 + component: Dryer, 145 + colors: 3 146 + }, 147 + 'kenney-furniture-kit-kitchenBar': { 148 + component: KitchenBar, 149 + colors: 3 150 + }, 151 + 'kenney-furniture-kit-kitchenCabinet': { 152 + component: KitchenCabinet, 153 + colors: 3 154 + }, 155 + 'kenney-furniture-kit-kitchenCabinetDrawer': { 156 + component: KitchenCabinetDrawer, 157 + colors: 3 158 + } 159 + }; 160 + 161 + export const currentVersion = 0; 162 + 163 + export const roomState: { 164 + wallColor: string; 165 + floorColor: string; 166 + objects: RoomObjectData[]; 167 + 168 + size: { x: number; z: number }; 169 + 170 + id: number; 171 + 172 + version: number; 173 + } = $state({ 174 + wallColor: '#f97316', 175 + floorColor: '#fde047', 176 + 177 + objects: [], 178 + 179 + size: { x: 2, z: 3 }, 180 + 181 + id: 0, 182 + 183 + version: currentVersion 184 + }); 185 + 186 + export const editorState: { 187 + startIndex: number; 188 + shownCount: number; 189 + 190 + selectedObject: RoomObjectData | null; 191 + transformControls: TransformControls | undefined; 192 + placingObject: RoomObjectData | null; 193 + 194 + isEditing: boolean; 195 + } = $state({ 196 + startIndex: 0, 197 + shownCount: 3, 198 + 199 + selectedObject: null, 200 + transformControls: undefined, 201 + placingObject: null, 202 + 203 + isEditing: false 204 + }); 205 + 206 + export type RoomObjectKind = keyof typeof AllObjects; 207 + 208 + export type RoomObjectData = { 209 + kind: RoomObjectKind; 210 + position: [number, number, number]; 211 + rotation: number; 212 + colors: string[]; 213 + placement: 'floor' | 'wallX' | 'wallZ'; 214 + }; 215 + 216 + export function applyTransformOfSelected() { 217 + if (editorState.selectedObject && editorState.transformControls) { 218 + editorState.selectedObject.position = [ 219 + editorState.transformControls.object.position.x, 220 + editorState.transformControls.object.position.y, 221 + editorState.transformControls.object.position.z 222 + ]; 223 + 224 + editorState.selectedObject.rotation = editorState.transformControls.object.rotation.y; 225 + 226 + saveRoomToLocalStorage(); 227 + } 228 + } 229 + 230 + export function saveRoomToLocalStorage() { 231 + localStorage.setItem('roomState', JSON.stringify(roomState)); 232 + } 233 + 234 + export function rotateObject(rotation: number) { 235 + if (editorState.selectedObject) { 236 + applyTransformOfSelected(); 237 + 238 + const selectedObject = editorState.selectedObject; 239 + editorState.selectedObject = null; 240 + 241 + setTimeout(() => { 242 + // rotate right 243 + selectedObject.rotation -= rotation; 244 + editorState.selectedObject = selectedObject; 245 + }, 0); 246 + saveRoomToLocalStorage(); 247 + } else if (editorState.placingObject) { 248 + editorState.placingObject.rotation = (editorState.placingObject.rotation ?? 0) - rotation; 249 + saveRoomToLocalStorage(); 250 + } 251 + } 252 + 253 + export function tryLoadingRoomFromLocalStorage(otherWiseReset: boolean = false) { 254 + const room = localStorage.getItem('roomState'); 255 + if (room) { 256 + const roomObject = JSON.parse(room); 257 + roomState.floorColor = roomObject.floorColor; 258 + roomState.wallColor = roomObject.wallColor; 259 + roomState.objects = roomObject.objects; 260 + roomState.size = roomObject.size; 261 + roomState.id = roomObject.id ?? Math.random(); 262 + } else if (otherWiseReset) { 263 + roomState.floorColor = '#a1a1a1'; 264 + roomState.wallColor = '#f1f1f1'; 265 + roomState.objects = []; 266 + roomState.size = { x: 2, z: 3 }; 267 + roomState.id = Math.random(); 268 + roomState.version = currentVersion; 269 + } 270 + }
+6
src/lib/utils.ts
··· 1 + import { type ClassValue, clsx } from 'clsx'; 2 + import { twMerge } from 'tailwind-merge'; 3 + 4 + export function cn(...inputs: ClassValue[]) { 5 + return twMerge(clsx(inputs)); 6 + }
+21
src/routes/+layout.svelte
··· 1 + <script lang="ts"> 2 + import '../app.css'; 3 + 4 + import { onMount } from 'svelte'; 5 + import { initClient, login } from '$lib/oauth'; 6 + import BlueskyLoginModal from '$lib/components/base/modal/BlueskyLoginModal.svelte'; 7 + import Head from '$lib/components/base/head/Head.svelte'; 8 + import { base } from '$app/paths'; 9 + 10 + let { children } = $props(); 11 + 12 + onMount(() => { 13 + initClient(); 14 + }); 15 + </script> 16 + 17 + {@render children()} 18 + 19 + <BlueskyLoginModal login={login} /> 20 + 21 + <Head emojiFavicon="🏠" title="my room" description="create your own room and share it on bluesky" image={base + "/og-image.png"} />
+1
src/routes/+layout.ts
··· 1 + export const prerender = true;
+745
src/routes/+page.svelte
··· 1 + <script lang="ts"> 2 + import { Canvas } from '@threlte/core'; 3 + import Scene from '$lib/Scene.svelte'; 4 + import { onMount } from 'svelte'; 5 + import { client, getRoom, resolveHandle } from '$lib/oauth/auth.svelte'; 6 + import { 7 + AllObjects, 8 + editorState, 9 + roomState, 10 + applyTransformOfSelected, 11 + rotateObject, 12 + type RoomObjectData, 13 + saveRoomToLocalStorage, 14 + currentVersion, 15 + tryLoadingRoomFromLocalStorage 16 + } from '$lib/state.svelte'; 17 + import { Button } from '$lib/components/base/button'; 18 + import { blueskyLoginModalState } from '$lib/components/base/modal/BlueskyLoginModal.svelte'; 19 + import NumberInput from '$lib/components/base/number-input/NumberInput.svelte'; 20 + import Modal from '$lib/components/base/modal/Modal.svelte'; 21 + import Heading from '$lib/components/base/heading/Heading.svelte'; 22 + import Subheading from '$lib/components/base/heading/Subheading.svelte'; 23 + 24 + import * as Popover from '$lib/components/base/popover'; 25 + import { cn } from '$lib/utils'; 26 + import ColorPicker from '$lib/components/extra/color-picker'; 27 + import { 28 + hex_to_rgb, 29 + okhsv_to_rgb, 30 + rgb_to_hex, 31 + rgb_to_okhsv 32 + } from '$lib/components/extra/color-picker/color'; 33 + import type { ProfileViewDetailed } from '@atproto/api/dist/client/types/app/bsky/actor/defs'; 34 + import Avatar from '$lib/components/base/avatar/Avatar.svelte'; 35 + 36 + async function saveRoomToBluesky() { 37 + if (!client.rpc || !client.profile) return; 38 + 39 + applyTransformOfSelected(); 40 + 41 + editorState.selectedObject = null; 42 + 43 + try { 44 + const hello = await client.rpc.call('com.atproto.repo.putRecord', { 45 + data: { 46 + collection: 'dev.flo-bit.room', 47 + repo: client.profile.did, 48 + rkey: 'self', 49 + record: { 50 + roomState: JSON.parse( 51 + JSON.stringify({ 52 + wallColor: roomState.wallColor, 53 + floorColor: roomState.floorColor, 54 + objects: roomState.objects, 55 + size: roomState.size, 56 + id: roomState.id, 57 + version: roomState.version 58 + }) 59 + ) 60 + } 61 + } 62 + }); 63 + 64 + // @ts-ignore 65 + if (window.umami) { 66 + // @ts-ignore 67 + window.umami?.track('room_save', { 68 + handle: client.profile.handle, 69 + objectCount: roomState.objects.length 70 + }); 71 + } 72 + 73 + // TODO: show success modal 74 + successModalState = true; 75 + } catch (e) { 76 + console.error(e); 77 + } 78 + } 79 + 80 + let profile: ProfileViewDetailed | null = $state(null); 81 + 82 + let loading = $state(true); 83 + 84 + let currentHandle = $state('flo-bit.dev'); 85 + 86 + let successModalState = $state(false); 87 + 88 + let signInSuccessModalState = $state(false); 89 + 90 + async function loadRoomFromBluesky(handle: string) { 91 + const did = await resolveHandle({ handle }); 92 + console.log(did); 93 + const room = await getRoom({ did }); 94 + console.log(room); 95 + 96 + roomState.wallColor = room.room.wallColor; 97 + roomState.floorColor = room.room.floorColor; 98 + 99 + let availableObjects = new Set(Object.keys(AllObjects)); 100 + 101 + roomState.objects = room.room.objects.filter((object: RoomObjectData) => 102 + availableObjects.has(object.kind) 103 + ); 104 + if (roomState.objects.length !== room.room.objects.length) { 105 + console.warn('some objects were not found and removed!'); 106 + } 107 + 108 + roomState.size = room.room.size; 109 + roomState.id = room.room.id; 110 + roomState.version = room.room.version; 111 + 112 + profile = room.profile; 113 + 114 + // @ts-ignore 115 + if (window.umami) { 116 + // @ts-ignore 117 + window.umami.track('room_view', { 118 + handle: handle 119 + }); 120 + } 121 + } 122 + 123 + onMount(async () => { 124 + if (client.justLoggedIn) { 125 + successModalState = true; 126 + client.justLoggedIn = false; 127 + } 128 + 129 + window.addEventListener('keydown', (e) => { 130 + if (e.key === 'x') { 131 + deleteSelectedObject(); 132 + } 133 + }); 134 + 135 + // get handle from url search params 136 + let handle = new URLSearchParams(window.location.search).get('handle'); 137 + if (!handle) { 138 + handle = 'flo-bit.dev'; 139 + } 140 + 141 + currentHandle = handle; 142 + 143 + await loadRoomFromBluesky(handle); 144 + 145 + loading = false; 146 + }); 147 + 148 + let roomSettingsModalState = $state(false); 149 + let createRoomModalState = $state(false); 150 + let infoModalState = $state(false); 151 + 152 + let okhsv = $state({ h: 180, s: 0.0, v: 0.95 }); 153 + let okhsv2 = $state({ h: 180, s: 0.0, v: 0.95 }); 154 + let okhsv3 = $state({ h: 180, s: 0.0, v: 0.95 }); 155 + let okhsv4 = $state({ h: 180, s: 0.0, v: 0.95 }); 156 + let okhsv5 = $state({ h: 180, s: 0.0, v: 0.95 }); 157 + 158 + let okhsv_wall = $state({ h: 180, s: 0.0, v: 0.95 }); 159 + let okhsv_floor = $state({ h: 180, s: 0.0, v: 0.95 }); 160 + 161 + function deleteSelectedObject() { 162 + roomState.objects = roomState.objects.filter((object) => object !== editorState.selectedObject); 163 + 164 + editorState.selectedObject = null; 165 + } 166 + 167 + $effect(() => { 168 + if (!editorState.selectedObject) return; 169 + 170 + okhsv = rgb_to_okhsv(hex_to_rgb(editorState.selectedObject.colors[0])); 171 + okhsv2 = rgb_to_okhsv(hex_to_rgb(editorState.selectedObject.colors[1])); 172 + okhsv3 = rgb_to_okhsv(hex_to_rgb(editorState.selectedObject.colors[2])); 173 + 174 + okhsv_wall = rgb_to_okhsv(hex_to_rgb(roomState.wallColor)); 175 + okhsv_floor = rgb_to_okhsv(hex_to_rgb(roomState.floorColor)); 176 + }); 177 + 178 + $effect(() => { 179 + if (client.justLoggedIn) { 180 + signInSuccessModalState = true; 181 + client.justLoggedIn = false; 182 + } 183 + }); 184 + </script> 185 + 186 + <div class="h-[100dvh] w-screen"> 187 + <Canvas> 188 + <Scene /> 189 + </Canvas> 190 + </div> 191 + 192 + {#if editorState.isEditing} 193 + <Button 194 + class="fixed right-4 bottom-4" 195 + onclick={() => { 196 + editorState.startIndex += editorState.shownCount; 197 + if (editorState.startIndex >= Object.keys(AllObjects).length) { 198 + editorState.startIndex = 0; 199 + } 200 + }}>&rarr;</Button 201 + > 202 + 203 + <Button 204 + class="fixed bottom-4 left-4" 205 + onclick={() => { 206 + editorState.startIndex -= editorState.shownCount; 207 + if (editorState.startIndex < 0) { 208 + editorState.startIndex = Object.keys(AllObjects).length - editorState.shownCount; 209 + } 210 + }}>&larr;</Button 211 + > 212 + 213 + <div class="fixed top-4 left-4 flex flex-col items-start gap-2"> 214 + <Button 215 + size="iconLg" 216 + onclick={() => { 217 + roomSettingsModalState = true; 218 + }} 219 + > 220 + <svg 221 + xmlns="http://www.w3.org/2000/svg" 222 + viewBox="0 0 24 24" 223 + fill="currentColor" 224 + class="size-6" 225 + > 226 + <path 227 + fill-rule="evenodd" 228 + d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 0 0-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 0 0-2.282.819l-.922 1.597a1.875 1.875 0 0 0 .432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 0 0 0 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 0 0-.432 2.385l.922 1.597a1.875 1.875 0 0 0 2.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 0 0 2.28-.819l.923-1.597a1.875 1.875 0 0 0-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 0 0 0-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 0 0-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 0 0-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 0 0-1.85-1.567h-1.843ZM12 15.75a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5Z" 229 + clip-rule="evenodd" 230 + /> 231 + </svg> 232 + </Button> 233 + 234 + {#if editorState.selectedObject} 235 + <Button 236 + variant="red" 237 + size="icon" 238 + class="mt-8" 239 + onclick={() => { 240 + roomState.objects = roomState.objects.filter( 241 + (object) => object !== editorState.selectedObject 242 + ); 243 + 244 + editorState.selectedObject = null; 245 + }} 246 + > 247 + <svg 248 + xmlns="http://www.w3.org/2000/svg" 249 + viewBox="0 0 24 24" 250 + fill="currentColor" 251 + class="size-6" 252 + > 253 + <path 254 + fill-rule="evenodd" 255 + d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z" 256 + clip-rule="evenodd" 257 + /> 258 + </svg> 259 + 260 + <span class="sr-only">Delete selected object</span> 261 + </Button> 262 + 263 + <div class="mt-4"> 264 + <Button 265 + size="icon" 266 + onclick={() => { 267 + rotateObject(-Math.PI / 6); 268 + }} 269 + variant="secondary" 270 + > 271 + <svg 272 + xmlns="http://www.w3.org/2000/svg" 273 + fill="none" 274 + viewBox="0 0 24 24" 275 + stroke-width="1.5" 276 + stroke="currentColor" 277 + class="-scale-x-100" 278 + > 279 + <path 280 + stroke-linecap="round" 281 + stroke-linejoin="round" 282 + d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" 283 + /> 284 + </svg> 285 + </Button> 286 + 287 + <Button 288 + size="icon" 289 + onclick={() => { 290 + rotateObject(Math.PI / 6); 291 + }} 292 + variant="secondary" 293 + > 294 + <svg 295 + xmlns="http://www.w3.org/2000/svg" 296 + fill="none" 297 + viewBox="0 0 24 24" 298 + stroke-width="1.5" 299 + stroke="currentColor" 300 + class="" 301 + > 302 + <path 303 + stroke-linecap="round" 304 + stroke-linejoin="round" 305 + d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" 306 + /> 307 + </svg> 308 + </Button> 309 + </div> 310 + 311 + <Popover.Root> 312 + <Popover.Trigger class={cn('mt-5 ml-1 cursor-pointer')}> 313 + <div 314 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 315 + style={`background-color: ${editorState.selectedObject.colors[0]};`} 316 + ></div> 317 + </Popover.Trigger> 318 + <Popover.Content side="right" sideOffset={10}> 319 + <ColorPicker 320 + bind:okhsv 321 + on:change={(e) => { 322 + if (!editorState.selectedObject) return; 323 + 324 + const rgb = okhsv_to_rgb(okhsv); 325 + editorState.selectedObject.colors[0] = rgb_to_hex(rgb); 326 + }} 327 + /> 328 + </Popover.Content> 329 + </Popover.Root> 330 + 331 + {#if AllObjects[editorState.selectedObject.kind].colors > 1} 332 + <Popover.Root> 333 + <Popover.Trigger class={cn('mt-2 ml-1 cursor-pointer')}> 334 + <div 335 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 336 + style={`background-color: ${editorState.selectedObject.colors[1]};`} 337 + ></div> 338 + </Popover.Trigger> 339 + <Popover.Content side="left" sideOffset={10}> 340 + <ColorPicker 341 + bind:okhsv={okhsv2} 342 + on:change={(e) => { 343 + if (!editorState.selectedObject) return; 344 + 345 + const rgb = okhsv_to_rgb(okhsv2); 346 + editorState.selectedObject.colors[1] = rgb_to_hex(rgb); 347 + 348 + saveRoomToLocalStorage(); 349 + }} 350 + /> 351 + </Popover.Content> 352 + </Popover.Root> 353 + {/if} 354 + 355 + {#if AllObjects[editorState.selectedObject.kind].colors > 2} 356 + <Popover.Root> 357 + <Popover.Trigger class={cn('mt-2 ml-1 cursor-pointer')}> 358 + <div 359 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 360 + style={`background-color: ${editorState.selectedObject.colors[2]};`} 361 + ></div> 362 + </Popover.Trigger> 363 + <Popover.Content side="left" sideOffset={10}> 364 + <ColorPicker 365 + bind:okhsv={okhsv3} 366 + on:change={(e) => { 367 + if (!editorState.selectedObject) return; 368 + 369 + const rgb = okhsv_to_rgb(okhsv3); 370 + editorState.selectedObject.colors[2] = rgb_to_hex(rgb); 371 + 372 + saveRoomToLocalStorage(); 373 + }} 374 + /> 375 + </Popover.Content> 376 + </Popover.Root> 377 + {/if} 378 + 379 + {#if AllObjects[editorState.selectedObject.kind].colors > 3} 380 + <Popover.Root> 381 + <Popover.Trigger class={cn('mt-2 ml-1 cursor-pointer')}> 382 + <div 383 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 384 + style={`background-color: ${editorState.selectedObject.colors[3]};`} 385 + ></div> 386 + </Popover.Trigger> 387 + <Popover.Content side="left" sideOffset={10}> 388 + <ColorPicker 389 + bind:okhsv={okhsv4} 390 + on:change={(e) => { 391 + if (!editorState.selectedObject) return; 392 + 393 + const rgb = okhsv_to_rgb(okhsv4); 394 + editorState.selectedObject.colors[3] = rgb_to_hex(rgb); 395 + 396 + saveRoomToLocalStorage(); 397 + }} 398 + /> 399 + </Popover.Content> 400 + </Popover.Root> 401 + {/if} 402 + 403 + {#if AllObjects[editorState.selectedObject.kind].colors > 4} 404 + <Popover.Root> 405 + <Popover.Trigger class={cn('mt-2 ml-1 cursor-pointer')}> 406 + <div 407 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 408 + style={`background-color: ${editorState.selectedObject.colors[4]};`} 409 + ></div> 410 + </Popover.Trigger> 411 + <Popover.Content side="left" sideOffset={10}> 412 + <ColorPicker 413 + bind:okhsv={okhsv5} 414 + on:change={(e) => { 415 + if (!editorState.selectedObject) return; 416 + 417 + const rgb = okhsv_to_rgb(okhsv5); 418 + editorState.selectedObject.colors[4] = rgb_to_hex(rgb); 419 + 420 + saveRoomToLocalStorage(); 421 + }} 422 + /> 423 + </Popover.Content> 424 + </Popover.Root> 425 + {/if} 426 + {/if} 427 + </div> 428 + <div class="fixed top-4 right-4 flex flex-col items-end gap-2"> 429 + {#if !client || !client.isLoggedIn} 430 + <Button 431 + onclick={() => { 432 + applyTransformOfSelected(); 433 + 434 + blueskyLoginModalState.open = true; 435 + }} 436 + > 437 + <svg 438 + role="img" 439 + viewBox="0 0 24 24" 440 + xmlns="http://www.w3.org/2000/svg" 441 + aria-hidden="true" 442 + fill="currentColor" 443 + ><path 444 + d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z" 445 + /></svg 446 + > 447 + Login</Button 448 + > 449 + {:else} 450 + <Button 451 + onclick={() => { 452 + saveRoomToBluesky(); 453 + }}>Save & Share</Button 454 + > 455 + {/if} 456 + 457 + <Popover.Root> 458 + <Popover.Trigger class={cn('mt-5 ml-1 cursor-pointer')}> 459 + <div 460 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 461 + style={`background-color: ${roomState.wallColor};`} 462 + ></div> 463 + </Popover.Trigger> 464 + <Popover.Content side="right" sideOffset={10}> 465 + <ColorPicker 466 + bind:okhsv={okhsv_wall} 467 + on:change={(e) => { 468 + const rgb = okhsv_to_rgb(okhsv_wall); 469 + roomState.wallColor = rgb_to_hex(rgb); 470 + saveRoomToLocalStorage(); 471 + }} 472 + /> 473 + </Popover.Content> 474 + </Popover.Root> 475 + 476 + <Popover.Root> 477 + <Popover.Trigger class={cn('mt-5 ml-1 cursor-pointer')}> 478 + <div 479 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 480 + style={`background-color: ${roomState.floorColor};`} 481 + ></div> 482 + </Popover.Trigger> 483 + <Popover.Content side="right" sideOffset={10}> 484 + <ColorPicker 485 + bind:okhsv={okhsv_floor} 486 + on:change={(e) => { 487 + const rgb = okhsv_to_rgb(okhsv_floor); 488 + roomState.floorColor = rgb_to_hex(rgb); 489 + saveRoomToLocalStorage(); 490 + }} 491 + /> 492 + </Popover.Content> 493 + </Popover.Root> 494 + </div> 495 + 496 + <Modal bind:open={roomSettingsModalState}> 497 + <div class="flex flex-col items-start gap-4"> 498 + <Heading>Room settings</Heading> 499 + 500 + <Button 501 + variant="secondary" 502 + class="mt-4" 503 + onclick={() => { 504 + currentHandle = client.profile?.handle ?? ''; 505 + profile = client.profile; 506 + editorState.isEditing = false; 507 + roomSettingsModalState = false; 508 + }} 509 + > 510 + Stop editing & Preview 511 + </Button> 512 + 513 + <Subheading class="mt-2">Room Size</Subheading> 514 + <div class="flex items-center gap-2"> 515 + <NumberInput 516 + bind:value={roomState.size.x} 517 + min={1} 518 + max={8} 519 + onchange={() => saveRoomToLocalStorage()} 520 + /> 521 + <span class="text-accent-700 dark:text-accent-300"> 522 + <svg 523 + xmlns="http://www.w3.org/2000/svg" 524 + fill="none" 525 + viewBox="0 0 24 24" 526 + stroke-width="3" 527 + stroke="currentColor" 528 + class="size-4" 529 + > 530 + <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /> 531 + </svg> 532 + </span> 533 + <NumberInput 534 + bind:value={roomState.size.z} 535 + min={1} 536 + max={8} 537 + onchange={() => saveRoomToLocalStorage()} 538 + /> 539 + </div> 540 + 541 + <Subheading class="mt-4">Danger zone</Subheading> 542 + <div class="mt-4 flex gap-2"> 543 + {#if client.isLoggedIn} 544 + <Button 545 + variant="red" 546 + onclick={async () => { 547 + if (!client.profile?.handle) { 548 + console.error('no handle found'); 549 + return; 550 + } 551 + await loadRoomFromBluesky(client.profile.handle); 552 + roomSettingsModalState = false; 553 + }} 554 + > 555 + Load from bluesky 556 + </Button> 557 + {/if} 558 + 559 + <Button 560 + variant="red" 561 + onclick={() => { 562 + roomState.objects = []; 563 + editorState.selectedObject = null; 564 + roomState.size = { x: 2, z: 3 }; 565 + roomState.wallColor = '#f1f1f1'; 566 + roomState.floorColor = '#a1a1a1'; 567 + 568 + saveRoomToLocalStorage(); 569 + }} 570 + > 571 + Clear room 572 + </Button> 573 + </div> 574 + </div> 575 + </Modal> 576 + {:else if !loading} 577 + <div 578 + class="pointer-events-none fixed top-8 right-4 left-4 flex items-center justify-center gap-2" 579 + > 580 + {#if currentHandle} 581 + <Avatar src={profile?.avatar} /> 582 + {/if} 583 + <Heading> 584 + <a 585 + href={`https://bsky.app/profile/${profile?.handle}`} 586 + target="_blank" 587 + class="hover:text-accent-600 dark:hover:text-accent-500 pointer-events-auto" 588 + >{currentHandle}'s</a 589 + > 590 + room</Heading 591 + > 592 + </div> 593 + 594 + <div class="pointer-events-none fixed right-4 bottom-4 left-4 flex justify-between"> 595 + <Button 596 + onclick={() => { 597 + infoModalState = true; 598 + }} 599 + variant="secondary" 600 + > 601 + Info 602 + </Button> 603 + 604 + <Button 605 + onclick={() => { 606 + if (client.profile?.handle == currentHandle) { 607 + editorState.isEditing = true; 608 + return; 609 + } 610 + 611 + tryLoadingRoomFromLocalStorage(true); 612 + 613 + editorState.isEditing = true; 614 + }} 615 + > 616 + {client.profile?.handle == currentHandle ? 'Edit room' : 'Create room'} 617 + </Button> 618 + </div> 619 + {:else} 620 + <div 621 + class="bg-base-100 dark:bg-base-950 fixed inset-0 flex h-[100dvh] w-screen items-center justify-center" 622 + > 623 + <Heading>Loading...</Heading> 624 + </div> 625 + {/if} 626 + 627 + <Modal bind:open={infoModalState} class="text-base-900 dark:text-base-50"> 628 + <div> 629 + This is an experimental web app for creating tiny 3d rooms (and sharing them on bluesky) built 630 + by 631 + <a 632 + href="https://flo-bit.dev" 633 + target="_blank" 634 + class="text-accent-700 dark:text-accent-400 hover:text-accent-600 dark:hover:text-accent-500 font-medium" 635 + >flo-bit</a 636 + > 637 + </div> 638 + <div> 639 + It's still a work in progress, but it's open source (<a 640 + href="https://github.com/flo-bit/room" 641 + target="_blank" 642 + class="text-accent-700 dark:text-accent-400 hover:text-accent-600 dark:hover:text-accent-500 font-medium" 643 + >source code available here</a 644 + >), so feel free to contribute or suggest features on github 😊 645 + </div> 646 + 647 + <div> 648 + You can also send me a message on <a 649 + href="https://bsky.app/profile/flo-bit.dev" 650 + target="_blank" 651 + class="text-accent-700 dark:text-accent-400 hover:text-accent-600 dark:hover:text-accent-500 font-medium" 652 + >bluesky</a 653 + > (or tag me when sharing your room so I can admire your creation 😊) 654 + </div> 655 + </Modal> 656 + 657 + <Modal bind:open={createRoomModalState}></Modal> 658 + 659 + <Modal bind:open={successModalState}> 660 + <Heading class="flex items-center gap-4"> 661 + <svg 662 + xmlns="http://www.w3.org/2000/svg" 663 + fill="none" 664 + viewBox="0 0 24 24" 665 + stroke-width="1.5" 666 + stroke="currentColor" 667 + class="text-accent-600 dark:text-accent-400 size-8" 668 + > 669 + <path 670 + stroke-linecap="round" 671 + stroke-linejoin="round" 672 + d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" 673 + /> 674 + </svg> 675 + 676 + Room saved to your profile</Heading 677 + > 678 + <Button 679 + size="lg" 680 + class="mt-8 py-4" 681 + href={'https://bsky.app/intent/compose?text=' + 682 + encodeURIComponent( 683 + `Check out my bluesky room: https://flo-bit.dev/room/?handle=${client.profile?.handle}` 684 + )} 685 + target="_blank" 686 + > 687 + <svg 688 + role="img" 689 + viewBox="0 0 24 24" 690 + xmlns="http://www.w3.org/2000/svg" 691 + aria-hidden="true" 692 + fill="currentColor" 693 + ><path 694 + d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z" 695 + /></svg 696 + > 697 + Share on Bluesky</Button 698 + > 699 + </Modal> 700 + 701 + <Modal bind:open={signInSuccessModalState}> 702 + <Heading class="flex items-center gap-4"> 703 + <svg 704 + role="img" 705 + viewBox="0 0 24 24" 706 + xmlns="http://www.w3.org/2000/svg" 707 + aria-hidden="true" 708 + fill="currentColor" 709 + class="text-accent-600 dark:text-accent-400 size-8" 710 + ><path 711 + d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z" 712 + /></svg 713 + > 714 + Sign in successful</Heading 715 + > 716 + 717 + <div class="text-base-800 dark:text-base-200 flex flex-col gap-2 text-sm"> 718 + <p>If you haven't already, create a room to get started.</p> 719 + 720 + <p>You can then save your room to your profile and share it on bluesky.</p> 721 + 722 + <p>Feel free to tag me when sharing your room so I can admire your creation 😊</p> 723 + 724 + <p class="mt-4"> 725 + my handle: <a 726 + href="https://bsky.app/profile/flo-bit.dev" 727 + target="_blank" 728 + class="text-accent-700 dark:text-accent-400 hover:text-accent-600 dark:hover:text-accent-500 font-medium" 729 + >@flo-bit.dev</a 730 + > 731 + </p> 732 + 733 + <Button 734 + class="mt-4" 735 + onclick={() => { 736 + tryLoadingRoomFromLocalStorage(true); 737 + 738 + editorState.isEditing = true; 739 + signInSuccessModalState = false; 740 + }} 741 + > 742 + Go to my room 743 + </Button> 744 + </div> 745 + </Modal>
+8
src/routes/client-metadata.json/+server.ts
··· 1 + import { metadata } from '$lib/oauth'; 2 + import { json } from '@sveltejs/kit'; 3 + 4 + export const prerender = true; 5 + 6 + export async function GET() { 7 + return json(metadata); 8 + }
static/env/workshop.hdr

This is a binary file and will not be displayed.

static/env/workshop.jpg

This is a binary file and will not be displayed.

static/favicon.png

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/bedBunk.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/bedDouble.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/bedSingle.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/bench.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/benchCushion.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/benchCushionLow.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/bookcaseClosed.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/cabinetBed.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/cabinetBedDrawer.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/cabinetBedDrawerTable.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/chair.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/chairCushion.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/chairDesk.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/chairModernCushion.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/chairModernFrameCushion.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/chairRounded.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/coatRackStanding.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/desk.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/deskCorner.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/doorway.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/doorwayFront.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/dryer.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenBar.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenCabinet.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenCabinetDrawer.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenFridge.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenFridgeBuiltIn.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenFridgeLarge.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenFridgeSmall.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenSink.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenStove.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/kitchenStoveElectric.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/lampRoundFloor.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/lampSquareFloor.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeChair.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeChairRelax.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeDesignChair.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeDesignSofa.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeDesignSofaCorner.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeSofa.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeSofaCorner.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeSofaLong.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/loungeSofaOttoman.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/pottedPlant.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/radio.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/rugDoormat.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/rugRectangle.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/rugRound.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/rugRounded.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/rugSquare.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/shower.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/showerRound.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/sideTable.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/sideTableDrawers.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/speaker.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/speakerSmall.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/stairs.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/stairsCorner.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/stairsOpen.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/stairsOpenSingle.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/stoolBar.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/stoolBarSquare.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/table.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableCloth.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableCoffee.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableCoffeeGlass.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableCoffeeGlassSquare.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableCoffeeSquare.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableCross.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableCrossCloth.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableGlass.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/tableRound.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/televisionModern.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/televisionVintage.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/toilet.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/toiletSquare.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/trashcan.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/washer.glb

This is a binary file and will not be displayed.

static/models/kenney-furniture-kit/washerDryerStacked.glb

This is a binary file and will not be displayed.

static/og-image.png

This is a binary file and will not be displayed.

+21
svelte.config.js
··· 1 + import adapter from '@sveltejs/adapter-static'; 2 + import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 + 4 + /** @type {import('@sveltejs/kit').Config} */ 5 + const config = { 6 + // Consult https://svelte.dev/docs/kit/integrations 7 + // for more information about preprocessors 8 + preprocess: vitePreprocess(), 9 + 10 + kit: { 11 + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 12 + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 + // See https://svelte.dev/docs/kit/adapters for more information about adapters. 14 + adapter: adapter(), 15 + paths: { 16 + base: process.env.NODE_ENV === 'development' ? '' : '/my-atproto-room' 17 + } 18 + } 19 + }; 20 + 21 + export default config;
+19
tsconfig.json
··· 1 + { 2 + "extends": "./.svelte-kit/tsconfig.json", 3 + "compilerOptions": { 4 + "allowJs": true, 5 + "checkJs": true, 6 + "esModuleInterop": true, 7 + "forceConsistentCasingInFileNames": true, 8 + "resolveJsonModule": true, 9 + "skipLibCheck": true, 10 + "sourceMap": true, 11 + "strict": true, 12 + "moduleResolution": "bundler" 13 + } 14 + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 16 + // 17 + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 + // from the referenced tsconfig.json - TypeScript does not merge them in 19 + }
+12
vite.config.ts
··· 1 + import tailwindcss from '@tailwindcss/vite'; 2 + import { sveltekit } from '@sveltejs/kit/vite'; 3 + import { defineConfig } from 'vite'; 4 + import glsl from 'vite-plugin-glsl'; 5 + 6 + export default defineConfig({ 7 + plugins: [sveltekit(), tailwindcss(), glsl()], 8 + server: { 9 + host: '127.0.0.1', 10 + port: 5179 11 + } 12 + });