[READ-ONLY] Mirror of https://github.com/flo-bit/atmo-tools. atmo.tools
0

Configure Feed

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

refactor, improve stuff, etc, etc

Florian (Mar 3, 2026, 5:39 AM +0100) ea24bf8c 745e6544

+1012 -601
+2 -1
package.json
··· 1 1 { 2 - "name": "search-bluesky-likes", 2 + "name": "atmo-tools", 3 3 "private": true, 4 4 "version": "0.0.1", 5 5 "type": "module", ··· 51 51 "dependencies": { 52 52 "@foxui/core": "^0.5.1", 53 53 "@foxui/social": "^0.5.1", 54 + "dexie": "^4.3.0", 54 55 "flexsearch": "^0.8.212", 55 56 "valibot": "^1.2.0" 56 57 },
+140 -169
pnpm-lock.yaml
··· 14 14 '@foxui/social': 15 15 specifier: ^0.5.1 16 16 version: 0.5.1(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(svelte@5.53.6)(tailwindcss@4.2.1) 17 + dexie: 18 + specifier: ^4.3.0 19 + version: 4.3.0 17 20 flexsearch: 18 21 specifier: ^0.8.212 19 22 version: 0.8.212 ··· 187 190 engines: {node: '>= 18'} 188 191 189 192 '@cloudflare/kv-asset-handler@0.4.2': 190 - resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} 193 + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==, tarball: https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz} 191 194 engines: {node: '>=18.0.0'} 192 195 193 196 '@cloudflare/unenv-preset@2.14.0': 194 - resolution: {integrity: sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg==} 197 + resolution: {integrity: sha512-XKAkWhi1nBdNsSEoNG9nkcbyvfUrSjSf+VYVPfOto3gLTZVc3F4g6RASCMh6IixBKCG2yDgZKQIHGKtjcnLnKg==, tarball: https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.14.0.tgz} 195 198 peerDependencies: 196 199 unenv: 2.0.0-rc.24 197 200 workerd: ^1.20260218.0 ··· 200 203 optional: true 201 204 202 205 '@cloudflare/workerd-darwin-64@1.20260305.0': 203 - resolution: {integrity: sha512-chhKOpymo0Eh9J3nymrauMqKGboCc4uz/j0gA1G4gioMnKsN2ZDKJ+qjRZDnCoVGy8u2C4pxlmyIfsXCAfIzhQ==} 206 + resolution: {integrity: sha512-chhKOpymo0Eh9J3nymrauMqKGboCc4uz/j0gA1G4gioMnKsN2ZDKJ+qjRZDnCoVGy8u2C4pxlmyIfsXCAfIzhQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260305.0.tgz} 204 207 engines: {node: '>=16'} 205 208 cpu: [x64] 206 209 os: [darwin] 207 210 208 211 '@cloudflare/workerd-darwin-arm64@1.20260305.0': 209 - resolution: {integrity: sha512-K9aG2OQk5bBfOP+fyGPqLcqZ9OR3ra6uwnxJ8f2mveq2A2LsCI7ZeGxQiAj75Ti80ytH/gJffZIx4Np2JtU3aQ==} 212 + resolution: {integrity: sha512-K9aG2OQk5bBfOP+fyGPqLcqZ9OR3ra6uwnxJ8f2mveq2A2LsCI7ZeGxQiAj75Ti80ytH/gJffZIx4Np2JtU3aQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260305.0.tgz} 210 213 engines: {node: '>=16'} 211 214 cpu: [arm64] 212 215 os: [darwin] 213 216 214 217 '@cloudflare/workerd-linux-64@1.20260305.0': 215 - resolution: {integrity: sha512-tt7XUoIw/cYFeGbkPkcZ6XX1aZm26Aju/4ih+DXxOosbBeGshFSrNJDBfAKKOvkjsAZymJ+WWVDBU+hmNaGfwA==} 218 + resolution: {integrity: sha512-tt7XUoIw/cYFeGbkPkcZ6XX1aZm26Aju/4ih+DXxOosbBeGshFSrNJDBfAKKOvkjsAZymJ+WWVDBU+hmNaGfwA==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260305.0.tgz} 216 219 engines: {node: '>=16'} 217 220 cpu: [x64] 218 221 os: [linux] 219 222 220 223 '@cloudflare/workerd-linux-arm64@1.20260305.0': 221 - resolution: {integrity: sha512-72QTkY5EzylmvCZ8ZTrnJ9DctmQsfSof1OKyOWqu/pv/B2yACfuPMikq8RpPxvVu7hhS0ztGP6ZvXz72Htq4Zg==} 224 + resolution: {integrity: sha512-72QTkY5EzylmvCZ8ZTrnJ9DctmQsfSof1OKyOWqu/pv/B2yACfuPMikq8RpPxvVu7hhS0ztGP6ZvXz72Htq4Zg==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260305.0.tgz} 222 225 engines: {node: '>=16'} 223 226 cpu: [arm64] 224 227 os: [linux] 225 228 226 229 '@cloudflare/workerd-windows-64@1.20260305.0': 227 - resolution: {integrity: sha512-BA0uaQPOaI2F6mJtBDqplGnQQhpXCzwEMI33p/TnDxtSk9u8CGIfBFuI6uqo8mJ6ijIaPjeBLGOn2CiRMET4qg==} 230 + resolution: {integrity: sha512-BA0uaQPOaI2F6mJtBDqplGnQQhpXCzwEMI33p/TnDxtSk9u8CGIfBFuI6uqo8mJ6ijIaPjeBLGOn2CiRMET4qg==, tarball: https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260305.0.tgz} 228 231 engines: {node: '>=16'} 229 232 cpu: [x64] 230 233 os: [win32] ··· 233 236 resolution: {integrity: sha512-klKnECMb5A4GtVF0P5NH6rCjtyjqIEKJaz6kEtx9YPHhfFO2HUEarO+MI4F8WPchgeZqpGlEpDhRapzrOTw51Q==} 234 237 235 238 '@cspotcode/source-map-support@0.8.1': 236 - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 239 + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==, tarball: https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz} 237 240 engines: {node: '>=12'} 238 241 239 242 '@emnapi/runtime@1.8.1': 240 - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} 243 + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, tarball: https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz} 241 244 242 245 '@esbuild/aix-ppc64@0.27.3': 243 - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} 246 + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz} 244 247 engines: {node: '>=18'} 245 248 cpu: [ppc64] 246 249 os: [aix] 247 250 248 251 '@esbuild/android-arm64@0.27.3': 249 - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} 252 + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz} 250 253 engines: {node: '>=18'} 251 254 cpu: [arm64] 252 255 os: [android] 253 256 254 257 '@esbuild/android-arm@0.27.3': 255 - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} 258 + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz} 256 259 engines: {node: '>=18'} 257 260 cpu: [arm] 258 261 os: [android] 259 262 260 263 '@esbuild/android-x64@0.27.3': 261 - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} 264 + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz} 262 265 engines: {node: '>=18'} 263 266 cpu: [x64] 264 267 os: [android] 265 268 266 269 '@esbuild/darwin-arm64@0.27.3': 267 - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} 270 + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz} 268 271 engines: {node: '>=18'} 269 272 cpu: [arm64] 270 273 os: [darwin] 271 274 272 275 '@esbuild/darwin-x64@0.27.3': 273 - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} 276 + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz} 274 277 engines: {node: '>=18'} 275 278 cpu: [x64] 276 279 os: [darwin] 277 280 278 281 '@esbuild/freebsd-arm64@0.27.3': 279 - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} 282 + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz} 280 283 engines: {node: '>=18'} 281 284 cpu: [arm64] 282 285 os: [freebsd] 283 286 284 287 '@esbuild/freebsd-x64@0.27.3': 285 - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} 288 + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz} 286 289 engines: {node: '>=18'} 287 290 cpu: [x64] 288 291 os: [freebsd] 289 292 290 293 '@esbuild/linux-arm64@0.27.3': 291 - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} 294 + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz} 292 295 engines: {node: '>=18'} 293 296 cpu: [arm64] 294 297 os: [linux] 295 298 296 299 '@esbuild/linux-arm@0.27.3': 297 - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} 300 + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz} 298 301 engines: {node: '>=18'} 299 302 cpu: [arm] 300 303 os: [linux] 301 304 302 305 '@esbuild/linux-ia32@0.27.3': 303 - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} 306 + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz} 304 307 engines: {node: '>=18'} 305 308 cpu: [ia32] 306 309 os: [linux] 307 310 308 311 '@esbuild/linux-loong64@0.27.3': 309 - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} 312 + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz} 310 313 engines: {node: '>=18'} 311 314 cpu: [loong64] 312 315 os: [linux] 313 316 314 317 '@esbuild/linux-mips64el@0.27.3': 315 - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} 318 + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz} 316 319 engines: {node: '>=18'} 317 320 cpu: [mips64el] 318 321 os: [linux] 319 322 320 323 '@esbuild/linux-ppc64@0.27.3': 321 - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} 324 + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz} 322 325 engines: {node: '>=18'} 323 326 cpu: [ppc64] 324 327 os: [linux] 325 328 326 329 '@esbuild/linux-riscv64@0.27.3': 327 - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} 330 + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz} 328 331 engines: {node: '>=18'} 329 332 cpu: [riscv64] 330 333 os: [linux] 331 334 332 335 '@esbuild/linux-s390x@0.27.3': 333 - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} 336 + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz} 334 337 engines: {node: '>=18'} 335 338 cpu: [s390x] 336 339 os: [linux] 337 340 338 341 '@esbuild/linux-x64@0.27.3': 339 - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} 342 + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz} 340 343 engines: {node: '>=18'} 341 344 cpu: [x64] 342 345 os: [linux] 343 346 344 347 '@esbuild/netbsd-arm64@0.27.3': 345 - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} 348 + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz} 346 349 engines: {node: '>=18'} 347 350 cpu: [arm64] 348 351 os: [netbsd] 349 352 350 353 '@esbuild/netbsd-x64@0.27.3': 351 - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} 354 + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz} 352 355 engines: {node: '>=18'} 353 356 cpu: [x64] 354 357 os: [netbsd] 355 358 356 359 '@esbuild/openbsd-arm64@0.27.3': 357 - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} 360 + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz} 358 361 engines: {node: '>=18'} 359 362 cpu: [arm64] 360 363 os: [openbsd] 361 364 362 365 '@esbuild/openbsd-x64@0.27.3': 363 - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} 366 + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz} 364 367 engines: {node: '>=18'} 365 368 cpu: [x64] 366 369 os: [openbsd] 367 370 368 371 '@esbuild/openharmony-arm64@0.27.3': 369 - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} 372 + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==, tarball: https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz} 370 373 engines: {node: '>=18'} 371 374 cpu: [arm64] 372 375 os: [openharmony] 373 376 374 377 '@esbuild/sunos-x64@0.27.3': 375 - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} 378 + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz} 376 379 engines: {node: '>=18'} 377 380 cpu: [x64] 378 381 os: [sunos] 379 382 380 383 '@esbuild/win32-arm64@0.27.3': 381 - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} 384 + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz} 382 385 engines: {node: '>=18'} 383 386 cpu: [arm64] 384 387 os: [win32] 385 388 386 389 '@esbuild/win32-ia32@0.27.3': 387 - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} 390 + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz} 388 391 engines: {node: '>=18'} 389 392 cpu: [ia32] 390 393 os: [win32] 391 394 392 395 '@esbuild/win32-x64@0.27.3': 393 - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} 396 + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz} 394 397 engines: {node: '>=18'} 395 398 cpu: [x64] 396 399 os: [win32] ··· 487 490 engines: {node: '>=18.18'} 488 491 489 492 '@img/colour@1.1.0': 490 - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} 493 + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==, tarball: https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz} 491 494 engines: {node: '>=18'} 492 495 493 496 '@img/sharp-darwin-arm64@0.34.5': 494 - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} 497 + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==, tarball: https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz} 495 498 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 496 499 cpu: [arm64] 497 500 os: [darwin] 498 501 499 502 '@img/sharp-darwin-x64@0.34.5': 500 - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} 503 + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==, tarball: https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz} 501 504 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 502 505 cpu: [x64] 503 506 os: [darwin] 504 507 505 508 '@img/sharp-libvips-darwin-arm64@1.2.4': 506 - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} 509 + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==, tarball: https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz} 507 510 cpu: [arm64] 508 511 os: [darwin] 509 512 510 513 '@img/sharp-libvips-darwin-x64@1.2.4': 511 - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} 514 + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==, tarball: https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz} 512 515 cpu: [x64] 513 516 os: [darwin] 514 517 515 518 '@img/sharp-libvips-linux-arm64@1.2.4': 516 - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} 519 + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz} 517 520 cpu: [arm64] 518 521 os: [linux] 519 - libc: [glibc] 520 522 521 523 '@img/sharp-libvips-linux-arm@1.2.4': 522 - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} 524 + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz} 523 525 cpu: [arm] 524 526 os: [linux] 525 - libc: [glibc] 526 527 527 528 '@img/sharp-libvips-linux-ppc64@1.2.4': 528 - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} 529 + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz} 529 530 cpu: [ppc64] 530 531 os: [linux] 531 - libc: [glibc] 532 532 533 533 '@img/sharp-libvips-linux-riscv64@1.2.4': 534 - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} 534 + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz} 535 535 cpu: [riscv64] 536 536 os: [linux] 537 - libc: [glibc] 538 537 539 538 '@img/sharp-libvips-linux-s390x@1.2.4': 540 - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} 539 + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz} 541 540 cpu: [s390x] 542 541 os: [linux] 543 - libc: [glibc] 544 542 545 543 '@img/sharp-libvips-linux-x64@1.2.4': 546 - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} 544 + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz} 547 545 cpu: [x64] 548 546 os: [linux] 549 - libc: [glibc] 550 547 551 548 '@img/sharp-libvips-linuxmusl-arm64@1.2.4': 552 - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} 549 + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz} 553 550 cpu: [arm64] 554 551 os: [linux] 555 - libc: [musl] 556 552 557 553 '@img/sharp-libvips-linuxmusl-x64@1.2.4': 558 - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} 554 + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz} 559 555 cpu: [x64] 560 556 os: [linux] 561 - libc: [musl] 562 557 563 558 '@img/sharp-linux-arm64@0.34.5': 564 - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} 559 + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==, tarball: https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz} 565 560 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 566 561 cpu: [arm64] 567 562 os: [linux] 568 - libc: [glibc] 569 563 570 564 '@img/sharp-linux-arm@0.34.5': 571 - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} 565 + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==, tarball: https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz} 572 566 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 573 567 cpu: [arm] 574 568 os: [linux] 575 - libc: [glibc] 576 569 577 570 '@img/sharp-linux-ppc64@0.34.5': 578 - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} 571 + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==, tarball: https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz} 579 572 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 580 573 cpu: [ppc64] 581 574 os: [linux] 582 - libc: [glibc] 583 575 584 576 '@img/sharp-linux-riscv64@0.34.5': 585 - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} 577 + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==, tarball: https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz} 586 578 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 587 579 cpu: [riscv64] 588 580 os: [linux] 589 - libc: [glibc] 590 581 591 582 '@img/sharp-linux-s390x@0.34.5': 592 - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} 583 + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==, tarball: https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz} 593 584 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 594 585 cpu: [s390x] 595 586 os: [linux] 596 - libc: [glibc] 597 587 598 588 '@img/sharp-linux-x64@0.34.5': 599 - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} 589 + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==, tarball: https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz} 600 590 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 601 591 cpu: [x64] 602 592 os: [linux] 603 - libc: [glibc] 604 593 605 594 '@img/sharp-linuxmusl-arm64@0.34.5': 606 - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} 595 + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==, tarball: https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz} 607 596 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 608 597 cpu: [arm64] 609 598 os: [linux] 610 - libc: [musl] 611 599 612 600 '@img/sharp-linuxmusl-x64@0.34.5': 613 - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} 601 + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==, tarball: https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz} 614 602 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 615 603 cpu: [x64] 616 604 os: [linux] 617 - libc: [musl] 618 605 619 606 '@img/sharp-wasm32@0.34.5': 620 - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} 607 + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==, tarball: https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz} 621 608 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 622 609 cpu: [wasm32] 623 610 624 611 '@img/sharp-win32-arm64@0.34.5': 625 - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} 612 + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==, tarball: https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz} 626 613 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 627 614 cpu: [arm64] 628 615 os: [win32] 629 616 630 617 '@img/sharp-win32-ia32@0.34.5': 631 - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} 618 + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==, tarball: https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz} 632 619 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 633 620 cpu: [ia32] 634 621 os: [win32] 635 622 636 623 '@img/sharp-win32-x64@0.34.5': 637 - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} 624 + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==, tarball: https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz} 638 625 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 639 626 cpu: [x64] 640 627 os: [win32] 641 628 642 629 '@internationalized/date@3.11.0': 643 - resolution: {integrity: sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==} 630 + resolution: {integrity: sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==, tarball: https://registry.npmjs.org/@internationalized/date/-/date-3.11.0.tgz} 644 631 645 632 '@jridgewell/gen-mapping@0.3.13': 646 633 resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} ··· 659 646 resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 660 647 661 648 '@jridgewell/trace-mapping@0.3.9': 662 - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 649 + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==, tarball: https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz} 663 650 664 651 '@number-flow/svelte@0.3.13': 665 652 resolution: {integrity: sha512-mvbxDeSFa1o/E4vGhrWuawAFCgcn5qTQ/s++FIoD88es5+JQa/aMQUypTy7qXIreTtTvncpIbkKdw9DMnweaSw==} ··· 670 657 resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} 671 658 672 659 '@poppinss/colors@4.1.6': 673 - resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} 660 + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==, tarball: https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz} 674 661 675 662 '@poppinss/dumper@0.6.5': 676 - resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} 663 + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==, tarball: https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz} 677 664 678 665 '@poppinss/exception@1.2.3': 679 - resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} 666 + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==, tarball: https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz} 680 667 681 668 '@rollup/rollup-android-arm-eabi@4.59.0': 682 - resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} 669 + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz} 683 670 cpu: [arm] 684 671 os: [android] 685 672 686 673 '@rollup/rollup-android-arm64@4.59.0': 687 - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} 674 + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz} 688 675 cpu: [arm64] 689 676 os: [android] 690 677 691 678 '@rollup/rollup-darwin-arm64@4.59.0': 692 - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} 679 + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz} 693 680 cpu: [arm64] 694 681 os: [darwin] 695 682 696 683 '@rollup/rollup-darwin-x64@4.59.0': 697 - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} 684 + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz} 698 685 cpu: [x64] 699 686 os: [darwin] 700 687 701 688 '@rollup/rollup-freebsd-arm64@4.59.0': 702 - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} 689 + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz} 703 690 cpu: [arm64] 704 691 os: [freebsd] 705 692 706 693 '@rollup/rollup-freebsd-x64@4.59.0': 707 - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} 694 + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz} 708 695 cpu: [x64] 709 696 os: [freebsd] 710 697 711 698 '@rollup/rollup-linux-arm-gnueabihf@4.59.0': 712 - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} 699 + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz} 713 700 cpu: [arm] 714 701 os: [linux] 715 - libc: [glibc] 716 702 717 703 '@rollup/rollup-linux-arm-musleabihf@4.59.0': 718 - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} 704 + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz} 719 705 cpu: [arm] 720 706 os: [linux] 721 - libc: [musl] 722 707 723 708 '@rollup/rollup-linux-arm64-gnu@4.59.0': 724 - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} 709 + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz} 725 710 cpu: [arm64] 726 711 os: [linux] 727 - libc: [glibc] 728 712 729 713 '@rollup/rollup-linux-arm64-musl@4.59.0': 730 - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} 714 + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz} 731 715 cpu: [arm64] 732 716 os: [linux] 733 - libc: [musl] 734 717 735 718 '@rollup/rollup-linux-loong64-gnu@4.59.0': 736 - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} 719 + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz} 737 720 cpu: [loong64] 738 721 os: [linux] 739 - libc: [glibc] 740 722 741 723 '@rollup/rollup-linux-loong64-musl@4.59.0': 742 - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} 724 + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz} 743 725 cpu: [loong64] 744 726 os: [linux] 745 - libc: [musl] 746 727 747 728 '@rollup/rollup-linux-ppc64-gnu@4.59.0': 748 - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} 729 + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz} 749 730 cpu: [ppc64] 750 731 os: [linux] 751 - libc: [glibc] 752 732 753 733 '@rollup/rollup-linux-ppc64-musl@4.59.0': 754 - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} 734 + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz} 755 735 cpu: [ppc64] 756 736 os: [linux] 757 - libc: [musl] 758 737 759 738 '@rollup/rollup-linux-riscv64-gnu@4.59.0': 760 - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} 739 + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz} 761 740 cpu: [riscv64] 762 741 os: [linux] 763 - libc: [glibc] 764 742 765 743 '@rollup/rollup-linux-riscv64-musl@4.59.0': 766 - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} 744 + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz} 767 745 cpu: [riscv64] 768 746 os: [linux] 769 - libc: [musl] 770 747 771 748 '@rollup/rollup-linux-s390x-gnu@4.59.0': 772 - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} 749 + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz} 773 750 cpu: [s390x] 774 751 os: [linux] 775 - libc: [glibc] 776 752 777 753 '@rollup/rollup-linux-x64-gnu@4.59.0': 778 - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} 754 + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz} 779 755 cpu: [x64] 780 756 os: [linux] 781 - libc: [glibc] 782 757 783 758 '@rollup/rollup-linux-x64-musl@4.59.0': 784 - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} 759 + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz} 785 760 cpu: [x64] 786 761 os: [linux] 787 - libc: [musl] 788 762 789 763 '@rollup/rollup-openbsd-x64@4.59.0': 790 - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} 764 + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==, tarball: https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz} 791 765 cpu: [x64] 792 766 os: [openbsd] 793 767 794 768 '@rollup/rollup-openharmony-arm64@4.59.0': 795 - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} 769 + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==, tarball: https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz} 796 770 cpu: [arm64] 797 771 os: [openharmony] 798 772 799 773 '@rollup/rollup-win32-arm64-msvc@4.59.0': 800 - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} 774 + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz} 801 775 cpu: [arm64] 802 776 os: [win32] 803 777 804 778 '@rollup/rollup-win32-ia32-msvc@4.59.0': 805 - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} 779 + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz} 806 780 cpu: [ia32] 807 781 os: [win32] 808 782 809 783 '@rollup/rollup-win32-x64-gnu@4.59.0': 810 - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} 784 + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz} 811 785 cpu: [x64] 812 786 os: [win32] 813 787 814 788 '@rollup/rollup-win32-x64-msvc@4.59.0': 815 - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} 789 + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz} 816 790 cpu: [x64] 817 791 os: [win32] 818 792 819 793 '@sindresorhus/is@7.2.0': 820 - resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} 794 + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==, tarball: https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz} 821 795 engines: {node: '>=18'} 822 796 823 797 '@speed-highlight/core@1.2.14': 824 - resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} 798 + resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==, tarball: https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz} 825 799 826 800 '@standard-schema/spec@1.1.0': 827 801 resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} ··· 869 843 vite: ^6.3.0 || ^7.0.0 870 844 871 845 '@swc/helpers@0.5.19': 872 - resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} 846 + resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==, tarball: https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz} 873 847 874 848 '@tailwindcss/forms@0.5.11': 875 849 resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==} ··· 880 854 resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} 881 855 882 856 '@tailwindcss/oxide-android-arm64@4.2.1': 883 - resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} 857 + resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz} 884 858 engines: {node: '>= 20'} 885 859 cpu: [arm64] 886 860 os: [android] 887 861 888 862 '@tailwindcss/oxide-darwin-arm64@4.2.1': 889 - resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} 863 + resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz} 890 864 engines: {node: '>= 20'} 891 865 cpu: [arm64] 892 866 os: [darwin] 893 867 894 868 '@tailwindcss/oxide-darwin-x64@4.2.1': 895 - resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} 869 + resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz} 896 870 engines: {node: '>= 20'} 897 871 cpu: [x64] 898 872 os: [darwin] 899 873 900 874 '@tailwindcss/oxide-freebsd-x64@4.2.1': 901 - resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} 875 + resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz} 902 876 engines: {node: '>= 20'} 903 877 cpu: [x64] 904 878 os: [freebsd] 905 879 906 880 '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': 907 - resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} 881 + resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz} 908 882 engines: {node: '>= 20'} 909 883 cpu: [arm] 910 884 os: [linux] 911 885 912 886 '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': 913 - resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} 887 + resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz} 914 888 engines: {node: '>= 20'} 915 889 cpu: [arm64] 916 890 os: [linux] 917 - libc: [glibc] 918 891 919 892 '@tailwindcss/oxide-linux-arm64-musl@4.2.1': 920 - resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} 893 + resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz} 921 894 engines: {node: '>= 20'} 922 895 cpu: [arm64] 923 896 os: [linux] 924 - libc: [musl] 925 897 926 898 '@tailwindcss/oxide-linux-x64-gnu@4.2.1': 927 - resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} 899 + resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz} 928 900 engines: {node: '>= 20'} 929 901 cpu: [x64] 930 902 os: [linux] 931 - libc: [glibc] 932 903 933 904 '@tailwindcss/oxide-linux-x64-musl@4.2.1': 934 - resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} 905 + resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz} 935 906 engines: {node: '>= 20'} 936 907 cpu: [x64] 937 908 os: [linux] 938 - libc: [musl] 939 909 940 910 '@tailwindcss/oxide-wasm32-wasi@4.2.1': 941 - resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} 911 + resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz} 942 912 engines: {node: '>=14.0.0'} 943 913 cpu: [wasm32] 944 914 bundledDependencies: ··· 950 920 - tslib 951 921 952 922 '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': 953 - resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} 923 + resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz} 954 924 engines: {node: '>= 20'} 955 925 cpu: [arm64] 956 926 os: [win32] 957 927 958 928 '@tailwindcss/oxide-win32-x64-msvc@4.2.1': 959 - resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} 929 + resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==, tarball: https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz} 960 930 engines: {node: '>= 20'} 961 931 cpu: [x64] 962 932 os: [win32] ··· 1092 1062 svelte: ^5.33.0 1093 1063 1094 1064 blake3-wasm@2.1.5: 1095 - resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} 1065 + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==, tarball: https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz} 1096 1066 1097 1067 brace-expansion@5.0.4: 1098 1068 resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} ··· 1114 1084 engines: {node: '>= 0.6'} 1115 1085 1116 1086 cookie@1.1.1: 1117 - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} 1087 + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==, tarball: https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz} 1118 1088 engines: {node: '>=18'} 1119 1089 1120 1090 core-js@3.48.0: ··· 1159 1129 devalue@5.6.3: 1160 1130 resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} 1161 1131 1132 + dexie@4.3.0: 1133 + resolution: {integrity: sha512-5EeoQpJvMKHe6zWt/FSIIuRa3CWlZeIl6zKXt+Lz7BU6RoRRLgX9dZEynRfXrkLcldKYCBiz7xekTEylnie1Ug==, tarball: https://registry.npmjs.org/dexie/-/dexie-4.3.0.tgz} 1134 + 1162 1135 emoji-picker-element@1.29.1: 1163 1136 resolution: {integrity: sha512-TOiHzu9Dqib3x4MwcAi3wi3RdyT4SoeB4b15AvH1ks4SBwTl7DeebhZ0d3x6dNi4XfNU7IGRZ7NBQllj0RqwrQ==} 1164 1137 ··· 1167 1140 engines: {node: '>=10.13.0'} 1168 1141 1169 1142 error-stack-parser-es@1.0.5: 1170 - resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} 1143 + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==, tarball: https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz} 1171 1144 1172 1145 esbuild@0.27.3: 1173 1146 resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} ··· 1291 1264 resolution: {integrity: sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw==} 1292 1265 1293 1266 fsevents@2.3.3: 1294 - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 1267 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} 1295 1268 engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1296 1269 os: [darwin] 1297 1270 ··· 1379 1352 engines: {node: '>= 0.8.0'} 1380 1353 1381 1354 lightningcss-android-arm64@1.31.1: 1382 - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} 1355 + resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==, tarball: https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz} 1383 1356 engines: {node: '>= 12.0.0'} 1384 1357 cpu: [arm64] 1385 1358 os: [android] 1386 1359 1387 1360 lightningcss-darwin-arm64@1.31.1: 1388 - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} 1361 + resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==, tarball: https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz} 1389 1362 engines: {node: '>= 12.0.0'} 1390 1363 cpu: [arm64] 1391 1364 os: [darwin] 1392 1365 1393 1366 lightningcss-darwin-x64@1.31.1: 1394 - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} 1367 + resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==, tarball: https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz} 1395 1368 engines: {node: '>= 12.0.0'} 1396 1369 cpu: [x64] 1397 1370 os: [darwin] 1398 1371 1399 1372 lightningcss-freebsd-x64@1.31.1: 1400 - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} 1373 + resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==, tarball: https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz} 1401 1374 engines: {node: '>= 12.0.0'} 1402 1375 cpu: [x64] 1403 1376 os: [freebsd] 1404 1377 1405 1378 lightningcss-linux-arm-gnueabihf@1.31.1: 1406 - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} 1379 + resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==, tarball: https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz} 1407 1380 engines: {node: '>= 12.0.0'} 1408 1381 cpu: [arm] 1409 1382 os: [linux] 1410 1383 1411 1384 lightningcss-linux-arm64-gnu@1.31.1: 1412 - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} 1385 + resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==, tarball: https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz} 1413 1386 engines: {node: '>= 12.0.0'} 1414 1387 cpu: [arm64] 1415 1388 os: [linux] 1416 - libc: [glibc] 1417 1389 1418 1390 lightningcss-linux-arm64-musl@1.31.1: 1419 - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} 1391 + resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==, tarball: https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz} 1420 1392 engines: {node: '>= 12.0.0'} 1421 1393 cpu: [arm64] 1422 1394 os: [linux] 1423 - libc: [musl] 1424 1395 1425 1396 lightningcss-linux-x64-gnu@1.31.1: 1426 - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} 1397 + resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==, tarball: https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz} 1427 1398 engines: {node: '>= 12.0.0'} 1428 1399 cpu: [x64] 1429 1400 os: [linux] 1430 - libc: [glibc] 1431 1401 1432 1402 lightningcss-linux-x64-musl@1.31.1: 1433 - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} 1403 + resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==, tarball: https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz} 1434 1404 engines: {node: '>= 12.0.0'} 1435 1405 cpu: [x64] 1436 1406 os: [linux] 1437 - libc: [musl] 1438 1407 1439 1408 lightningcss-win32-arm64-msvc@1.31.1: 1440 - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} 1409 + resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==, tarball: https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz} 1441 1410 engines: {node: '>= 12.0.0'} 1442 1411 cpu: [arm64] 1443 1412 os: [win32] 1444 1413 1445 1414 lightningcss-win32-x64-msvc@1.31.1: 1446 - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} 1415 + resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==, tarball: https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz} 1447 1416 engines: {node: '>= 12.0.0'} 1448 1417 cpu: [x64] 1449 1418 os: [win32] ··· 1478 1447 hasBin: true 1479 1448 1480 1449 miniflare@4.20260305.0: 1481 - resolution: {integrity: sha512-jVhtKJtiwaZa3rI+WgoLvSJmEazDsoUmAPYRUmEe2VO6VSbvkhbnDRm+dsPbYRatgNIExwrpqG1rv96jHiSb0w==} 1450 + resolution: {integrity: sha512-jVhtKJtiwaZa3rI+WgoLvSJmEazDsoUmAPYRUmEe2VO6VSbvkhbnDRm+dsPbYRatgNIExwrpqG1rv96jHiSb0w==, tarball: https://registry.npmjs.org/miniflare/-/miniflare-4.20260305.0.tgz} 1482 1451 engines: {node: '>=18.0.0'} 1483 1452 hasBin: true 1484 1453 ··· 1549 1518 engines: {node: '>=8'} 1550 1519 1551 1520 path-to-regexp@6.3.0: 1552 - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} 1521 + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==, tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz} 1553 1522 1554 1523 pathe@2.0.3: 1555 - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1524 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, tarball: https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz} 1556 1525 1557 1526 picocolors@1.1.1: 1558 1527 resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} ··· 1726 1695 resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} 1727 1696 1728 1697 sharp@0.34.5: 1729 - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} 1698 + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==, tarball: https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz} 1730 1699 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1731 1700 1732 1701 shebang-command@2.0.0: ··· 1749 1718 resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} 1750 1719 1751 1720 supports-color@10.2.2: 1752 - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} 1721 + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==, tarball: https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz} 1753 1722 engines: {node: '>=18'} 1754 1723 1755 1724 svelte-check@4.4.4: ··· 1862 1831 resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} 1863 1832 1864 1833 undici@7.18.2: 1865 - resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} 1834 + resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==, tarball: https://registry.npmjs.org/undici/-/undici-7.18.2.tgz} 1866 1835 engines: {node: '>=20.18.1'} 1867 1836 1868 1837 unenv@2.0.0-rc.24: 1869 - resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} 1838 + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==, tarball: https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz} 1870 1839 1871 1840 unicode-segmenter@0.14.5: 1872 1841 resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} ··· 1946 1915 engines: {node: '>=0.10.0'} 1947 1916 1948 1917 workerd@1.20260305.0: 1949 - resolution: {integrity: sha512-JkhfCLU+w+KbQmZ9k49IcDYc78GBo7eG8Mir8E2+KVjR7otQAmpcLlsous09YLh8WQ3Bt3Mi6/WMStvMAPukeA==} 1918 + resolution: {integrity: sha512-JkhfCLU+w+KbQmZ9k49IcDYc78GBo7eG8Mir8E2+KVjR7otQAmpcLlsous09YLh8WQ3Bt3Mi6/WMStvMAPukeA==, tarball: https://registry.npmjs.org/workerd/-/workerd-1.20260305.0.tgz} 1950 1919 engines: {node: '>=16'} 1951 1920 hasBin: true 1952 1921 ··· 1955 1924 engines: {node: '>=12'} 1956 1925 1957 1926 wrangler@4.69.0: 1958 - resolution: {integrity: sha512-EmVfIM65I5b4ITHe3Y9R7zQyf4NUBQ1leStakMlWiVR9n6VlDwuEltyQI2l3i0JciDnWyR3uqe+T6C08ivniTQ==} 1927 + resolution: {integrity: sha512-EmVfIM65I5b4ITHe3Y9R7zQyf4NUBQ1leStakMlWiVR9n6VlDwuEltyQI2l3i0JciDnWyR3uqe+T6C08ivniTQ==, tarball: https://registry.npmjs.org/wrangler/-/wrangler-4.69.0.tgz} 1959 1928 engines: {node: '>=20.0.0'} 1960 1929 hasBin: true 1961 1930 peerDependencies: ··· 1965 1934 optional: true 1966 1935 1967 1936 ws@8.18.0: 1968 - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} 1937 + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==, tarball: https://registry.npmjs.org/ws/-/ws-8.18.0.tgz} 1969 1938 engines: {node: '>=10.0.0'} 1970 1939 peerDependencies: 1971 1940 bufferutil: ^4.0.1 ··· 1985 1954 engines: {node: '>=10'} 1986 1955 1987 1956 youch-core@0.3.3: 1988 - resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} 1957 + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==, tarball: https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz} 1989 1958 1990 1959 youch@4.1.0-beta.10: 1991 - resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} 1960 + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==, tarball: https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz} 1992 1961 1993 1962 zimmerframe@1.1.4: 1994 1963 resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} ··· 2895 2864 detect-libc@2.1.2: {} 2896 2865 2897 2866 devalue@5.6.3: {} 2867 + 2868 + dexie@4.3.0: {} 2898 2869 2899 2870 emoji-picker-element@1.29.1: {} 2900 2871
+1 -31
src/app.html
··· 6 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 7 %sveltekit.head% 8 8 9 - 10 - <script> 11 - let dark = localStorage.getItem('darkMode'); 12 - if (dark) { 13 - dark = JSON.parse(dark); 14 - } else { 15 - dark = window.matchMedia('(prefers-color-scheme: dark)').matches; 16 - } 17 - 18 - var root = document.getElementsByTagName('html')[0]; 19 - if (dark) { 20 - root.classList.add('dark'); 21 - } else { 22 - root.classList.remove('dark'); 23 - } 24 - 25 - // get accent color from local storage 26 - let accentColor = localStorage.getItem('accentColor'); 27 - if (accentColor) { 28 - accentColor = JSON.parse(accentColor); 29 - root.classList.add(accentColor); 30 - } 31 - 32 - // get base color from local storage 33 - let baseColor = localStorage.getItem('baseColor'); 34 - if (baseColor) { 35 - baseColor = JSON.parse(baseColor); 36 - root.classList.add(baseColor); 37 - } 38 - </script> 39 9 40 10 <script 41 11 is:inline ··· 45 15 data-domains="flo-bit.dev" 46 16 ></script> 47 17 </head> 48 - <body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-950"> 18 + <body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-900"> 49 19 <div style="display: contents">%sveltekit.body%</div> 50 20 </body> 51 21 </html>
+25
src/lib/atproto/server/search.remote.ts
··· 52 52 } 53 53 ); 54 54 55 + export const listRepostRecords = command( 56 + v.object({ 57 + limit: v.optional(v.pipe(v.number(), v.minValue(1), v.maxValue(100))), 58 + cursor: v.optional(v.string()) 59 + }), 60 + async (input) => { 61 + const { locals } = getRequestEvent(); 62 + if (!locals.client || !locals.did) error(401, 'Not authenticated'); 63 + 64 + const response = await locals.client.get('com.atproto.repo.listRecords', { 65 + params: { 66 + repo: locals.did, 67 + collection: 'app.bsky.feed.repost', 68 + limit: input.limit ?? 100, 69 + cursor: input.cursor, 70 + reverse: false 71 + } 72 + }); 73 + 74 + if (!response.ok) error(500, 'Failed to fetch repost records'); 75 + 76 + return response.data; 77 + } 78 + ); 79 + 55 80 export const getBookmarks = command( 56 81 v.object({ 57 82 limit: v.optional(v.pipe(v.number(), v.minValue(1), v.maxValue(100))),
+25
src/lib/db.ts
··· 1 + import Dexie, { type EntityTable } from 'dexie'; 2 + 3 + export interface StoredPost { 4 + uri: string; 5 + sources: string[]; 6 + savedAt: number; 7 + fetchedAt: number; 8 + // PostView fields stored inline 9 + [key: string]: any; 10 + } 11 + 12 + export interface SourceMeta { 13 + source: string; 14 + tailCursor?: string; // deepest point reached paginating into the past 15 + } 16 + 17 + export const db = new Dexie('atmo') as Dexie & { 18 + posts: EntityTable<StoredPost, 'uri'>; 19 + meta: EntityTable<SourceMeta, 'source'>; 20 + }; 21 + 22 + db.version(1).stores({ 23 + posts: 'uri, *sources, savedAt, fetchedAt, likeCount, repostCount, replyCount', 24 + meta: 'source' 25 + });
+388 -151
src/lib/search-state.svelte.ts
··· 2 2 import { toast } from '@foxui/core'; 3 3 import { Document, Charset, IndexedDB } from 'flexsearch'; 4 4 import { Client, simpleFetchHandler } from '@atcute/client'; 5 + import { db, type StoredPost } from '$lib/db'; 5 6 6 - export type SourceType = 'likes' | 'bookmarks' | 'posts'; 7 + export type SourceType = 'likes' | 'bookmarks' | 'posts' | 'reposts'; 8 + 9 + export type SearchFilters = { 10 + handles: string[]; 11 + minLikes: number; 12 + minReposts: number; 13 + minReplies: number; 14 + dateAfter: string; 15 + dateBefore: string; 16 + hasImage: boolean; 17 + hasLink: boolean; 18 + hasVideo: boolean; 19 + showReplies: boolean; 20 + }; 21 + 22 + export const DEFAULT_FILTERS: SearchFilters = { 23 + handles: [], 24 + minLikes: 0, 25 + minReposts: 0, 26 + minReplies: 0, 27 + dateAfter: '', 28 + dateBefore: '', 29 + hasImage: false, 30 + hasLink: false, 31 + hasVideo: false, 32 + showReplies: true 33 + }; 7 34 8 35 export const SOURCE_LABELS: Record<SourceType, string> = { 9 36 likes: 'Likes', 10 37 bookmarks: 'Bookmarks', 11 - posts: 'Posts' 38 + posts: 'Posts', 39 + reposts: 'Reposts' 12 40 }; 13 41 14 42 export const PLACEHOLDERS: Record<SourceType, string> = { 15 43 likes: 'Search liked posts', 16 44 bookmarks: 'Search bookmarks', 17 - posts: 'Search my posts' 45 + posts: 'Search my posts', 46 + reposts: 'Search reposted posts' 18 47 }; 19 48 20 - export const ALL_SOURCES: SourceType[] = ['likes', 'bookmarks', 'posts']; 49 + export const ALL_SOURCES: SourceType[] = ['likes', 'bookmarks', 'posts', 'reposts']; 21 50 22 51 type SourceState = { 23 - knownIds: Set<string>; 24 52 index: Document | null; 25 53 count: number; 26 54 indexed: number; 27 55 totalToIndex: number; 28 56 phase: 'idle' | 'fetching' | 'hydrating' | 'done'; 29 - cursor?: string; 30 57 pendingUris: string[]; 31 58 pendingIndex: number; 32 59 }; 33 60 34 61 function createSourceState(): SourceState { 35 62 return { 36 - knownIds: new Set(), 37 63 index: null, 38 64 count: 0, 39 65 indexed: 0, 40 66 totalToIndex: 0, 41 67 phase: 'idle', 42 - cursor: undefined, 43 68 pendingUris: [], 44 69 pendingIndex: 0 45 70 }; ··· 49 74 return new Document({ 50 75 document: { 51 76 id: 'uri', 52 - store: true, 77 + store: false, 53 78 index: [ 54 79 { 55 80 field: 'author:handle', ··· 66 91 tokenize: 'forward', 67 92 encoder: Charset.LatinBalance 68 93 } 69 - ], 70 - tag: [ 71 - { field: 'likeCount' }, 72 - { field: 'replyCount' }, 73 - { field: 'author:handle' }, 74 - { field: 'author:displayName' } 75 94 ] 76 95 } 77 96 }); ··· 85 104 sources: { 86 105 likes: createSourceState(), 87 106 bookmarks: createSourceState(), 88 - posts: createSourceState() 107 + posts: createSourceState(), 108 + reposts: createSourceState() 89 109 } as Record<SourceType, SourceState>, 90 - activeSource: 'likes' as SourceType, 91 - loading: true 110 + activeSource: 'likes' as SourceType 92 111 }); 93 112 94 113 let generation = 0; 95 114 96 - function loadKnownIds(source: SourceType) { 97 - const ids = localStorage.getItem(`${source}-ids`); 98 - if (ids) { 99 - searchState.sources[source].knownIds = new Set(ids.split(',')); 115 + export async function initSources() { 116 + // Clean up legacy localStorage data 117 + for (const source of ALL_SOURCES) { 118 + localStorage.removeItem(`${source}-ids`); 119 + localStorage.removeItem(`${source}-cursor`); 100 120 } 101 - searchState.sources[source].count = searchState.sources[source].knownIds.size; 102 - } 121 + 122 + await Promise.all( 123 + ALL_SOURCES.map(async (source) => { 124 + const flexDb = new IndexedDB(`${source}-idx`); 125 + searchState.sources[source].index = createIndex(); 126 + await searchState.sources[source].index!.mount(flexDb); 103 127 104 - function addKnownId(source: SourceType, id: string) { 105 - searchState.sources[source].knownIds.add(id); 106 - localStorage.setItem( 107 - `${source}-ids`, 108 - Array.from(searchState.sources[source].knownIds).join(',') 128 + // Get count from Dexie 129 + searchState.sources[source].count = await db.posts 130 + .where('sources') 131 + .equals(source) 132 + .count(); 133 + }) 109 134 ); 110 - searchState.sources[source].count = searchState.sources[source].knownIds.size; 111 - } 112 135 113 - export function clearSource(source: SourceType) { 114 - searchState.sources[source].knownIds.clear(); 115 - localStorage.removeItem(`${source}-ids`); 116 - localStorage.removeItem(`${source}-cursor`); 117 - searchState.sources[source].count = 0; 118 - searchState.sources[source].indexed = 0; 119 - searchState.sources[source].totalToIndex = 0; 120 - searchState.sources[source].phase = 'idle'; 121 - searchState.sources[source].cursor = undefined; 122 - searchState.sources[source].pendingUris = []; 123 - searchState.sources[source].pendingIndex = 0; 124 - searchState.sources[source].index?.clear(); 125 - } 126 - 127 - export async function initSources() { 128 - for (const source of ALL_SOURCES) { 129 - const db = new IndexedDB(`${source}-store`); 130 - searchState.sources[source].index = createIndex(); 131 - await searchState.sources[source].index!.mount(db); 132 - loadKnownIds(source); 133 - } 134 - 135 - searchState.loading = false; 136 - startLoading('likes'); 136 + // Start loading (active source first via loadNext) 137 + startLoading(searchState.activeSource); 137 138 } 138 139 139 140 export function switchSource(source: SourceType) { 140 141 if (source === searchState.activeSource) return; 141 142 searchState.activeSource = source; 142 - // Bump generation to cancel any in-flight loading 143 143 generation++; 144 144 if (searchState.sources[source].phase !== 'done') { 145 145 startLoading(source); ··· 174 174 } 175 175 } 176 176 177 + async function fetchRecordPage( 178 + source: SourceType, 179 + s: SourceState, 180 + fetchFn: Function, 181 + cursor: string | undefined, 182 + myGen: number 183 + ): Promise<{ cursor: string | undefined; done: boolean }> { 184 + if (myGen !== generation) return { cursor, done: true }; 185 + 186 + let data: any; 187 + try { 188 + data = await fetchFn({ limit: 100, cursor }); 189 + } catch (err) { 190 + console.error(`Failed to fetch ${source}:`, err); 191 + toast.error(`Failed to fetch ${source}`); 192 + return { cursor, done: true }; 193 + } 194 + 195 + const uris = data.records.map((r: any) => 196 + source === 'posts' ? r.uri : r.value.subject.uri 197 + ); 198 + const existingDocs = await db.posts.bulkGet(uris); 199 + const toUpdate: any[] = []; 200 + const now = Date.now(); 201 + 202 + for (let j = 0; j < uris.length; j++) { 203 + const subjectUri = uris[j]; 204 + const existing = existingDocs[j]; 205 + 206 + if (existing) { 207 + if (existing.sources.includes(source)) { 208 + // Already indexed for this source — flush updates and stop 209 + if (toUpdate.length > 0) await db.posts.bulkPut(toUpdate); 210 + return { cursor: data.cursor, done: true }; 211 + } 212 + // Post exists from another source — queue source tag update 213 + toUpdate.push({ ...existing, sources: [...existing.sources, source], fetchedAt: now }); 214 + s.index!.add(existing as any); 215 + s.count++; 216 + continue; 217 + } 218 + 219 + s.pendingUris.push(subjectUri); 220 + } 221 + 222 + if (toUpdate.length > 0) await db.posts.bulkPut(toUpdate); 223 + 224 + const nextCursor = data.records.length > 0 ? data.cursor : undefined; 225 + return { cursor: nextCursor, done: !nextCursor }; 226 + } 227 + 177 228 async function loadRecords(source: SourceType, myGen: number) { 178 229 if (!user.did || !searchState.sources[source].index) return; 179 230 180 - const { listLikeRecords, listPostRecords } = await import( 231 + const { listLikeRecords, listPostRecords, listRepostRecords } = await import( 181 232 '$lib/atproto/server/search.remote' 182 233 ); 183 - const fetchFn = source === 'likes' ? listLikeRecords : listPostRecords; 234 + const fetchFn = 235 + source === 'likes' 236 + ? listLikeRecords 237 + : source === 'reposts' 238 + ? listRepostRecords 239 + : listPostRecords; 184 240 185 241 const s = searchState.sources[source]; 186 242 187 - // Phase 1: Fetch records if not already done 188 243 if (s.phase === 'idle' || s.phase === 'fetching') { 189 244 s.phase = 'fetching'; 190 245 191 - let cursor = s.cursor; 192 - let found = false; 193 - let data: any; 246 + const meta = await db.meta.get(source); 194 247 248 + // Step 1: Fetch new posts from the top until we hit one we already have 249 + let result = { cursor: undefined as string | undefined, done: false }; 195 250 do { 196 - if (myGen !== generation) return; 251 + result = await fetchRecordPage(source, s, fetchFn, result.cursor, myGen); 252 + } while (!result.done && myGen === generation); 197 253 198 - try { 199 - data = await fetchFn({ limit: 100, cursor }); 200 - } catch (err) { 201 - console.error(`Failed to fetch ${source}:`, err); 202 - toast.error(`Failed to fetch ${source}`); 203 - return; 204 - } 254 + if (myGen !== generation) return; 205 255 206 - cursor = data.cursor; 256 + // Step 2: Continue from where we left off last time (tail) 257 + if (meta?.tailCursor) { 258 + result = { cursor: meta.tailCursor, done: false }; 259 + do { 260 + result = await fetchRecordPage(source, s, fetchFn, result.cursor, myGen); 261 + } while (!result.done && myGen === generation); 207 262 208 - for (const record of data.records) { 209 - const subjectUri = source === 'likes' ? record.value.subject.uri : record.uri; 210 - if (!s.knownIds.has(subjectUri)) { 211 - addKnownId(source, subjectUri); 212 - s.pendingUris.push(subjectUri); 213 - continue; 214 - } 263 + if (myGen !== generation) return; 264 + } 215 265 216 - found = true; 217 - cursor = localStorage.getItem(`${source}-cursor`) ?? undefined; 218 - 219 - if (cursor) { 220 - localStorage.removeItem(`${source}-cursor`); 221 - found = false; 222 - } 223 - 224 - break; 225 - } 226 - 227 - if (cursor && data.records.length > 0) { 228 - localStorage.setItem(`${source}-cursor`, cursor); 229 - s.cursor = cursor; 230 - } else { 231 - localStorage.removeItem(`${source}-cursor`); 232 - s.cursor = undefined; 233 - } 234 - } while (cursor && data.records.length > 0 && !found); 266 + // Save how far we got into the past 267 + if (result.cursor) { 268 + await db.meta.put({ source, tailCursor: result.cursor }); 269 + } else { 270 + // We've reached the very end — no tail cursor needed 271 + await db.meta.put({ source, tailCursor: undefined }); 272 + } 235 273 236 274 s.totalToIndex = s.pendingUris.length; 237 275 s.phase = 'hydrating'; 238 276 } 239 277 240 - // Phase 2: Hydrate with getPosts 241 278 if (s.phase === 'hydrating') { 242 279 await hydrateUris(source, myGen); 243 280 } ··· 269 306 ) 270 307 ); 271 308 309 + // Collect all posts from this chunk 310 + const allPosts: any[] = []; 272 311 for (const result of results) { 273 312 if (result?.ok) { 274 - for (const post of result.data.posts) { 275 - s.index!.add(post as any); 276 - s.indexed++; 313 + allPosts.push(...result.data.posts); 314 + } 315 + } 316 + 317 + if (allPosts.length > 0) { 318 + // Batch-check which already exist in Dexie 319 + const uris = allPosts.map((p) => p.uri); 320 + const existing = await db.posts.bulkGet(uris); 321 + const existingMap = new Map<string, StoredPost>(); 322 + for (const doc of existing) { 323 + if (doc) existingMap.set(doc.uri, doc); 324 + } 325 + 326 + // Build batch for bulkPut 327 + const now = Date.now(); 328 + const toPut: any[] = []; 329 + for (const post of allPosts) { 330 + const ex = existingMap.get(post.uri); 331 + if (ex) { 332 + toPut.push({ ...post, sources: [...ex.sources, source], savedAt: ex.savedAt, fetchedAt: now }); 333 + } else { 334 + toPut.push({ ...post, sources: [source], savedAt: now, fetchedAt: now }); 277 335 } 336 + s.index!.add(post as any); 337 + s.indexed++; 338 + s.count++; 278 339 } 340 + 341 + await db.posts.bulkPut(toPut); 279 342 } 280 343 281 344 s.pendingIndex += chunk.reduce((sum, b) => sum + b.length, 0); ··· 286 349 loadNext(myGen); 287 350 } 288 351 352 + async function fetchBookmarkPage( 353 + source: SourceType, 354 + s: SourceState, 355 + getBookmarks: Function, 356 + cursor: string | undefined, 357 + myGen: number 358 + ): Promise<{ cursor: string | undefined; done: boolean }> { 359 + if (myGen !== generation) return { cursor, done: true }; 360 + 361 + let data: any; 362 + try { 363 + data = await getBookmarks({ limit: 100, cursor }); 364 + } catch (err: any) { 365 + console.error('Failed to fetch bookmarks:', err); 366 + toast.error(`Failed to fetch bookmarks: ${err?.body?.message ?? err?.message ?? err}`); 367 + return { cursor, done: true }; 368 + } 369 + 370 + const validBookmarks = data.bookmarks.filter((b: any) => b.item?.uri); 371 + const uris = validBookmarks.map((b: any) => b.item.uri); 372 + const existingDocs = await db.posts.bulkGet(uris); 373 + const toPut: any[] = []; 374 + const now = Date.now(); 375 + 376 + for (let j = 0; j < validBookmarks.length; j++) { 377 + const bookmark = validBookmarks[j]; 378 + const postUri = uris[j]; 379 + const existing = existingDocs[j]; 380 + 381 + if (existing) { 382 + if (existing.sources.includes(source)) { 383 + // Already indexed for this source — flush and stop 384 + if (toPut.length > 0) await db.posts.bulkPut(toPut); 385 + return { cursor: data.cursor, done: true }; 386 + } 387 + // Exists from another source — queue tag update 388 + toPut.push({ ...existing, sources: [...existing.sources, source], fetchedAt: now }); 389 + s.index!.add(existing as any); 390 + s.count++; 391 + continue; 392 + } 393 + 394 + // Bookmarks come with full PostView — queue for batch write 395 + toPut.push({ ...bookmark.item, sources: [source], savedAt: now, fetchedAt: now }); 396 + s.index!.add(bookmark.item as any); 397 + s.indexed++; 398 + s.count++; 399 + } 400 + 401 + if (toPut.length > 0) await db.posts.bulkPut(toPut); 402 + 403 + const nextCursor = data.bookmarks.length > 0 ? data.cursor : undefined; 404 + return { cursor: nextCursor, done: !nextCursor }; 405 + } 406 + 289 407 async function loadBookmarks(source: SourceType, myGen: number) { 290 408 if (!user.did || !searchState.sources[source].index) return; 291 409 ··· 294 412 const s = searchState.sources[source]; 295 413 s.phase = 'fetching'; 296 414 297 - let cursor = s.cursor; 298 - let found = false; 299 - let data: any; 415 + const meta = await db.meta.get(source); 300 416 417 + // Step 1: Fetch new bookmarks from the top until we hit one we already have 418 + let result = { cursor: undefined as string | undefined, done: false }; 301 419 do { 420 + result = await fetchBookmarkPage(source, s, getBookmarks, result.cursor, myGen); 421 + await s.index!.commit(); 422 + } while (!result.done && myGen === generation); 423 + 424 + if (myGen !== generation) return; 425 + 426 + // Step 2: Continue from where we left off last time (tail) 427 + if (meta?.tailCursor) { 428 + result = { cursor: meta.tailCursor, done: false }; 429 + do { 430 + result = await fetchBookmarkPage(source, s, getBookmarks, result.cursor, myGen); 431 + await s.index!.commit(); 432 + } while (!result.done && myGen === generation); 433 + 302 434 if (myGen !== generation) return; 435 + } 436 + 437 + // Save how far we got into the past 438 + if (result.cursor) { 439 + await db.meta.put({ source, tailCursor: result.cursor }); 440 + } else { 441 + await db.meta.put({ source, tailCursor: undefined }); 442 + } 443 + 444 + await s.index!.commit(); 445 + s.phase = 'done'; 446 + loadNext(myGen); 447 + } 448 + 449 + // --- Filter helpers --- 303 450 304 - try { 305 - data = await getBookmarks({ limit: 100, cursor }); 306 - } catch (err: any) { 307 - console.error('Failed to fetch bookmarks:', err); 308 - toast.error(`Failed to fetch bookmarks: ${err?.body?.message ?? err?.message ?? err}`); 309 - return; 310 - } 451 + function hasEmbedType(doc: any, type: string): boolean { 452 + const embed = doc.embed; 453 + if (!embed) return false; 454 + if (embed.$type === type) return true; 455 + if (embed.$type === 'app.bsky.embed.recordWithMedia#view') { 456 + return embed.media?.$type === type; 457 + } 458 + return false; 459 + } 460 + 461 + function hasLinkInFacets(doc: any): boolean { 462 + const facets = doc.record?.facets; 463 + if (!Array.isArray(facets)) return false; 464 + return facets.some( 465 + (f: any) => f.features?.some((feat: any) => feat.$type === 'app.bsky.richtext.facet#link') 466 + ); 467 + } 468 + 469 + function applyFilters(results: any[], filters: SearchFilters): any[] { 470 + let r = results; 471 + if (filters.handles.length > 0) { 472 + r = r.filter((item) => { 473 + const h = item.doc.author?.handle?.toLowerCase(); 474 + return h && filters.handles.some((fh) => h.includes(fh.toLowerCase())); 475 + }); 476 + } 477 + if (filters.minLikes > 0) { 478 + r = r.filter((item) => (item.doc.likeCount ?? 0) >= filters.minLikes); 479 + } 480 + if (filters.minReposts > 0) { 481 + r = r.filter((item) => (item.doc.repostCount ?? 0) >= filters.minReposts); 482 + } 483 + if (filters.minReplies > 0) { 484 + r = r.filter((item) => (item.doc.replyCount ?? 0) >= filters.minReplies); 485 + } 486 + if (filters.dateAfter) { 487 + const after = new Date(filters.dateAfter).getTime(); 488 + r = r.filter((item) => { 489 + const created = item.doc.record?.createdAt; 490 + return created && new Date(created).getTime() >= after; 491 + }); 492 + } 493 + if (filters.dateBefore) { 494 + const before = new Date(filters.dateBefore).getTime() + 86400000; 495 + r = r.filter((item) => { 496 + const created = item.doc.record?.createdAt; 497 + return created && new Date(created).getTime() <= before; 498 + }); 499 + } 500 + if (filters.hasImage) { 501 + r = r.filter((item) => hasEmbedType(item.doc, 'app.bsky.embed.images#view')); 502 + } 503 + if (filters.hasLink) { 504 + r = r.filter( 505 + (item) => 506 + hasEmbedType(item.doc, 'app.bsky.embed.external#view') || hasLinkInFacets(item.doc) 507 + ); 508 + } 509 + if (filters.hasVideo) { 510 + r = r.filter((item) => hasEmbedType(item.doc, 'app.bsky.embed.video#view')); 511 + } 512 + if (!filters.showReplies) { 513 + r = r.filter((item) => !item.doc.record?.reply); 514 + } 515 + return r; 516 + } 311 517 312 - cursor = data.cursor; 518 + export function filtersActive(filters: SearchFilters): boolean { 519 + return ( 520 + filters.handles.length > 0 || 521 + filters.minLikes > 0 || 522 + filters.minReposts > 0 || 523 + filters.minReplies > 0 || 524 + !!filters.dateAfter || 525 + !!filters.dateBefore || 526 + filters.hasImage || 527 + filters.hasLink || 528 + filters.hasVideo || 529 + !filters.showReplies 530 + ); 531 + } 313 532 314 - for (const bookmark of data.bookmarks) { 315 - // item can be PostView, BlockedPost, or NotFoundPost — only index PostView 316 - if (!bookmark.item?.uri) continue; 533 + function sortByDate(items: any[]): any[] { 534 + return items.sort((a, b) => { 535 + const dateA = a.doc.record?.createdAt ? new Date(a.doc.record.createdAt).getTime() : 0; 536 + const dateB = b.doc.record?.createdAt ? new Date(b.doc.record.createdAt).getTime() : 0; 537 + return dateB - dateA; 538 + }); 539 + } 317 540 318 - const postUri = bookmark.item.uri; 319 - if (!s.knownIds.has(postUri)) { 320 - addKnownId(source, postUri); 321 - s.index!.add(bookmark.item as any); 322 - s.indexed++; 323 - s.count = s.knownIds.size; 324 - continue; 325 - } 541 + export async function searchIndex( 542 + query: string, 543 + filters: SearchFilters = DEFAULT_FILTERS, 544 + limit: number = 50 545 + ): Promise<{ results: any[]; hasMore: boolean }> { 546 + const source = searchState.activeSource; 547 + const hasFilters = filtersActive(filters); 326 548 327 - found = true; 328 - cursor = localStorage.getItem(`${source}-cursor`) ?? undefined; 549 + let docs: any[]; 329 550 330 - if (cursor) { 331 - localStorage.removeItem(`${source}-cursor`); 332 - found = false; 333 - } 551 + if (query) { 552 + // Text search via FlexSearch → get URIs → lookup in Dexie 553 + const idx = searchState.sources[source].index; 554 + if (!idx) return { results: [], hasMore: false }; 334 555 335 - break; 336 - } 556 + const raw = await idx.search({ 557 + query, 558 + merge: true, 559 + limit: hasFilters ? 5000 : limit + 1 560 + }); 337 561 338 - if (cursor && data.bookmarks.length > 0) { 339 - localStorage.setItem(`${source}-cursor`, cursor); 340 - s.cursor = cursor; 341 - } else { 342 - localStorage.removeItem(`${source}-cursor`); 343 - s.cursor = undefined; 344 - } 345 - } while (cursor && data.bookmarks.length > 0 && !found); 562 + const uris = raw.map((r: any) => r.id); 563 + docs = (await db.posts.bulkGet(uris)).filter(Boolean) as any[]; 564 + } else { 565 + // No query: get all docs for this source from Dexie 566 + docs = await db.posts.where('sources').equals(source).toArray(); 567 + } 568 + 569 + let wrapped = docs.map((doc) => ({ doc })); 570 + if (hasFilters) wrapped = applyFilters(wrapped, filters); 571 + if (!query) wrapped = sortByDate(wrapped); 346 572 347 - await s.index!.commit(); 348 - s.phase = 'done'; 349 - loadNext(myGen); 573 + const hasMore = wrapped.length > limit; 574 + return { results: wrapped.slice(0, limit), hasMore }; 350 575 } 351 576 352 - export async function searchIndex(query: string): Promise<any[]> { 353 - const idx = searchState.sources[searchState.activeSource].index; 354 - if (!idx || !query) return []; 577 + export async function clearSource(source: SourceType) { 578 + // Remove source tag from all posts, delete orphans 579 + const posts = await db.posts.where('sources').equals(source).toArray(); 580 + await db.transaction('rw', db.posts, async () => { 581 + for (const post of posts) { 582 + const newSources = post.sources.filter((s) => s !== source); 583 + if (newSources.length === 0) { 584 + await db.posts.delete(post.uri); 585 + } else { 586 + await db.posts.update(post.uri, { sources: newSources }); 587 + } 588 + } 589 + }); 590 + await db.meta.delete(source); 355 591 356 - return idx.search({ 357 - query, 358 - enrich: true, 359 - merge: true, 360 - limit: 20 361 - }); 592 + searchState.sources[source].index?.clear(); 593 + searchState.sources[source].count = 0; 594 + searchState.sources[source].indexed = 0; 595 + searchState.sources[source].totalToIndex = 0; 596 + searchState.sources[source].phase = 'idle'; 597 + searchState.sources[source].pendingUris = []; 598 + searchState.sources[source].pendingIndex = 0; 362 599 } 363 600 364 601 export function getLink(uri: string, handle: string) {
+2 -2
src/routes/+layout.svelte
··· 19 19 20 20 <Toaster /> 21 21 22 - <Head title="Search Bluesky" emojiFavicon="🦋" /> 22 + <Head title="atmo.tools" emojiFavicon="🦋" /> 23 23 24 - <ThemeToggle class="fixed top-4 right-4" /> 24 + <ThemeToggle class="fixed top-2 right-2" />
+33 -61
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 - import { user } from '$lib/atproto'; 2 + import { Heading } from '@foxui/core'; 3 3 import { atProtoLoginModalState } from '@foxui/social'; 4 - import Search from './Search.svelte'; 5 - import { Button, Heading } from '@foxui/core'; 4 + import { user } from '$lib/atproto'; 6 5 </script> 7 6 8 7 <div class="mx-auto my-18 max-w-xl px-4 md:my-28"> 9 - {#if !user.isLoggedIn} 10 - <Heading class="mb-8">Search your bluesky</Heading> 11 - <Button onclick={() => atProtoLoginModalState.show()}> 12 - <svg 13 - fill="currentColor" 14 - xmlns="http://www.w3.org/2000/svg" 15 - viewBox="-40 -40 680 620" 16 - version="1.1" 17 - class="size-5" 18 - aria-hidden="true" 19 - > 20 - <path 21 - 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" 22 - /> 23 - </svg> 24 - Login 25 - </Button> 8 + <Heading class="mb-8">atmo.tools</Heading> 26 9 27 - <div class="text-base-700 dark:text-base-300 mt-36 flex flex-col gap-4 text-sm"> 28 - <div class="flex gap-2"> 29 - Made by 30 - <div class="flex items-center gap-2"> 31 - <a 32 - target="_blank" 33 - href="https://flo-bit.dev" 34 - class="text-accent-600 dark:text-accent-400 hover:text-accent-500 dark:hover:text-accent-500 font-semibold" 35 - >flo-bit</a 36 - > 37 - 38 - <a 39 - href="https://bsky.app/profile/flo-bit.dev" 40 - target="_blank" 41 - class="text-accent-700 hover:text-accent-600 dark:text-accent-400 dark:hover:text-accent-500 transition-colors" 42 - > 43 - <span class="sr-only">Bluesky</span> 44 - 45 - <svg 46 - role="img" 47 - viewBox="0 0 24 24" 48 - xmlns="http://www.w3.org/2000/svg" 49 - class={['size-4']} 50 - aria-hidden="true" 51 - fill="currentColor" 52 - ><path 53 - 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" 54 - /></svg 55 - > 56 - </a> 57 - </div> 10 + <div class="flex flex-col gap-3"> 11 + <a 12 + href="/search" 13 + onclick={(e) => { 14 + if (!user.isLoggedIn) { 15 + e.preventDefault(); 16 + atProtoLoginModalState.show(); 17 + } 18 + }} 19 + class="bg-base-200 dark:bg-base-950/50 border-base-400/40 dark:border-base-800 hover:border-accent-400 dark:hover:border-accent-600 flex items-center gap-5 rounded-2xl border p-4 inset-shadow-sm inset-shadow-base-800/10 dark:inset-shadow-black/20 transition-colors" 20 + > 21 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="text-accent-600 dark:text-accent-400 size-5"> 22 + <path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11ZM2 9a7 7 0 1 1 12.452 4.391l3.328 3.329a.75.75 0 1 1-1.06 1.06l-3.329-3.328A7 7 0 0 1 2 9Z" clip-rule="evenodd" /> 23 + </svg> 24 + <div> 25 + <div class="text-base-800 dark:text-base-200 text-sm font-semibold">Search</div> 26 + <div class="text-base-500 dark:text-base-400 text-xs mt-1">Search your likes, bookmarks, and posts</div> 58 27 </div> 28 + </a> 59 29 30 + {#if user.isLoggedIn} 60 31 <a 61 - href="https://github.com/flo-bit/search-bluesky-likes" 62 - target="_blank" 63 - class="text-accent-600 hover:text-accent-500 dark:text-base-300 dark:hover:text-accent-400 font-semibold transition-colors" 32 + href="/settings" 33 + class="bg-base-200 dark:bg-base-950/50 border-base-400/40 dark:border-base-800 hover:border-accent-400 dark:hover:border-accent-600 flex items-center gap-5 rounded-2xl border p-4 inset-shadow-sm inset-shadow-base-800/10 dark:inset-shadow-black/20 transition-colors" 64 34 > 65 - Source code 35 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="text-accent-600 dark:text-accent-400 size-5"> 36 + <path fill-rule="evenodd" d="M7.84 1.804A1 1 0 0 1 8.82 1h2.36a1 1 0 0 1 .98.804l.331 1.652a6.993 6.993 0 0 1 1.929 1.115l1.598-.54a1 1 0 0 1 1.186.447l1.18 2.044a1 1 0 0 1-.205 1.251l-1.267 1.113a7.047 7.047 0 0 1 0 2.228l1.267 1.113a1 1 0 0 1 .206 1.25l-1.18 2.045a1 1 0 0 1-1.187.447l-1.598-.54a6.993 6.993 0 0 1-1.929 1.115l-.33 1.652a1 1 0 0 1-.98.804H8.82a1 1 0 0 1-.98-.804l-.331-1.652a6.993 6.993 0 0 1-1.929-1.115l-1.598.54a1 1 0 0 1-1.186-.447l-1.18-2.044a1 1 0 0 1 .205-1.251l1.267-1.114a7.05 7.05 0 0 1 0-2.227L1.821 7.773a1 1 0 0 1-.206-1.25l1.18-2.045a1 1 0 0 1 1.187-.447l1.598.54A6.993 6.993 0 0 1 7.51 3.456l.33-1.652ZM10 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" clip-rule="evenodd" /> 37 + </svg> 38 + <div> 39 + <div class="text-base-800 dark:text-base-200 text-sm font-semibold">Settings</div> 40 + <div class="text-base-500 dark:text-base-400 text-xs mt-1">Manage data and account</div> 41 + </div> 66 42 </a> 67 - </div> 68 - {/if} 69 - 70 - {#if user.isLoggedIn} 71 - <Search /> 72 - {/if} 43 + {/if} 44 + </div> 73 45 </div>
-183
src/routes/Search.svelte
··· 1 - <script lang="ts"> 2 - import { user, logout } from '$lib/atproto'; 3 - import { Avatar, Button, Heading, Input, Modal, Navbar, Popover, ThemeToggle } from '@foxui/core'; 4 - import { blueskyPostToPostData, Post } from '@foxui/social'; 5 - import { onMount } from 'svelte'; 6 - import { 7 - searchState, 8 - initSources, 9 - switchSource, 10 - clearSource, 11 - startLoading, 12 - searchIndex, 13 - SOURCE_LABELS, 14 - PLACEHOLDERS, 15 - ALL_SOURCES, 16 - type SourceType 17 - } from '$lib/search-state.svelte'; 18 - 19 - let input: HTMLInputElement | null = $state(null); 20 - let search = $state(''); 21 - let results: any[] = $state([]); 22 - let infoModalOpen = $state(false); 23 - 24 - onMount(() => { 25 - initSources(); 26 - }); 27 - 28 - function handleSwitchSource(source: SourceType) { 29 - switchSource(source); 30 - search = ''; 31 - results = []; 32 - } 33 - 34 - $effect(() => { 35 - if (!search) { 36 - results = []; 37 - return; 38 - } 39 - 40 - searchIndex(search).then((res) => { 41 - results = res; 42 - }); 43 - }); 44 - </script> 45 - 46 - <svelte:window 47 - onkeydown={() => { 48 - input?.focus(); 49 - }} 50 - /> 51 - 52 - {#if searchState.loading} 53 - <Heading>Loading...</Heading> 54 - {:else} 55 - <Navbar class="mx-2 h-auto max-w-xl flex-col items-start sm:mx-auto md:top-10"> 56 - <div class="mb-4 mx-2 flex items-baseline gap-2"> 57 - <span class="text-base-600 dark:text-base-400 text-sm font-medium">Search my</span> 58 - <div class="flex gap-1"> 59 - {#each ALL_SOURCES as source} 60 - <button 61 - class="cursor-pointer rounded-full px-3 py-1 text-sm font-medium transition-colors {searchState.activeSource === 62 - source 63 - ? 'bg-accent-600 text-white' 64 - : 'text-base-600 dark:text-base-400 hover:bg-base-200 dark:hover:bg-base-800'}" 65 - onclick={() => handleSwitchSource(source)} 66 - > 67 - {SOURCE_LABELS[source]} 68 - </button> 69 - {/each} 70 - </div> 71 - </div> 72 - 73 - <Input 74 - bind:ref={input} 75 - bind:value={search} 76 - class="w-full" 77 - sizeVariant="lg" 78 - placeholder={PLACEHOLDERS[searchState.activeSource]} 79 - /> 80 - 81 - <span class="text-base-600 dark:text-base-400 mt-2 w-full text-center text-xs"> 82 - {#if searchState.sources[searchState.activeSource].phase === 'fetching'} 83 - loading {SOURCE_LABELS[searchState.activeSource].toLowerCase()}... ({searchState.sources[ 84 - searchState.activeSource 85 - ].count}) 86 - {:else if searchState.sources[searchState.activeSource].phase === 'hydrating'} 87 - indexed {searchState.sources[searchState.activeSource].indexed} out of {searchState.sources[ 88 - searchState.activeSource 89 - ].totalToIndex} 90 - {SOURCE_LABELS[searchState.activeSource].toLowerCase()} 91 - {:else} 92 - results: {search ? results.length : 0}, {SOURCE_LABELS[ 93 - searchState.activeSource 94 - ].toLowerCase()} loaded: {searchState.sources[searchState.activeSource].count} 95 - {/if} 96 - </span> 97 - </Navbar> 98 - {/if} 99 - 100 - {#if results.length > 0 && search} 101 - <ul class="pt-20 flex flex-col divide-y text-sm"> 102 - {#each results as result (result.doc.uri)} 103 - <div class="border-base-200 dark:border-base-900 relative border-b py-2"> 104 - <Post 105 - liked={searchState.activeSource === 'likes'} 106 - data={blueskyPostToPostData(result.doc)} 107 - class="pb-2" 108 - /> 109 - </div> 110 - {/each} 111 - </ul> 112 - {:else if search} 113 - <div class="text-base-600 dark:text-base-400 mt-4 text-sm font-semibold">No results</div> 114 - {/if} 115 - 116 - <div class="fixed right-3 bottom-2"> 117 - <Popover class="flex flex-col items-start gap-2 p-2"> 118 - {#snippet child({ props })} 119 - <button {...props} class="cursor-pointer hover:opacity-90"> 120 - <Avatar src={user.profile?.avatar} /> 121 - </button> 122 - {/snippet} 123 - <ThemeToggle class="absolute top-1 right-1 backdrop-blur-none" /> 124 - <Button class="backdrop-blur-none" variant="ghost" onclick={() => (infoModalOpen = true)} 125 - >Info</Button 126 - > 127 - <Button 128 - class="backdrop-blur-none" 129 - variant="ghost" 130 - onclick={() => { 131 - clearSource(searchState.activeSource); 132 - startLoading(searchState.activeSource); 133 - }}>Refresh {SOURCE_LABELS[searchState.activeSource].toLowerCase()}</Button 134 - > 135 - <Button class="backdrop-blur-none" variant="ghost" onclick={logout}>Logout</Button> 136 - </Popover> 137 - </div> 138 - 139 - <Modal bind:open={infoModalOpen}> 140 - <div class="text-base-700 dark:text-base-300 flex flex-col gap-4 text-sm"> 141 - <div class="flex gap-2"> 142 - Made by 143 - <div class="flex items-center gap-2"> 144 - <a 145 - target="_blank" 146 - href="https://flo-bit.dev" 147 - class="text-accent-600 dark:text-accent-400 hover:text-accent-500 dark:hover:text-accent-500 font-semibold" 148 - >flo-bit</a 149 - > 150 - 151 - <a 152 - href="https://bsky.app/profile/flo-bit.dev" 153 - target="_blank" 154 - class="text-accent-700 hover:text-accent-600 dark:text-accent-400 dark:hover:text-accent-500 transition-colors" 155 - > 156 - <span class="sr-only">Bluesky</span> 157 - 158 - <svg 159 - role="img" 160 - viewBox="0 0 24 24" 161 - xmlns="http://www.w3.org/2000/svg" 162 - class={['size-4']} 163 - aria-hidden="true" 164 - fill="currentColor" 165 - ><path 166 - 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" 167 - /></svg 168 - > 169 - </a> 170 - </div> 171 - </div> 172 - 173 - <p>Send me a message if you have any questions, problems or feedback!</p> 174 - 175 - <a 176 - href="https://github.com/flo-bit/search-bluesky-likes" 177 - target="_blank" 178 - class="text-accent-600 hover:text-accent-500 dark:text-base-300 dark:hover:text-accent-400 font-semibold transition-colors" 179 - > 180 - Source code 181 - </a> 182 - </div> 183 - </Modal>
+31
src/routes/search/+page.svelte
··· 1 + <script lang="ts"> 2 + import { user } from '$lib/atproto'; 3 + import { atProtoLoginModalState } from '@foxui/social'; 4 + import { Button, Heading } from '@foxui/core'; 5 + import Search from './Search.svelte'; 6 + </script> 7 + 8 + <div class="mx-auto my-18 max-w-xl px-4 md:my-28"> 9 + {#if !user.isLoggedIn} 10 + <Heading class="mb-8">Search your bluesky</Heading> 11 + <Button onclick={() => atProtoLoginModalState.show()}> 12 + <svg 13 + fill="currentColor" 14 + xmlns="http://www.w3.org/2000/svg" 15 + viewBox="-40 -40 680 620" 16 + version="1.1" 17 + class="size-5" 18 + aria-hidden="true" 19 + > 20 + <path 21 + 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" 22 + /> 23 + </svg> 24 + Login 25 + </Button> 26 + {/if} 27 + 28 + {#if user.isLoggedIn} 29 + <Search /> 30 + {/if} 31 + </div>
+302
src/routes/search/Search.svelte
··· 1 + <script lang="ts"> 2 + import { Input, Navbar } from '@foxui/core'; 3 + import { AtprotoHandlePopup, blueskyPostToPostData, Post } from '@foxui/social'; 4 + import { onMount } from 'svelte'; 5 + import { 6 + searchState, 7 + initSources, 8 + switchSource, 9 + searchIndex, 10 + filtersActive, 11 + DEFAULT_FILTERS, 12 + SOURCE_LABELS, 13 + PLACEHOLDERS, 14 + ALL_SOURCES, 15 + type SourceType, 16 + type SearchFilters 17 + } from '$lib/search-state.svelte'; 18 + 19 + let input: HTMLInputElement | null = $state(null); 20 + let search = $state(''); 21 + let results: any[] = $state([]); 22 + let hasMore = $state(false); 23 + let limit = $state(50); 24 + let sentinel: HTMLElement | null = $state(null); 25 + let scrollY = $state(0); 26 + 27 + let showFilters = $state(false); 28 + let filters = $state<SearchFilters>({ ...DEFAULT_FILTERS }); 29 + let handleInput = $state(''); 30 + let hasActiveFilters = $derived(filtersActive(filters)); 31 + 32 + onMount(() => { 33 + initSources(); 34 + }); 35 + 36 + function handleSwitchSource(source: SourceType) { 37 + switchSource(source); 38 + } 39 + 40 + // Reset limit when search params change 41 + $effect(() => { 42 + searchState.activeSource; 43 + $state.snapshot(filters); 44 + search; 45 + limit = 50; 46 + }); 47 + 48 + // Fetch results 49 + $effect(() => { 50 + const _source = searchState.activeSource; 51 + const _filters = $state.snapshot(filters); 52 + const _count = searchState.sources[_source].count; 53 + const _limit = limit; 54 + 55 + searchIndex(search, _filters, _limit).then((res) => { 56 + results = res.results; 57 + hasMore = res.hasMore; 58 + }); 59 + }); 60 + 61 + // Infinite scroll via IntersectionObserver 62 + $effect(() => { 63 + const el = sentinel; 64 + if (!el) return; 65 + 66 + const observer = new IntersectionObserver( 67 + (entries) => { 68 + if (entries[0].isIntersecting && hasMore) { 69 + limit += 50; 70 + } 71 + }, 72 + { rootMargin: '400px' } 73 + ); 74 + 75 + observer.observe(el); 76 + return () => observer.disconnect(); 77 + }); 78 + 79 + const pillClass = (active: boolean) => 80 + `cursor-pointer rounded-full px-3 py-1 text-xs font-medium transition-colors ${ 81 + active 82 + ? 'bg-accent-600 text-white' 83 + : 'text-base-600 dark:text-base-400 hover:bg-base-200 dark:hover:bg-base-800' 84 + }`; 85 + </script> 86 + 87 + <svelte:window 88 + bind:scrollY={scrollY} 89 + onkeydown={() => { 90 + const tag = document.activeElement?.tagName; 91 + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return; 92 + input?.focus(); 93 + }} 94 + /> 95 + 96 + <a 97 + href="/" 98 + class="text-base-500 dark:text-base-400 hover:text-accent-600 dark:hover:text-accent-400 fixed top-4 left-4 z-50 inline-flex items-center gap-1 text-sm transition-colors" 99 + > 100 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-4"> 101 + <path fill-rule="evenodd" d="M17 10a.75.75 0 0 1-.75.75H5.612l4.158 3.96a.75.75 0 1 1-1.04 1.08l-5.5-5.25a.75.75 0 0 1 0-1.08l5.5-5.25a.75.75 0 1 1 1.04 1.08L5.612 9.25H16.25A.75.75 0 0 1 17 10Z" clip-rule="evenodd" /> 102 + </svg> 103 + Back 104 + </a> 105 + 106 + <Navbar class="mx-2 py-3 h-auto max-w-xl flex-col items-start sm:mx-auto top-12 md:top-10"> 107 + <div class="mb-4 mx-2 flex items-baseline gap-2"> 108 + <span class="text-base-600 dark:text-base-400 text-sm font-medium">Search my</span> 109 + <div class="flex gap-1"> 110 + {#each ALL_SOURCES as source} 111 + <button 112 + class="cursor-pointer rounded-full px-3 py-1 text-sm font-medium transition-colors {searchState.activeSource === 113 + source 114 + ? 'bg-accent-600 text-white' 115 + : 'text-base-600 dark:text-base-400 hover:bg-base-200 dark:hover:bg-base-800'}" 116 + onclick={() => handleSwitchSource(source)} 117 + > 118 + {SOURCE_LABELS[source]} 119 + </button> 120 + {/each} 121 + </div> 122 + </div> 123 + 124 + <div class="flex w-full items-center gap-2"> 125 + <Input 126 + bind:ref={input} 127 + bind:value={search} 128 + class="w-full" 129 + sizeVariant="lg" 130 + placeholder={PLACEHOLDERS[searchState.activeSource]} 131 + /> 132 + <button 133 + class="text-base-500 dark:text-base-400 hover:text-accent-600 dark:hover:text-accent-400 relative cursor-pointer p-2 transition-colors" 134 + onclick={() => (showFilters = !showFilters)} 135 + aria-label="Toggle filters" 136 + > 137 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5"> 138 + <path fill-rule="evenodd" d="M2.628 1.601C5.028 1.206 7.49 1 10 1s4.973.206 7.372.601a.75.75 0 0 1 .628.74v2.288a2.25 2.25 0 0 1-.659 1.59l-4.682 4.683a2.25 2.25 0 0 0-.659 1.59v3.037c0 .684-.31 1.33-.844 1.757l-1.937 1.55A.75.75 0 0 1 8 18.25v-5.757a2.25 2.25 0 0 0-.659-1.591L2.659 6.22A2.25 2.25 0 0 1 2 4.629V2.34a.75.75 0 0 1 .628-.74Z" clip-rule="evenodd" /> 139 + </svg> 140 + {#if hasActiveFilters} 141 + <span class="bg-accent-600 absolute top-1 right-1 size-2 rounded-full"></span> 142 + {/if} 143 + </button> 144 + </div> 145 + 146 + {#if showFilters} 147 + <div class="mt-3 flex w-full flex-col gap-3 px-2"> 148 + {#if searchState.activeSource !== 'posts'} 149 + <div class="flex flex-col gap-1.5"> 150 + <div class="flex items-center gap-2"> 151 + <label class="text-base-500 dark:text-base-400 text-xs w-16 shrink-0">Handle</label> 152 + <AtprotoHandlePopup 153 + bind:value={handleInput} 154 + onselected={(actor) => { 155 + if (!filters.handles.includes(actor.handle)) { 156 + filters.handles = [...filters.handles, actor.handle]; 157 + } 158 + handleInput = ''; 159 + }} 160 + /> 161 + </div> 162 + {#if filters.handles.length > 0} 163 + <div class="ml-18 flex flex-wrap gap-1"> 164 + {#each filters.handles as handle} 165 + <span class="bg-base-200 dark:bg-base-800 text-base-700 dark:text-base-300 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs"> 166 + @{handle} 167 + <button 168 + class="text-base-500 hover:text-base-800 dark:hover:text-base-100 cursor-pointer" 169 + onclick={() => (filters.handles = filters.handles.filter((h) => h !== handle))} 170 + aria-label="Remove {handle}" 171 + > 172 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-3"> 173 + <path d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z" /> 174 + </svg> 175 + </button> 176 + </span> 177 + {/each} 178 + </div> 179 + {/if} 180 + </div> 181 + {/if} 182 + 183 + <div class="flex items-center gap-3"> 184 + <div class="flex items-center gap-1"> 185 + <label class="text-base-500 dark:text-base-400 text-xs">Min likes</label> 186 + <input 187 + type="number" 188 + bind:value={filters.minLikes} 189 + min="0" 190 + class="bg-base-100 dark:bg-base-900 border-base-300 dark:border-base-700 text-base-800 dark:text-base-200 w-16 rounded-md border px-2 py-1 text-xs" 191 + /> 192 + </div> 193 + <div class="flex items-center gap-1"> 194 + <label class="text-base-500 dark:text-base-400 text-xs">Reposts</label> 195 + <input 196 + type="number" 197 + bind:value={filters.minReposts} 198 + min="0" 199 + class="bg-base-100 dark:bg-base-900 border-base-300 dark:border-base-700 text-base-800 dark:text-base-200 w-16 rounded-md border px-2 py-1 text-xs" 200 + /> 201 + </div> 202 + <div class="flex items-center gap-1"> 203 + <label class="text-base-500 dark:text-base-400 text-xs">Replies</label> 204 + <input 205 + type="number" 206 + bind:value={filters.minReplies} 207 + min="0" 208 + class="bg-base-100 dark:bg-base-900 border-base-300 dark:border-base-700 text-base-800 dark:text-base-200 w-16 rounded-md border px-2 py-1 text-xs" 209 + /> 210 + </div> 211 + </div> 212 + 213 + <div class="flex items-center gap-3"> 214 + <div class="flex items-center gap-1"> 215 + <label class="text-base-500 dark:text-base-400 text-xs">After</label> 216 + <input 217 + type="date" 218 + bind:value={filters.dateAfter} 219 + class="bg-base-100 dark:bg-base-900 border-base-300 dark:border-base-700 text-base-800 dark:text-base-200 rounded-md border px-2 py-1 text-xs" 220 + /> 221 + </div> 222 + <div class="flex items-center gap-1"> 223 + <label class="text-base-500 dark:text-base-400 text-xs">Before</label> 224 + <input 225 + type="date" 226 + bind:value={filters.dateBefore} 227 + class="bg-base-100 dark:bg-base-900 border-base-300 dark:border-base-700 text-base-800 dark:text-base-200 rounded-md border px-2 py-1 text-xs" 228 + /> 229 + </div> 230 + </div> 231 + 232 + <div class="flex items-center gap-2"> 233 + <span class="text-base-500 dark:text-base-400 text-xs">Has:</span> 234 + <button class={pillClass(filters.hasImage)} onclick={() => (filters.hasImage = !filters.hasImage)}>Image</button> 235 + <button class={pillClass(filters.hasLink)} onclick={() => (filters.hasLink = !filters.hasLink)}>Link</button> 236 + <button class={pillClass(filters.hasVideo)} onclick={() => (filters.hasVideo = !filters.hasVideo)}>Video</button> 237 + </div> 238 + 239 + <div class="flex items-center gap-2"> 240 + <button class={pillClass(!filters.showReplies)} onclick={() => (filters.showReplies = !filters.showReplies)}>Hide replies</button> 241 + </div> 242 + 243 + {#if hasActiveFilters} 244 + <button 245 + class="text-base-500 dark:text-base-400 hover:text-accent-600 dark:hover:text-accent-400 cursor-pointer self-start text-xs underline" 246 + onclick={() => (filters = { ...DEFAULT_FILTERS })} 247 + > 248 + Clear filters 249 + </button> 250 + {/if} 251 + </div> 252 + {/if} 253 + 254 + {@const src = searchState.sources[searchState.activeSource]} 255 + <span class="text-base-600 dark:text-base-400 mt-2 mx-4 text-xs"> 256 + {#if src.phase === 'idle'} 257 + loading... 258 + {:else if src.phase === 'fetching'} 259 + fetching {SOURCE_LABELS[searchState.activeSource].toLowerCase()}... ({src.count + src.pendingUris.length} found) 260 + {:else if src.phase === 'hydrating'} 261 + indexing {src.indexed}/{src.totalToIndex} {SOURCE_LABELS[searchState.activeSource].toLowerCase()}... ({src.count} searchable) 262 + {:else} 263 + {SOURCE_LABELS[searchState.activeSource].toLowerCase()}: {src.count}{#if results.length > 0}, {results.length} results{/if} 264 + {/if} 265 + </span> 266 + </Navbar> 267 + 268 + {#if results.length > 0} 269 + <ul class="{showFilters ? 'pt-90 md:pt-80' : 'pt-32 md:pt-20'} flex flex-col divide-y divide-base-200 dark:divide-base-800 text-sm"> 270 + {#each results as result (result.doc.uri)} 271 + <li class="py-2"> 272 + <Post 273 + liked={result.doc.sources?.includes('likes')} 274 + bookmarked={result.doc.sources?.includes('bookmarks')} 275 + data={blueskyPostToPostData(result.doc)} 276 + class="pb-2" 277 + /> 278 + </li> 279 + {/each} 280 + </ul> 281 + <div bind:this={sentinel} class="text-base-500 dark:text-base-400 py-8 text-center text-xs"> 282 + {#if hasMore} 283 + loading more... 284 + {:else} 285 + that's all 286 + {/if} 287 + </div> 288 + {:else if search || hasActiveFilters} 289 + <div class="text-base-600 dark:text-base-400 {showFilters ? 'pt-90 md:pt-80' : 'pt-32 md:pt-20'} text-center text-sm font-semibold">No results</div> 290 + {/if} 291 + 292 + {#if scrollY > 300} 293 + <button 294 + class="bg-base-200/80 dark:bg-base-800/80 text-base-600 dark:text-base-300 hover:bg-base-300 dark:hover:bg-base-700 fixed right-3 bottom-3 cursor-pointer rounded-full p-2.5 shadow-md backdrop-blur-sm transition-all" 295 + onclick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} 296 + aria-label="Scroll to top" 297 + > 298 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5"> 299 + <path fill-rule="evenodd" d="M9.47 6.47a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 1 1-1.06 1.06L10 8.06l-3.72 3.72a.75.75 0 0 1-1.06-1.06l4.25-4.25Z" clip-rule="evenodd" /> 300 + </svg> 301 + </button> 302 + {/if}
+60
src/routes/settings/+page.svelte
··· 1 + <script lang="ts"> 2 + import { user, logout } from '$lib/atproto'; 3 + import { Button, Heading, toast } from '@foxui/core'; 4 + import { clearSource, ALL_SOURCES } from '$lib/search-state.svelte'; 5 + import { goto } from '$app/navigation'; 6 + </script> 7 + 8 + {#if user.isLoggedIn} 9 + <div class="mx-auto my-18 max-w-xl px-4 md:my-28"> 10 + <a href="/" class="text-base-500 dark:text-base-400 hover:text-accent-600 dark:hover:text-accent-400 mb-6 inline-flex items-center gap-1 text-sm transition-colors"> 11 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-4"> 12 + <path fill-rule="evenodd" d="M17 10a.75.75 0 0 1-.75.75H5.612l4.158 3.96a.75.75 0 1 1-1.04 1.08l-5.5-5.25a.75.75 0 0 1 0-1.08l5.5-5.25a.75.75 0 1 1 1.04 1.08L5.612 9.25H16.25A.75.75 0 0 1 17 10Z" clip-rule="evenodd" /> 13 + </svg> 14 + Back 15 + </a> 16 + 17 + <Heading class="mb-8">Settings</Heading> 18 + 19 + <div class="flex flex-col gap-4"> 20 + <div class="flex flex-col gap-2"> 21 + <p class="text-base-600 dark:text-base-400 text-sm">Signed in as <span class="text-base-800 dark:text-base-200 font-medium">@{user.profile?.handle}</span></p> 22 + </div> 23 + 24 + <div class="border-base-300 dark:border-base-800 flex flex-col gap-3 border-t pt-4"> 25 + <Button 26 + variant="ghost" 27 + onclick={async () => { 28 + for (const source of ALL_SOURCES) { 29 + await clearSource(source); 30 + } 31 + const dbs = await indexedDB.databases(); 32 + for (const dbInfo of dbs) { 33 + if (dbInfo.name) indexedDB.deleteDatabase(dbInfo.name); 34 + } 35 + toast.success('All data cleared'); 36 + setTimeout(() => location.reload(), 500); 37 + }} 38 + > 39 + Reset all data 40 + </Button> 41 + 42 + <Button 43 + variant="ghost" 44 + onclick={async () => { 45 + await logout(); 46 + goto('/'); 47 + }} 48 + > 49 + Logout 50 + </Button> 51 + </div> 52 + </div> 53 + </div> 54 + {:else} 55 + <div class="mx-auto my-18 max-w-xl px-4 md:my-28"> 56 + <Heading class="mb-4">Settings</Heading> 57 + <p class="text-base-600 dark:text-base-400 text-sm">You need to be logged in to view settings.</p> 58 + <a href="/" class="text-accent-600 dark:text-accent-400 hover:text-accent-500 mt-2 inline-block text-sm font-medium">Go home</a> 59 + </div> 60 + {/if}
+3 -3
wrangler.jsonc
··· 1 1 { 2 - "name": "search-bluesky-likes", 2 + "name": "atmo-tools", 3 3 "main": ".svelte-kit/cloudflare/_worker.js", 4 4 "compatibility_date": "2025-01-01", 5 5 "compatibility_flags": ["nodejs_compat_v2"], ··· 8 8 "directory": ".svelte-kit/cloudflare" 9 9 }, 10 10 "kv_namespaces": [ 11 - { "binding": "OAUTH_SESSIONS", "id": "TODO" }, 12 - { "binding": "OAUTH_STATES", "id": "TODO" } 11 + { "binding": "OAUTH_SESSIONS", "id": "87987a15aa874ff28635173747ed2484" }, 12 + { "binding": "OAUTH_STATES", "id": "2f40be642daa488c87f196e8f2cbdc04" } 13 13 ] 14 14 }