an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm

Initial Red Dwarf Open Source Release

rimar1337 1c939a15

+11
.cta.json
··· 1 + { 2 + "projectName": "red-dwarf-tanstack", 3 + "mode": "file-router", 4 + "typescript": true, 5 + "tailwind": true, 6 + "packageManager": "npm", 7 + "git": true, 8 + "version": 1, 9 + "framework": "react-cra", 10 + "chosenAddOns": [] 11 + }
+9
.gitignore
··· 1 + node_modules 2 + .DS_Store 3 + dist 4 + dist-ssr 5 + *.local 6 + count.txt 7 + .env 8 + .nitro 9 + .tanstack
+11
.vscode/settings.json
··· 1 + { 2 + "files.watcherExclude": { 3 + "**/routeTree.gen.ts": true 4 + }, 5 + "search.exclude": { 6 + "**/routeTree.gen.ts": true 7 + }, 8 + "files.readonlyInclude": { 9 + "**/routeTree.gen.ts": true 10 + } 11 + }
+37
README.md
··· 1 + # Initial Red Dwarf Open Source Release 2 + i made red dwarf in three days 3 + 4 + it isnt really that well made 5 + (go take a look at `UniversalPostRenderer.tsx`) 6 + 7 + further development is pending 8 + (especially around future plans for user-resolved constellation instances) 9 + 10 + huge thanks to Constellation ([Microcosm](https://microcosm.blue/)) for making this possible 11 + 12 + ## UniversalPostRenderer 13 + its a mega component rooted in my Masonry "[TestFront](https://testfront-87q.pages.dev/)" project. its goal is simple: have one component render everything. it has several shims to normalize different post data formats into a single format the component can handle. unlike TestFront, it has no animations, though some weird component splits might linger from the old version. 14 + 15 + to adapt TestFront's bsky-api-based `UniversalPostRenderer` to Red Dwarf's model of fetching records directly from each user's PDS and then querying constellation for backlinks, i wrap it in `UniversalPostRendererATURILoader`, which handles raw record and backlink fetching. to bridge the gap between bsky api shapes like `PostView` and the raw record, i use `UniversalPostRendererRawRecordShim`. this way, the core `UniversalPostRenderer` remains the same between TestFront and Red Dwarf (with the only difference being in the red dwarf version the framer motion animations are removed). 16 + 17 + 18 + ## PassAuthProvider 19 + a really bad app-password auth provider, inherited from TestFront and used in all my projects from TestFront to ForumTest (im very good at naming things). in ForumTest, its been superseded by the [OAuthProvider](https://tangled.sh/@whey.party/forumtest/blob/main/src/providers/OAuthProvider.tsx). i havent backported it here and maybe soon, although oauth makes it slightly more annoying to do development because it requires a tunnel so maybe someday if i managed to merge the password and oauth logins to provide both options 20 + 21 + ## Constellation 22 + the beating heart of Red Dwarf, the backlink index that provides contextual information not available from direct PDS queries. Every post's likes, replies, and reposts all come from constellation. Unfortunately i wasnt using tanstack query at the time (compared to its intensive use in the old version of ForumTest) so it is not using any caching 23 + 24 + Red Dwarf was made before Microcosm [Slingshot](https://slingshot.microcosm.blue) existed, and it would be a very good idea to migrate to it in the future maybe (to reduce load from individual PDS servers) 25 + 26 + ## Custom Feeds 27 + they work, but i havent implemented a simple way of viewing arbitraty feeds. currently it either loads discover (logged out) or your saved feeds (logged in) and its not a technical limitation i just havent implemented it yet 28 + 29 + ## The Following Feed and List Feeds 30 + they wont work at all 31 + they need an appview or any feed server 32 + in place of a following feed you can just use any custom feed that implements the following feed, like mine ([Fresh](https://bsky.app/profile/whey.party/feed/rinds)) 33 + 34 + and for list feeds, you can just use something like graze or skyfeed to input a list of users and output a custom feed 35 + 36 + ## Tanstack Router 37 + it does the job, nothing very specific was used here
+21
index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <link rel="icon" href="/favicon.ico" /> 7 + <meta name="theme-color" content="#000000" /> 8 + <meta 9 + name="description" 10 + content="Web site created using create-tsrouter-app" 11 + /> 12 + <link rel="apple-touch-icon" href="/redstar.png" /> 13 + <link rel="manifest" href="/manifest.json" /> 14 + <link rel="stylesheet" href="/src/styles/app.css" /> 15 + <title>Red Dwarf lite</title> 16 + </head> 17 + <body> 18 + <div id="app"></div> 19 + <script type="module" src="/src/main.tsx"></script> 20 + </body> 21 + </html>
+5066
package-lock.json
··· 1 + { 2 + "name": "red-dwarf-tanstack", 3 + "lockfileVersion": 3, 4 + "requires": true, 5 + "packages": { 6 + "": { 7 + "name": "red-dwarf-tanstack", 8 + "dependencies": { 9 + "@atproto/api": "^0.16.6", 10 + "@tailwindcss/vite": "^4.0.6", 11 + "@tanstack/react-devtools": "^0.2.2", 12 + "@tanstack/react-router": "^1.130.2", 13 + "@tanstack/react-router-devtools": "^1.131.5", 14 + "@tanstack/router-plugin": "^1.121.2", 15 + "idb-keyval": "^6.2.2", 16 + "react": "^19.0.0", 17 + "react-dom": "^19.0.0", 18 + "react-player": "^3.3.2", 19 + "tailwindcss": "^4.0.6" 20 + }, 21 + "devDependencies": { 22 + "@testing-library/dom": "^10.4.0", 23 + "@testing-library/react": "^16.2.0", 24 + "@types/node": "^24.3.0", 25 + "@types/react": "^19.0.8", 26 + "@types/react-dom": "^19.0.3", 27 + "@vitejs/plugin-react": "^4.3.4", 28 + "jsdom": "^26.0.0", 29 + "prettier": "^3.6.2", 30 + "typescript": "^5.7.2", 31 + "vite": "^6.3.5", 32 + "vitest": "^3.0.5", 33 + "web-vitals": "^4.2.4" 34 + } 35 + }, 36 + "node_modules/@ampproject/remapping": { 37 + "version": "2.3.0", 38 + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 39 + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 40 + "license": "Apache-2.0", 41 + "dependencies": { 42 + "@jridgewell/gen-mapping": "^0.3.5", 43 + "@jridgewell/trace-mapping": "^0.3.24" 44 + }, 45 + "engines": { 46 + "node": ">=6.0.0" 47 + } 48 + }, 49 + "node_modules/@asamuzakjp/css-color": { 50 + "version": "3.2.0", 51 + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", 52 + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", 53 + "dev": true, 54 + "license": "MIT", 55 + "dependencies": { 56 + "@csstools/css-calc": "^2.1.3", 57 + "@csstools/css-color-parser": "^3.0.9", 58 + "@csstools/css-parser-algorithms": "^3.0.4", 59 + "@csstools/css-tokenizer": "^3.0.3", 60 + "lru-cache": "^10.4.3" 61 + } 62 + }, 63 + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { 64 + "version": "10.4.3", 65 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", 66 + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", 67 + "dev": true, 68 + "license": "ISC" 69 + }, 70 + "node_modules/@atproto/api": { 71 + "version": "0.16.6", 72 + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.16.6.tgz", 73 + "integrity": "sha512-/ZDWeHNAMmQFicyITAoGCxQujDU+wyzsWjpPnQfVa+ZKMfZEngipMFOr4fBwxHixEFLmuh58+5vJyylAFVrQ4g==", 74 + "license": "MIT", 75 + "dependencies": { 76 + "@atproto/common-web": "^0.4.2", 77 + "@atproto/lexicon": "^0.5.0", 78 + "@atproto/syntax": "^0.4.1", 79 + "@atproto/xrpc": "^0.7.4", 80 + "await-lock": "^2.2.2", 81 + "multiformats": "^9.9.0", 82 + "tlds": "^1.234.0", 83 + "zod": "^3.23.8" 84 + } 85 + }, 86 + "node_modules/@atproto/common-web": { 87 + "version": "0.4.2", 88 + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.2.tgz", 89 + "integrity": "sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==", 90 + "license": "MIT", 91 + "dependencies": { 92 + "graphemer": "^1.4.0", 93 + "multiformats": "^9.9.0", 94 + "uint8arrays": "3.0.0", 95 + "zod": "^3.23.8" 96 + } 97 + }, 98 + "node_modules/@atproto/lexicon": { 99 + "version": "0.5.0", 100 + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.5.0.tgz", 101 + "integrity": "sha512-3aAzEAy9EAPs3CxznzMhEcqDd7m3vz1eze/ya9/ThbB7yleqJIhz5GY2q76tCCwHPhn5qDDMhlA9kKV6fG23gA==", 102 + "license": "MIT", 103 + "dependencies": { 104 + "@atproto/common-web": "^0.4.2", 105 + "@atproto/syntax": "^0.4.1", 106 + "iso-datestring-validator": "^2.2.2", 107 + "multiformats": "^9.9.0", 108 + "zod": "^3.23.8" 109 + } 110 + }, 111 + "node_modules/@atproto/syntax": { 112 + "version": "0.4.1", 113 + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.1.tgz", 114 + "integrity": "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw==", 115 + "license": "MIT" 116 + }, 117 + "node_modules/@atproto/xrpc": { 118 + "version": "0.7.4", 119 + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.4.tgz", 120 + "integrity": "sha512-sDi68+QE1XHegTaNAndlX41Gp827pouSzSs8CyAwhrqZdsJUxE3P7TMtrA0z+zAjvxVyvzscRc0TsN/fGUGrhw==", 121 + "license": "MIT", 122 + "dependencies": { 123 + "@atproto/lexicon": "^0.5.0", 124 + "zod": "^3.23.8" 125 + } 126 + }, 127 + "node_modules/@babel/code-frame": { 128 + "version": "7.27.1", 129 + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 130 + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", 131 + "license": "MIT", 132 + "dependencies": { 133 + "@babel/helper-validator-identifier": "^7.27.1", 134 + "js-tokens": "^4.0.0", 135 + "picocolors": "^1.1.1" 136 + }, 137 + "engines": { 138 + "node": ">=6.9.0" 139 + } 140 + }, 141 + "node_modules/@babel/compat-data": { 142 + "version": "7.28.0", 143 + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", 144 + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", 145 + "license": "MIT", 146 + "engines": { 147 + "node": ">=6.9.0" 148 + } 149 + }, 150 + "node_modules/@babel/core": { 151 + "version": "7.28.3", 152 + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", 153 + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", 154 + "license": "MIT", 155 + "dependencies": { 156 + "@ampproject/remapping": "^2.2.0", 157 + "@babel/code-frame": "^7.27.1", 158 + "@babel/generator": "^7.28.3", 159 + "@babel/helper-compilation-targets": "^7.27.2", 160 + "@babel/helper-module-transforms": "^7.28.3", 161 + "@babel/helpers": "^7.28.3", 162 + "@babel/parser": "^7.28.3", 163 + "@babel/template": "^7.27.2", 164 + "@babel/traverse": "^7.28.3", 165 + "@babel/types": "^7.28.2", 166 + "convert-source-map": "^2.0.0", 167 + "debug": "^4.1.0", 168 + "gensync": "^1.0.0-beta.2", 169 + "json5": "^2.2.3", 170 + "semver": "^6.3.1" 171 + }, 172 + "engines": { 173 + "node": ">=6.9.0" 174 + }, 175 + "funding": { 176 + "type": "opencollective", 177 + "url": "https://opencollective.com/babel" 178 + } 179 + }, 180 + "node_modules/@babel/generator": { 181 + "version": "7.28.3", 182 + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", 183 + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", 184 + "license": "MIT", 185 + "dependencies": { 186 + "@babel/parser": "^7.28.3", 187 + "@babel/types": "^7.28.2", 188 + "@jridgewell/gen-mapping": "^0.3.12", 189 + "@jridgewell/trace-mapping": "^0.3.28", 190 + "jsesc": "^3.0.2" 191 + }, 192 + "engines": { 193 + "node": ">=6.9.0" 194 + } 195 + }, 196 + "node_modules/@babel/helper-annotate-as-pure": { 197 + "version": "7.27.3", 198 + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", 199 + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", 200 + "license": "MIT", 201 + "dependencies": { 202 + "@babel/types": "^7.27.3" 203 + }, 204 + "engines": { 205 + "node": ">=6.9.0" 206 + } 207 + }, 208 + "node_modules/@babel/helper-compilation-targets": { 209 + "version": "7.27.2", 210 + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", 211 + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", 212 + "license": "MIT", 213 + "dependencies": { 214 + "@babel/compat-data": "^7.27.2", 215 + "@babel/helper-validator-option": "^7.27.1", 216 + "browserslist": "^4.24.0", 217 + "lru-cache": "^5.1.1", 218 + "semver": "^6.3.1" 219 + }, 220 + "engines": { 221 + "node": ">=6.9.0" 222 + } 223 + }, 224 + "node_modules/@babel/helper-create-class-features-plugin": { 225 + "version": "7.28.3", 226 + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", 227 + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", 228 + "license": "MIT", 229 + "dependencies": { 230 + "@babel/helper-annotate-as-pure": "^7.27.3", 231 + "@babel/helper-member-expression-to-functions": "^7.27.1", 232 + "@babel/helper-optimise-call-expression": "^7.27.1", 233 + "@babel/helper-replace-supers": "^7.27.1", 234 + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", 235 + "@babel/traverse": "^7.28.3", 236 + "semver": "^6.3.1" 237 + }, 238 + "engines": { 239 + "node": ">=6.9.0" 240 + }, 241 + "peerDependencies": { 242 + "@babel/core": "^7.0.0" 243 + } 244 + }, 245 + "node_modules/@babel/helper-globals": { 246 + "version": "7.28.0", 247 + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", 248 + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", 249 + "license": "MIT", 250 + "engines": { 251 + "node": ">=6.9.0" 252 + } 253 + }, 254 + "node_modules/@babel/helper-member-expression-to-functions": { 255 + "version": "7.27.1", 256 + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", 257 + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", 258 + "license": "MIT", 259 + "dependencies": { 260 + "@babel/traverse": "^7.27.1", 261 + "@babel/types": "^7.27.1" 262 + }, 263 + "engines": { 264 + "node": ">=6.9.0" 265 + } 266 + }, 267 + "node_modules/@babel/helper-module-imports": { 268 + "version": "7.27.1", 269 + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", 270 + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", 271 + "license": "MIT", 272 + "dependencies": { 273 + "@babel/traverse": "^7.27.1", 274 + "@babel/types": "^7.27.1" 275 + }, 276 + "engines": { 277 + "node": ">=6.9.0" 278 + } 279 + }, 280 + "node_modules/@babel/helper-module-transforms": { 281 + "version": "7.28.3", 282 + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", 283 + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", 284 + "license": "MIT", 285 + "dependencies": { 286 + "@babel/helper-module-imports": "^7.27.1", 287 + "@babel/helper-validator-identifier": "^7.27.1", 288 + "@babel/traverse": "^7.28.3" 289 + }, 290 + "engines": { 291 + "node": ">=6.9.0" 292 + }, 293 + "peerDependencies": { 294 + "@babel/core": "^7.0.0" 295 + } 296 + }, 297 + "node_modules/@babel/helper-optimise-call-expression": { 298 + "version": "7.27.1", 299 + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", 300 + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", 301 + "license": "MIT", 302 + "dependencies": { 303 + "@babel/types": "^7.27.1" 304 + }, 305 + "engines": { 306 + "node": ">=6.9.0" 307 + } 308 + }, 309 + "node_modules/@babel/helper-plugin-utils": { 310 + "version": "7.27.1", 311 + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", 312 + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", 313 + "license": "MIT", 314 + "engines": { 315 + "node": ">=6.9.0" 316 + } 317 + }, 318 + "node_modules/@babel/helper-replace-supers": { 319 + "version": "7.27.1", 320 + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", 321 + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", 322 + "license": "MIT", 323 + "dependencies": { 324 + "@babel/helper-member-expression-to-functions": "^7.27.1", 325 + "@babel/helper-optimise-call-expression": "^7.27.1", 326 + "@babel/traverse": "^7.27.1" 327 + }, 328 + "engines": { 329 + "node": ">=6.9.0" 330 + }, 331 + "peerDependencies": { 332 + "@babel/core": "^7.0.0" 333 + } 334 + }, 335 + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { 336 + "version": "7.27.1", 337 + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", 338 + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", 339 + "license": "MIT", 340 + "dependencies": { 341 + "@babel/traverse": "^7.27.1", 342 + "@babel/types": "^7.27.1" 343 + }, 344 + "engines": { 345 + "node": ">=6.9.0" 346 + } 347 + }, 348 + "node_modules/@babel/helper-string-parser": { 349 + "version": "7.27.1", 350 + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 351 + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 352 + "license": "MIT", 353 + "engines": { 354 + "node": ">=6.9.0" 355 + } 356 + }, 357 + "node_modules/@babel/helper-validator-identifier": { 358 + "version": "7.27.1", 359 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", 360 + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", 361 + "license": "MIT", 362 + "engines": { 363 + "node": ">=6.9.0" 364 + } 365 + }, 366 + "node_modules/@babel/helper-validator-option": { 367 + "version": "7.27.1", 368 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", 369 + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", 370 + "license": "MIT", 371 + "engines": { 372 + "node": ">=6.9.0" 373 + } 374 + }, 375 + "node_modules/@babel/helpers": { 376 + "version": "7.28.3", 377 + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", 378 + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", 379 + "license": "MIT", 380 + "dependencies": { 381 + "@babel/template": "^7.27.2", 382 + "@babel/types": "^7.28.2" 383 + }, 384 + "engines": { 385 + "node": ">=6.9.0" 386 + } 387 + }, 388 + "node_modules/@babel/parser": { 389 + "version": "7.28.3", 390 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", 391 + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", 392 + "license": "MIT", 393 + "dependencies": { 394 + "@babel/types": "^7.28.2" 395 + }, 396 + "bin": { 397 + "parser": "bin/babel-parser.js" 398 + }, 399 + "engines": { 400 + "node": ">=6.0.0" 401 + } 402 + }, 403 + "node_modules/@babel/plugin-syntax-jsx": { 404 + "version": "7.27.1", 405 + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", 406 + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", 407 + "license": "MIT", 408 + "dependencies": { 409 + "@babel/helper-plugin-utils": "^7.27.1" 410 + }, 411 + "engines": { 412 + "node": ">=6.9.0" 413 + }, 414 + "peerDependencies": { 415 + "@babel/core": "^7.0.0-0" 416 + } 417 + }, 418 + "node_modules/@babel/plugin-syntax-typescript": { 419 + "version": "7.27.1", 420 + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", 421 + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", 422 + "license": "MIT", 423 + "dependencies": { 424 + "@babel/helper-plugin-utils": "^7.27.1" 425 + }, 426 + "engines": { 427 + "node": ">=6.9.0" 428 + }, 429 + "peerDependencies": { 430 + "@babel/core": "^7.0.0-0" 431 + } 432 + }, 433 + "node_modules/@babel/plugin-transform-modules-commonjs": { 434 + "version": "7.27.1", 435 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", 436 + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", 437 + "license": "MIT", 438 + "dependencies": { 439 + "@babel/helper-module-transforms": "^7.27.1", 440 + "@babel/helper-plugin-utils": "^7.27.1" 441 + }, 442 + "engines": { 443 + "node": ">=6.9.0" 444 + }, 445 + "peerDependencies": { 446 + "@babel/core": "^7.0.0-0" 447 + } 448 + }, 449 + "node_modules/@babel/plugin-transform-react-jsx-self": { 450 + "version": "7.27.1", 451 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", 452 + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", 453 + "dev": true, 454 + "license": "MIT", 455 + "dependencies": { 456 + "@babel/helper-plugin-utils": "^7.27.1" 457 + }, 458 + "engines": { 459 + "node": ">=6.9.0" 460 + }, 461 + "peerDependencies": { 462 + "@babel/core": "^7.0.0-0" 463 + } 464 + }, 465 + "node_modules/@babel/plugin-transform-react-jsx-source": { 466 + "version": "7.27.1", 467 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", 468 + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", 469 + "dev": true, 470 + "license": "MIT", 471 + "dependencies": { 472 + "@babel/helper-plugin-utils": "^7.27.1" 473 + }, 474 + "engines": { 475 + "node": ">=6.9.0" 476 + }, 477 + "peerDependencies": { 478 + "@babel/core": "^7.0.0-0" 479 + } 480 + }, 481 + "node_modules/@babel/plugin-transform-typescript": { 482 + "version": "7.28.0", 483 + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", 484 + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", 485 + "license": "MIT", 486 + "dependencies": { 487 + "@babel/helper-annotate-as-pure": "^7.27.3", 488 + "@babel/helper-create-class-features-plugin": "^7.27.1", 489 + "@babel/helper-plugin-utils": "^7.27.1", 490 + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", 491 + "@babel/plugin-syntax-typescript": "^7.27.1" 492 + }, 493 + "engines": { 494 + "node": ">=6.9.0" 495 + }, 496 + "peerDependencies": { 497 + "@babel/core": "^7.0.0-0" 498 + } 499 + }, 500 + "node_modules/@babel/preset-typescript": { 501 + "version": "7.27.1", 502 + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", 503 + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", 504 + "license": "MIT", 505 + "dependencies": { 506 + "@babel/helper-plugin-utils": "^7.27.1", 507 + "@babel/helper-validator-option": "^7.27.1", 508 + "@babel/plugin-syntax-jsx": "^7.27.1", 509 + "@babel/plugin-transform-modules-commonjs": "^7.27.1", 510 + "@babel/plugin-transform-typescript": "^7.27.1" 511 + }, 512 + "engines": { 513 + "node": ">=6.9.0" 514 + }, 515 + "peerDependencies": { 516 + "@babel/core": "^7.0.0-0" 517 + } 518 + }, 519 + "node_modules/@babel/runtime": { 520 + "version": "7.28.3", 521 + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", 522 + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", 523 + "dev": true, 524 + "license": "MIT", 525 + "engines": { 526 + "node": ">=6.9.0" 527 + } 528 + }, 529 + "node_modules/@babel/template": { 530 + "version": "7.27.2", 531 + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 532 + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 533 + "license": "MIT", 534 + "dependencies": { 535 + "@babel/code-frame": "^7.27.1", 536 + "@babel/parser": "^7.27.2", 537 + "@babel/types": "^7.27.1" 538 + }, 539 + "engines": { 540 + "node": ">=6.9.0" 541 + } 542 + }, 543 + "node_modules/@babel/traverse": { 544 + "version": "7.28.3", 545 + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", 546 + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", 547 + "license": "MIT", 548 + "dependencies": { 549 + "@babel/code-frame": "^7.27.1", 550 + "@babel/generator": "^7.28.3", 551 + "@babel/helper-globals": "^7.28.0", 552 + "@babel/parser": "^7.28.3", 553 + "@babel/template": "^7.27.2", 554 + "@babel/types": "^7.28.2", 555 + "debug": "^4.3.1" 556 + }, 557 + "engines": { 558 + "node": ">=6.9.0" 559 + } 560 + }, 561 + "node_modules/@babel/types": { 562 + "version": "7.28.2", 563 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", 564 + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", 565 + "license": "MIT", 566 + "dependencies": { 567 + "@babel/helper-string-parser": "^7.27.1", 568 + "@babel/helper-validator-identifier": "^7.27.1" 569 + }, 570 + "engines": { 571 + "node": ">=6.9.0" 572 + } 573 + }, 574 + "node_modules/@csstools/color-helpers": { 575 + "version": "5.1.0", 576 + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", 577 + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", 578 + "dev": true, 579 + "funding": [ 580 + { 581 + "type": "github", 582 + "url": "https://github.com/sponsors/csstools" 583 + }, 584 + { 585 + "type": "opencollective", 586 + "url": "https://opencollective.com/csstools" 587 + } 588 + ], 589 + "license": "MIT-0", 590 + "engines": { 591 + "node": ">=18" 592 + } 593 + }, 594 + "node_modules/@csstools/css-calc": { 595 + "version": "2.1.4", 596 + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", 597 + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", 598 + "dev": true, 599 + "funding": [ 600 + { 601 + "type": "github", 602 + "url": "https://github.com/sponsors/csstools" 603 + }, 604 + { 605 + "type": "opencollective", 606 + "url": "https://opencollective.com/csstools" 607 + } 608 + ], 609 + "license": "MIT", 610 + "engines": { 611 + "node": ">=18" 612 + }, 613 + "peerDependencies": { 614 + "@csstools/css-parser-algorithms": "^3.0.5", 615 + "@csstools/css-tokenizer": "^3.0.4" 616 + } 617 + }, 618 + "node_modules/@csstools/css-color-parser": { 619 + "version": "3.1.0", 620 + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", 621 + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", 622 + "dev": true, 623 + "funding": [ 624 + { 625 + "type": "github", 626 + "url": "https://github.com/sponsors/csstools" 627 + }, 628 + { 629 + "type": "opencollective", 630 + "url": "https://opencollective.com/csstools" 631 + } 632 + ], 633 + "license": "MIT", 634 + "dependencies": { 635 + "@csstools/color-helpers": "^5.1.0", 636 + "@csstools/css-calc": "^2.1.4" 637 + }, 638 + "engines": { 639 + "node": ">=18" 640 + }, 641 + "peerDependencies": { 642 + "@csstools/css-parser-algorithms": "^3.0.5", 643 + "@csstools/css-tokenizer": "^3.0.4" 644 + } 645 + }, 646 + "node_modules/@csstools/css-parser-algorithms": { 647 + "version": "3.0.5", 648 + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", 649 + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", 650 + "dev": true, 651 + "funding": [ 652 + { 653 + "type": "github", 654 + "url": "https://github.com/sponsors/csstools" 655 + }, 656 + { 657 + "type": "opencollective", 658 + "url": "https://opencollective.com/csstools" 659 + } 660 + ], 661 + "license": "MIT", 662 + "engines": { 663 + "node": ">=18" 664 + }, 665 + "peerDependencies": { 666 + "@csstools/css-tokenizer": "^3.0.4" 667 + } 668 + }, 669 + "node_modules/@csstools/css-tokenizer": { 670 + "version": "3.0.4", 671 + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", 672 + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", 673 + "dev": true, 674 + "funding": [ 675 + { 676 + "type": "github", 677 + "url": "https://github.com/sponsors/csstools" 678 + }, 679 + { 680 + "type": "opencollective", 681 + "url": "https://opencollective.com/csstools" 682 + } 683 + ], 684 + "license": "MIT", 685 + "engines": { 686 + "node": ">=18" 687 + } 688 + }, 689 + "node_modules/@esbuild/aix-ppc64": { 690 + "version": "0.25.9", 691 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", 692 + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", 693 + "cpu": [ 694 + "ppc64" 695 + ], 696 + "license": "MIT", 697 + "optional": true, 698 + "os": [ 699 + "aix" 700 + ], 701 + "engines": { 702 + "node": ">=18" 703 + } 704 + }, 705 + "node_modules/@esbuild/android-arm": { 706 + "version": "0.25.9", 707 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", 708 + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", 709 + "cpu": [ 710 + "arm" 711 + ], 712 + "license": "MIT", 713 + "optional": true, 714 + "os": [ 715 + "android" 716 + ], 717 + "engines": { 718 + "node": ">=18" 719 + } 720 + }, 721 + "node_modules/@esbuild/android-arm64": { 722 + "version": "0.25.9", 723 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", 724 + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", 725 + "cpu": [ 726 + "arm64" 727 + ], 728 + "license": "MIT", 729 + "optional": true, 730 + "os": [ 731 + "android" 732 + ], 733 + "engines": { 734 + "node": ">=18" 735 + } 736 + }, 737 + "node_modules/@esbuild/android-x64": { 738 + "version": "0.25.9", 739 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", 740 + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", 741 + "cpu": [ 742 + "x64" 743 + ], 744 + "license": "MIT", 745 + "optional": true, 746 + "os": [ 747 + "android" 748 + ], 749 + "engines": { 750 + "node": ">=18" 751 + } 752 + }, 753 + "node_modules/@esbuild/darwin-arm64": { 754 + "version": "0.25.9", 755 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", 756 + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", 757 + "cpu": [ 758 + "arm64" 759 + ], 760 + "license": "MIT", 761 + "optional": true, 762 + "os": [ 763 + "darwin" 764 + ], 765 + "engines": { 766 + "node": ">=18" 767 + } 768 + }, 769 + "node_modules/@esbuild/darwin-x64": { 770 + "version": "0.25.9", 771 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", 772 + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", 773 + "cpu": [ 774 + "x64" 775 + ], 776 + "license": "MIT", 777 + "optional": true, 778 + "os": [ 779 + "darwin" 780 + ], 781 + "engines": { 782 + "node": ">=18" 783 + } 784 + }, 785 + "node_modules/@esbuild/freebsd-arm64": { 786 + "version": "0.25.9", 787 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", 788 + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", 789 + "cpu": [ 790 + "arm64" 791 + ], 792 + "license": "MIT", 793 + "optional": true, 794 + "os": [ 795 + "freebsd" 796 + ], 797 + "engines": { 798 + "node": ">=18" 799 + } 800 + }, 801 + "node_modules/@esbuild/freebsd-x64": { 802 + "version": "0.25.9", 803 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", 804 + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", 805 + "cpu": [ 806 + "x64" 807 + ], 808 + "license": "MIT", 809 + "optional": true, 810 + "os": [ 811 + "freebsd" 812 + ], 813 + "engines": { 814 + "node": ">=18" 815 + } 816 + }, 817 + "node_modules/@esbuild/linux-arm": { 818 + "version": "0.25.9", 819 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", 820 + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", 821 + "cpu": [ 822 + "arm" 823 + ], 824 + "license": "MIT", 825 + "optional": true, 826 + "os": [ 827 + "linux" 828 + ], 829 + "engines": { 830 + "node": ">=18" 831 + } 832 + }, 833 + "node_modules/@esbuild/linux-arm64": { 834 + "version": "0.25.9", 835 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", 836 + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", 837 + "cpu": [ 838 + "arm64" 839 + ], 840 + "license": "MIT", 841 + "optional": true, 842 + "os": [ 843 + "linux" 844 + ], 845 + "engines": { 846 + "node": ">=18" 847 + } 848 + }, 849 + "node_modules/@esbuild/linux-ia32": { 850 + "version": "0.25.9", 851 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", 852 + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", 853 + "cpu": [ 854 + "ia32" 855 + ], 856 + "license": "MIT", 857 + "optional": true, 858 + "os": [ 859 + "linux" 860 + ], 861 + "engines": { 862 + "node": ">=18" 863 + } 864 + }, 865 + "node_modules/@esbuild/linux-loong64": { 866 + "version": "0.25.9", 867 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", 868 + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", 869 + "cpu": [ 870 + "loong64" 871 + ], 872 + "license": "MIT", 873 + "optional": true, 874 + "os": [ 875 + "linux" 876 + ], 877 + "engines": { 878 + "node": ">=18" 879 + } 880 + }, 881 + "node_modules/@esbuild/linux-mips64el": { 882 + "version": "0.25.9", 883 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", 884 + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", 885 + "cpu": [ 886 + "mips64el" 887 + ], 888 + "license": "MIT", 889 + "optional": true, 890 + "os": [ 891 + "linux" 892 + ], 893 + "engines": { 894 + "node": ">=18" 895 + } 896 + }, 897 + "node_modules/@esbuild/linux-ppc64": { 898 + "version": "0.25.9", 899 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", 900 + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", 901 + "cpu": [ 902 + "ppc64" 903 + ], 904 + "license": "MIT", 905 + "optional": true, 906 + "os": [ 907 + "linux" 908 + ], 909 + "engines": { 910 + "node": ">=18" 911 + } 912 + }, 913 + "node_modules/@esbuild/linux-riscv64": { 914 + "version": "0.25.9", 915 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", 916 + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", 917 + "cpu": [ 918 + "riscv64" 919 + ], 920 + "license": "MIT", 921 + "optional": true, 922 + "os": [ 923 + "linux" 924 + ], 925 + "engines": { 926 + "node": ">=18" 927 + } 928 + }, 929 + "node_modules/@esbuild/linux-s390x": { 930 + "version": "0.25.9", 931 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", 932 + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", 933 + "cpu": [ 934 + "s390x" 935 + ], 936 + "license": "MIT", 937 + "optional": true, 938 + "os": [ 939 + "linux" 940 + ], 941 + "engines": { 942 + "node": ">=18" 943 + } 944 + }, 945 + "node_modules/@esbuild/linux-x64": { 946 + "version": "0.25.9", 947 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", 948 + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", 949 + "cpu": [ 950 + "x64" 951 + ], 952 + "license": "MIT", 953 + "optional": true, 954 + "os": [ 955 + "linux" 956 + ], 957 + "engines": { 958 + "node": ">=18" 959 + } 960 + }, 961 + "node_modules/@esbuild/netbsd-arm64": { 962 + "version": "0.25.9", 963 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", 964 + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", 965 + "cpu": [ 966 + "arm64" 967 + ], 968 + "license": "MIT", 969 + "optional": true, 970 + "os": [ 971 + "netbsd" 972 + ], 973 + "engines": { 974 + "node": ">=18" 975 + } 976 + }, 977 + "node_modules/@esbuild/netbsd-x64": { 978 + "version": "0.25.9", 979 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", 980 + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", 981 + "cpu": [ 982 + "x64" 983 + ], 984 + "license": "MIT", 985 + "optional": true, 986 + "os": [ 987 + "netbsd" 988 + ], 989 + "engines": { 990 + "node": ">=18" 991 + } 992 + }, 993 + "node_modules/@esbuild/openbsd-arm64": { 994 + "version": "0.25.9", 995 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", 996 + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", 997 + "cpu": [ 998 + "arm64" 999 + ], 1000 + "license": "MIT", 1001 + "optional": true, 1002 + "os": [ 1003 + "openbsd" 1004 + ], 1005 + "engines": { 1006 + "node": ">=18" 1007 + } 1008 + }, 1009 + "node_modules/@esbuild/openbsd-x64": { 1010 + "version": "0.25.9", 1011 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", 1012 + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", 1013 + "cpu": [ 1014 + "x64" 1015 + ], 1016 + "license": "MIT", 1017 + "optional": true, 1018 + "os": [ 1019 + "openbsd" 1020 + ], 1021 + "engines": { 1022 + "node": ">=18" 1023 + } 1024 + }, 1025 + "node_modules/@esbuild/openharmony-arm64": { 1026 + "version": "0.25.9", 1027 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", 1028 + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", 1029 + "cpu": [ 1030 + "arm64" 1031 + ], 1032 + "license": "MIT", 1033 + "optional": true, 1034 + "os": [ 1035 + "openharmony" 1036 + ], 1037 + "engines": { 1038 + "node": ">=18" 1039 + } 1040 + }, 1041 + "node_modules/@esbuild/sunos-x64": { 1042 + "version": "0.25.9", 1043 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", 1044 + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", 1045 + "cpu": [ 1046 + "x64" 1047 + ], 1048 + "license": "MIT", 1049 + "optional": true, 1050 + "os": [ 1051 + "sunos" 1052 + ], 1053 + "engines": { 1054 + "node": ">=18" 1055 + } 1056 + }, 1057 + "node_modules/@esbuild/win32-arm64": { 1058 + "version": "0.25.9", 1059 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", 1060 + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", 1061 + "cpu": [ 1062 + "arm64" 1063 + ], 1064 + "license": "MIT", 1065 + "optional": true, 1066 + "os": [ 1067 + "win32" 1068 + ], 1069 + "engines": { 1070 + "node": ">=18" 1071 + } 1072 + }, 1073 + "node_modules/@esbuild/win32-ia32": { 1074 + "version": "0.25.9", 1075 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", 1076 + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", 1077 + "cpu": [ 1078 + "ia32" 1079 + ], 1080 + "license": "MIT", 1081 + "optional": true, 1082 + "os": [ 1083 + "win32" 1084 + ], 1085 + "engines": { 1086 + "node": ">=18" 1087 + } 1088 + }, 1089 + "node_modules/@esbuild/win32-x64": { 1090 + "version": "0.25.9", 1091 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", 1092 + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", 1093 + "cpu": [ 1094 + "x64" 1095 + ], 1096 + "license": "MIT", 1097 + "optional": true, 1098 + "os": [ 1099 + "win32" 1100 + ], 1101 + "engines": { 1102 + "node": ">=18" 1103 + } 1104 + }, 1105 + "node_modules/@isaacs/fs-minipass": { 1106 + "version": "4.0.1", 1107 + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", 1108 + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", 1109 + "license": "ISC", 1110 + "dependencies": { 1111 + "minipass": "^7.0.4" 1112 + }, 1113 + "engines": { 1114 + "node": ">=18.0.0" 1115 + } 1116 + }, 1117 + "node_modules/@jridgewell/gen-mapping": { 1118 + "version": "0.3.13", 1119 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 1120 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 1121 + "license": "MIT", 1122 + "dependencies": { 1123 + "@jridgewell/sourcemap-codec": "^1.5.0", 1124 + "@jridgewell/trace-mapping": "^0.3.24" 1125 + } 1126 + }, 1127 + "node_modules/@jridgewell/remapping": { 1128 + "version": "2.3.5", 1129 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 1130 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 1131 + "license": "MIT", 1132 + "dependencies": { 1133 + "@jridgewell/gen-mapping": "^0.3.5", 1134 + "@jridgewell/trace-mapping": "^0.3.24" 1135 + } 1136 + }, 1137 + "node_modules/@jridgewell/resolve-uri": { 1138 + "version": "3.1.2", 1139 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 1140 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 1141 + "license": "MIT", 1142 + "engines": { 1143 + "node": ">=6.0.0" 1144 + } 1145 + }, 1146 + "node_modules/@jridgewell/sourcemap-codec": { 1147 + "version": "1.5.5", 1148 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 1149 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 1150 + "license": "MIT" 1151 + }, 1152 + "node_modules/@jridgewell/trace-mapping": { 1153 + "version": "0.3.30", 1154 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", 1155 + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", 1156 + "license": "MIT", 1157 + "dependencies": { 1158 + "@jridgewell/resolve-uri": "^3.1.0", 1159 + "@jridgewell/sourcemap-codec": "^1.4.14" 1160 + } 1161 + }, 1162 + "node_modules/@mux/mux-data-google-ima": { 1163 + "version": "0.2.8", 1164 + "resolved": "https://registry.npmjs.org/@mux/mux-data-google-ima/-/mux-data-google-ima-0.2.8.tgz", 1165 + "integrity": "sha512-0ZEkHdcZ6bS8QtcjFcoJeZxJTpX7qRIledf4q1trMWPznugvtajCjCM2kieK/pzkZj1JM6liDRFs1PJSfVUs2A==", 1166 + "license": "MIT", 1167 + "dependencies": { 1168 + "mux-embed": "5.9.0" 1169 + } 1170 + }, 1171 + "node_modules/@mux/mux-player": { 1172 + "version": "3.5.3", 1173 + "resolved": "https://registry.npmjs.org/@mux/mux-player/-/mux-player-3.5.3.tgz", 1174 + "integrity": "sha512-uXKFXbdtioAi+clSVfD60Rw4r7OvA62u2jV6aar9loW9qMsmKv8LU+8uaIaWQjyAORp6E0S37GOVjo72T6O2eQ==", 1175 + "license": "MIT", 1176 + "dependencies": { 1177 + "@mux/mux-video": "0.26.1", 1178 + "@mux/playback-core": "0.30.1", 1179 + "media-chrome": "~4.11.1", 1180 + "player.style": "^0.1.9" 1181 + } 1182 + }, 1183 + "node_modules/@mux/mux-player-react": { 1184 + "version": "3.5.3", 1185 + "resolved": "https://registry.npmjs.org/@mux/mux-player-react/-/mux-player-react-3.5.3.tgz", 1186 + "integrity": "sha512-f0McZbIXYDkzecFwhhkf0JgEInPnsOClgBqBhkdhRlLRdrAzMATib+D3Di3rPkRHNH7rc/WWORvSxgJz6m6zkA==", 1187 + "license": "MIT", 1188 + "dependencies": { 1189 + "@mux/mux-player": "3.5.3", 1190 + "@mux/playback-core": "0.30.1", 1191 + "prop-types": "^15.8.1" 1192 + }, 1193 + "peerDependencies": { 1194 + "@types/react": "^17.0.0 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0", 1195 + "react": "^17.0.2 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0", 1196 + "react-dom": "^17.0.2 || ^17.0.2-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0" 1197 + }, 1198 + "peerDependenciesMeta": { 1199 + "@types/react": { 1200 + "optional": true 1201 + }, 1202 + "@types/react-dom": { 1203 + "optional": true 1204 + } 1205 + } 1206 + }, 1207 + "node_modules/@mux/mux-video": { 1208 + "version": "0.26.1", 1209 + "resolved": "https://registry.npmjs.org/@mux/mux-video/-/mux-video-0.26.1.tgz", 1210 + "integrity": "sha512-gkMdBAgNlB4+krANZHkQFzYWjWeNsJz69y1/hnPtmNQnpvW+O7oc71OffcZrbblyibSxWMQ6MQpYmBVjXlp6sA==", 1211 + "license": "MIT", 1212 + "dependencies": { 1213 + "@mux/mux-data-google-ima": "0.2.8", 1214 + "@mux/playback-core": "0.30.1", 1215 + "castable-video": "~1.1.10", 1216 + "custom-media-element": "~1.4.5", 1217 + "media-tracks": "~0.3.3" 1218 + } 1219 + }, 1220 + "node_modules/@mux/playback-core": { 1221 + "version": "0.30.1", 1222 + "resolved": "https://registry.npmjs.org/@mux/playback-core/-/playback-core-0.30.1.tgz", 1223 + "integrity": "sha512-rnO1NE9xHDyzbAkmE6ygJYcD7cyyMt7xXqWTykxlceaoSXLjUqgp42HDio7Lcidto4x/O4FIa7ztjV2aCBCXgQ==", 1224 + "license": "MIT", 1225 + "dependencies": { 1226 + "hls.js": "~1.6.6", 1227 + "mux-embed": "^5.8.3" 1228 + } 1229 + }, 1230 + "node_modules/@rolldown/pluginutils": { 1231 + "version": "1.0.0-beta.27", 1232 + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", 1233 + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", 1234 + "dev": true, 1235 + "license": "MIT" 1236 + }, 1237 + "node_modules/@rollup/rollup-android-arm-eabi": { 1238 + "version": "4.49.0", 1239 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", 1240 + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", 1241 + "cpu": [ 1242 + "arm" 1243 + ], 1244 + "license": "MIT", 1245 + "optional": true, 1246 + "os": [ 1247 + "android" 1248 + ] 1249 + }, 1250 + "node_modules/@rollup/rollup-android-arm64": { 1251 + "version": "4.49.0", 1252 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", 1253 + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", 1254 + "cpu": [ 1255 + "arm64" 1256 + ], 1257 + "license": "MIT", 1258 + "optional": true, 1259 + "os": [ 1260 + "android" 1261 + ] 1262 + }, 1263 + "node_modules/@rollup/rollup-darwin-arm64": { 1264 + "version": "4.49.0", 1265 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", 1266 + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", 1267 + "cpu": [ 1268 + "arm64" 1269 + ], 1270 + "license": "MIT", 1271 + "optional": true, 1272 + "os": [ 1273 + "darwin" 1274 + ] 1275 + }, 1276 + "node_modules/@rollup/rollup-darwin-x64": { 1277 + "version": "4.49.0", 1278 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", 1279 + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", 1280 + "cpu": [ 1281 + "x64" 1282 + ], 1283 + "license": "MIT", 1284 + "optional": true, 1285 + "os": [ 1286 + "darwin" 1287 + ] 1288 + }, 1289 + "node_modules/@rollup/rollup-freebsd-arm64": { 1290 + "version": "4.49.0", 1291 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", 1292 + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", 1293 + "cpu": [ 1294 + "arm64" 1295 + ], 1296 + "license": "MIT", 1297 + "optional": true, 1298 + "os": [ 1299 + "freebsd" 1300 + ] 1301 + }, 1302 + "node_modules/@rollup/rollup-freebsd-x64": { 1303 + "version": "4.49.0", 1304 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", 1305 + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", 1306 + "cpu": [ 1307 + "x64" 1308 + ], 1309 + "license": "MIT", 1310 + "optional": true, 1311 + "os": [ 1312 + "freebsd" 1313 + ] 1314 + }, 1315 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1316 + "version": "4.49.0", 1317 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", 1318 + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", 1319 + "cpu": [ 1320 + "arm" 1321 + ], 1322 + "license": "MIT", 1323 + "optional": true, 1324 + "os": [ 1325 + "linux" 1326 + ] 1327 + }, 1328 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1329 + "version": "4.49.0", 1330 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", 1331 + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", 1332 + "cpu": [ 1333 + "arm" 1334 + ], 1335 + "license": "MIT", 1336 + "optional": true, 1337 + "os": [ 1338 + "linux" 1339 + ] 1340 + }, 1341 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1342 + "version": "4.49.0", 1343 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", 1344 + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", 1345 + "cpu": [ 1346 + "arm64" 1347 + ], 1348 + "license": "MIT", 1349 + "optional": true, 1350 + "os": [ 1351 + "linux" 1352 + ] 1353 + }, 1354 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1355 + "version": "4.49.0", 1356 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", 1357 + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", 1358 + "cpu": [ 1359 + "arm64" 1360 + ], 1361 + "license": "MIT", 1362 + "optional": true, 1363 + "os": [ 1364 + "linux" 1365 + ] 1366 + }, 1367 + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 1368 + "version": "4.49.0", 1369 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", 1370 + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", 1371 + "cpu": [ 1372 + "loong64" 1373 + ], 1374 + "license": "MIT", 1375 + "optional": true, 1376 + "os": [ 1377 + "linux" 1378 + ] 1379 + }, 1380 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 1381 + "version": "4.49.0", 1382 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", 1383 + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", 1384 + "cpu": [ 1385 + "ppc64" 1386 + ], 1387 + "license": "MIT", 1388 + "optional": true, 1389 + "os": [ 1390 + "linux" 1391 + ] 1392 + }, 1393 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1394 + "version": "4.49.0", 1395 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", 1396 + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", 1397 + "cpu": [ 1398 + "riscv64" 1399 + ], 1400 + "license": "MIT", 1401 + "optional": true, 1402 + "os": [ 1403 + "linux" 1404 + ] 1405 + }, 1406 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 1407 + "version": "4.49.0", 1408 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", 1409 + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", 1410 + "cpu": [ 1411 + "riscv64" 1412 + ], 1413 + "license": "MIT", 1414 + "optional": true, 1415 + "os": [ 1416 + "linux" 1417 + ] 1418 + }, 1419 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1420 + "version": "4.49.0", 1421 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", 1422 + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", 1423 + "cpu": [ 1424 + "s390x" 1425 + ], 1426 + "license": "MIT", 1427 + "optional": true, 1428 + "os": [ 1429 + "linux" 1430 + ] 1431 + }, 1432 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1433 + "version": "4.49.0", 1434 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", 1435 + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", 1436 + "cpu": [ 1437 + "x64" 1438 + ], 1439 + "license": "MIT", 1440 + "optional": true, 1441 + "os": [ 1442 + "linux" 1443 + ] 1444 + }, 1445 + "node_modules/@rollup/rollup-linux-x64-musl": { 1446 + "version": "4.49.0", 1447 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", 1448 + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", 1449 + "cpu": [ 1450 + "x64" 1451 + ], 1452 + "license": "MIT", 1453 + "optional": true, 1454 + "os": [ 1455 + "linux" 1456 + ] 1457 + }, 1458 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1459 + "version": "4.49.0", 1460 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", 1461 + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", 1462 + "cpu": [ 1463 + "arm64" 1464 + ], 1465 + "license": "MIT", 1466 + "optional": true, 1467 + "os": [ 1468 + "win32" 1469 + ] 1470 + }, 1471 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1472 + "version": "4.49.0", 1473 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", 1474 + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", 1475 + "cpu": [ 1476 + "ia32" 1477 + ], 1478 + "license": "MIT", 1479 + "optional": true, 1480 + "os": [ 1481 + "win32" 1482 + ] 1483 + }, 1484 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1485 + "version": "4.49.0", 1486 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", 1487 + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", 1488 + "cpu": [ 1489 + "x64" 1490 + ], 1491 + "license": "MIT", 1492 + "optional": true, 1493 + "os": [ 1494 + "win32" 1495 + ] 1496 + }, 1497 + "node_modules/@solid-primitives/event-listener": { 1498 + "version": "2.4.3", 1499 + "resolved": "https://registry.npmjs.org/@solid-primitives/event-listener/-/event-listener-2.4.3.tgz", 1500 + "integrity": "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==", 1501 + "license": "MIT", 1502 + "dependencies": { 1503 + "@solid-primitives/utils": "^6.3.2" 1504 + }, 1505 + "peerDependencies": { 1506 + "solid-js": "^1.6.12" 1507 + } 1508 + }, 1509 + "node_modules/@solid-primitives/keyboard": { 1510 + "version": "1.3.3", 1511 + "resolved": "https://registry.npmjs.org/@solid-primitives/keyboard/-/keyboard-1.3.3.tgz", 1512 + "integrity": "sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==", 1513 + "license": "MIT", 1514 + "dependencies": { 1515 + "@solid-primitives/event-listener": "^2.4.3", 1516 + "@solid-primitives/rootless": "^1.5.2", 1517 + "@solid-primitives/utils": "^6.3.2" 1518 + }, 1519 + "peerDependencies": { 1520 + "solid-js": "^1.6.12" 1521 + } 1522 + }, 1523 + "node_modules/@solid-primitives/rootless": { 1524 + "version": "1.5.2", 1525 + "resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.5.2.tgz", 1526 + "integrity": "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==", 1527 + "license": "MIT", 1528 + "dependencies": { 1529 + "@solid-primitives/utils": "^6.3.2" 1530 + }, 1531 + "peerDependencies": { 1532 + "solid-js": "^1.6.12" 1533 + } 1534 + }, 1535 + "node_modules/@solid-primitives/utils": { 1536 + "version": "6.3.2", 1537 + "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.3.2.tgz", 1538 + "integrity": "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==", 1539 + "license": "MIT", 1540 + "peerDependencies": { 1541 + "solid-js": "^1.6.12" 1542 + } 1543 + }, 1544 + "node_modules/@svta/common-media-library": { 1545 + "version": "0.12.4", 1546 + "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.12.4.tgz", 1547 + "integrity": "sha512-9EuOoaNmz7JrfGwjsrD9SxF9otU5TNMnbLu1yU4BeLK0W5cDxVXXR58Z89q9u2AnHjIctscjMTYdlqQ1gojTuw==", 1548 + "license": "Apache-2.0" 1549 + }, 1550 + "node_modules/@tailwindcss/node": { 1551 + "version": "4.1.12", 1552 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", 1553 + "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", 1554 + "license": "MIT", 1555 + "dependencies": { 1556 + "@jridgewell/remapping": "^2.3.4", 1557 + "enhanced-resolve": "^5.18.3", 1558 + "jiti": "^2.5.1", 1559 + "lightningcss": "1.30.1", 1560 + "magic-string": "^0.30.17", 1561 + "source-map-js": "^1.2.1", 1562 + "tailwindcss": "4.1.12" 1563 + } 1564 + }, 1565 + "node_modules/@tailwindcss/oxide": { 1566 + "version": "4.1.12", 1567 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", 1568 + "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", 1569 + "hasInstallScript": true, 1570 + "license": "MIT", 1571 + "dependencies": { 1572 + "detect-libc": "^2.0.4", 1573 + "tar": "^7.4.3" 1574 + }, 1575 + "engines": { 1576 + "node": ">= 10" 1577 + }, 1578 + "optionalDependencies": { 1579 + "@tailwindcss/oxide-android-arm64": "4.1.12", 1580 + "@tailwindcss/oxide-darwin-arm64": "4.1.12", 1581 + "@tailwindcss/oxide-darwin-x64": "4.1.12", 1582 + "@tailwindcss/oxide-freebsd-x64": "4.1.12", 1583 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", 1584 + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", 1585 + "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", 1586 + "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", 1587 + "@tailwindcss/oxide-linux-x64-musl": "4.1.12", 1588 + "@tailwindcss/oxide-wasm32-wasi": "4.1.12", 1589 + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", 1590 + "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" 1591 + } 1592 + }, 1593 + "node_modules/@tailwindcss/oxide-android-arm64": { 1594 + "version": "4.1.12", 1595 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz", 1596 + "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==", 1597 + "cpu": [ 1598 + "arm64" 1599 + ], 1600 + "license": "MIT", 1601 + "optional": true, 1602 + "os": [ 1603 + "android" 1604 + ], 1605 + "engines": { 1606 + "node": ">= 10" 1607 + } 1608 + }, 1609 + "node_modules/@tailwindcss/oxide-darwin-arm64": { 1610 + "version": "4.1.12", 1611 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz", 1612 + "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==", 1613 + "cpu": [ 1614 + "arm64" 1615 + ], 1616 + "license": "MIT", 1617 + "optional": true, 1618 + "os": [ 1619 + "darwin" 1620 + ], 1621 + "engines": { 1622 + "node": ">= 10" 1623 + } 1624 + }, 1625 + "node_modules/@tailwindcss/oxide-darwin-x64": { 1626 + "version": "4.1.12", 1627 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", 1628 + "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", 1629 + "cpu": [ 1630 + "x64" 1631 + ], 1632 + "license": "MIT", 1633 + "optional": true, 1634 + "os": [ 1635 + "darwin" 1636 + ], 1637 + "engines": { 1638 + "node": ">= 10" 1639 + } 1640 + }, 1641 + "node_modules/@tailwindcss/oxide-freebsd-x64": { 1642 + "version": "4.1.12", 1643 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz", 1644 + "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==", 1645 + "cpu": [ 1646 + "x64" 1647 + ], 1648 + "license": "MIT", 1649 + "optional": true, 1650 + "os": [ 1651 + "freebsd" 1652 + ], 1653 + "engines": { 1654 + "node": ">= 10" 1655 + } 1656 + }, 1657 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 1658 + "version": "4.1.12", 1659 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz", 1660 + "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==", 1661 + "cpu": [ 1662 + "arm" 1663 + ], 1664 + "license": "MIT", 1665 + "optional": true, 1666 + "os": [ 1667 + "linux" 1668 + ], 1669 + "engines": { 1670 + "node": ">= 10" 1671 + } 1672 + }, 1673 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 1674 + "version": "4.1.12", 1675 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz", 1676 + "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==", 1677 + "cpu": [ 1678 + "arm64" 1679 + ], 1680 + "license": "MIT", 1681 + "optional": true, 1682 + "os": [ 1683 + "linux" 1684 + ], 1685 + "engines": { 1686 + "node": ">= 10" 1687 + } 1688 + }, 1689 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 1690 + "version": "4.1.12", 1691 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz", 1692 + "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==", 1693 + "cpu": [ 1694 + "arm64" 1695 + ], 1696 + "license": "MIT", 1697 + "optional": true, 1698 + "os": [ 1699 + "linux" 1700 + ], 1701 + "engines": { 1702 + "node": ">= 10" 1703 + } 1704 + }, 1705 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 1706 + "version": "4.1.12", 1707 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz", 1708 + "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==", 1709 + "cpu": [ 1710 + "x64" 1711 + ], 1712 + "license": "MIT", 1713 + "optional": true, 1714 + "os": [ 1715 + "linux" 1716 + ], 1717 + "engines": { 1718 + "node": ">= 10" 1719 + } 1720 + }, 1721 + "node_modules/@tailwindcss/oxide-linux-x64-musl": { 1722 + "version": "4.1.12", 1723 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz", 1724 + "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==", 1725 + "cpu": [ 1726 + "x64" 1727 + ], 1728 + "license": "MIT", 1729 + "optional": true, 1730 + "os": [ 1731 + "linux" 1732 + ], 1733 + "engines": { 1734 + "node": ">= 10" 1735 + } 1736 + }, 1737 + "node_modules/@tailwindcss/oxide-wasm32-wasi": { 1738 + "version": "4.1.12", 1739 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz", 1740 + "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==", 1741 + "bundleDependencies": [ 1742 + "@napi-rs/wasm-runtime", 1743 + "@emnapi/core", 1744 + "@emnapi/runtime", 1745 + "@tybys/wasm-util", 1746 + "@emnapi/wasi-threads", 1747 + "tslib" 1748 + ], 1749 + "cpu": [ 1750 + "wasm32" 1751 + ], 1752 + "license": "MIT", 1753 + "optional": true, 1754 + "dependencies": { 1755 + "@emnapi/core": "^1.4.5", 1756 + "@emnapi/runtime": "^1.4.5", 1757 + "@emnapi/wasi-threads": "^1.0.4", 1758 + "@napi-rs/wasm-runtime": "^0.2.12", 1759 + "@tybys/wasm-util": "^0.10.0", 1760 + "tslib": "^2.8.0" 1761 + }, 1762 + "engines": { 1763 + "node": ">=14.0.0" 1764 + } 1765 + }, 1766 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 1767 + "version": "4.1.12", 1768 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", 1769 + "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==", 1770 + "cpu": [ 1771 + "arm64" 1772 + ], 1773 + "license": "MIT", 1774 + "optional": true, 1775 + "os": [ 1776 + "win32" 1777 + ], 1778 + "engines": { 1779 + "node": ">= 10" 1780 + } 1781 + }, 1782 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 1783 + "version": "4.1.12", 1784 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", 1785 + "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", 1786 + "cpu": [ 1787 + "x64" 1788 + ], 1789 + "license": "MIT", 1790 + "optional": true, 1791 + "os": [ 1792 + "win32" 1793 + ], 1794 + "engines": { 1795 + "node": ">= 10" 1796 + } 1797 + }, 1798 + "node_modules/@tailwindcss/vite": { 1799 + "version": "4.1.12", 1800 + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz", 1801 + "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==", 1802 + "license": "MIT", 1803 + "dependencies": { 1804 + "@tailwindcss/node": "4.1.12", 1805 + "@tailwindcss/oxide": "4.1.12", 1806 + "tailwindcss": "4.1.12" 1807 + }, 1808 + "peerDependencies": { 1809 + "vite": "^5.2.0 || ^6 || ^7" 1810 + } 1811 + }, 1812 + "node_modules/@tanstack/devtools": { 1813 + "version": "0.3.0", 1814 + "resolved": "https://registry.npmjs.org/@tanstack/devtools/-/devtools-0.3.0.tgz", 1815 + "integrity": "sha512-uxj3MM2/ZlQDlhnBoUSlJC8oneJ+sPh6hK0kgiKZAEgrlUvZfUNgKGhrmMHZaAGl/WEglhZoxXwvvo9HAFT8yw==", 1816 + "license": "MIT", 1817 + "dependencies": { 1818 + "@solid-primitives/keyboard": "^1.2.8", 1819 + "@tanstack/devtools-event-bus": "0.2.1", 1820 + "@tanstack/devtools-ui": "0.2.2", 1821 + "clsx": "^2.1.1", 1822 + "goober": "^2.1.16", 1823 + "solid-js": "^1.9.7" 1824 + }, 1825 + "engines": { 1826 + "node": ">=18" 1827 + }, 1828 + "funding": { 1829 + "type": "github", 1830 + "url": "https://github.com/sponsors/tannerlinsley" 1831 + }, 1832 + "peerDependencies": { 1833 + "solid-js": ">=1.9.7" 1834 + } 1835 + }, 1836 + "node_modules/@tanstack/devtools-event-bus": { 1837 + "version": "0.2.1", 1838 + "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-bus/-/devtools-event-bus-0.2.1.tgz", 1839 + "integrity": "sha512-JMq3AmrQR2LH9P8Rcj1MTq8Iq/mPk/PyuqSw1L0hO2Wl8G1oz5ue31fS8u8lIgOCVR/mGdJah18p+Pj5OosRJA==", 1840 + "license": "MIT", 1841 + "dependencies": { 1842 + "ws": "^8.18.3" 1843 + }, 1844 + "engines": { 1845 + "node": ">=18" 1846 + }, 1847 + "funding": { 1848 + "type": "github", 1849 + "url": "https://github.com/sponsors/tannerlinsley" 1850 + } 1851 + }, 1852 + "node_modules/@tanstack/devtools-ui": { 1853 + "version": "0.2.2", 1854 + "resolved": "https://registry.npmjs.org/@tanstack/devtools-ui/-/devtools-ui-0.2.2.tgz", 1855 + "integrity": "sha512-G2gRyoGpjtr25w9BqQzPbefiJ9WALWYLOVxVr+NoqOLDA680nUO5KJjp7oLZMfnKssWxACgulaQ3DUmGGJuysQ==", 1856 + "license": "MIT", 1857 + "dependencies": { 1858 + "goober": "^2.1.16", 1859 + "solid-js": "^1.9.7" 1860 + }, 1861 + "engines": { 1862 + "node": ">=18" 1863 + }, 1864 + "funding": { 1865 + "type": "github", 1866 + "url": "https://github.com/sponsors/tannerlinsley" 1867 + }, 1868 + "peerDependencies": { 1869 + "solid-js": ">=1.9.7" 1870 + } 1871 + }, 1872 + "node_modules/@tanstack/history": { 1873 + "version": "1.131.2", 1874 + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.131.2.tgz", 1875 + "integrity": "sha512-cs1WKawpXIe+vSTeiZUuSBy8JFjEuDgdMKZFRLKwQysKo8y2q6Q1HvS74Yw+m5IhOW1nTZooa6rlgdfXcgFAaw==", 1876 + "license": "MIT", 1877 + "engines": { 1878 + "node": ">=12" 1879 + }, 1880 + "funding": { 1881 + "type": "github", 1882 + "url": "https://github.com/sponsors/tannerlinsley" 1883 + } 1884 + }, 1885 + "node_modules/@tanstack/react-devtools": { 1886 + "version": "0.2.2", 1887 + "resolved": "https://registry.npmjs.org/@tanstack/react-devtools/-/react-devtools-0.2.2.tgz", 1888 + "integrity": "sha512-Ig8ZYqUPJ+nwRvF/RpkQHPbgEkrL3b2PjeYBgXgT5OemyRUlmG12UutvMBV+bJuBsSOKHrNf29IvzC0Vw9Bt1A==", 1889 + "license": "MIT", 1890 + "dependencies": { 1891 + "@tanstack/devtools": "0.3.0" 1892 + }, 1893 + "engines": { 1894 + "node": ">=18" 1895 + }, 1896 + "funding": { 1897 + "type": "github", 1898 + "url": "https://github.com/sponsors/tannerlinsley" 1899 + }, 1900 + "peerDependencies": { 1901 + "@types/react": ">=16.8", 1902 + "@types/react-dom": ">=16.8", 1903 + "react": ">=16.8", 1904 + "react-dom": ">=16.8" 1905 + } 1906 + }, 1907 + "node_modules/@tanstack/react-router": { 1908 + "version": "1.131.28", 1909 + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.131.28.tgz", 1910 + "integrity": "sha512-vWExhrqHJuT9v+6/2DCQ4pVvPaYoLazMNw8WXiLNuzBXh1FuEoIGaW3jw3DEP0OJCmMiWtTi34NzQnakkQZlQg==", 1911 + "license": "MIT", 1912 + "dependencies": { 1913 + "@tanstack/history": "1.131.2", 1914 + "@tanstack/react-store": "^0.7.0", 1915 + "@tanstack/router-core": "1.131.28", 1916 + "isbot": "^5.1.22", 1917 + "tiny-invariant": "^1.3.3", 1918 + "tiny-warning": "^1.0.3" 1919 + }, 1920 + "engines": { 1921 + "node": ">=12" 1922 + }, 1923 + "funding": { 1924 + "type": "github", 1925 + "url": "https://github.com/sponsors/tannerlinsley" 1926 + }, 1927 + "peerDependencies": { 1928 + "react": ">=18.0.0 || >=19.0.0", 1929 + "react-dom": ">=18.0.0 || >=19.0.0" 1930 + } 1931 + }, 1932 + "node_modules/@tanstack/react-router-devtools": { 1933 + "version": "1.131.28", 1934 + "resolved": "https://registry.npmjs.org/@tanstack/react-router-devtools/-/react-router-devtools-1.131.28.tgz", 1935 + "integrity": "sha512-2EOxuvc2k7vT14XVEGJRuqEZhQkZ7RnHwpw2aGY6m/7xprl2elNwKtLExTntirAxE6HDokg8sRAnqvySHf1OVA==", 1936 + "license": "MIT", 1937 + "dependencies": { 1938 + "@tanstack/router-devtools-core": "1.131.28" 1939 + }, 1940 + "engines": { 1941 + "node": ">=12" 1942 + }, 1943 + "funding": { 1944 + "type": "github", 1945 + "url": "https://github.com/sponsors/tannerlinsley" 1946 + }, 1947 + "peerDependencies": { 1948 + "@tanstack/react-router": "^1.131.28", 1949 + "react": ">=18.0.0 || >=19.0.0", 1950 + "react-dom": ">=18.0.0 || >=19.0.0" 1951 + } 1952 + }, 1953 + "node_modules/@tanstack/react-store": { 1954 + "version": "0.7.4", 1955 + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.4.tgz", 1956 + "integrity": "sha512-DyG1e5Qz/c1cNLt/NdFbCA7K1QGuFXQYT6EfUltYMJoQ4LzBOGnOl5IjuxepNcRtmIKkGpmdMzdFZEkevgU9bQ==", 1957 + "license": "MIT", 1958 + "dependencies": { 1959 + "@tanstack/store": "0.7.4", 1960 + "use-sync-external-store": "^1.5.0" 1961 + }, 1962 + "funding": { 1963 + "type": "github", 1964 + "url": "https://github.com/sponsors/tannerlinsley" 1965 + }, 1966 + "peerDependencies": { 1967 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", 1968 + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 1969 + } 1970 + }, 1971 + "node_modules/@tanstack/router-core": { 1972 + "version": "1.131.28", 1973 + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.131.28.tgz", 1974 + "integrity": "sha512-f+vdfr3WKSS/BcqgI5s4vZg9xYb7NkvIolkaMELrbz3l+khkw1aTjx8wqCHRY4dqwIAxq+iZBZtMWXA7pztGJg==", 1975 + "license": "MIT", 1976 + "dependencies": { 1977 + "@tanstack/history": "1.131.2", 1978 + "@tanstack/store": "^0.7.0", 1979 + "cookie-es": "^1.2.2", 1980 + "seroval": "^1.3.2", 1981 + "seroval-plugins": "^1.3.2", 1982 + "tiny-invariant": "^1.3.3", 1983 + "tiny-warning": "^1.0.3" 1984 + }, 1985 + "engines": { 1986 + "node": ">=12" 1987 + }, 1988 + "funding": { 1989 + "type": "github", 1990 + "url": "https://github.com/sponsors/tannerlinsley" 1991 + } 1992 + }, 1993 + "node_modules/@tanstack/router-devtools-core": { 1994 + "version": "1.131.28", 1995 + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools-core/-/router-devtools-core-1.131.28.tgz", 1996 + "integrity": "sha512-CPj8wv/00sfHm5tjUCJ44A5tWBYvui5PVstkNfEyNW/Cmo6aknMk4SHiWIa/khCbj5HGjVMWSBRn6XZixEdOxw==", 1997 + "license": "MIT", 1998 + "dependencies": { 1999 + "clsx": "^2.1.1", 2000 + "goober": "^2.1.16", 2001 + "solid-js": "^1.9.5" 2002 + }, 2003 + "engines": { 2004 + "node": ">=12" 2005 + }, 2006 + "funding": { 2007 + "type": "github", 2008 + "url": "https://github.com/sponsors/tannerlinsley" 2009 + }, 2010 + "peerDependencies": { 2011 + "@tanstack/router-core": "^1.131.28", 2012 + "csstype": "^3.0.10", 2013 + "solid-js": ">=1.9.5", 2014 + "tiny-invariant": "^1.3.3" 2015 + }, 2016 + "peerDependenciesMeta": { 2017 + "csstype": { 2018 + "optional": true 2019 + } 2020 + } 2021 + }, 2022 + "node_modules/@tanstack/router-generator": { 2023 + "version": "1.131.28", 2024 + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.131.28.tgz", 2025 + "integrity": "sha512-e/6+2bfKhdiAgbFh4X0fADcnS7jNr6HqmDQ8Dcx9zpIGzWnj3pi9HUfHi7kmgZvxtCv8286BdVJsC7PqgxFHJw==", 2026 + "license": "MIT", 2027 + "dependencies": { 2028 + "@tanstack/router-core": "1.131.28", 2029 + "@tanstack/router-utils": "1.131.2", 2030 + "@tanstack/virtual-file-routes": "1.131.2", 2031 + "prettier": "^3.5.0", 2032 + "recast": "^0.23.11", 2033 + "source-map": "^0.7.4", 2034 + "tsx": "^4.19.2", 2035 + "zod": "^3.24.2" 2036 + }, 2037 + "engines": { 2038 + "node": ">=12" 2039 + }, 2040 + "funding": { 2041 + "type": "github", 2042 + "url": "https://github.com/sponsors/tannerlinsley" 2043 + } 2044 + }, 2045 + "node_modules/@tanstack/router-plugin": { 2046 + "version": "1.131.28", 2047 + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.131.28.tgz", 2048 + "integrity": "sha512-7PxDjczsv90YQtphmjaakvHi8yF+d1mSs+ro8yIA/KrGD1+TaWvguAdFceDn/2ZGy5/tmCOVXQwuOg95HL8u6g==", 2049 + "license": "MIT", 2050 + "dependencies": { 2051 + "@babel/core": "^7.27.7", 2052 + "@babel/plugin-syntax-jsx": "^7.27.1", 2053 + "@babel/plugin-syntax-typescript": "^7.27.1", 2054 + "@babel/template": "^7.27.2", 2055 + "@babel/traverse": "^7.27.7", 2056 + "@babel/types": "^7.27.7", 2057 + "@tanstack/router-core": "1.131.28", 2058 + "@tanstack/router-generator": "1.131.28", 2059 + "@tanstack/router-utils": "1.131.2", 2060 + "@tanstack/virtual-file-routes": "1.131.2", 2061 + "babel-dead-code-elimination": "^1.0.10", 2062 + "chokidar": "^3.6.0", 2063 + "unplugin": "^2.1.2", 2064 + "zod": "^3.24.2" 2065 + }, 2066 + "engines": { 2067 + "node": ">=12" 2068 + }, 2069 + "funding": { 2070 + "type": "github", 2071 + "url": "https://github.com/sponsors/tannerlinsley" 2072 + }, 2073 + "peerDependencies": { 2074 + "@rsbuild/core": ">=1.0.2", 2075 + "@tanstack/react-router": "^1.131.28", 2076 + "vite": ">=5.0.0 || >=6.0.0", 2077 + "vite-plugin-solid": "^2.11.2", 2078 + "webpack": ">=5.92.0" 2079 + }, 2080 + "peerDependenciesMeta": { 2081 + "@rsbuild/core": { 2082 + "optional": true 2083 + }, 2084 + "@tanstack/react-router": { 2085 + "optional": true 2086 + }, 2087 + "vite": { 2088 + "optional": true 2089 + }, 2090 + "vite-plugin-solid": { 2091 + "optional": true 2092 + }, 2093 + "webpack": { 2094 + "optional": true 2095 + } 2096 + } 2097 + }, 2098 + "node_modules/@tanstack/router-utils": { 2099 + "version": "1.131.2", 2100 + "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.131.2.tgz", 2101 + "integrity": "sha512-sr3x0d2sx9YIJoVth0QnfEcAcl+39sQYaNQxThtHmRpyeFYNyM2TTH+Ud3TNEnI3bbzmLYEUD+7YqB987GzhDA==", 2102 + "license": "MIT", 2103 + "dependencies": { 2104 + "@babel/core": "^7.27.4", 2105 + "@babel/generator": "^7.27.5", 2106 + "@babel/parser": "^7.27.5", 2107 + "@babel/preset-typescript": "^7.27.1", 2108 + "ansis": "^4.1.0", 2109 + "diff": "^8.0.2" 2110 + }, 2111 + "engines": { 2112 + "node": ">=12" 2113 + }, 2114 + "funding": { 2115 + "type": "github", 2116 + "url": "https://github.com/sponsors/tannerlinsley" 2117 + } 2118 + }, 2119 + "node_modules/@tanstack/store": { 2120 + "version": "0.7.4", 2121 + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.4.tgz", 2122 + "integrity": "sha512-F1XqZQici1Aq6WigEfcxJSml92nW+85Om8ElBMokPNg5glCYVOmPkZGIQeieYFxcPiKTfwo0MTOQpUyJtwncrg==", 2123 + "license": "MIT", 2124 + "funding": { 2125 + "type": "github", 2126 + "url": "https://github.com/sponsors/tannerlinsley" 2127 + } 2128 + }, 2129 + "node_modules/@tanstack/virtual-file-routes": { 2130 + "version": "1.131.2", 2131 + "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.131.2.tgz", 2132 + "integrity": "sha512-VEEOxc4mvyu67O+Bl0APtYjwcNRcL9it9B4HKbNgcBTIOEalhk+ufBl4kiqc8WP1sx1+NAaiS+3CcJBhrqaSRg==", 2133 + "license": "MIT", 2134 + "engines": { 2135 + "node": ">=12" 2136 + }, 2137 + "funding": { 2138 + "type": "github", 2139 + "url": "https://github.com/sponsors/tannerlinsley" 2140 + } 2141 + }, 2142 + "node_modules/@testing-library/dom": { 2143 + "version": "10.4.1", 2144 + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", 2145 + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", 2146 + "dev": true, 2147 + "license": "MIT", 2148 + "dependencies": { 2149 + "@babel/code-frame": "^7.10.4", 2150 + "@babel/runtime": "^7.12.5", 2151 + "@types/aria-query": "^5.0.1", 2152 + "aria-query": "5.3.0", 2153 + "dom-accessibility-api": "^0.5.9", 2154 + "lz-string": "^1.5.0", 2155 + "picocolors": "1.1.1", 2156 + "pretty-format": "^27.0.2" 2157 + }, 2158 + "engines": { 2159 + "node": ">=18" 2160 + } 2161 + }, 2162 + "node_modules/@testing-library/react": { 2163 + "version": "16.3.0", 2164 + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", 2165 + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", 2166 + "dev": true, 2167 + "license": "MIT", 2168 + "dependencies": { 2169 + "@babel/runtime": "^7.12.5" 2170 + }, 2171 + "engines": { 2172 + "node": ">=18" 2173 + }, 2174 + "peerDependencies": { 2175 + "@testing-library/dom": "^10.0.0", 2176 + "@types/react": "^18.0.0 || ^19.0.0", 2177 + "@types/react-dom": "^18.0.0 || ^19.0.0", 2178 + "react": "^18.0.0 || ^19.0.0", 2179 + "react-dom": "^18.0.0 || ^19.0.0" 2180 + }, 2181 + "peerDependenciesMeta": { 2182 + "@types/react": { 2183 + "optional": true 2184 + }, 2185 + "@types/react-dom": { 2186 + "optional": true 2187 + } 2188 + } 2189 + }, 2190 + "node_modules/@types/aria-query": { 2191 + "version": "5.0.4", 2192 + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", 2193 + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", 2194 + "dev": true, 2195 + "license": "MIT" 2196 + }, 2197 + "node_modules/@types/babel__core": { 2198 + "version": "7.20.5", 2199 + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", 2200 + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", 2201 + "dev": true, 2202 + "license": "MIT", 2203 + "dependencies": { 2204 + "@babel/parser": "^7.20.7", 2205 + "@babel/types": "^7.20.7", 2206 + "@types/babel__generator": "*", 2207 + "@types/babel__template": "*", 2208 + "@types/babel__traverse": "*" 2209 + } 2210 + }, 2211 + "node_modules/@types/babel__generator": { 2212 + "version": "7.27.0", 2213 + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", 2214 + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", 2215 + "dev": true, 2216 + "license": "MIT", 2217 + "dependencies": { 2218 + "@babel/types": "^7.0.0" 2219 + } 2220 + }, 2221 + "node_modules/@types/babel__template": { 2222 + "version": "7.4.4", 2223 + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", 2224 + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", 2225 + "dev": true, 2226 + "license": "MIT", 2227 + "dependencies": { 2228 + "@babel/parser": "^7.1.0", 2229 + "@babel/types": "^7.0.0" 2230 + } 2231 + }, 2232 + "node_modules/@types/babel__traverse": { 2233 + "version": "7.28.0", 2234 + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", 2235 + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", 2236 + "dev": true, 2237 + "license": "MIT", 2238 + "dependencies": { 2239 + "@babel/types": "^7.28.2" 2240 + } 2241 + }, 2242 + "node_modules/@types/chai": { 2243 + "version": "5.2.2", 2244 + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", 2245 + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", 2246 + "dev": true, 2247 + "license": "MIT", 2248 + "dependencies": { 2249 + "@types/deep-eql": "*" 2250 + } 2251 + }, 2252 + "node_modules/@types/deep-eql": { 2253 + "version": "4.0.2", 2254 + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", 2255 + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", 2256 + "dev": true, 2257 + "license": "MIT" 2258 + }, 2259 + "node_modules/@types/estree": { 2260 + "version": "1.0.8", 2261 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 2262 + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 2263 + "license": "MIT" 2264 + }, 2265 + "node_modules/@types/node": { 2266 + "version": "24.3.0", 2267 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", 2268 + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", 2269 + "devOptional": true, 2270 + "license": "MIT", 2271 + "dependencies": { 2272 + "undici-types": "~7.10.0" 2273 + } 2274 + }, 2275 + "node_modules/@types/react": { 2276 + "version": "19.1.12", 2277 + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", 2278 + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", 2279 + "license": "MIT", 2280 + "dependencies": { 2281 + "csstype": "^3.0.2" 2282 + } 2283 + }, 2284 + "node_modules/@types/react-dom": { 2285 + "version": "19.1.9", 2286 + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", 2287 + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", 2288 + "license": "MIT", 2289 + "peerDependencies": { 2290 + "@types/react": "^19.0.0" 2291 + } 2292 + }, 2293 + "node_modules/@vercel/edge": { 2294 + "version": "1.2.2", 2295 + "resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.2.2.tgz", 2296 + "integrity": "sha512-1+y+f6rk0Yc9ss9bRDgz/gdpLimwoRteKHhrcgHvEpjbP1nyT3ByqEMWm2BTcpIO5UtDmIFXc8zdq4LR190PDA==", 2297 + "license": "Apache-2.0" 2298 + }, 2299 + "node_modules/@vimeo/player": { 2300 + "version": "2.29.0", 2301 + "resolved": "https://registry.npmjs.org/@vimeo/player/-/player-2.29.0.tgz", 2302 + "integrity": "sha512-9JjvjeqUndb9otCCFd0/+2ESsLk7VkDE6sxOBy9iy2ukezuQbplVRi+g9g59yAurKofbmTi/KcKxBGO/22zWRw==", 2303 + "license": "MIT", 2304 + "dependencies": { 2305 + "native-promise-only": "0.8.1", 2306 + "weakmap-polyfill": "2.0.4" 2307 + } 2308 + }, 2309 + "node_modules/@vitejs/plugin-react": { 2310 + "version": "4.7.0", 2311 + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", 2312 + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", 2313 + "dev": true, 2314 + "license": "MIT", 2315 + "dependencies": { 2316 + "@babel/core": "^7.28.0", 2317 + "@babel/plugin-transform-react-jsx-self": "^7.27.1", 2318 + "@babel/plugin-transform-react-jsx-source": "^7.27.1", 2319 + "@rolldown/pluginutils": "1.0.0-beta.27", 2320 + "@types/babel__core": "^7.20.5", 2321 + "react-refresh": "^0.17.0" 2322 + }, 2323 + "engines": { 2324 + "node": "^14.18.0 || >=16.0.0" 2325 + }, 2326 + "peerDependencies": { 2327 + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" 2328 + } 2329 + }, 2330 + "node_modules/@vitest/expect": { 2331 + "version": "3.2.4", 2332 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", 2333 + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", 2334 + "dev": true, 2335 + "license": "MIT", 2336 + "dependencies": { 2337 + "@types/chai": "^5.2.2", 2338 + "@vitest/spy": "3.2.4", 2339 + "@vitest/utils": "3.2.4", 2340 + "chai": "^5.2.0", 2341 + "tinyrainbow": "^2.0.0" 2342 + }, 2343 + "funding": { 2344 + "url": "https://opencollective.com/vitest" 2345 + } 2346 + }, 2347 + "node_modules/@vitest/mocker": { 2348 + "version": "3.2.4", 2349 + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", 2350 + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", 2351 + "dev": true, 2352 + "license": "MIT", 2353 + "dependencies": { 2354 + "@vitest/spy": "3.2.4", 2355 + "estree-walker": "^3.0.3", 2356 + "magic-string": "^0.30.17" 2357 + }, 2358 + "funding": { 2359 + "url": "https://opencollective.com/vitest" 2360 + }, 2361 + "peerDependencies": { 2362 + "msw": "^2.4.9", 2363 + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" 2364 + }, 2365 + "peerDependenciesMeta": { 2366 + "msw": { 2367 + "optional": true 2368 + }, 2369 + "vite": { 2370 + "optional": true 2371 + } 2372 + } 2373 + }, 2374 + "node_modules/@vitest/pretty-format": { 2375 + "version": "3.2.4", 2376 + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", 2377 + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", 2378 + "dev": true, 2379 + "license": "MIT", 2380 + "dependencies": { 2381 + "tinyrainbow": "^2.0.0" 2382 + }, 2383 + "funding": { 2384 + "url": "https://opencollective.com/vitest" 2385 + } 2386 + }, 2387 + "node_modules/@vitest/runner": { 2388 + "version": "3.2.4", 2389 + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", 2390 + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", 2391 + "dev": true, 2392 + "license": "MIT", 2393 + "dependencies": { 2394 + "@vitest/utils": "3.2.4", 2395 + "pathe": "^2.0.3", 2396 + "strip-literal": "^3.0.0" 2397 + }, 2398 + "funding": { 2399 + "url": "https://opencollective.com/vitest" 2400 + } 2401 + }, 2402 + "node_modules/@vitest/snapshot": { 2403 + "version": "3.2.4", 2404 + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", 2405 + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", 2406 + "dev": true, 2407 + "license": "MIT", 2408 + "dependencies": { 2409 + "@vitest/pretty-format": "3.2.4", 2410 + "magic-string": "^0.30.17", 2411 + "pathe": "^2.0.3" 2412 + }, 2413 + "funding": { 2414 + "url": "https://opencollective.com/vitest" 2415 + } 2416 + }, 2417 + "node_modules/@vitest/spy": { 2418 + "version": "3.2.4", 2419 + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", 2420 + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", 2421 + "dev": true, 2422 + "license": "MIT", 2423 + "dependencies": { 2424 + "tinyspy": "^4.0.3" 2425 + }, 2426 + "funding": { 2427 + "url": "https://opencollective.com/vitest" 2428 + } 2429 + }, 2430 + "node_modules/@vitest/utils": { 2431 + "version": "3.2.4", 2432 + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", 2433 + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", 2434 + "dev": true, 2435 + "license": "MIT", 2436 + "dependencies": { 2437 + "@vitest/pretty-format": "3.2.4", 2438 + "loupe": "^3.1.4", 2439 + "tinyrainbow": "^2.0.0" 2440 + }, 2441 + "funding": { 2442 + "url": "https://opencollective.com/vitest" 2443 + } 2444 + }, 2445 + "node_modules/acorn": { 2446 + "version": "8.15.0", 2447 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 2448 + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 2449 + "license": "MIT", 2450 + "bin": { 2451 + "acorn": "bin/acorn" 2452 + }, 2453 + "engines": { 2454 + "node": ">=0.4.0" 2455 + } 2456 + }, 2457 + "node_modules/agent-base": { 2458 + "version": "7.1.4", 2459 + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", 2460 + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", 2461 + "dev": true, 2462 + "license": "MIT", 2463 + "engines": { 2464 + "node": ">= 14" 2465 + } 2466 + }, 2467 + "node_modules/ansi-regex": { 2468 + "version": "5.0.1", 2469 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 2470 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 2471 + "dev": true, 2472 + "license": "MIT", 2473 + "engines": { 2474 + "node": ">=8" 2475 + } 2476 + }, 2477 + "node_modules/ansi-styles": { 2478 + "version": "5.2.0", 2479 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", 2480 + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", 2481 + "dev": true, 2482 + "license": "MIT", 2483 + "engines": { 2484 + "node": ">=10" 2485 + }, 2486 + "funding": { 2487 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2488 + } 2489 + }, 2490 + "node_modules/ansis": { 2491 + "version": "4.1.0", 2492 + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", 2493 + "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", 2494 + "license": "ISC", 2495 + "engines": { 2496 + "node": ">=14" 2497 + } 2498 + }, 2499 + "node_modules/anymatch": { 2500 + "version": "3.1.3", 2501 + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 2502 + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 2503 + "license": "ISC", 2504 + "dependencies": { 2505 + "normalize-path": "^3.0.0", 2506 + "picomatch": "^2.0.4" 2507 + }, 2508 + "engines": { 2509 + "node": ">= 8" 2510 + } 2511 + }, 2512 + "node_modules/aria-query": { 2513 + "version": "5.3.0", 2514 + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", 2515 + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", 2516 + "dev": true, 2517 + "license": "Apache-2.0", 2518 + "dependencies": { 2519 + "dequal": "^2.0.3" 2520 + } 2521 + }, 2522 + "node_modules/assertion-error": { 2523 + "version": "2.0.1", 2524 + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 2525 + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 2526 + "dev": true, 2527 + "license": "MIT", 2528 + "engines": { 2529 + "node": ">=12" 2530 + } 2531 + }, 2532 + "node_modules/ast-types": { 2533 + "version": "0.16.1", 2534 + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", 2535 + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", 2536 + "license": "MIT", 2537 + "dependencies": { 2538 + "tslib": "^2.0.1" 2539 + }, 2540 + "engines": { 2541 + "node": ">=4" 2542 + } 2543 + }, 2544 + "node_modules/await-lock": { 2545 + "version": "2.2.2", 2546 + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", 2547 + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", 2548 + "license": "MIT" 2549 + }, 2550 + "node_modules/babel-dead-code-elimination": { 2551 + "version": "1.0.10", 2552 + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.10.tgz", 2553 + "integrity": "sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==", 2554 + "license": "MIT", 2555 + "dependencies": { 2556 + "@babel/core": "^7.23.7", 2557 + "@babel/parser": "^7.23.6", 2558 + "@babel/traverse": "^7.23.7", 2559 + "@babel/types": "^7.23.6" 2560 + } 2561 + }, 2562 + "node_modules/bcp-47": { 2563 + "version": "2.1.0", 2564 + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", 2565 + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", 2566 + "license": "MIT", 2567 + "dependencies": { 2568 + "is-alphabetical": "^2.0.0", 2569 + "is-alphanumerical": "^2.0.0", 2570 + "is-decimal": "^2.0.0" 2571 + }, 2572 + "funding": { 2573 + "type": "github", 2574 + "url": "https://github.com/sponsors/wooorm" 2575 + } 2576 + }, 2577 + "node_modules/bcp-47-match": { 2578 + "version": "2.0.3", 2579 + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", 2580 + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", 2581 + "license": "MIT", 2582 + "funding": { 2583 + "type": "github", 2584 + "url": "https://github.com/sponsors/wooorm" 2585 + } 2586 + }, 2587 + "node_modules/bcp-47-normalize": { 2588 + "version": "2.3.0", 2589 + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz", 2590 + "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==", 2591 + "license": "MIT", 2592 + "dependencies": { 2593 + "bcp-47": "^2.0.0", 2594 + "bcp-47-match": "^2.0.0" 2595 + }, 2596 + "funding": { 2597 + "type": "github", 2598 + "url": "https://github.com/sponsors/wooorm" 2599 + } 2600 + }, 2601 + "node_modules/binary-extensions": { 2602 + "version": "2.3.0", 2603 + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 2604 + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 2605 + "license": "MIT", 2606 + "engines": { 2607 + "node": ">=8" 2608 + }, 2609 + "funding": { 2610 + "url": "https://github.com/sponsors/sindresorhus" 2611 + } 2612 + }, 2613 + "node_modules/braces": { 2614 + "version": "3.0.3", 2615 + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 2616 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 2617 + "license": "MIT", 2618 + "dependencies": { 2619 + "fill-range": "^7.1.1" 2620 + }, 2621 + "engines": { 2622 + "node": ">=8" 2623 + } 2624 + }, 2625 + "node_modules/browserslist": { 2626 + "version": "4.25.4", 2627 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", 2628 + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", 2629 + "funding": [ 2630 + { 2631 + "type": "opencollective", 2632 + "url": "https://opencollective.com/browserslist" 2633 + }, 2634 + { 2635 + "type": "tidelift", 2636 + "url": "https://tidelift.com/funding/github/npm/browserslist" 2637 + }, 2638 + { 2639 + "type": "github", 2640 + "url": "https://github.com/sponsors/ai" 2641 + } 2642 + ], 2643 + "license": "MIT", 2644 + "dependencies": { 2645 + "caniuse-lite": "^1.0.30001737", 2646 + "electron-to-chromium": "^1.5.211", 2647 + "node-releases": "^2.0.19", 2648 + "update-browserslist-db": "^1.1.3" 2649 + }, 2650 + "bin": { 2651 + "browserslist": "cli.js" 2652 + }, 2653 + "engines": { 2654 + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 2655 + } 2656 + }, 2657 + "node_modules/cac": { 2658 + "version": "6.7.14", 2659 + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 2660 + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 2661 + "dev": true, 2662 + "license": "MIT", 2663 + "engines": { 2664 + "node": ">=8" 2665 + } 2666 + }, 2667 + "node_modules/caniuse-lite": { 2668 + "version": "1.0.30001737", 2669 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", 2670 + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", 2671 + "funding": [ 2672 + { 2673 + "type": "opencollective", 2674 + "url": "https://opencollective.com/browserslist" 2675 + }, 2676 + { 2677 + "type": "tidelift", 2678 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 2679 + }, 2680 + { 2681 + "type": "github", 2682 + "url": "https://github.com/sponsors/ai" 2683 + } 2684 + ], 2685 + "license": "CC-BY-4.0" 2686 + }, 2687 + "node_modules/castable-video": { 2688 + "version": "1.1.10", 2689 + "resolved": "https://registry.npmjs.org/castable-video/-/castable-video-1.1.10.tgz", 2690 + "integrity": "sha512-/T1I0A4VG769wTEZ8gWuy1Crn9saAfRTd1UYTb8xbOPlN78+zOi/1nU2dD5koNkfE5VWvgabkIqrGKmyNXOjSQ==", 2691 + "license": "MIT", 2692 + "dependencies": { 2693 + "custom-media-element": "~1.4.5" 2694 + } 2695 + }, 2696 + "node_modules/ce-la-react": { 2697 + "version": "0.3.1", 2698 + "resolved": "https://registry.npmjs.org/ce-la-react/-/ce-la-react-0.3.1.tgz", 2699 + "integrity": "sha512-g0YwpZDPIwTwFumGTzNHcgJA6VhFfFCJkSNdUdC04br2UfU+56JDrJrJva3FZ7MToB4NDHAFBiPE/PZdNl1mQA==", 2700 + "license": "BSD-3-Clause", 2701 + "peerDependencies": { 2702 + "react": ">=17.0.0" 2703 + } 2704 + }, 2705 + "node_modules/chai": { 2706 + "version": "5.3.3", 2707 + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", 2708 + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", 2709 + "dev": true, 2710 + "license": "MIT", 2711 + "dependencies": { 2712 + "assertion-error": "^2.0.1", 2713 + "check-error": "^2.1.1", 2714 + "deep-eql": "^5.0.1", 2715 + "loupe": "^3.1.0", 2716 + "pathval": "^2.0.0" 2717 + }, 2718 + "engines": { 2719 + "node": ">=18" 2720 + } 2721 + }, 2722 + "node_modules/check-error": { 2723 + "version": "2.1.1", 2724 + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", 2725 + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", 2726 + "dev": true, 2727 + "license": "MIT", 2728 + "engines": { 2729 + "node": ">= 16" 2730 + } 2731 + }, 2732 + "node_modules/chokidar": { 2733 + "version": "3.6.0", 2734 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 2735 + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 2736 + "license": "MIT", 2737 + "dependencies": { 2738 + "anymatch": "~3.1.2", 2739 + "braces": "~3.0.2", 2740 + "glob-parent": "~5.1.2", 2741 + "is-binary-path": "~2.1.0", 2742 + "is-glob": "~4.0.1", 2743 + "normalize-path": "~3.0.0", 2744 + "readdirp": "~3.6.0" 2745 + }, 2746 + "engines": { 2747 + "node": ">= 8.10.0" 2748 + }, 2749 + "funding": { 2750 + "url": "https://paulmillr.com/funding/" 2751 + }, 2752 + "optionalDependencies": { 2753 + "fsevents": "~2.3.2" 2754 + } 2755 + }, 2756 + "node_modules/chownr": { 2757 + "version": "3.0.0", 2758 + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", 2759 + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", 2760 + "license": "BlueOak-1.0.0", 2761 + "engines": { 2762 + "node": ">=18" 2763 + } 2764 + }, 2765 + "node_modules/cloudflare-video-element": { 2766 + "version": "1.3.4", 2767 + "resolved": "https://registry.npmjs.org/cloudflare-video-element/-/cloudflare-video-element-1.3.4.tgz", 2768 + "integrity": "sha512-F9g+tXzGEXI6v6L48qXxr8vnR8+L6yy7IhpJxK++lpzuVekMHTixxH7/dzLuq6OacVGziU4RB5pzZYJ7/LYtJg==", 2769 + "license": "MIT" 2770 + }, 2771 + "node_modules/clsx": { 2772 + "version": "2.1.1", 2773 + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 2774 + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 2775 + "license": "MIT", 2776 + "engines": { 2777 + "node": ">=6" 2778 + } 2779 + }, 2780 + "node_modules/codem-isoboxer": { 2781 + "version": "0.3.10", 2782 + "resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.10.tgz", 2783 + "integrity": "sha512-eNk3TRV+xQMJ1PEj0FQGY8KD4m0GPxT487XJ+Iftm7mVa9WpPFDMWqPt+46buiP5j5Wzqe5oMIhqBcAeKfygSA==", 2784 + "license": "MIT" 2785 + }, 2786 + "node_modules/convert-source-map": { 2787 + "version": "2.0.0", 2788 + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 2789 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 2790 + "license": "MIT" 2791 + }, 2792 + "node_modules/cookie-es": { 2793 + "version": "1.2.2", 2794 + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", 2795 + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", 2796 + "license": "MIT" 2797 + }, 2798 + "node_modules/cssstyle": { 2799 + "version": "4.6.0", 2800 + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", 2801 + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", 2802 + "dev": true, 2803 + "license": "MIT", 2804 + "dependencies": { 2805 + "@asamuzakjp/css-color": "^3.2.0", 2806 + "rrweb-cssom": "^0.8.0" 2807 + }, 2808 + "engines": { 2809 + "node": ">=18" 2810 + } 2811 + }, 2812 + "node_modules/csstype": { 2813 + "version": "3.1.3", 2814 + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 2815 + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 2816 + "license": "MIT" 2817 + }, 2818 + "node_modules/custom-media-element": { 2819 + "version": "1.4.5", 2820 + "resolved": "https://registry.npmjs.org/custom-media-element/-/custom-media-element-1.4.5.tgz", 2821 + "integrity": "sha512-cjrsQufETwxjvwZbYbKBCJNvmQ2++G9AvT45zDi7NXL9k2PdVcs2h0jQz96J6G4TMKRCcEsoJ+QTgQD00Igtjw==", 2822 + "license": "MIT" 2823 + }, 2824 + "node_modules/dash-video-element": { 2825 + "version": "0.1.6", 2826 + "resolved": "https://registry.npmjs.org/dash-video-element/-/dash-video-element-0.1.6.tgz", 2827 + "integrity": "sha512-4gHShaQjcFv6diX5EzB6qAdUGKlIUGGZY8J8yp2pQkWqR0jX4c6plYy0cFraN7mr0DZINe8ujDN1fssDYxJjcg==", 2828 + "license": "MIT", 2829 + "dependencies": { 2830 + "custom-media-element": "^1.4.5", 2831 + "dashjs": "^5.0.3" 2832 + } 2833 + }, 2834 + "node_modules/dashjs": { 2835 + "version": "5.0.3", 2836 + "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-5.0.3.tgz", 2837 + "integrity": "sha512-TXndNnCUjFjF2nYBxDVba+hWRpVkadkQ8flLp7kHkem+5+wZTfRShJCnVkPUosmjS0YPE9fVNLbYPJxHBeQZvA==", 2838 + "license": "BSD-3-Clause", 2839 + "dependencies": { 2840 + "@svta/common-media-library": "^0.12.4", 2841 + "bcp-47-match": "^2.0.3", 2842 + "bcp-47-normalize": "^2.3.0", 2843 + "codem-isoboxer": "0.3.10", 2844 + "fast-deep-equal": "3.1.3", 2845 + "html-entities": "^2.5.2", 2846 + "imsc": "^1.1.5", 2847 + "localforage": "^1.10.0", 2848 + "path-browserify": "^1.0.1", 2849 + "ua-parser-js": "^1.0.37" 2850 + } 2851 + }, 2852 + "node_modules/data-urls": { 2853 + "version": "5.0.0", 2854 + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", 2855 + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", 2856 + "dev": true, 2857 + "license": "MIT", 2858 + "dependencies": { 2859 + "whatwg-mimetype": "^4.0.0", 2860 + "whatwg-url": "^14.0.0" 2861 + }, 2862 + "engines": { 2863 + "node": ">=18" 2864 + } 2865 + }, 2866 + "node_modules/debug": { 2867 + "version": "4.4.1", 2868 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 2869 + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 2870 + "license": "MIT", 2871 + "dependencies": { 2872 + "ms": "^2.1.3" 2873 + }, 2874 + "engines": { 2875 + "node": ">=6.0" 2876 + }, 2877 + "peerDependenciesMeta": { 2878 + "supports-color": { 2879 + "optional": true 2880 + } 2881 + } 2882 + }, 2883 + "node_modules/decimal.js": { 2884 + "version": "10.6.0", 2885 + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", 2886 + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", 2887 + "dev": true, 2888 + "license": "MIT" 2889 + }, 2890 + "node_modules/deep-eql": { 2891 + "version": "5.0.2", 2892 + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", 2893 + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", 2894 + "dev": true, 2895 + "license": "MIT", 2896 + "engines": { 2897 + "node": ">=6" 2898 + } 2899 + }, 2900 + "node_modules/dequal": { 2901 + "version": "2.0.3", 2902 + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", 2903 + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", 2904 + "dev": true, 2905 + "license": "MIT", 2906 + "engines": { 2907 + "node": ">=6" 2908 + } 2909 + }, 2910 + "node_modules/detect-libc": { 2911 + "version": "2.0.4", 2912 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", 2913 + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", 2914 + "license": "Apache-2.0", 2915 + "engines": { 2916 + "node": ">=8" 2917 + } 2918 + }, 2919 + "node_modules/diff": { 2920 + "version": "8.0.2", 2921 + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", 2922 + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", 2923 + "license": "BSD-3-Clause", 2924 + "engines": { 2925 + "node": ">=0.3.1" 2926 + } 2927 + }, 2928 + "node_modules/dom-accessibility-api": { 2929 + "version": "0.5.16", 2930 + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", 2931 + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", 2932 + "dev": true, 2933 + "license": "MIT" 2934 + }, 2935 + "node_modules/electron-to-chromium": { 2936 + "version": "1.5.211", 2937 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz", 2938 + "integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==", 2939 + "license": "ISC" 2940 + }, 2941 + "node_modules/enhanced-resolve": { 2942 + "version": "5.18.3", 2943 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", 2944 + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", 2945 + "license": "MIT", 2946 + "dependencies": { 2947 + "graceful-fs": "^4.2.4", 2948 + "tapable": "^2.2.0" 2949 + }, 2950 + "engines": { 2951 + "node": ">=10.13.0" 2952 + } 2953 + }, 2954 + "node_modules/entities": { 2955 + "version": "6.0.1", 2956 + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", 2957 + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", 2958 + "dev": true, 2959 + "license": "BSD-2-Clause", 2960 + "engines": { 2961 + "node": ">=0.12" 2962 + }, 2963 + "funding": { 2964 + "url": "https://github.com/fb55/entities?sponsor=1" 2965 + } 2966 + }, 2967 + "node_modules/es-module-lexer": { 2968 + "version": "1.7.0", 2969 + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", 2970 + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", 2971 + "dev": true, 2972 + "license": "MIT" 2973 + }, 2974 + "node_modules/esbuild": { 2975 + "version": "0.25.9", 2976 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", 2977 + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", 2978 + "hasInstallScript": true, 2979 + "license": "MIT", 2980 + "bin": { 2981 + "esbuild": "bin/esbuild" 2982 + }, 2983 + "engines": { 2984 + "node": ">=18" 2985 + }, 2986 + "optionalDependencies": { 2987 + "@esbuild/aix-ppc64": "0.25.9", 2988 + "@esbuild/android-arm": "0.25.9", 2989 + "@esbuild/android-arm64": "0.25.9", 2990 + "@esbuild/android-x64": "0.25.9", 2991 + "@esbuild/darwin-arm64": "0.25.9", 2992 + "@esbuild/darwin-x64": "0.25.9", 2993 + "@esbuild/freebsd-arm64": "0.25.9", 2994 + "@esbuild/freebsd-x64": "0.25.9", 2995 + "@esbuild/linux-arm": "0.25.9", 2996 + "@esbuild/linux-arm64": "0.25.9", 2997 + "@esbuild/linux-ia32": "0.25.9", 2998 + "@esbuild/linux-loong64": "0.25.9", 2999 + "@esbuild/linux-mips64el": "0.25.9", 3000 + "@esbuild/linux-ppc64": "0.25.9", 3001 + "@esbuild/linux-riscv64": "0.25.9", 3002 + "@esbuild/linux-s390x": "0.25.9", 3003 + "@esbuild/linux-x64": "0.25.9", 3004 + "@esbuild/netbsd-arm64": "0.25.9", 3005 + "@esbuild/netbsd-x64": "0.25.9", 3006 + "@esbuild/openbsd-arm64": "0.25.9", 3007 + "@esbuild/openbsd-x64": "0.25.9", 3008 + "@esbuild/openharmony-arm64": "0.25.9", 3009 + "@esbuild/sunos-x64": "0.25.9", 3010 + "@esbuild/win32-arm64": "0.25.9", 3011 + "@esbuild/win32-ia32": "0.25.9", 3012 + "@esbuild/win32-x64": "0.25.9" 3013 + } 3014 + }, 3015 + "node_modules/escalade": { 3016 + "version": "3.2.0", 3017 + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 3018 + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 3019 + "license": "MIT", 3020 + "engines": { 3021 + "node": ">=6" 3022 + } 3023 + }, 3024 + "node_modules/esprima": { 3025 + "version": "4.0.1", 3026 + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 3027 + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 3028 + "license": "BSD-2-Clause", 3029 + "bin": { 3030 + "esparse": "bin/esparse.js", 3031 + "esvalidate": "bin/esvalidate.js" 3032 + }, 3033 + "engines": { 3034 + "node": ">=4" 3035 + } 3036 + }, 3037 + "node_modules/estree-walker": { 3038 + "version": "3.0.3", 3039 + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 3040 + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 3041 + "dev": true, 3042 + "license": "MIT", 3043 + "dependencies": { 3044 + "@types/estree": "^1.0.0" 3045 + } 3046 + }, 3047 + "node_modules/expect-type": { 3048 + "version": "1.2.2", 3049 + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", 3050 + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", 3051 + "dev": true, 3052 + "license": "Apache-2.0", 3053 + "engines": { 3054 + "node": ">=12.0.0" 3055 + } 3056 + }, 3057 + "node_modules/fast-deep-equal": { 3058 + "version": "3.1.3", 3059 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 3060 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 3061 + "license": "MIT" 3062 + }, 3063 + "node_modules/fill-range": { 3064 + "version": "7.1.1", 3065 + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 3066 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 3067 + "license": "MIT", 3068 + "dependencies": { 3069 + "to-regex-range": "^5.0.1" 3070 + }, 3071 + "engines": { 3072 + "node": ">=8" 3073 + } 3074 + }, 3075 + "node_modules/fsevents": { 3076 + "version": "2.3.3", 3077 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 3078 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 3079 + "hasInstallScript": true, 3080 + "license": "MIT", 3081 + "optional": true, 3082 + "os": [ 3083 + "darwin" 3084 + ], 3085 + "engines": { 3086 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 3087 + } 3088 + }, 3089 + "node_modules/gensync": { 3090 + "version": "1.0.0-beta.2", 3091 + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 3092 + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 3093 + "license": "MIT", 3094 + "engines": { 3095 + "node": ">=6.9.0" 3096 + } 3097 + }, 3098 + "node_modules/get-tsconfig": { 3099 + "version": "4.10.1", 3100 + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", 3101 + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", 3102 + "license": "MIT", 3103 + "dependencies": { 3104 + "resolve-pkg-maps": "^1.0.0" 3105 + }, 3106 + "funding": { 3107 + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 3108 + } 3109 + }, 3110 + "node_modules/glob-parent": { 3111 + "version": "5.1.2", 3112 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 3113 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 3114 + "license": "ISC", 3115 + "dependencies": { 3116 + "is-glob": "^4.0.1" 3117 + }, 3118 + "engines": { 3119 + "node": ">= 6" 3120 + } 3121 + }, 3122 + "node_modules/goober": { 3123 + "version": "2.1.16", 3124 + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", 3125 + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", 3126 + "license": "MIT", 3127 + "peerDependencies": { 3128 + "csstype": "^3.0.10" 3129 + } 3130 + }, 3131 + "node_modules/graceful-fs": { 3132 + "version": "4.2.11", 3133 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 3134 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 3135 + "license": "ISC" 3136 + }, 3137 + "node_modules/graphemer": { 3138 + "version": "1.4.0", 3139 + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 3140 + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 3141 + "license": "MIT" 3142 + }, 3143 + "node_modules/hls-video-element": { 3144 + "version": "1.5.7", 3145 + "resolved": "https://registry.npmjs.org/hls-video-element/-/hls-video-element-1.5.7.tgz", 3146 + "integrity": "sha512-R+uYimNZQndT2iqBgW7Gm0KiHT6pmlt5tnT63rYIcqOEcKD59M6pmdwqtX2vKPfHo+1ACM14Fy9JF1YMwlrLdQ==", 3147 + "license": "MIT", 3148 + "dependencies": { 3149 + "custom-media-element": "^1.4.5", 3150 + "hls.js": "^1.6.5", 3151 + "media-tracks": "^0.3.3" 3152 + } 3153 + }, 3154 + "node_modules/hls.js": { 3155 + "version": "1.6.11", 3156 + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.11.tgz", 3157 + "integrity": "sha512-tdDwOAgPGXohSiNE4oxGr3CI9Hx9lsGLFe6TULUvRk2TfHS+w1tSAJntrvxsHaxvjtr6BXsDZM7NOqJFhU4mmg==", 3158 + "license": "Apache-2.0" 3159 + }, 3160 + "node_modules/html-encoding-sniffer": { 3161 + "version": "4.0.0", 3162 + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", 3163 + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", 3164 + "dev": true, 3165 + "license": "MIT", 3166 + "dependencies": { 3167 + "whatwg-encoding": "^3.1.1" 3168 + }, 3169 + "engines": { 3170 + "node": ">=18" 3171 + } 3172 + }, 3173 + "node_modules/html-entities": { 3174 + "version": "2.6.0", 3175 + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", 3176 + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", 3177 + "funding": [ 3178 + { 3179 + "type": "github", 3180 + "url": "https://github.com/sponsors/mdevils" 3181 + }, 3182 + { 3183 + "type": "patreon", 3184 + "url": "https://patreon.com/mdevils" 3185 + } 3186 + ], 3187 + "license": "MIT" 3188 + }, 3189 + "node_modules/http-proxy-agent": { 3190 + "version": "7.0.2", 3191 + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", 3192 + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", 3193 + "dev": true, 3194 + "license": "MIT", 3195 + "dependencies": { 3196 + "agent-base": "^7.1.0", 3197 + "debug": "^4.3.4" 3198 + }, 3199 + "engines": { 3200 + "node": ">= 14" 3201 + } 3202 + }, 3203 + "node_modules/https-proxy-agent": { 3204 + "version": "7.0.6", 3205 + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", 3206 + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", 3207 + "dev": true, 3208 + "license": "MIT", 3209 + "dependencies": { 3210 + "agent-base": "^7.1.2", 3211 + "debug": "4" 3212 + }, 3213 + "engines": { 3214 + "node": ">= 14" 3215 + } 3216 + }, 3217 + "node_modules/iconv-lite": { 3218 + "version": "0.6.3", 3219 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 3220 + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 3221 + "dev": true, 3222 + "license": "MIT", 3223 + "dependencies": { 3224 + "safer-buffer": ">= 2.1.2 < 3.0.0" 3225 + }, 3226 + "engines": { 3227 + "node": ">=0.10.0" 3228 + } 3229 + }, 3230 + "node_modules/idb-keyval": { 3231 + "version": "6.2.2", 3232 + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", 3233 + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", 3234 + "license": "Apache-2.0" 3235 + }, 3236 + "node_modules/immediate": { 3237 + "version": "3.0.6", 3238 + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 3239 + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", 3240 + "license": "MIT" 3241 + }, 3242 + "node_modules/imsc": { 3243 + "version": "1.1.5", 3244 + "resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.5.tgz", 3245 + "integrity": "sha512-V8je+CGkcvGhgl2C1GlhqFFiUOIEdwXbXLiu1Fcubvvbo+g9inauqT3l0pNYXGoLPBj3jxtZz9t+wCopMkwadQ==", 3246 + "license": "BSD-2-Clause", 3247 + "dependencies": { 3248 + "sax": "1.2.1" 3249 + } 3250 + }, 3251 + "node_modules/is-alphabetical": { 3252 + "version": "2.0.1", 3253 + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", 3254 + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", 3255 + "license": "MIT", 3256 + "funding": { 3257 + "type": "github", 3258 + "url": "https://github.com/sponsors/wooorm" 3259 + } 3260 + }, 3261 + "node_modules/is-alphanumerical": { 3262 + "version": "2.0.1", 3263 + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", 3264 + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", 3265 + "license": "MIT", 3266 + "dependencies": { 3267 + "is-alphabetical": "^2.0.0", 3268 + "is-decimal": "^2.0.0" 3269 + }, 3270 + "funding": { 3271 + "type": "github", 3272 + "url": "https://github.com/sponsors/wooorm" 3273 + } 3274 + }, 3275 + "node_modules/is-binary-path": { 3276 + "version": "2.1.0", 3277 + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 3278 + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 3279 + "license": "MIT", 3280 + "dependencies": { 3281 + "binary-extensions": "^2.0.0" 3282 + }, 3283 + "engines": { 3284 + "node": ">=8" 3285 + } 3286 + }, 3287 + "node_modules/is-decimal": { 3288 + "version": "2.0.1", 3289 + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", 3290 + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", 3291 + "license": "MIT", 3292 + "funding": { 3293 + "type": "github", 3294 + "url": "https://github.com/sponsors/wooorm" 3295 + } 3296 + }, 3297 + "node_modules/is-extglob": { 3298 + "version": "2.1.1", 3299 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 3300 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 3301 + "license": "MIT", 3302 + "engines": { 3303 + "node": ">=0.10.0" 3304 + } 3305 + }, 3306 + "node_modules/is-glob": { 3307 + "version": "4.0.3", 3308 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 3309 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 3310 + "license": "MIT", 3311 + "dependencies": { 3312 + "is-extglob": "^2.1.1" 3313 + }, 3314 + "engines": { 3315 + "node": ">=0.10.0" 3316 + } 3317 + }, 3318 + "node_modules/is-number": { 3319 + "version": "7.0.0", 3320 + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 3321 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 3322 + "license": "MIT", 3323 + "engines": { 3324 + "node": ">=0.12.0" 3325 + } 3326 + }, 3327 + "node_modules/is-potential-custom-element-name": { 3328 + "version": "1.0.1", 3329 + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", 3330 + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", 3331 + "dev": true, 3332 + "license": "MIT" 3333 + }, 3334 + "node_modules/isbot": { 3335 + "version": "5.1.30", 3336 + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.30.tgz", 3337 + "integrity": "sha512-3wVJEonAns1OETX83uWsk5IAne2S5zfDcntD2hbtU23LelSqNXzXs9zKjMPOLMzroCgIjCfjYAEHrd2D6FOkiA==", 3338 + "license": "Unlicense", 3339 + "engines": { 3340 + "node": ">=18" 3341 + } 3342 + }, 3343 + "node_modules/iso-datestring-validator": { 3344 + "version": "2.2.2", 3345 + "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 3346 + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 3347 + "license": "MIT" 3348 + }, 3349 + "node_modules/jiti": { 3350 + "version": "2.5.1", 3351 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", 3352 + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", 3353 + "license": "MIT", 3354 + "bin": { 3355 + "jiti": "lib/jiti-cli.mjs" 3356 + } 3357 + }, 3358 + "node_modules/js-tokens": { 3359 + "version": "4.0.0", 3360 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 3361 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 3362 + "license": "MIT" 3363 + }, 3364 + "node_modules/jsdom": { 3365 + "version": "26.1.0", 3366 + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", 3367 + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", 3368 + "dev": true, 3369 + "license": "MIT", 3370 + "dependencies": { 3371 + "cssstyle": "^4.2.1", 3372 + "data-urls": "^5.0.0", 3373 + "decimal.js": "^10.5.0", 3374 + "html-encoding-sniffer": "^4.0.0", 3375 + "http-proxy-agent": "^7.0.2", 3376 + "https-proxy-agent": "^7.0.6", 3377 + "is-potential-custom-element-name": "^1.0.1", 3378 + "nwsapi": "^2.2.16", 3379 + "parse5": "^7.2.1", 3380 + "rrweb-cssom": "^0.8.0", 3381 + "saxes": "^6.0.0", 3382 + "symbol-tree": "^3.2.4", 3383 + "tough-cookie": "^5.1.1", 3384 + "w3c-xmlserializer": "^5.0.0", 3385 + "webidl-conversions": "^7.0.0", 3386 + "whatwg-encoding": "^3.1.1", 3387 + "whatwg-mimetype": "^4.0.0", 3388 + "whatwg-url": "^14.1.1", 3389 + "ws": "^8.18.0", 3390 + "xml-name-validator": "^5.0.0" 3391 + }, 3392 + "engines": { 3393 + "node": ">=18" 3394 + }, 3395 + "peerDependencies": { 3396 + "canvas": "^3.0.0" 3397 + }, 3398 + "peerDependenciesMeta": { 3399 + "canvas": { 3400 + "optional": true 3401 + } 3402 + } 3403 + }, 3404 + "node_modules/jsesc": { 3405 + "version": "3.1.0", 3406 + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", 3407 + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", 3408 + "license": "MIT", 3409 + "bin": { 3410 + "jsesc": "bin/jsesc" 3411 + }, 3412 + "engines": { 3413 + "node": ">=6" 3414 + } 3415 + }, 3416 + "node_modules/json5": { 3417 + "version": "2.2.3", 3418 + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 3419 + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 3420 + "license": "MIT", 3421 + "bin": { 3422 + "json5": "lib/cli.js" 3423 + }, 3424 + "engines": { 3425 + "node": ">=6" 3426 + } 3427 + }, 3428 + "node_modules/lie": { 3429 + "version": "3.1.1", 3430 + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", 3431 + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", 3432 + "license": "MIT", 3433 + "dependencies": { 3434 + "immediate": "~3.0.5" 3435 + } 3436 + }, 3437 + "node_modules/lightningcss": { 3438 + "version": "1.30.1", 3439 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", 3440 + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", 3441 + "license": "MPL-2.0", 3442 + "dependencies": { 3443 + "detect-libc": "^2.0.3" 3444 + }, 3445 + "engines": { 3446 + "node": ">= 12.0.0" 3447 + }, 3448 + "funding": { 3449 + "type": "opencollective", 3450 + "url": "https://opencollective.com/parcel" 3451 + }, 3452 + "optionalDependencies": { 3453 + "lightningcss-darwin-arm64": "1.30.1", 3454 + "lightningcss-darwin-x64": "1.30.1", 3455 + "lightningcss-freebsd-x64": "1.30.1", 3456 + "lightningcss-linux-arm-gnueabihf": "1.30.1", 3457 + "lightningcss-linux-arm64-gnu": "1.30.1", 3458 + "lightningcss-linux-arm64-musl": "1.30.1", 3459 + "lightningcss-linux-x64-gnu": "1.30.1", 3460 + "lightningcss-linux-x64-musl": "1.30.1", 3461 + "lightningcss-win32-arm64-msvc": "1.30.1", 3462 + "lightningcss-win32-x64-msvc": "1.30.1" 3463 + } 3464 + }, 3465 + "node_modules/lightningcss-darwin-arm64": { 3466 + "version": "1.30.1", 3467 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", 3468 + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", 3469 + "cpu": [ 3470 + "arm64" 3471 + ], 3472 + "license": "MPL-2.0", 3473 + "optional": true, 3474 + "os": [ 3475 + "darwin" 3476 + ], 3477 + "engines": { 3478 + "node": ">= 12.0.0" 3479 + }, 3480 + "funding": { 3481 + "type": "opencollective", 3482 + "url": "https://opencollective.com/parcel" 3483 + } 3484 + }, 3485 + "node_modules/lightningcss-darwin-x64": { 3486 + "version": "1.30.1", 3487 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", 3488 + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", 3489 + "cpu": [ 3490 + "x64" 3491 + ], 3492 + "license": "MPL-2.0", 3493 + "optional": true, 3494 + "os": [ 3495 + "darwin" 3496 + ], 3497 + "engines": { 3498 + "node": ">= 12.0.0" 3499 + }, 3500 + "funding": { 3501 + "type": "opencollective", 3502 + "url": "https://opencollective.com/parcel" 3503 + } 3504 + }, 3505 + "node_modules/lightningcss-freebsd-x64": { 3506 + "version": "1.30.1", 3507 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", 3508 + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", 3509 + "cpu": [ 3510 + "x64" 3511 + ], 3512 + "license": "MPL-2.0", 3513 + "optional": true, 3514 + "os": [ 3515 + "freebsd" 3516 + ], 3517 + "engines": { 3518 + "node": ">= 12.0.0" 3519 + }, 3520 + "funding": { 3521 + "type": "opencollective", 3522 + "url": "https://opencollective.com/parcel" 3523 + } 3524 + }, 3525 + "node_modules/lightningcss-linux-arm-gnueabihf": { 3526 + "version": "1.30.1", 3527 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", 3528 + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", 3529 + "cpu": [ 3530 + "arm" 3531 + ], 3532 + "license": "MPL-2.0", 3533 + "optional": true, 3534 + "os": [ 3535 + "linux" 3536 + ], 3537 + "engines": { 3538 + "node": ">= 12.0.0" 3539 + }, 3540 + "funding": { 3541 + "type": "opencollective", 3542 + "url": "https://opencollective.com/parcel" 3543 + } 3544 + }, 3545 + "node_modules/lightningcss-linux-arm64-gnu": { 3546 + "version": "1.30.1", 3547 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", 3548 + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", 3549 + "cpu": [ 3550 + "arm64" 3551 + ], 3552 + "license": "MPL-2.0", 3553 + "optional": true, 3554 + "os": [ 3555 + "linux" 3556 + ], 3557 + "engines": { 3558 + "node": ">= 12.0.0" 3559 + }, 3560 + "funding": { 3561 + "type": "opencollective", 3562 + "url": "https://opencollective.com/parcel" 3563 + } 3564 + }, 3565 + "node_modules/lightningcss-linux-arm64-musl": { 3566 + "version": "1.30.1", 3567 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", 3568 + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", 3569 + "cpu": [ 3570 + "arm64" 3571 + ], 3572 + "license": "MPL-2.0", 3573 + "optional": true, 3574 + "os": [ 3575 + "linux" 3576 + ], 3577 + "engines": { 3578 + "node": ">= 12.0.0" 3579 + }, 3580 + "funding": { 3581 + "type": "opencollective", 3582 + "url": "https://opencollective.com/parcel" 3583 + } 3584 + }, 3585 + "node_modules/lightningcss-linux-x64-gnu": { 3586 + "version": "1.30.1", 3587 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", 3588 + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", 3589 + "cpu": [ 3590 + "x64" 3591 + ], 3592 + "license": "MPL-2.0", 3593 + "optional": true, 3594 + "os": [ 3595 + "linux" 3596 + ], 3597 + "engines": { 3598 + "node": ">= 12.0.0" 3599 + }, 3600 + "funding": { 3601 + "type": "opencollective", 3602 + "url": "https://opencollective.com/parcel" 3603 + } 3604 + }, 3605 + "node_modules/lightningcss-linux-x64-musl": { 3606 + "version": "1.30.1", 3607 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", 3608 + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", 3609 + "cpu": [ 3610 + "x64" 3611 + ], 3612 + "license": "MPL-2.0", 3613 + "optional": true, 3614 + "os": [ 3615 + "linux" 3616 + ], 3617 + "engines": { 3618 + "node": ">= 12.0.0" 3619 + }, 3620 + "funding": { 3621 + "type": "opencollective", 3622 + "url": "https://opencollective.com/parcel" 3623 + } 3624 + }, 3625 + "node_modules/lightningcss-win32-arm64-msvc": { 3626 + "version": "1.30.1", 3627 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", 3628 + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", 3629 + "cpu": [ 3630 + "arm64" 3631 + ], 3632 + "license": "MPL-2.0", 3633 + "optional": true, 3634 + "os": [ 3635 + "win32" 3636 + ], 3637 + "engines": { 3638 + "node": ">= 12.0.0" 3639 + }, 3640 + "funding": { 3641 + "type": "opencollective", 3642 + "url": "https://opencollective.com/parcel" 3643 + } 3644 + }, 3645 + "node_modules/lightningcss-win32-x64-msvc": { 3646 + "version": "1.30.1", 3647 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", 3648 + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", 3649 + "cpu": [ 3650 + "x64" 3651 + ], 3652 + "license": "MPL-2.0", 3653 + "optional": true, 3654 + "os": [ 3655 + "win32" 3656 + ], 3657 + "engines": { 3658 + "node": ">= 12.0.0" 3659 + }, 3660 + "funding": { 3661 + "type": "opencollective", 3662 + "url": "https://opencollective.com/parcel" 3663 + } 3664 + }, 3665 + "node_modules/localforage": { 3666 + "version": "1.10.0", 3667 + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", 3668 + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", 3669 + "license": "Apache-2.0", 3670 + "dependencies": { 3671 + "lie": "3.1.1" 3672 + } 3673 + }, 3674 + "node_modules/loose-envify": { 3675 + "version": "1.4.0", 3676 + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 3677 + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 3678 + "license": "MIT", 3679 + "dependencies": { 3680 + "js-tokens": "^3.0.0 || ^4.0.0" 3681 + }, 3682 + "bin": { 3683 + "loose-envify": "cli.js" 3684 + } 3685 + }, 3686 + "node_modules/loupe": { 3687 + "version": "3.2.1", 3688 + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", 3689 + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", 3690 + "dev": true, 3691 + "license": "MIT" 3692 + }, 3693 + "node_modules/lru-cache": { 3694 + "version": "5.1.1", 3695 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 3696 + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 3697 + "license": "ISC", 3698 + "dependencies": { 3699 + "yallist": "^3.0.2" 3700 + } 3701 + }, 3702 + "node_modules/lz-string": { 3703 + "version": "1.5.0", 3704 + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", 3705 + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", 3706 + "dev": true, 3707 + "license": "MIT", 3708 + "bin": { 3709 + "lz-string": "bin/bin.js" 3710 + } 3711 + }, 3712 + "node_modules/magic-string": { 3713 + "version": "0.30.18", 3714 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", 3715 + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", 3716 + "license": "MIT", 3717 + "dependencies": { 3718 + "@jridgewell/sourcemap-codec": "^1.5.5" 3719 + } 3720 + }, 3721 + "node_modules/media-chrome": { 3722 + "version": "4.11.1", 3723 + "resolved": "https://registry.npmjs.org/media-chrome/-/media-chrome-4.11.1.tgz", 3724 + "integrity": "sha512-+2niDc4qOwlpFAjwxg1OaizK/zKV6y7QqGm4nBFEVlSaG0ZBgOmfc4IXAPiirZqAlZGaFFUaMqCl1SpGU0/naA==", 3725 + "license": "MIT", 3726 + "dependencies": { 3727 + "@vercel/edge": "^1.2.1", 3728 + "ce-la-react": "^0.3.0" 3729 + } 3730 + }, 3731 + "node_modules/media-tracks": { 3732 + "version": "0.3.3", 3733 + "resolved": "https://registry.npmjs.org/media-tracks/-/media-tracks-0.3.3.tgz", 3734 + "integrity": "sha512-9P2FuUHnZZ3iji+2RQk7Zkh5AmZTnOG5fODACnjhCVveX1McY3jmCRHofIEI+yTBqplz7LXy48c7fQ3Uigp88w==", 3735 + "license": "MIT" 3736 + }, 3737 + "node_modules/minipass": { 3738 + "version": "7.1.2", 3739 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 3740 + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 3741 + "license": "ISC", 3742 + "engines": { 3743 + "node": ">=16 || 14 >=14.17" 3744 + } 3745 + }, 3746 + "node_modules/minizlib": { 3747 + "version": "3.0.2", 3748 + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", 3749 + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", 3750 + "license": "MIT", 3751 + "dependencies": { 3752 + "minipass": "^7.1.2" 3753 + }, 3754 + "engines": { 3755 + "node": ">= 18" 3756 + } 3757 + }, 3758 + "node_modules/mkdirp": { 3759 + "version": "3.0.1", 3760 + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", 3761 + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", 3762 + "license": "MIT", 3763 + "bin": { 3764 + "mkdirp": "dist/cjs/src/bin.js" 3765 + }, 3766 + "engines": { 3767 + "node": ">=10" 3768 + }, 3769 + "funding": { 3770 + "url": "https://github.com/sponsors/isaacs" 3771 + } 3772 + }, 3773 + "node_modules/ms": { 3774 + "version": "2.1.3", 3775 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 3776 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 3777 + "license": "MIT" 3778 + }, 3779 + "node_modules/multiformats": { 3780 + "version": "9.9.0", 3781 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 3782 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 3783 + "license": "(Apache-2.0 AND MIT)" 3784 + }, 3785 + "node_modules/mux-embed": { 3786 + "version": "5.9.0", 3787 + "resolved": "https://registry.npmjs.org/mux-embed/-/mux-embed-5.9.0.tgz", 3788 + "integrity": "sha512-wmunL3uoPhma/tWy8PrDPZkvJpXvSFBwbD3KkC4PG8Ztjfb1X3hRJwGUAQyRz7z99b/ovLm2UTTitrkvStjH4w==", 3789 + "license": "MIT" 3790 + }, 3791 + "node_modules/nanoid": { 3792 + "version": "3.3.11", 3793 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 3794 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 3795 + "funding": [ 3796 + { 3797 + "type": "github", 3798 + "url": "https://github.com/sponsors/ai" 3799 + } 3800 + ], 3801 + "license": "MIT", 3802 + "bin": { 3803 + "nanoid": "bin/nanoid.cjs" 3804 + }, 3805 + "engines": { 3806 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 3807 + } 3808 + }, 3809 + "node_modules/native-promise-only": { 3810 + "version": "0.8.1", 3811 + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", 3812 + "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", 3813 + "license": "MIT" 3814 + }, 3815 + "node_modules/node-releases": { 3816 + "version": "2.0.19", 3817 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", 3818 + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", 3819 + "license": "MIT" 3820 + }, 3821 + "node_modules/normalize-path": { 3822 + "version": "3.0.0", 3823 + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 3824 + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 3825 + "license": "MIT", 3826 + "engines": { 3827 + "node": ">=0.10.0" 3828 + } 3829 + }, 3830 + "node_modules/nwsapi": { 3831 + "version": "2.2.21", 3832 + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", 3833 + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", 3834 + "dev": true, 3835 + "license": "MIT" 3836 + }, 3837 + "node_modules/object-assign": { 3838 + "version": "4.1.1", 3839 + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 3840 + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 3841 + "license": "MIT", 3842 + "engines": { 3843 + "node": ">=0.10.0" 3844 + } 3845 + }, 3846 + "node_modules/parse5": { 3847 + "version": "7.3.0", 3848 + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", 3849 + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", 3850 + "dev": true, 3851 + "license": "MIT", 3852 + "dependencies": { 3853 + "entities": "^6.0.0" 3854 + }, 3855 + "funding": { 3856 + "url": "https://github.com/inikulin/parse5?sponsor=1" 3857 + } 3858 + }, 3859 + "node_modules/path-browserify": { 3860 + "version": "1.0.1", 3861 + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", 3862 + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", 3863 + "license": "MIT" 3864 + }, 3865 + "node_modules/pathe": { 3866 + "version": "2.0.3", 3867 + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 3868 + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 3869 + "dev": true, 3870 + "license": "MIT" 3871 + }, 3872 + "node_modules/pathval": { 3873 + "version": "2.0.1", 3874 + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", 3875 + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", 3876 + "dev": true, 3877 + "license": "MIT", 3878 + "engines": { 3879 + "node": ">= 14.16" 3880 + } 3881 + }, 3882 + "node_modules/picocolors": { 3883 + "version": "1.1.1", 3884 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 3885 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 3886 + "license": "ISC" 3887 + }, 3888 + "node_modules/picomatch": { 3889 + "version": "2.3.1", 3890 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 3891 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 3892 + "license": "MIT", 3893 + "engines": { 3894 + "node": ">=8.6" 3895 + }, 3896 + "funding": { 3897 + "url": "https://github.com/sponsors/jonschlinkert" 3898 + } 3899 + }, 3900 + "node_modules/player.style": { 3901 + "version": "0.1.10", 3902 + "resolved": "https://registry.npmjs.org/player.style/-/player.style-0.1.10.tgz", 3903 + "integrity": "sha512-Jxv7tlaQ3SFCddsN35jzoGnCHB3/xMTbJOgn4zcsmF0lcZvRPq5UkRRAD5tZm8CvzKndUvtoDlG6GSPL/N/SrA==", 3904 + "license": "MIT", 3905 + "workspaces": [ 3906 + ".", 3907 + "site", 3908 + "examples/*", 3909 + "scripts/*", 3910 + "themes/*" 3911 + ], 3912 + "dependencies": { 3913 + "media-chrome": "~4.11.0" 3914 + } 3915 + }, 3916 + "node_modules/postcss": { 3917 + "version": "8.5.6", 3918 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 3919 + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 3920 + "funding": [ 3921 + { 3922 + "type": "opencollective", 3923 + "url": "https://opencollective.com/postcss/" 3924 + }, 3925 + { 3926 + "type": "tidelift", 3927 + "url": "https://tidelift.com/funding/github/npm/postcss" 3928 + }, 3929 + { 3930 + "type": "github", 3931 + "url": "https://github.com/sponsors/ai" 3932 + } 3933 + ], 3934 + "license": "MIT", 3935 + "dependencies": { 3936 + "nanoid": "^3.3.11", 3937 + "picocolors": "^1.1.1", 3938 + "source-map-js": "^1.2.1" 3939 + }, 3940 + "engines": { 3941 + "node": "^10 || ^12 || >=14" 3942 + } 3943 + }, 3944 + "node_modules/prettier": { 3945 + "version": "3.6.2", 3946 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", 3947 + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", 3948 + "license": "MIT", 3949 + "bin": { 3950 + "prettier": "bin/prettier.cjs" 3951 + }, 3952 + "engines": { 3953 + "node": ">=14" 3954 + }, 3955 + "funding": { 3956 + "url": "https://github.com/prettier/prettier?sponsor=1" 3957 + } 3958 + }, 3959 + "node_modules/pretty-format": { 3960 + "version": "27.5.1", 3961 + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", 3962 + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", 3963 + "dev": true, 3964 + "license": "MIT", 3965 + "dependencies": { 3966 + "ansi-regex": "^5.0.1", 3967 + "ansi-styles": "^5.0.0", 3968 + "react-is": "^17.0.1" 3969 + }, 3970 + "engines": { 3971 + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" 3972 + } 3973 + }, 3974 + "node_modules/prop-types": { 3975 + "version": "15.8.1", 3976 + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", 3977 + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", 3978 + "license": "MIT", 3979 + "dependencies": { 3980 + "loose-envify": "^1.4.0", 3981 + "object-assign": "^4.1.1", 3982 + "react-is": "^16.13.1" 3983 + } 3984 + }, 3985 + "node_modules/prop-types/node_modules/react-is": { 3986 + "version": "16.13.1", 3987 + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 3988 + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 3989 + "license": "MIT" 3990 + }, 3991 + "node_modules/punycode": { 3992 + "version": "2.3.1", 3993 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 3994 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 3995 + "dev": true, 3996 + "license": "MIT", 3997 + "engines": { 3998 + "node": ">=6" 3999 + } 4000 + }, 4001 + "node_modules/react": { 4002 + "version": "19.1.1", 4003 + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", 4004 + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", 4005 + "license": "MIT", 4006 + "engines": { 4007 + "node": ">=0.10.0" 4008 + } 4009 + }, 4010 + "node_modules/react-dom": { 4011 + "version": "19.1.1", 4012 + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", 4013 + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", 4014 + "license": "MIT", 4015 + "dependencies": { 4016 + "scheduler": "^0.26.0" 4017 + }, 4018 + "peerDependencies": { 4019 + "react": "^19.1.1" 4020 + } 4021 + }, 4022 + "node_modules/react-is": { 4023 + "version": "17.0.2", 4024 + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", 4025 + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", 4026 + "dev": true, 4027 + "license": "MIT" 4028 + }, 4029 + "node_modules/react-player": { 4030 + "version": "3.3.2", 4031 + "resolved": "https://registry.npmjs.org/react-player/-/react-player-3.3.2.tgz", 4032 + "integrity": "sha512-MBSCxTA1FPyMR19Wy+2LtVjguhrLl9p2l5nODY4fbumgsoaCEuhMLpZvxh8RWjzzvqL8V3jYcPfw/XhqrbTzFw==", 4033 + "license": "MIT", 4034 + "dependencies": { 4035 + "@mux/mux-player-react": "^3.5.1", 4036 + "cloudflare-video-element": "^1.3.3", 4037 + "dash-video-element": "^0.1.6", 4038 + "hls-video-element": "^1.5.6", 4039 + "spotify-audio-element": "^1.0.2", 4040 + "tiktok-video-element": "^0.1.0", 4041 + "twitch-video-element": "^0.1.2", 4042 + "vimeo-video-element": "^1.5.3", 4043 + "wistia-video-element": "^1.3.3", 4044 + "youtube-video-element": "^1.6.1" 4045 + }, 4046 + "peerDependencies": { 4047 + "@types/react": "^17.0.0 || ^18 || ^19", 4048 + "react": "^17.0.2 || ^18 || ^19", 4049 + "react-dom": "^17.0.2 || ^18 || ^19" 4050 + } 4051 + }, 4052 + "node_modules/react-refresh": { 4053 + "version": "0.17.0", 4054 + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", 4055 + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", 4056 + "dev": true, 4057 + "license": "MIT", 4058 + "engines": { 4059 + "node": ">=0.10.0" 4060 + } 4061 + }, 4062 + "node_modules/readdirp": { 4063 + "version": "3.6.0", 4064 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 4065 + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 4066 + "license": "MIT", 4067 + "dependencies": { 4068 + "picomatch": "^2.2.1" 4069 + }, 4070 + "engines": { 4071 + "node": ">=8.10.0" 4072 + } 4073 + }, 4074 + "node_modules/recast": { 4075 + "version": "0.23.11", 4076 + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", 4077 + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", 4078 + "license": "MIT", 4079 + "dependencies": { 4080 + "ast-types": "^0.16.1", 4081 + "esprima": "~4.0.0", 4082 + "source-map": "~0.6.1", 4083 + "tiny-invariant": "^1.3.3", 4084 + "tslib": "^2.0.1" 4085 + }, 4086 + "engines": { 4087 + "node": ">= 4" 4088 + } 4089 + }, 4090 + "node_modules/recast/node_modules/source-map": { 4091 + "version": "0.6.1", 4092 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 4093 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 4094 + "license": "BSD-3-Clause", 4095 + "engines": { 4096 + "node": ">=0.10.0" 4097 + } 4098 + }, 4099 + "node_modules/resolve-pkg-maps": { 4100 + "version": "1.0.0", 4101 + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 4102 + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 4103 + "license": "MIT", 4104 + "funding": { 4105 + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 4106 + } 4107 + }, 4108 + "node_modules/rollup": { 4109 + "version": "4.49.0", 4110 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", 4111 + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", 4112 + "license": "MIT", 4113 + "dependencies": { 4114 + "@types/estree": "1.0.8" 4115 + }, 4116 + "bin": { 4117 + "rollup": "dist/bin/rollup" 4118 + }, 4119 + "engines": { 4120 + "node": ">=18.0.0", 4121 + "npm": ">=8.0.0" 4122 + }, 4123 + "optionalDependencies": { 4124 + "@rollup/rollup-android-arm-eabi": "4.49.0", 4125 + "@rollup/rollup-android-arm64": "4.49.0", 4126 + "@rollup/rollup-darwin-arm64": "4.49.0", 4127 + "@rollup/rollup-darwin-x64": "4.49.0", 4128 + "@rollup/rollup-freebsd-arm64": "4.49.0", 4129 + "@rollup/rollup-freebsd-x64": "4.49.0", 4130 + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", 4131 + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", 4132 + "@rollup/rollup-linux-arm64-gnu": "4.49.0", 4133 + "@rollup/rollup-linux-arm64-musl": "4.49.0", 4134 + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", 4135 + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", 4136 + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", 4137 + "@rollup/rollup-linux-riscv64-musl": "4.49.0", 4138 + "@rollup/rollup-linux-s390x-gnu": "4.49.0", 4139 + "@rollup/rollup-linux-x64-gnu": "4.49.0", 4140 + "@rollup/rollup-linux-x64-musl": "4.49.0", 4141 + "@rollup/rollup-win32-arm64-msvc": "4.49.0", 4142 + "@rollup/rollup-win32-ia32-msvc": "4.49.0", 4143 + "@rollup/rollup-win32-x64-msvc": "4.49.0", 4144 + "fsevents": "~2.3.2" 4145 + } 4146 + }, 4147 + "node_modules/rrweb-cssom": { 4148 + "version": "0.8.0", 4149 + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", 4150 + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", 4151 + "dev": true, 4152 + "license": "MIT" 4153 + }, 4154 + "node_modules/safer-buffer": { 4155 + "version": "2.1.2", 4156 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 4157 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 4158 + "dev": true, 4159 + "license": "MIT" 4160 + }, 4161 + "node_modules/sax": { 4162 + "version": "1.2.1", 4163 + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 4164 + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", 4165 + "license": "ISC" 4166 + }, 4167 + "node_modules/saxes": { 4168 + "version": "6.0.0", 4169 + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", 4170 + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", 4171 + "dev": true, 4172 + "license": "ISC", 4173 + "dependencies": { 4174 + "xmlchars": "^2.2.0" 4175 + }, 4176 + "engines": { 4177 + "node": ">=v12.22.7" 4178 + } 4179 + }, 4180 + "node_modules/scheduler": { 4181 + "version": "0.26.0", 4182 + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", 4183 + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", 4184 + "license": "MIT" 4185 + }, 4186 + "node_modules/semver": { 4187 + "version": "6.3.1", 4188 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 4189 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 4190 + "license": "ISC", 4191 + "bin": { 4192 + "semver": "bin/semver.js" 4193 + } 4194 + }, 4195 + "node_modules/seroval": { 4196 + "version": "1.3.2", 4197 + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", 4198 + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", 4199 + "license": "MIT", 4200 + "engines": { 4201 + "node": ">=10" 4202 + } 4203 + }, 4204 + "node_modules/seroval-plugins": { 4205 + "version": "1.3.2", 4206 + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.2.tgz", 4207 + "integrity": "sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==", 4208 + "license": "MIT", 4209 + "engines": { 4210 + "node": ">=10" 4211 + }, 4212 + "peerDependencies": { 4213 + "seroval": "^1.0" 4214 + } 4215 + }, 4216 + "node_modules/siginfo": { 4217 + "version": "2.0.0", 4218 + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 4219 + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 4220 + "dev": true, 4221 + "license": "ISC" 4222 + }, 4223 + "node_modules/solid-js": { 4224 + "version": "1.9.9", 4225 + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", 4226 + "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", 4227 + "license": "MIT", 4228 + "dependencies": { 4229 + "csstype": "^3.1.0", 4230 + "seroval": "~1.3.0", 4231 + "seroval-plugins": "~1.3.0" 4232 + } 4233 + }, 4234 + "node_modules/source-map": { 4235 + "version": "0.7.6", 4236 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", 4237 + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", 4238 + "license": "BSD-3-Clause", 4239 + "engines": { 4240 + "node": ">= 12" 4241 + } 4242 + }, 4243 + "node_modules/source-map-js": { 4244 + "version": "1.2.1", 4245 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 4246 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 4247 + "license": "BSD-3-Clause", 4248 + "engines": { 4249 + "node": ">=0.10.0" 4250 + } 4251 + }, 4252 + "node_modules/spotify-audio-element": { 4253 + "version": "1.0.3", 4254 + "resolved": "https://registry.npmjs.org/spotify-audio-element/-/spotify-audio-element-1.0.3.tgz", 4255 + "integrity": "sha512-I1/qD8cg/UnTlCIMiKSdZUJTyYfYhaqFK7LIVElc48eOqUUbVCaw1bqL8I6mJzdMJTh3eoNyF/ewvB7NoS/g9A==", 4256 + "license": "MIT" 4257 + }, 4258 + "node_modules/stackback": { 4259 + "version": "0.0.2", 4260 + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 4261 + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 4262 + "dev": true, 4263 + "license": "MIT" 4264 + }, 4265 + "node_modules/std-env": { 4266 + "version": "3.9.0", 4267 + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", 4268 + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", 4269 + "dev": true, 4270 + "license": "MIT" 4271 + }, 4272 + "node_modules/strip-literal": { 4273 + "version": "3.0.0", 4274 + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", 4275 + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", 4276 + "dev": true, 4277 + "license": "MIT", 4278 + "dependencies": { 4279 + "js-tokens": "^9.0.1" 4280 + }, 4281 + "funding": { 4282 + "url": "https://github.com/sponsors/antfu" 4283 + } 4284 + }, 4285 + "node_modules/strip-literal/node_modules/js-tokens": { 4286 + "version": "9.0.1", 4287 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", 4288 + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", 4289 + "dev": true, 4290 + "license": "MIT" 4291 + }, 4292 + "node_modules/super-media-element": { 4293 + "version": "1.4.2", 4294 + "resolved": "https://registry.npmjs.org/super-media-element/-/super-media-element-1.4.2.tgz", 4295 + "integrity": "sha512-9pP/CVNp4NF2MNlRzLwQkjiTgKKe9WYXrLh9+8QokWmMxz+zt2mf1utkWLco26IuA3AfVcTb//qtlTIjY3VHxA==", 4296 + "license": "MIT" 4297 + }, 4298 + "node_modules/symbol-tree": { 4299 + "version": "3.2.4", 4300 + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", 4301 + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", 4302 + "dev": true, 4303 + "license": "MIT" 4304 + }, 4305 + "node_modules/tailwindcss": { 4306 + "version": "4.1.12", 4307 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", 4308 + "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", 4309 + "license": "MIT" 4310 + }, 4311 + "node_modules/tapable": { 4312 + "version": "2.2.3", 4313 + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", 4314 + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", 4315 + "license": "MIT", 4316 + "engines": { 4317 + "node": ">=6" 4318 + }, 4319 + "funding": { 4320 + "type": "opencollective", 4321 + "url": "https://opencollective.com/webpack" 4322 + } 4323 + }, 4324 + "node_modules/tar": { 4325 + "version": "7.4.3", 4326 + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", 4327 + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", 4328 + "license": "ISC", 4329 + "dependencies": { 4330 + "@isaacs/fs-minipass": "^4.0.0", 4331 + "chownr": "^3.0.0", 4332 + "minipass": "^7.1.2", 4333 + "minizlib": "^3.0.1", 4334 + "mkdirp": "^3.0.1", 4335 + "yallist": "^5.0.0" 4336 + }, 4337 + "engines": { 4338 + "node": ">=18" 4339 + } 4340 + }, 4341 + "node_modules/tar/node_modules/yallist": { 4342 + "version": "5.0.0", 4343 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", 4344 + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", 4345 + "license": "BlueOak-1.0.0", 4346 + "engines": { 4347 + "node": ">=18" 4348 + } 4349 + }, 4350 + "node_modules/tiktok-video-element": { 4351 + "version": "0.1.1", 4352 + "resolved": "https://registry.npmjs.org/tiktok-video-element/-/tiktok-video-element-0.1.1.tgz", 4353 + "integrity": "sha512-BaiVzvNz2UXDKTdSrXzrNf4q6Ecc+/utYUh7zdEu2jzYcJVDoqYbVfUl0bCfMoOeeAqg28vD/yN63Y3E9jOrlA==", 4354 + "license": "MIT" 4355 + }, 4356 + "node_modules/tiny-invariant": { 4357 + "version": "1.3.3", 4358 + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", 4359 + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", 4360 + "license": "MIT" 4361 + }, 4362 + "node_modules/tiny-warning": { 4363 + "version": "1.0.3", 4364 + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", 4365 + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", 4366 + "license": "MIT" 4367 + }, 4368 + "node_modules/tinybench": { 4369 + "version": "2.9.0", 4370 + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", 4371 + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", 4372 + "dev": true, 4373 + "license": "MIT" 4374 + }, 4375 + "node_modules/tinyexec": { 4376 + "version": "0.3.2", 4377 + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", 4378 + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", 4379 + "dev": true, 4380 + "license": "MIT" 4381 + }, 4382 + "node_modules/tinyglobby": { 4383 + "version": "0.2.14", 4384 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", 4385 + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 4386 + "license": "MIT", 4387 + "dependencies": { 4388 + "fdir": "^6.4.4", 4389 + "picomatch": "^4.0.2" 4390 + }, 4391 + "engines": { 4392 + "node": ">=12.0.0" 4393 + }, 4394 + "funding": { 4395 + "url": "https://github.com/sponsors/SuperchupuDev" 4396 + } 4397 + }, 4398 + "node_modules/tinyglobby/node_modules/fdir": { 4399 + "version": "6.5.0", 4400 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 4401 + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 4402 + "license": "MIT", 4403 + "engines": { 4404 + "node": ">=12.0.0" 4405 + }, 4406 + "peerDependencies": { 4407 + "picomatch": "^3 || ^4" 4408 + }, 4409 + "peerDependenciesMeta": { 4410 + "picomatch": { 4411 + "optional": true 4412 + } 4413 + } 4414 + }, 4415 + "node_modules/tinyglobby/node_modules/picomatch": { 4416 + "version": "4.0.3", 4417 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 4418 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 4419 + "license": "MIT", 4420 + "engines": { 4421 + "node": ">=12" 4422 + }, 4423 + "funding": { 4424 + "url": "https://github.com/sponsors/jonschlinkert" 4425 + } 4426 + }, 4427 + "node_modules/tinypool": { 4428 + "version": "1.1.1", 4429 + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", 4430 + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", 4431 + "dev": true, 4432 + "license": "MIT", 4433 + "engines": { 4434 + "node": "^18.0.0 || >=20.0.0" 4435 + } 4436 + }, 4437 + "node_modules/tinyrainbow": { 4438 + "version": "2.0.0", 4439 + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", 4440 + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", 4441 + "dev": true, 4442 + "license": "MIT", 4443 + "engines": { 4444 + "node": ">=14.0.0" 4445 + } 4446 + }, 4447 + "node_modules/tinyspy": { 4448 + "version": "4.0.3", 4449 + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", 4450 + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", 4451 + "dev": true, 4452 + "license": "MIT", 4453 + "engines": { 4454 + "node": ">=14.0.0" 4455 + } 4456 + }, 4457 + "node_modules/tlds": { 4458 + "version": "1.260.0", 4459 + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.260.0.tgz", 4460 + "integrity": "sha512-78+28EWBhCEE7qlyaHA9OR3IPvbCLiDh3Ckla593TksfFc9vfTsgvH7eS+dr3o9qr31gwGbogcI16yN91PoRjQ==", 4461 + "license": "MIT", 4462 + "bin": { 4463 + "tlds": "bin.js" 4464 + } 4465 + }, 4466 + "node_modules/tldts": { 4467 + "version": "6.1.86", 4468 + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", 4469 + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", 4470 + "dev": true, 4471 + "license": "MIT", 4472 + "dependencies": { 4473 + "tldts-core": "^6.1.86" 4474 + }, 4475 + "bin": { 4476 + "tldts": "bin/cli.js" 4477 + } 4478 + }, 4479 + "node_modules/tldts-core": { 4480 + "version": "6.1.86", 4481 + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", 4482 + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", 4483 + "dev": true, 4484 + "license": "MIT" 4485 + }, 4486 + "node_modules/to-regex-range": { 4487 + "version": "5.0.1", 4488 + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 4489 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 4490 + "license": "MIT", 4491 + "dependencies": { 4492 + "is-number": "^7.0.0" 4493 + }, 4494 + "engines": { 4495 + "node": ">=8.0" 4496 + } 4497 + }, 4498 + "node_modules/tough-cookie": { 4499 + "version": "5.1.2", 4500 + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", 4501 + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", 4502 + "dev": true, 4503 + "license": "BSD-3-Clause", 4504 + "dependencies": { 4505 + "tldts": "^6.1.32" 4506 + }, 4507 + "engines": { 4508 + "node": ">=16" 4509 + } 4510 + }, 4511 + "node_modules/tr46": { 4512 + "version": "5.1.1", 4513 + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", 4514 + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", 4515 + "dev": true, 4516 + "license": "MIT", 4517 + "dependencies": { 4518 + "punycode": "^2.3.1" 4519 + }, 4520 + "engines": { 4521 + "node": ">=18" 4522 + } 4523 + }, 4524 + "node_modules/tslib": { 4525 + "version": "2.8.1", 4526 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 4527 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 4528 + "license": "0BSD" 4529 + }, 4530 + "node_modules/tsx": { 4531 + "version": "4.20.5", 4532 + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", 4533 + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", 4534 + "license": "MIT", 4535 + "dependencies": { 4536 + "esbuild": "~0.25.0", 4537 + "get-tsconfig": "^4.7.5" 4538 + }, 4539 + "bin": { 4540 + "tsx": "dist/cli.mjs" 4541 + }, 4542 + "engines": { 4543 + "node": ">=18.0.0" 4544 + }, 4545 + "optionalDependencies": { 4546 + "fsevents": "~2.3.3" 4547 + } 4548 + }, 4549 + "node_modules/twitch-video-element": { 4550 + "version": "0.1.4", 4551 + "resolved": "https://registry.npmjs.org/twitch-video-element/-/twitch-video-element-0.1.4.tgz", 4552 + "integrity": "sha512-SDpZ4f7sZmwHF6XG5PF0KWuP18pH/kNG04MhTcpqJby7Lk/D3TS/lCYd+RSg0rIAAVi1LDgSIo1yJs9kmHlhgw==", 4553 + "license": "MIT" 4554 + }, 4555 + "node_modules/typescript": { 4556 + "version": "5.9.2", 4557 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", 4558 + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", 4559 + "dev": true, 4560 + "license": "Apache-2.0", 4561 + "bin": { 4562 + "tsc": "bin/tsc", 4563 + "tsserver": "bin/tsserver" 4564 + }, 4565 + "engines": { 4566 + "node": ">=14.17" 4567 + } 4568 + }, 4569 + "node_modules/ua-parser-js": { 4570 + "version": "1.0.41", 4571 + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", 4572 + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", 4573 + "funding": [ 4574 + { 4575 + "type": "opencollective", 4576 + "url": "https://opencollective.com/ua-parser-js" 4577 + }, 4578 + { 4579 + "type": "paypal", 4580 + "url": "https://paypal.me/faisalman" 4581 + }, 4582 + { 4583 + "type": "github", 4584 + "url": "https://github.com/sponsors/faisalman" 4585 + } 4586 + ], 4587 + "license": "MIT", 4588 + "bin": { 4589 + "ua-parser-js": "script/cli.js" 4590 + }, 4591 + "engines": { 4592 + "node": "*" 4593 + } 4594 + }, 4595 + "node_modules/uint8arrays": { 4596 + "version": "3.0.0", 4597 + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 4598 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 4599 + "license": "MIT", 4600 + "dependencies": { 4601 + "multiformats": "^9.4.2" 4602 + } 4603 + }, 4604 + "node_modules/undici-types": { 4605 + "version": "7.10.0", 4606 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", 4607 + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", 4608 + "devOptional": true, 4609 + "license": "MIT" 4610 + }, 4611 + "node_modules/unplugin": { 4612 + "version": "2.3.9", 4613 + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.9.tgz", 4614 + "integrity": "sha512-2dcbZq6aprwXTkzptq3k5qm5B8cvpjG9ynPd5fyM2wDJuuF7PeUK64Sxf0d+X1ZyDOeGydbNzMqBSIVlH8GIfA==", 4615 + "license": "MIT", 4616 + "dependencies": { 4617 + "@jridgewell/remapping": "^2.3.5", 4618 + "acorn": "^8.15.0", 4619 + "picomatch": "^4.0.3", 4620 + "webpack-virtual-modules": "^0.6.2" 4621 + }, 4622 + "engines": { 4623 + "node": ">=18.12.0" 4624 + } 4625 + }, 4626 + "node_modules/unplugin/node_modules/picomatch": { 4627 + "version": "4.0.3", 4628 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 4629 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 4630 + "license": "MIT", 4631 + "engines": { 4632 + "node": ">=12" 4633 + }, 4634 + "funding": { 4635 + "url": "https://github.com/sponsors/jonschlinkert" 4636 + } 4637 + }, 4638 + "node_modules/update-browserslist-db": { 4639 + "version": "1.1.3", 4640 + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", 4641 + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", 4642 + "funding": [ 4643 + { 4644 + "type": "opencollective", 4645 + "url": "https://opencollective.com/browserslist" 4646 + }, 4647 + { 4648 + "type": "tidelift", 4649 + "url": "https://tidelift.com/funding/github/npm/browserslist" 4650 + }, 4651 + { 4652 + "type": "github", 4653 + "url": "https://github.com/sponsors/ai" 4654 + } 4655 + ], 4656 + "license": "MIT", 4657 + "dependencies": { 4658 + "escalade": "^3.2.0", 4659 + "picocolors": "^1.1.1" 4660 + }, 4661 + "bin": { 4662 + "update-browserslist-db": "cli.js" 4663 + }, 4664 + "peerDependencies": { 4665 + "browserslist": ">= 4.21.0" 4666 + } 4667 + }, 4668 + "node_modules/use-sync-external-store": { 4669 + "version": "1.5.0", 4670 + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", 4671 + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", 4672 + "license": "MIT", 4673 + "peerDependencies": { 4674 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 4675 + } 4676 + }, 4677 + "node_modules/vimeo-video-element": { 4678 + "version": "1.5.5", 4679 + "resolved": "https://registry.npmjs.org/vimeo-video-element/-/vimeo-video-element-1.5.5.tgz", 4680 + "integrity": "sha512-9QVvKPPnubMNeNYHY5KZqAYerVMuVG+7PSK+6IrEUD7a/wnCGtzb8Sfxl9qNxDAL6Q8i+p+5SDoVKobCd866vw==", 4681 + "license": "MIT", 4682 + "dependencies": { 4683 + "@vimeo/player": "2.29.0" 4684 + } 4685 + }, 4686 + "node_modules/vite": { 4687 + "version": "6.3.5", 4688 + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", 4689 + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", 4690 + "license": "MIT", 4691 + "dependencies": { 4692 + "esbuild": "^0.25.0", 4693 + "fdir": "^6.4.4", 4694 + "picomatch": "^4.0.2", 4695 + "postcss": "^8.5.3", 4696 + "rollup": "^4.34.9", 4697 + "tinyglobby": "^0.2.13" 4698 + }, 4699 + "bin": { 4700 + "vite": "bin/vite.js" 4701 + }, 4702 + "engines": { 4703 + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 4704 + }, 4705 + "funding": { 4706 + "url": "https://github.com/vitejs/vite?sponsor=1" 4707 + }, 4708 + "optionalDependencies": { 4709 + "fsevents": "~2.3.3" 4710 + }, 4711 + "peerDependencies": { 4712 + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 4713 + "jiti": ">=1.21.0", 4714 + "less": "*", 4715 + "lightningcss": "^1.21.0", 4716 + "sass": "*", 4717 + "sass-embedded": "*", 4718 + "stylus": "*", 4719 + "sugarss": "*", 4720 + "terser": "^5.16.0", 4721 + "tsx": "^4.8.1", 4722 + "yaml": "^2.4.2" 4723 + }, 4724 + "peerDependenciesMeta": { 4725 + "@types/node": { 4726 + "optional": true 4727 + }, 4728 + "jiti": { 4729 + "optional": true 4730 + }, 4731 + "less": { 4732 + "optional": true 4733 + }, 4734 + "lightningcss": { 4735 + "optional": true 4736 + }, 4737 + "sass": { 4738 + "optional": true 4739 + }, 4740 + "sass-embedded": { 4741 + "optional": true 4742 + }, 4743 + "stylus": { 4744 + "optional": true 4745 + }, 4746 + "sugarss": { 4747 + "optional": true 4748 + }, 4749 + "terser": { 4750 + "optional": true 4751 + }, 4752 + "tsx": { 4753 + "optional": true 4754 + }, 4755 + "yaml": { 4756 + "optional": true 4757 + } 4758 + } 4759 + }, 4760 + "node_modules/vite-node": { 4761 + "version": "3.2.4", 4762 + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", 4763 + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", 4764 + "dev": true, 4765 + "license": "MIT", 4766 + "dependencies": { 4767 + "cac": "^6.7.14", 4768 + "debug": "^4.4.1", 4769 + "es-module-lexer": "^1.7.0", 4770 + "pathe": "^2.0.3", 4771 + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" 4772 + }, 4773 + "bin": { 4774 + "vite-node": "vite-node.mjs" 4775 + }, 4776 + "engines": { 4777 + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 4778 + }, 4779 + "funding": { 4780 + "url": "https://opencollective.com/vitest" 4781 + } 4782 + }, 4783 + "node_modules/vite/node_modules/fdir": { 4784 + "version": "6.5.0", 4785 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 4786 + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 4787 + "license": "MIT", 4788 + "engines": { 4789 + "node": ">=12.0.0" 4790 + }, 4791 + "peerDependencies": { 4792 + "picomatch": "^3 || ^4" 4793 + }, 4794 + "peerDependenciesMeta": { 4795 + "picomatch": { 4796 + "optional": true 4797 + } 4798 + } 4799 + }, 4800 + "node_modules/vite/node_modules/picomatch": { 4801 + "version": "4.0.3", 4802 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 4803 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 4804 + "license": "MIT", 4805 + "engines": { 4806 + "node": ">=12" 4807 + }, 4808 + "funding": { 4809 + "url": "https://github.com/sponsors/jonschlinkert" 4810 + } 4811 + }, 4812 + "node_modules/vitest": { 4813 + "version": "3.2.4", 4814 + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", 4815 + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", 4816 + "dev": true, 4817 + "license": "MIT", 4818 + "dependencies": { 4819 + "@types/chai": "^5.2.2", 4820 + "@vitest/expect": "3.2.4", 4821 + "@vitest/mocker": "3.2.4", 4822 + "@vitest/pretty-format": "^3.2.4", 4823 + "@vitest/runner": "3.2.4", 4824 + "@vitest/snapshot": "3.2.4", 4825 + "@vitest/spy": "3.2.4", 4826 + "@vitest/utils": "3.2.4", 4827 + "chai": "^5.2.0", 4828 + "debug": "^4.4.1", 4829 + "expect-type": "^1.2.1", 4830 + "magic-string": "^0.30.17", 4831 + "pathe": "^2.0.3", 4832 + "picomatch": "^4.0.2", 4833 + "std-env": "^3.9.0", 4834 + "tinybench": "^2.9.0", 4835 + "tinyexec": "^0.3.2", 4836 + "tinyglobby": "^0.2.14", 4837 + "tinypool": "^1.1.1", 4838 + "tinyrainbow": "^2.0.0", 4839 + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", 4840 + "vite-node": "3.2.4", 4841 + "why-is-node-running": "^2.3.0" 4842 + }, 4843 + "bin": { 4844 + "vitest": "vitest.mjs" 4845 + }, 4846 + "engines": { 4847 + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 4848 + }, 4849 + "funding": { 4850 + "url": "https://opencollective.com/vitest" 4851 + }, 4852 + "peerDependencies": { 4853 + "@edge-runtime/vm": "*", 4854 + "@types/debug": "^4.1.12", 4855 + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 4856 + "@vitest/browser": "3.2.4", 4857 + "@vitest/ui": "3.2.4", 4858 + "happy-dom": "*", 4859 + "jsdom": "*" 4860 + }, 4861 + "peerDependenciesMeta": { 4862 + "@edge-runtime/vm": { 4863 + "optional": true 4864 + }, 4865 + "@types/debug": { 4866 + "optional": true 4867 + }, 4868 + "@types/node": { 4869 + "optional": true 4870 + }, 4871 + "@vitest/browser": { 4872 + "optional": true 4873 + }, 4874 + "@vitest/ui": { 4875 + "optional": true 4876 + }, 4877 + "happy-dom": { 4878 + "optional": true 4879 + }, 4880 + "jsdom": { 4881 + "optional": true 4882 + } 4883 + } 4884 + }, 4885 + "node_modules/vitest/node_modules/picomatch": { 4886 + "version": "4.0.3", 4887 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 4888 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 4889 + "dev": true, 4890 + "license": "MIT", 4891 + "engines": { 4892 + "node": ">=12" 4893 + }, 4894 + "funding": { 4895 + "url": "https://github.com/sponsors/jonschlinkert" 4896 + } 4897 + }, 4898 + "node_modules/w3c-xmlserializer": { 4899 + "version": "5.0.0", 4900 + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", 4901 + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", 4902 + "dev": true, 4903 + "license": "MIT", 4904 + "dependencies": { 4905 + "xml-name-validator": "^5.0.0" 4906 + }, 4907 + "engines": { 4908 + "node": ">=18" 4909 + } 4910 + }, 4911 + "node_modules/weakmap-polyfill": { 4912 + "version": "2.0.4", 4913 + "resolved": "https://registry.npmjs.org/weakmap-polyfill/-/weakmap-polyfill-2.0.4.tgz", 4914 + "integrity": "sha512-ZzxBf288iALJseijWelmECm/1x7ZwQn3sMYIkDr2VvZp7r6SEKuT8D0O9Wiq6L9Nl5mazrOMcmiZE/2NCenaxw==", 4915 + "license": "MIT", 4916 + "engines": { 4917 + "node": ">=8.10.0" 4918 + } 4919 + }, 4920 + "node_modules/web-vitals": { 4921 + "version": "4.2.4", 4922 + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", 4923 + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", 4924 + "dev": true, 4925 + "license": "Apache-2.0" 4926 + }, 4927 + "node_modules/webidl-conversions": { 4928 + "version": "7.0.0", 4929 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 4930 + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 4931 + "dev": true, 4932 + "license": "BSD-2-Clause", 4933 + "engines": { 4934 + "node": ">=12" 4935 + } 4936 + }, 4937 + "node_modules/webpack-virtual-modules": { 4938 + "version": "0.6.2", 4939 + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", 4940 + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", 4941 + "license": "MIT" 4942 + }, 4943 + "node_modules/whatwg-encoding": { 4944 + "version": "3.1.1", 4945 + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", 4946 + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", 4947 + "dev": true, 4948 + "license": "MIT", 4949 + "dependencies": { 4950 + "iconv-lite": "0.6.3" 4951 + }, 4952 + "engines": { 4953 + "node": ">=18" 4954 + } 4955 + }, 4956 + "node_modules/whatwg-mimetype": { 4957 + "version": "4.0.0", 4958 + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", 4959 + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", 4960 + "dev": true, 4961 + "license": "MIT", 4962 + "engines": { 4963 + "node": ">=18" 4964 + } 4965 + }, 4966 + "node_modules/whatwg-url": { 4967 + "version": "14.2.0", 4968 + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", 4969 + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", 4970 + "dev": true, 4971 + "license": "MIT", 4972 + "dependencies": { 4973 + "tr46": "^5.1.0", 4974 + "webidl-conversions": "^7.0.0" 4975 + }, 4976 + "engines": { 4977 + "node": ">=18" 4978 + } 4979 + }, 4980 + "node_modules/why-is-node-running": { 4981 + "version": "2.3.0", 4982 + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 4983 + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 4984 + "dev": true, 4985 + "license": "MIT", 4986 + "dependencies": { 4987 + "siginfo": "^2.0.0", 4988 + "stackback": "0.0.2" 4989 + }, 4990 + "bin": { 4991 + "why-is-node-running": "cli.js" 4992 + }, 4993 + "engines": { 4994 + "node": ">=8" 4995 + } 4996 + }, 4997 + "node_modules/wistia-video-element": { 4998 + "version": "1.3.4", 4999 + "resolved": "https://registry.npmjs.org/wistia-video-element/-/wistia-video-element-1.3.4.tgz", 5000 + "integrity": "sha512-2l22oaQe4jUfi3yvsh2m2oCEgvbqTzaSYx6aJnZAvV5hlMUJlyZheFUnaj0JU2wGlHdVGV7xNY+5KpKu+ruLYA==", 5001 + "license": "MIT", 5002 + "dependencies": { 5003 + "super-media-element": "~1.4.2" 5004 + } 5005 + }, 5006 + "node_modules/ws": { 5007 + "version": "8.18.3", 5008 + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", 5009 + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", 5010 + "license": "MIT", 5011 + "engines": { 5012 + "node": ">=10.0.0" 5013 + }, 5014 + "peerDependencies": { 5015 + "bufferutil": "^4.0.1", 5016 + "utf-8-validate": ">=5.0.2" 5017 + }, 5018 + "peerDependenciesMeta": { 5019 + "bufferutil": { 5020 + "optional": true 5021 + }, 5022 + "utf-8-validate": { 5023 + "optional": true 5024 + } 5025 + } 5026 + }, 5027 + "node_modules/xml-name-validator": { 5028 + "version": "5.0.0", 5029 + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", 5030 + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", 5031 + "dev": true, 5032 + "license": "Apache-2.0", 5033 + "engines": { 5034 + "node": ">=18" 5035 + } 5036 + }, 5037 + "node_modules/xmlchars": { 5038 + "version": "2.2.0", 5039 + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", 5040 + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", 5041 + "dev": true, 5042 + "license": "MIT" 5043 + }, 5044 + "node_modules/yallist": { 5045 + "version": "3.1.1", 5046 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 5047 + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 5048 + "license": "ISC" 5049 + }, 5050 + "node_modules/youtube-video-element": { 5051 + "version": "1.6.2", 5052 + "resolved": "https://registry.npmjs.org/youtube-video-element/-/youtube-video-element-1.6.2.tgz", 5053 + "integrity": "sha512-YHDIOAqgRpfl1Ois9HcB8UFtWOxK8KJrV5TXpImj4BKYP1rWT04f/fMM9tQ9SYZlBKukT7NR+9wcI3UpB5BMDQ==", 5054 + "license": "MIT" 5055 + }, 5056 + "node_modules/zod": { 5057 + "version": "3.25.76", 5058 + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", 5059 + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", 5060 + "license": "MIT", 5061 + "funding": { 5062 + "url": "https://github.com/sponsors/colinhacks" 5063 + } 5064 + } 5065 + } 5066 + }
+39
package.json
··· 1 + { 2 + "name": "red-dwarf-tanstack", 3 + "private": true, 4 + "type": "module", 5 + "scripts": { 6 + "dev": "vite --port 3000", 7 + "start": "vite --port 3000", 8 + "build": "vite build && tsc", 9 + "serve": "vite preview", 10 + "test": "vitest run" 11 + }, 12 + "dependencies": { 13 + "@atproto/api": "^0.16.6", 14 + "@tailwindcss/vite": "^4.0.6", 15 + "@tanstack/react-devtools": "^0.2.2", 16 + "@tanstack/react-router": "^1.130.2", 17 + "@tanstack/react-router-devtools": "^1.131.5", 18 + "@tanstack/router-plugin": "^1.121.2", 19 + "idb-keyval": "^6.2.2", 20 + "react": "^19.0.0", 21 + "react-dom": "^19.0.0", 22 + "react-player": "^3.3.2", 23 + "tailwindcss": "^4.0.6" 24 + }, 25 + "devDependencies": { 26 + "@testing-library/dom": "^10.4.0", 27 + "@testing-library/react": "^16.2.0", 28 + "@types/node": "^24.3.0", 29 + "@types/react": "^19.0.8", 30 + "@types/react-dom": "^19.0.3", 31 + "@vitejs/plugin-react": "^4.3.4", 32 + "jsdom": "^26.0.0", 33 + "prettier": "^3.6.2", 34 + "typescript": "^5.7.2", 35 + "vite": "^6.3.5", 36 + "vitest": "^3.0.5", 37 + "web-vitals": "^4.2.4" 38 + } 39 + }
public/android-chrome-192x192.png

This is a binary file and will not be displayed.

public/android-chrome-512x512.png

This is a binary file and will not be displayed.

public/apple-touch-icon.png

This is a binary file and will not be displayed.

public/favicon-16x16.png

This is a binary file and will not be displayed.

public/favicon-32x32.png

This is a binary file and will not be displayed.

public/favicon-old.ico

This is a binary file and will not be displayed.

public/favicon-old.png

This is a binary file and will not be displayed.

public/favicon.ico

This is a binary file and will not be displayed.

public/favicon.png

This is a binary file and will not be displayed.

public/logo192.png

This is a binary file and will not be displayed.

public/logo512.png

This is a binary file and will not be displayed.

+25
public/manifest.json
··· 1 + { 2 + "short_name": "Red Dwarf", 3 + "name": "Red Dwarf", 4 + "icons": [ 5 + { 6 + "src": "favicon.ico", 7 + "sizes": "64x64 32x32 24x24 16x16", 8 + "type": "image/x-icon" 9 + }, 10 + { 11 + "src": "redstar.png", 12 + "type": "image/png", 13 + "sizes": "192x192" 14 + }, 15 + { 16 + "src": "redstar.png", 17 + "type": "image/png", 18 + "sizes": "512x512" 19 + } 20 + ], 21 + "start_url": ".", 22 + "display": "standalone", 23 + "theme_color": "#000000", 24 + "background_color": "#ffffff" 25 + }
public/redstar.png

This is a binary file and will not be displayed.

public/redstarccby4.png

This is a binary file and will not be displayed.

+3
public/robots.txt
··· 1 + # https://www.robotstxt.org/robotstxt.html 2 + User-agent: * 3 + Disallow:
+19
public/site.webmanifest
··· 1 + { 2 + "name": "", 3 + "short_name": "", 4 + "icons": [ 5 + { 6 + "src": "/android-chrome-192x192.png", 7 + "sizes": "192x192", 8 + "type": "image/png" 9 + }, 10 + { 11 + "src": "/android-chrome-512x512.png", 12 + "sizes": "512x512", 13 + "type": "image/png" 14 + } 15 + ], 16 + "theme_color": "#ffffff", 17 + "background_color": "#ffffff", 18 + "display": "standalone" 19 + }
+1
public/star.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10.577 8.704C11.21 7.568 11.527 7 12 7s.79.568 1.423 1.704l.164.294c.18.323.27.484.41.59c.14.107.316.147.665.226l.318.072c1.23.278 1.845.417 1.991.888c.147.47-.273.96-1.111 1.941l-.217.254c-.238.278-.357.418-.41.59c-.055.172-.037.358 0 .73l.032.338c.127 1.308.19 1.962-.193 2.253c-.383.29-.958.026-2.11-.504l-.298-.138c-.327-.15-.49-.226-.664-.226c-.173 0-.337.076-.664.226l-.298.138c-1.152.53-1.727.795-2.11.504c-.383-.29-.32-.945-.193-2.253l.032-.338c.037-.372.055-.558 0-.73c-.053-.172-.172-.312-.41-.59l-.217-.254c-.838-.98-1.258-1.47-1.111-1.941c.146-.47.76-.61 1.99-.888l.319-.072c.35-.08.524-.119.665-.225c.14-.107.23-.268.41-.59z"/><path fill="currentColor" fill-rule="evenodd" d="M12 1.25a.75.75 0 0 1 .75.75v2a.75.75 0 0 1-1.5 0V2a.75.75 0 0 1 .75-.75M1.25 12a.75.75 0 0 1 .75-.75h2a.75.75 0 0 1 0 1.5H2a.75.75 0 0 1-.75-.75m18 0a.75.75 0 0 1 .75-.75h2a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1-.75-.75M12 19.25a.75.75 0 0 1 .75.75v2a.75.75 0 0 1-1.5 0v-2a.75.75 0 0 1 .75-.75" clip-rule="evenodd" opacity="0.8"/><path fill="currentColor" d="M18.53 5.47a.75.75 0 0 1 0 1.06l-.343.344a.75.75 0 0 1-1.06-1.061l.343-.343a.75.75 0 0 1 1.06 0m-13.06 0a.75.75 0 0 1 1.06 0l.344.343a.75.75 0 0 1-1.061 1.06L5.47 6.53a.75.75 0 0 1 0-1.06m1.403 11.657a.75.75 0 0 1 0 1.06l-.343.343a.75.75 0 0 1-1.06-1.06l.343-.343a.75.75 0 0 1 1.06 0m10.254 0a.75.75 0 0 1 1.06 0l.343.343a.75.75 0 0 1-1.06 1.06l-.343-.343a.75.75 0 0 1 0-1.06" opacity="0.5"/></svg>
+53
src/components/DefaultCatchBoundary.tsx
··· 1 + import { 2 + ErrorComponent, 3 + Link, 4 + rootRouteId, 5 + useMatch, 6 + useRouter, 7 + } from "@tanstack/react-router"; 8 + import type { ErrorComponentProps } from "@tanstack/react-router"; 9 + 10 + export function DefaultCatchBoundary({ error }: ErrorComponentProps) { 11 + const router = useRouter(); 12 + const isRoot = useMatch({ 13 + strict: false, 14 + select: (state) => state.id === rootRouteId, 15 + }); 16 + 17 + console.error("DefaultCatchBoundary Error:", error); 18 + 19 + return ( 20 + <div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6"> 21 + <ErrorComponent error={error} /> 22 + <div className="flex gap-2 items-center flex-wrap"> 23 + <button 24 + onClick={() => { 25 + router.invalidate(); 26 + }} 27 + className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} 28 + > 29 + Try Again 30 + </button> 31 + {isRoot ? ( 32 + <Link 33 + to="/" 34 + className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} 35 + > 36 + Home 37 + </Link> 38 + ) : ( 39 + <Link 40 + to="/" 41 + className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} 42 + onClick={(e) => { 43 + e.preventDefault(); 44 + window.history.back(); 45 + }} 46 + > 47 + Go Back 48 + </Link> 49 + )} 50 + </div> 51 + </div> 52 + ); 53 + }
+227
src/components/Login.tsx
··· 1 + import React, { useEffect, useState, useRef } from "react"; 2 + import { useAuth } from "~/providers/PassAuthProvider"; 3 + 4 + interface LoginProps { 5 + compact?: boolean; 6 + } 7 + 8 + export default function Login({ compact = false }: LoginProps) { 9 + const { loginStatus, login, logout, loading, authed } = useAuth(); 10 + const [user, setUser] = useState(""); 11 + const [password, setPassword] = useState(""); 12 + const [serviceURL, setServiceURL] = useState("bsky.social"); 13 + const [showLoginForm, setShowLoginForm] = useState(false); 14 + const formRef = useRef<HTMLDivElement>(null); 15 + 16 + useEffect(() => { 17 + function handleClickOutside(event: MouseEvent) { 18 + if (formRef.current && !formRef.current.contains(event.target as Node)) { 19 + setShowLoginForm(false); 20 + } 21 + } 22 + 23 + if (showLoginForm) { 24 + document.addEventListener("mousedown", handleClickOutside); 25 + } 26 + 27 + return () => { 28 + document.removeEventListener("mousedown", handleClickOutside); 29 + }; 30 + }, [showLoginForm]); 31 + 32 + if (loading) { 33 + return ( 34 + <div className="flex items-center justify-center p-6 text-gray-500 dark:text-gray-400"> 35 + Loading... 36 + </div> 37 + ); 38 + } 39 + 40 + if (compact) { 41 + if (authed) { 42 + return ( 43 + <button 44 + onClick={logout} 45 + className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded px-3 py-1 font-medium transition-colors" 46 + > 47 + Log out 48 + </button> 49 + ); 50 + } else { 51 + return ( 52 + <div className="relative" ref={formRef}> 53 + <button 54 + onClick={() => setShowLoginForm(!showLoginForm)} 55 + className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded px-3 py-1 font-medium transition-colors" 56 + > 57 + Log in 58 + </button> 59 + {showLoginForm && ( 60 + <div className="absolute top-full right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50"> 61 + <form 62 + onSubmit={(e) => { 63 + e.preventDefault(); 64 + login(user, password, `https://${serviceURL}`); 65 + setShowLoginForm(false); 66 + }} 67 + className="flex flex-col gap-3" 68 + > 69 + <p className="text-xs text-gray-500 dark:text-gray-400"> 70 + sorry for the temporary login, 71 + <br /> 72 + oauth will come soon enough i swear 73 + </p> 74 + <input 75 + type="text" 76 + placeholder="Username" 77 + value={user} 78 + onChange={(e) => setUser(e.target.value)} 79 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" 80 + autoComplete="username" 81 + /> 82 + <input 83 + type="password" 84 + placeholder="Password" 85 + value={password} 86 + onChange={(e) => setPassword(e.target.value)} 87 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" 88 + autoComplete="current-password" 89 + /> 90 + <input 91 + type="text" 92 + placeholder="bsky.social" 93 + value={serviceURL} 94 + onChange={(e) => setServiceURL(e.target.value)} 95 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" 96 + /> 97 + <button 98 + type="submit" 99 + className="bg-gray-600 hover:bg-gray-700 text-white rounded px-4 py-2 font-medium text-sm transition-colors" 100 + > 101 + Log in 102 + </button> 103 + </form> 104 + </div> 105 + )} 106 + </div> 107 + ); 108 + } 109 + } 110 + 111 + return ( 112 + <div className="p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-6 mx-4"> 113 + {authed ? ( 114 + <div className="flex flex-col items-center justify-center text-center"> 115 + <p className="text-lg font-semibold mb-6 text-gray-800 dark:text-gray-100"> 116 + You are logged in! 117 + </p> 118 + <button 119 + onClick={logout} 120 + className="bg-gray-600 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors" 121 + > 122 + Log out 123 + </button> 124 + </div> 125 + ) : ( 126 + <form 127 + onSubmit={(e) => { 128 + e.preventDefault(); 129 + login(user, password, `https://${serviceURL}`); 130 + }} 131 + className="flex flex-col gap-4" 132 + > 133 + <p className="text-sm text-gray-500 dark:text-gray-400 mb-2"> 134 + sorry for the temporary login, 135 + <br /> 136 + oauth will come soon enough i swear 137 + </p> 138 + <input 139 + type="text" 140 + placeholder="Username" 141 + value={user} 142 + onChange={(e) => setUser(e.target.value)} 143 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-base focus:outline-none focus:ring-2 focus:ring-blue-500" 144 + autoComplete="username" 145 + /> 146 + <input 147 + type="password" 148 + placeholder="Password" 149 + value={password} 150 + onChange={(e) => setPassword(e.target.value)} 151 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-base focus:outline-none focus:ring-2 focus:ring-blue-500" 152 + autoComplete="current-password" 153 + /> 154 + <input 155 + type="text" 156 + placeholder="bsky.social" 157 + value={serviceURL} 158 + onChange={(e) => setServiceURL(e.target.value)} 159 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-base focus:outline-none focus:ring-2 focus:ring-blue-500" 160 + /> 161 + <button 162 + type="submit" 163 + className="bg-gray-600 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors mt-2" 164 + > 165 + Log in 166 + </button> 167 + </form> 168 + )} 169 + </div> 170 + ); 171 + } 172 + 173 + export const ProfileThing = () => { 174 + const { agent, loading, loginStatus, authed } = useAuth(); 175 + const [response, setResponse] = useState<any>(null); 176 + 177 + useEffect(() => { 178 + if (loginStatus && agent && !loading && authed) { 179 + fetchUser(); 180 + } 181 + // eslint-disable-next-line 182 + }, [loginStatus, agent, loading, authed]); 183 + 184 + const fetchUser = async () => { 185 + if (!agent) { 186 + console.error("Agent is null or undefined"); 187 + return; 188 + } 189 + const res = await agent.app.bsky.actor.getProfile({ 190 + actor: agent.assertDid, 191 + }); 192 + setResponse(res.data); 193 + }; 194 + 195 + if (!authed) { 196 + return ( 197 + <div className="inline-block"> 198 + <span className="text-gray-100 text-base font-medium px-1.5"> 199 + Login 200 + </span> 201 + </div> 202 + ); 203 + } 204 + 205 + if (!response) { 206 + return ( 207 + <div className="flex flex-col items-start gap-1.5"> 208 + <span className="w-5 h-5 border-2 border-gray-200 dark:border-gray-600 border-t-transparent rounded-full animate-spin inline-block" /> 209 + <span className="text-gray-100">Loading... </span> 210 + </div> 211 + ); 212 + } 213 + 214 + return ( 215 + <div className="flex flex-row items-start gap-1.5"> 216 + <img 217 + src={response?.avatar} 218 + alt="avatar" 219 + className="w-[30px] h-[30px] rounded-full object-cover" 220 + /> 221 + <div> 222 + <div className="text-gray-100 text-xs">{response?.displayName}</div> 223 + <div className="text-gray-100 text-xs">@{response?.handle}</div> 224 + </div> 225 + </div> 226 + ); 227 + };
+25
src/components/NotFound.tsx
··· 1 + import { Link } from "@tanstack/react-router"; 2 + 3 + export function NotFound({ children }: { children?: any }) { 4 + return ( 5 + <div className="space-y-2 p-2"> 6 + <div className="text-gray-600 dark:text-gray-400"> 7 + {children || <p>The page you are looking for does not exist.</p>} 8 + </div> 9 + <p className="flex items-center gap-2 flex-wrap"> 10 + <button 11 + onClick={() => window.history.back()} 12 + className="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm" 13 + > 14 + Go back 15 + </button> 16 + <Link 17 + to="/" 18 + className="bg-cyan-600 text-white px-2 py-1 rounded uppercase font-black text-sm" 19 + > 20 + Start Over 21 + </Link> 22 + </p> 23 + </div> 24 + ); 25 + }
+5
src/components/PostError.tsx
··· 1 + import { ErrorComponent, ErrorComponentProps } from "@tanstack/react-router"; 2 + 3 + export function PostErrorComponent({ error }: ErrorComponentProps) { 4 + return <ErrorComponent error={error} />; 5 + }
+2598
src/components/UniversalPostRenderer.tsx
··· 1 + import * as React from "react"; 2 + import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 3 + import { useNavigate } from "@tanstack/react-router"; 4 + import { type SVGProps } from "react"; 5 + 6 + function asTyped<T extends { $type: string }>(obj: T): $Typed<T> { 7 + return obj as $Typed<T>; 8 + } 9 + 10 + export const CACHE_TIMEOUT = 5 * 60 * 1000; 11 + const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 12 + 13 + export interface UniversalPostRendererATURILoaderProps { 14 + atUri: string; 15 + onConstellation?: (data: any) => void; 16 + detailed?: boolean; 17 + } 18 + 19 + export async function cachedGetRecord({ 20 + atUri, 21 + cacheTimeout = CACHE_TIMEOUT, 22 + get, 23 + set, 24 + }: { 25 + atUri: string; 26 + //resolved: { pdsUrl: string; did: string } | null | undefined; 27 + cacheTimeout?: number; 28 + get: (key: string) => any; 29 + set: (key: string, value: string) => void; 30 + }): Promise<any> { 31 + const cacheKey = `record:${atUri}`; 32 + const cached = get(cacheKey); 33 + const now = Date.now(); 34 + if ( 35 + cached && 36 + cached.value && 37 + cached.time && 38 + now - cached.time < cacheTimeout 39 + ) { 40 + try { 41 + return JSON.parse(cached.value); 42 + } catch { 43 + // fall through to fetch 44 + } 45 + } 46 + const parsed = parseAtUri(atUri); 47 + if (!parsed) return null; 48 + const resolved = await cachedResolveIdentity({ 49 + didOrHandle: parsed.did, 50 + get, 51 + set, 52 + }); 53 + if (!resolved?.pdsUrl || !resolved?.did) 54 + throw new Error("Missing resolved PDS info"); 55 + 56 + if (!parsed) throw new Error("Invalid atUri"); 57 + const { collection, rkey } = parsed; 58 + const url = `${ 59 + resolved.pdsUrl 60 + }/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent( 61 + resolved.did, 62 + )}&collection=${encodeURIComponent(collection)}&rkey=${encodeURIComponent( 63 + rkey, 64 + )}`; 65 + const res = await fetch(url); 66 + if (!res.ok) throw new Error("Failed to fetch base record"); 67 + const data = await res.json(); 68 + set(cacheKey, JSON.stringify(data)); 69 + return data; 70 + } 71 + 72 + export async function cachedResolveIdentity({ 73 + didOrHandle, 74 + cacheTimeout = HANDLE_DID_CACHE_TIMEOUT, 75 + get, 76 + set, 77 + }: { 78 + didOrHandle: string; 79 + cacheTimeout?: number; 80 + get: (key: string) => any; 81 + set: (key: string, value: string) => void; 82 + }): Promise<any> { 83 + const isDidInput = didOrHandle.startsWith("did:"); 84 + const cacheKey = `handleDid:${didOrHandle}`; 85 + const now = Date.now(); 86 + const cached = get(cacheKey); 87 + if ( 88 + cached && 89 + cached.value && 90 + cached.time && 91 + now - cached.time < cacheTimeout 92 + ) { 93 + try { 94 + return JSON.parse(cached.value); 95 + } catch {} 96 + } 97 + const url = `https://free-fly-24.deno.dev/?${ 98 + isDidInput 99 + ? `did=${encodeURIComponent(didOrHandle)}` 100 + : `handle=${encodeURIComponent(didOrHandle)}` 101 + }`; 102 + const res = await fetch(url); 103 + if (!res.ok) throw new Error("Failed to resolve handle/did"); 104 + const data = await res.json(); 105 + set(cacheKey, JSON.stringify(data)); 106 + if (!isDidInput && data.did) { 107 + set(`handleDid:${data.did}`, JSON.stringify(data)); 108 + } 109 + return data; 110 + } 111 + 112 + export function UniversalPostRendererATURILoader({ 113 + atUri, 114 + onConstellation, 115 + detailed = false, 116 + }: UniversalPostRendererATURILoaderProps) { 117 + console.log("atUri", atUri); 118 + const { get, set } = usePersistentStore(); 119 + const [record, setRecord] = React.useState<any>(null); 120 + const [links, setLinks] = React.useState<any>(null); 121 + //const [error, setError] = React.useState<string | null>(null); 122 + //const [cacheTime, setCacheTime] = React.useState<number | null>(null); 123 + const [resolved, setResolved] = React.useState<any>(null); // { did, pdsUrl, bskyPds, handle } 124 + const [opProfile, setOpProfile] = React.useState<any>(null); 125 + // const [opProfileCacheTime, setOpProfileCacheTime] = React.useState< 126 + // number | null 127 + // >(null); 128 + //const router = useRouter(); 129 + 130 + const parsed = React.useMemo(() => parseAtUri(atUri), [atUri]); 131 + const did = parsed?.did; 132 + const rkey = parsed?.rkey; 133 + console.log("did", did); 134 + console.log("rkey", rkey); 135 + 136 + React.useEffect(() => { 137 + const checkCache = async () => { 138 + const postUri = atUri; 139 + const cacheKey = `record:${postUri}`; 140 + const cached = await get(cacheKey); 141 + const now = Date.now(); 142 + console.log( 143 + "UniversalPostRenderer checking cache for", 144 + cacheKey, 145 + "cached:", 146 + !!cached, 147 + ); 148 + if ( 149 + cached && 150 + cached.value && 151 + cached.time && 152 + now - cached.time < CACHE_TIMEOUT 153 + ) { 154 + try { 155 + console.log("UniversalPostRenderer found cached data for", cacheKey); 156 + setRecord(JSON.parse(cached.value)); 157 + } catch { 158 + setRecord(null); 159 + } 160 + } 161 + }; 162 + checkCache(); 163 + }, [atUri, get]); 164 + 165 + React.useEffect(() => { 166 + if (!did || record) return; 167 + (async () => { 168 + try { 169 + const resolvedData = await cachedResolveIdentity({ 170 + didOrHandle: did, 171 + get, 172 + set, 173 + }); 174 + setResolved(resolvedData); 175 + } catch (e: any) { 176 + //setError("Failed to resolve handle/did: " + e?.message); 177 + } 178 + })(); 179 + }, [did, get, set, record]); 180 + 181 + React.useEffect(() => { 182 + if (!resolved || !resolved.pdsUrl || !resolved.did || !rkey || record) 183 + return; 184 + let ignore = false; 185 + (async () => { 186 + try { 187 + const data = await cachedGetRecord({ 188 + atUri, 189 + get, 190 + set, 191 + }); 192 + if (!ignore) setRecord(data); 193 + } catch (e: any) { 194 + //if (!ignore) setError("Failed to fetch base record: " + e?.message); 195 + } 196 + })(); 197 + return () => { 198 + ignore = true; 199 + }; 200 + }, [resolved, rkey, atUri, record]); 201 + 202 + React.useEffect(() => { 203 + if (!resolved || !resolved.did || !rkey) return; 204 + const fetchLinks = async () => { 205 + const postUri = atUri; 206 + const cacheKey = `constellation:${postUri}`; 207 + const cached = await get(cacheKey); 208 + const now = Date.now(); 209 + if ( 210 + cached && 211 + cached.value && 212 + cached.time && 213 + now - cached.time < CACHE_TIMEOUT 214 + ) { 215 + try { 216 + const data = JSON.parse(cached.value); 217 + setLinks(data); 218 + if (onConstellation) onConstellation(data); 219 + } catch { 220 + setLinks(null); 221 + } 222 + //setCacheTime(cached.time); 223 + return; 224 + } 225 + try { 226 + const url = `https://constellation.microcosm.blue/links/all?target=${encodeURIComponent( 227 + atUri, 228 + )}`; 229 + const res = await fetch(url); 230 + if (!res.ok) throw new Error("Failed to fetch constellation links"); 231 + const data = await res.json(); 232 + setLinks(data); 233 + //setCacheTime(now); 234 + set(cacheKey, JSON.stringify(data)); 235 + if (onConstellation) onConstellation(data); 236 + } catch (e: any) { 237 + //setError("Failed to fetch constellation links: " + e?.message); 238 + } 239 + }; 240 + fetchLinks(); 241 + }, [resolved, rkey, get, set, atUri, onConstellation]); 242 + 243 + React.useEffect(() => { 244 + if (!record || !resolved || !resolved.did) return; 245 + const fetchOpProfile = async () => { 246 + const opDid = resolved.did; 247 + const postUri = atUri; 248 + const cacheKey = `profile:${postUri}`; 249 + const cached = await get(cacheKey); 250 + const now = Date.now(); 251 + if ( 252 + cached && 253 + cached.value && 254 + cached.time && 255 + now - cached.time < CACHE_TIMEOUT 256 + ) { 257 + try { 258 + setOpProfile(JSON.parse(cached.value)); 259 + } catch { 260 + setOpProfile(null); 261 + } 262 + //setOpProfileCacheTime(cached.time); 263 + return; 264 + } 265 + try { 266 + let opResolvedRaw = await get(`handleDid:${opDid}`); 267 + let opResolved: any = null; 268 + if ( 269 + opResolvedRaw && 270 + opResolvedRaw.value && 271 + opResolvedRaw.time && 272 + now - opResolvedRaw.time < HANDLE_DID_CACHE_TIMEOUT 273 + ) { 274 + try { 275 + opResolved = JSON.parse(opResolvedRaw.value); 276 + } catch { 277 + opResolved = null; 278 + } 279 + } else { 280 + const url = `https://free-fly-24.deno.dev/?did=${encodeURIComponent( 281 + opDid, 282 + )}`; 283 + const res = await fetch(url); 284 + if (!res.ok) throw new Error("Failed to resolve OP did"); 285 + opResolved = await res.json(); 286 + set(`handleDid:${opDid}`, JSON.stringify(opResolved)); 287 + } 288 + if (!opResolved || !opResolved.pdsUrl) 289 + throw new Error("OP did resolution failed or missing pdsUrl"); 290 + const profileUrl = `${ 291 + opResolved.pdsUrl 292 + }/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent( 293 + opDid, 294 + )}&collection=app.bsky.actor.profile&rkey=self`; 295 + const profileRes = await fetch(profileUrl); 296 + if (!profileRes.ok) throw new Error("Failed to fetch OP profile"); 297 + const profileData = await profileRes.json(); 298 + setOpProfile(profileData); 299 + //setOpProfileCacheTime(now); 300 + set(cacheKey, JSON.stringify(profileData)); 301 + } catch (e: any) { 302 + //setError("Failed to fetch OP profile: " + e?.message); 303 + } 304 + }; 305 + fetchOpProfile(); 306 + }, [record, get, set, rkey, resolved, atUri]); 307 + 308 + // const displayName = 309 + // opProfile?.value?.displayName || resolved?.handle || resolved?.did; 310 + // const handle = resolved?.handle ? `@${resolved.handle}` : resolved?.did; 311 + 312 + // const postText = record?.value?.text || ""; 313 + // const createdAt = record?.value?.createdAt 314 + // ? new Date(record.value.createdAt) 315 + // : null; 316 + // const langTags = record?.value?.langs || []; 317 + 318 + const [likes, setLikes] = React.useState<number | null>(null); 319 + const [reposts, setReposts] = React.useState<number | null>(null); 320 + const [replies, setReplies] = React.useState<number | null>(null); 321 + 322 + React.useEffect(() => { 323 + console.log(JSON.stringify(links, null, 2)); 324 + setLikes( 325 + links 326 + ? links?.links?.["app.bsky.feed.like"]?.[".subject.uri"]?.records || 0 327 + : null, 328 + ); 329 + setReposts( 330 + links 331 + ? links?.links?.["app.bsky.feed.repost"]?.[".subject.uri"]?.records || 0 332 + : null, 333 + ); 334 + setReplies( 335 + links 336 + ? links?.links?.["app.bsky.feed.post"]?.[".reply.parent.uri"] 337 + ?.records || 0 338 + : null, 339 + ); 340 + }, [links]); 341 + 342 + // const navigateToProfile = (e: React.MouseEvent) => { 343 + // e.stopPropagation(); 344 + // if (resolved?.did) { 345 + // router.navigate({ 346 + // to: "/profile/$did", 347 + // params: { did: resolved.did }, 348 + // }); 349 + // } 350 + // }; 351 + 352 + return ( 353 + <UniversalPostRendererRawRecordShim 354 + detailed={detailed} 355 + postRecord={record} 356 + profileRecord={opProfile} 357 + aturi={atUri} 358 + resolved={resolved} 359 + likesCount={likes} 360 + repostsCount={reposts} 361 + repliesCount={replies} 362 + /> 363 + ); 364 + } 365 + 366 + export function UniversalPostRendererRawRecordShim({ 367 + postRecord, 368 + profileRecord, 369 + aturi, 370 + resolved, 371 + likesCount, 372 + repostsCount, 373 + repliesCount, 374 + detailed = false, 375 + }: { 376 + postRecord: any; 377 + profileRecord: any; 378 + aturi: string; 379 + resolved: any; 380 + likesCount?: number | null; 381 + repostsCount?: number | null; 382 + repliesCount?: number | null; 383 + detailed?: boolean; 384 + }) { 385 + const navigate = useNavigate(); 386 + 387 + const { get, set } = usePersistentStore(); 388 + function getAvatarUrl(opProfile: any) { 389 + const link = opProfile?.value?.avatar?.ref?.["$link"]; 390 + if (!link) return null; 391 + return `https://cdn.bsky.app/img/avatar/plain/${resolved?.did}/${link}@jpeg`; 392 + } 393 + 394 + const [hydratedEmbed, setHydratedEmbed] = useState<any>(undefined); 395 + 396 + useEffect(() => { 397 + const run = async () => { 398 + if (!postRecord?.value?.embed) return; 399 + const embed = postRecord?.value?.embed; 400 + if (!embed || !embed.$type) { 401 + setHydratedEmbed(undefined); 402 + return; 403 + } 404 + 405 + try { 406 + let result: any; 407 + 408 + if (embed?.$type === "app.bsky.embed.recordWithMedia") { 409 + const mediaEmbed = embed.media; 410 + 411 + let hydratedMedia; 412 + if (mediaEmbed?.$type === "app.bsky.embed.images") { 413 + hydratedMedia = hydrateEmbedImages(mediaEmbed, resolved?.did); 414 + } else if (mediaEmbed?.$type === "app.bsky.embed.external") { 415 + hydratedMedia = hydrateEmbedExternal(mediaEmbed, resolved?.did); 416 + } else if (mediaEmbed?.$type === "app.bsky.embed.video") { 417 + hydratedMedia = hydrateEmbedVideo(mediaEmbed, resolved?.did); 418 + } else { 419 + throw new Error("idiot"); 420 + } 421 + if (!hydratedMedia) throw new Error("idiot"); 422 + 423 + // hydrate the outer recordWithMedia now using the hydrated media 424 + result = await hydrateEmbedRecordWithMedia( 425 + embed, 426 + resolved?.did, 427 + hydratedMedia, 428 + get, 429 + set, 430 + ); 431 + } else { 432 + const hydrated = 433 + embed?.$type === "app.bsky.embed.images" 434 + ? hydrateEmbedImages(embed, resolved?.did) 435 + : embed?.$type === "app.bsky.embed.external" 436 + ? hydrateEmbedExternal(embed, resolved?.did) 437 + : embed?.$type === "app.bsky.embed.video" 438 + ? hydrateEmbedVideo(embed, resolved?.did) 439 + : embed?.$type === "app.bsky.embed.record" 440 + ? hydrateEmbedRecord(embed, resolved?.did, get, set) 441 + : undefined; 442 + 443 + result = hydrated instanceof Promise ? await hydrated : hydrated; 444 + } 445 + 446 + console.log( 447 + String(result) + " hydrateEmbedRecordWithMedia hey hyeh ye", 448 + ); 449 + setHydratedEmbed(result); 450 + } catch (e) { 451 + console.error("Error hydrating embed", e); 452 + setHydratedEmbed(undefined); 453 + } 454 + }; 455 + 456 + run(); 457 + }, [postRecord, resolved?.did]); 458 + 459 + const parsedaturi = parseAtUri(aturi); 460 + 461 + return ( 462 + <> 463 + {/* <p> 464 + {postRecord?.value?.embed.$type + " " + JSON.stringify(hydratedEmbed)} 465 + </p> */} 466 + <UniversalPostRenderer 467 + expanded={detailed} 468 + onPostClick={() => 469 + parsedaturi && 470 + navigate({ 471 + to: "/profile/$did/post/$rkey", 472 + params: { did: parsedaturi.did, rkey: parsedaturi.rkey }, 473 + }) 474 + } 475 + // onProfileClick={() => parsedaturi && navigate({to: "/profile/$did", 476 + // params: {did: parsedaturi.did} 477 + // })} 478 + onProfileClick={(e) => { 479 + e.stopPropagation(); 480 + if (parsedaturi) { 481 + navigate({ 482 + to: "/profile/$did", 483 + params: { did: parsedaturi.did }, 484 + }); 485 + } 486 + }} 487 + post={{ 488 + $type: "app.bsky.feed.defs#postView", 489 + uri: aturi, 490 + cid: postRecord?.cid || "", 491 + author: { 492 + did: resolved?.did || "", 493 + handle: resolved?.handle || "", 494 + displayName: profileRecord?.value?.displayName || "", 495 + avatar: getAvatarUrl(profileRecord) || "", 496 + viewer: undefined, 497 + labels: profileRecord?.labels || undefined, 498 + verification: undefined, 499 + }, 500 + record: postRecord?.value || {}, 501 + embed: hydratedEmbed ?? undefined, 502 + replyCount: repliesCount ?? 0, 503 + repostCount: repostsCount ?? 0, 504 + likeCount: likesCount ?? 0, 505 + quoteCount: 0, 506 + indexedAt: postRecord?.value?.createdAt || "", 507 + viewer: undefined, 508 + labels: postRecord?.labels || undefined, 509 + threadgate: undefined, 510 + }} 511 + salt={aturi} 512 + /> 513 + </> 514 + ); 515 + } 516 + 517 + function hydrateEmbedImages( 518 + embed: any, 519 + did: string, 520 + ): $Typed<AppBskyEmbedImages.View> | undefined { 521 + if (!embed || embed.$type !== "app.bsky.embed.images") return undefined; 522 + if (!Array.isArray(embed.images)) return undefined; 523 + return asTyped({ 524 + $type: "app.bsky.embed.images#view" as const, // <-- literal type 525 + images: embed.images 526 + .map((img: any) => { 527 + const link = img?.image?.ref?.["$link"]; 528 + if (!link) return null; 529 + return { 530 + thumb: `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${link}@jpeg`, 531 + fullsize: `https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${link}@jpeg`, 532 + alt: img.alt || "", 533 + aspectRatio: img.aspectRatio, 534 + }; 535 + }) 536 + .filter(Boolean), 537 + }); 538 + } 539 + 540 + function hydrateEmbedExternal( 541 + /*{embed, did} : {*/ embed: any, 542 + did: string, //} 543 + ): $Typed<AppBskyEmbedExternal.View> | undefined { 544 + if (!embed || embed.$type !== "app.bsky.embed.external") return undefined; 545 + if (!embed.external) return undefined; 546 + return asTyped({ 547 + $type: "app.bsky.embed.external#view" as const, 548 + external: { 549 + uri: embed.external.uri, 550 + title: embed.external.title, 551 + description: embed.external.description, 552 + thumb: embed?.external?.thumb?.ref?.$link 553 + ? `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${embed.external.thumb.ref.$link}@jpeg` 554 + : undefined, 555 + }, 556 + }); 557 + } 558 + 559 + function hydrateEmbedVideo( 560 + embed: any, 561 + did: string, 562 + ): $Typed<AppBskyEmbedVideo.View> | undefined { 563 + if (!embed || embed.$type !== "app.bsky.embed.video") return undefined; 564 + if (!embed.video || !embed.video.ref?.$link) return undefined; 565 + 566 + const videoLink = embed.video.ref.$link; 567 + 568 + return asTyped({ 569 + $type: "app.bsky.embed.video#view" as const, 570 + playlist: `https://video.bsky.app/watch/${did}/${videoLink}/playlist.m3u8`, 571 + thumbnail: `https://video.bsky.app/watch/${did}/${videoLink}/thumbnail.jpg`, 572 + aspectRatio: embed.aspectRatio, 573 + cid: videoLink, 574 + }); 575 + } 576 + async function hydrateEmbedRecordWithMedia( 577 + embed: any, 578 + did: string, 579 + mediaHydratedEmbed: 580 + | $Typed<AppBskyEmbedImages.View> 581 + | $Typed<AppBskyEmbedVideo.View> 582 + | $Typed<AppBskyEmbedExternal.View> 583 + | { $type: string }, 584 + get: (key: string) => any, 585 + set: (key: string, value: string) => void, 586 + ): Promise<$Typed<AppBskyEmbedRecordWithMedia.View> | undefined> { 587 + //return({"hello": "wow"} as any) 588 + console.log("hydrateEmbedRecordWithMedia called!!"); 589 + if (!embed || embed.$type !== "app.bsky.embed.recordWithMedia") 590 + return undefined; 591 + console.log("hydrateEmbedRecordWithMedia 1!!"); 592 + async function deferredrecordget(): Promise< 593 + $Typed<AppBskyEmbedRecord.ViewRecord> 594 + > { 595 + console.log("hydrateEmbedRecordWithMedia 3!!"); 596 + const quoterr = await cachedGetRecord({ 597 + atUri: embed.record.record.uri, 598 + get, 599 + set, 600 + }); 601 + async function defferedQuotedRecordget(): Promise<{ 602 + [_ in string]: unknown; 603 + }> { 604 + console.log("hydrateEmbedRecordWithMedia 4!!"); 605 + return quoterr.value; 606 + } 607 + async function defferedOPRecordget(): Promise< 608 + $Typed<AppBskyActorDefs.ProfileViewBasic> 609 + > { 610 + const parseduri = parseAtUri(embed.record.record.uri); 611 + if (!parseduri) throw new Error("invalid uri"); 612 + console.log("deep- hydrateEmbedRecordWithMedia " + parseduri.did); 613 + const didwhat = parseduri?.did; 614 + console.log("hydrateEmbedRecordWithMedia 4.97!!"); 615 + const opr = await cachedGetRecord({ 616 + atUri: `at://${didwhat}/app.bsky.actor.profile/self`, 617 + get, 618 + set, 619 + }); 620 + console.log("hydrateEmbedRecordWithMedia 4.98!! opr:" + opr); 621 + const opi = await cachedResolveIdentity({ 622 + didOrHandle: didwhat, 623 + get, 624 + set, 625 + }); 626 + console.log("hydrateEmbedRecordWithMedia 4.99!!"); 627 + console.log("hydrateEmbedRecordWithMedia 5!!"); 628 + const thedid = didwhat; 629 + console.log("hydrateEmbedRecordWithMedia 5.01!! " + thedid); 630 + const thehandle = opi?.handle || ""; 631 + console.log("hydrateEmbedRecordWithMedia 5.02!! " + thehandle); 632 + const thedisplayname = (opr.value?.displayName ?? opi?.handle) || ""; 633 + console.log("hydrateEmbedRecordWithMedia 5.03!! " + thedisplayname); 634 + const theavatar = opr.value?.avatar?.ref?.$link 635 + ? `https://cdn.bsky.app/img/avatar/plain/${didwhat}/${opr.value?.avatar?.ref?.$link}@jpeg` 636 + : undefined; 637 + console.log("hydrateEmbedRecordWithMedia 5.04!! " + theavatar); 638 + console.log("hydrateEmbedRecordWithMedia 5.05!!"); 639 + const thecreatedat = opr.value?.createdAt ?? undefined; 640 + console.log("hydrateEmbedRecordWithMedia 5.06!! " + thecreatedat); 641 + console.log("hydrateEmbedRecordWithMedia 5.07!!"); 642 + console.log("hydrateEmbedRecordWithMedia 5.08!!"); 643 + const crying = { 644 + $type: "app.bsky.actor.defs#profileViewBasic" as const, 645 + did: thedid, 646 + handle: thehandle, 647 + displayName: thedisplayname, 648 + avatar: theavatar, 649 + associated: { 650 + chat: { 651 + allowIncoming: "all", 652 + }, 653 + }, 654 + labels: [], 655 + createdAt: thecreatedat, 656 + }; 657 + return asTyped(crying); 658 + } 659 + 660 + const record = await defferedQuotedRecordget(); 661 + const OP = await defferedOPRecordget(); 662 + 663 + console.log("hydrateEmbedRecordWithMedia victory-lap 6!!"); 664 + return asTyped({ 665 + $type: "app.bsky.embed.record#viewRecord" as const, 666 + uri: embed.record.record.uri, 667 + cid: embed.record.record.cid, 668 + indexedAt: String(record.createdAt || "") || "", 669 + author: OP, 670 + value: record, 671 + }); 672 + } 673 + console.log("hydrateEmbedRecordWithMedia 2!!"); 674 + 675 + const recordion = await deferredrecordget(); 676 + console.log("hydrateEmbedRecordWithMedia victory-lap 7!!"); 677 + 678 + const final = asTyped({ 679 + $type: "app.bsky.embed.recordWithMedia#view" as const, 680 + record: { 681 + //$type: "app.bsky.embed.record#view" as const, 682 + record: recordion, 683 + }, 684 + media: mediaHydratedEmbed, 685 + // media: asTyped({ 686 + // $type: "app.bsky.embed.images" as const, 687 + // images: embed.media.images 688 + // ? embed.media.images 689 + // .map((img: any) => { 690 + // const link = img?.image?.ref?.["$link"]; 691 + // if (!link) return null; 692 + // return { 693 + // thumb: `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${link}@jpeg`, 694 + // fullsize: `https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${link}@jpeg`, 695 + // alt: img.alt || "", 696 + // aspectRatio: img.aspectRatio, 697 + // }; 698 + // }) 699 + // .filter(Boolean) 700 + // : undefined, 701 + // }), 702 + }); 703 + console.log("hydrateEmbedRecordWithMedia final " + final); 704 + return final; 705 + } 706 + 707 + async function hydrateEmbedRecord( 708 + embed: any, 709 + did: string, 710 + get: (key: string) => any, 711 + set: (key: string, value: string) => void, 712 + ): Promise<$Typed<AppBskyEmbedRecord.View> | undefined> { 713 + if (!embed || embed.$type !== "app.bsky.embed.record") return undefined; 714 + 715 + const recordRef = embed.record?.record?.uri 716 + ? embed.record.record 717 + : embed.record; 718 + 719 + const quoted = await cachedGetRecord({ 720 + atUri: recordRef.uri, 721 + get, 722 + set, 723 + }); 724 + 725 + const parseduri = parseAtUri(recordRef.uri); 726 + if (!parseduri) throw new Error("invalid uri"); 727 + const didwhat = parseduri.did; 728 + 729 + const opr = await cachedGetRecord({ 730 + atUri: `at://${didwhat}/app.bsky.actor.profile/self`, 731 + get, 732 + set, 733 + }); 734 + const opi = await cachedResolveIdentity({ 735 + didOrHandle: didwhat, 736 + get, 737 + set, 738 + }); 739 + 740 + const author = { 741 + $type: "app.bsky.actor.defs#profileViewBasic" as const, 742 + did: didwhat, 743 + handle: opi?.handle || "", 744 + displayName: (opr.value?.displayName ?? opi?.handle) || "", 745 + avatar: opr.value?.avatar?.ref?.$link 746 + ? `https://cdn.bsky.app/img/avatar/plain/${didwhat}/${opr.value?.avatar?.ref?.$link}@jpeg` 747 + : undefined, 748 + associated: { 749 + chat: { 750 + allowIncoming: "all", 751 + }, 752 + }, 753 + labels: [], 754 + createdAt: opr.value?.createdAt ?? undefined, 755 + }; 756 + 757 + const viewRecord: $Typed<AppBskyEmbedRecord.ViewRecord> = asTyped({ 758 + $type: "app.bsky.embed.record#viewRecord" as const, 759 + uri: recordRef.uri, 760 + cid: recordRef.cid, 761 + indexedAt: String(quoted.value.createdAt || "") || "", 762 + author, 763 + value: quoted.value, 764 + replyCount: quoted.value.replyCount, 765 + repostCount: quoted.value.repostCount, 766 + likeCount: quoted.value.likeCount, 767 + quoteCount: quoted.value.quoteCount, 768 + labels: quoted.value.labels, 769 + embeds: quoted.value.embed ? [quoted.value.embed] : undefined, 770 + }); 771 + 772 + return asTyped({ 773 + $type: "app.bsky.embed.record#view" as const, 774 + record: viewRecord, 775 + }); 776 + } 777 + 778 + export function parseAtUri( 779 + atUri: string, 780 + ): { did: string; collection: string; rkey: string } | null { 781 + const PREFIX = "at://"; 782 + if (!atUri.startsWith(PREFIX)) { 783 + return null; 784 + } 785 + 786 + const parts = atUri.slice(PREFIX.length).split("/"); 787 + 788 + if (parts.length !== 3) { 789 + return null; 790 + } 791 + 792 + const [did, collection, rkey] = parts; 793 + 794 + if (!did || !collection || !rkey) { 795 + return null; 796 + } 797 + 798 + return { did, collection, rkey }; 799 + } 800 + 801 + export function MdiCommentOutline(props: SVGProps<SVGSVGElement>) { 802 + return ( 803 + <svg 804 + xmlns="http://www.w3.org/2000/svg" 805 + width={16} 806 + height={16} 807 + viewBox="0 0 24 24" 808 + {...props} 809 + > 810 + <path 811 + fill="oklch(0.704 0.05 28)" 812 + d="M9 22a1 1 0 0 1-1-1v-3H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6.1l-3.7 3.71c-.2.19-.45.29-.7.29zm1-6v3.08L13.08 16H20V4H4v12z" 813 + ></path> 814 + </svg> 815 + ); 816 + } 817 + 818 + export function MdiRepeat(props: SVGProps<SVGSVGElement>) { 819 + return ( 820 + <svg 821 + xmlns="http://www.w3.org/2000/svg" 822 + width={16} 823 + height={16} 824 + viewBox="0 0 24 24" 825 + {...props} 826 + > 827 + <path 828 + fill="oklch(0.704 0.05 28)" 829 + d="M17 17H7v-3l-4 4l4 4v-3h12v-6h-2M7 7h10v3l4-4l-4-4v3H5v6h2z" 830 + ></path> 831 + </svg> 832 + ); 833 + } 834 + 835 + export function MdiRepeatGreen(props: SVGProps<SVGSVGElement>) { 836 + return ( 837 + <svg 838 + xmlns="http://www.w3.org/2000/svg" 839 + width={16} 840 + height={16} 841 + viewBox="0 0 24 24" 842 + {...props} 843 + > 844 + <path 845 + fill="#5CEFAA" 846 + d="M17 17H7v-3l-4 4l4 4v-3h12v-6h-2M7 7h10v3l4-4l-4-4v3H5v6h2z" 847 + ></path> 848 + </svg> 849 + ); 850 + } 851 + 852 + export function MdiCardsHeart(props: SVGProps<SVGSVGElement>) { 853 + return ( 854 + <svg 855 + xmlns="http://www.w3.org/2000/svg" 856 + width={16} 857 + height={16} 858 + viewBox="0 0 24 24" 859 + {...props} 860 + > 861 + <path 862 + fill="#EC4899" 863 + d="m12 21.35l-1.45-1.32C5.4 15.36 2 12.27 2 8.5C2 5.41 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.08C13.09 3.81 14.76 3 16.5 3C19.58 3 22 5.41 22 8.5c0 3.77-3.4 6.86-8.55 11.53z" 864 + ></path> 865 + </svg> 866 + ); 867 + } 868 + 869 + export function MdiCardsHeartOutline(props: SVGProps<SVGSVGElement>) { 870 + return ( 871 + <svg 872 + xmlns="http://www.w3.org/2000/svg" 873 + width={16} 874 + height={16} 875 + viewBox="0 0 24 24" 876 + {...props} 877 + > 878 + <path 879 + fill="oklch(0.704 0.05 28)" 880 + d="m12.1 18.55l-.1.1l-.11-.1C7.14 14.24 4 11.39 4 8.5C4 6.5 5.5 5 7.5 5c1.54 0 3.04 1 3.57 2.36h1.86C13.46 6 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5c0 2.89-3.14 5.74-7.9 10.05M16.5 3c-1.74 0-3.41.81-4.5 2.08C10.91 3.81 9.24 3 7.5 3C4.42 3 2 5.41 2 8.5c0 3.77 3.4 6.86 8.55 11.53L12 21.35l1.45-1.32C18.6 15.36 22 12.27 22 8.5C22 5.41 19.58 3 16.5 3" 881 + ></path> 882 + </svg> 883 + ); 884 + } 885 + 886 + export function MdiShareVariant(props: SVGProps<SVGSVGElement>) { 887 + return ( 888 + <svg 889 + xmlns="http://www.w3.org/2000/svg" 890 + width={16} 891 + height={16} 892 + viewBox="0 0 24 24" 893 + {...props} 894 + > 895 + <path 896 + fill="oklch(0.704 0.05 28)" 897 + d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3a3 3 0 0 0-3-3a3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3a3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66c0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08" 898 + ></path> 899 + </svg> 900 + ); 901 + } 902 + 903 + export function MdiMoreHoriz(props: SVGProps<SVGSVGElement>) { 904 + return ( 905 + <svg 906 + xmlns="http://www.w3.org/2000/svg" 907 + width={16} 908 + height={16} 909 + viewBox="0 0 24 24" 910 + {...props} 911 + > 912 + <path 913 + fill="oklch(0.704 0.05 28)" 914 + d="M16 12a2 2 0 0 1 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2m-6 0a2 2 0 0 1 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2m-6 0a2 2 0 0 1 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2" 915 + ></path> 916 + </svg> 917 + ); 918 + } 919 + 920 + export function MdiGlobe(props: SVGProps<SVGSVGElement>) { 921 + return ( 922 + <svg 923 + xmlns="http://www.w3.org/2000/svg" 924 + width={12} 925 + height={12} 926 + viewBox="0 0 24 24" 927 + {...props} 928 + > 929 + <path 930 + fill="oklch(0.704 0.05 28)" 931 + d="M17.9 17.39c-.26-.8-1.01-1.39-1.9-1.39h-1v-3a1 1 0 0 0-1-1H8v-2h2a1 1 0 0 0 1-1V7h2a2 2 0 0 0 2-2v-.41a7.984 7.984 0 0 1 2.9 12.8M11 19.93c-3.95-.49-7-3.85-7-7.93c0-.62.08-1.22.21-1.79L9 15v1a2 2 0 0 0 2 2m1-16A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2" 932 + ></path> 933 + </svg> 934 + ); 935 + } 936 + 937 + export function MdiVerified(props: SVGProps<SVGSVGElement>) { 938 + return ( 939 + <svg 940 + xmlns="http://www.w3.org/2000/svg" 941 + width={16} 942 + height={16} 943 + viewBox="0 0 24 24" 944 + {...props} 945 + > 946 + <path 947 + fill="#1297ff" 948 + d="m23 12l-2.44-2.78l.34-3.68l-3.61-.82l-1.89-3.18L12 3L8.6 1.54L6.71 4.72l-3.61.81l.34 3.68L1 12l2.44 2.78l-.34 3.69l3.61.82l1.89 3.18L12 21l3.4 1.46l1.89-3.18l3.61-.82l-.34-3.68zm-13 5l-4-4l1.41-1.41L10 14.17l6.59-6.59L18 9z" 949 + ></path> 950 + </svg> 951 + ); 952 + } 953 + 954 + export function MdiReply(props: SVGProps<SVGSVGElement>) { 955 + return ( 956 + <svg 957 + xmlns="http://www.w3.org/2000/svg" 958 + width={14} 959 + height={14} 960 + viewBox="0 0 24 24" 961 + {...props} 962 + > 963 + <path 964 + fill="oklch(0.704 0.05 28)" 965 + d="M10 9V5l-7 7l7 7v-4.1c5 0 8.5 1.6 11 5.1c-1-5-4-10-11-11" 966 + ></path> 967 + </svg> 968 + ); 969 + } 970 + 971 + export function LineMdLoadingLoop(props: SVGProps<SVGSVGElement>) { 972 + return ( 973 + <svg 974 + xmlns="http://www.w3.org/2000/svg" 975 + width={24} 976 + height={24} 977 + viewBox="0 0 24 24" 978 + {...props} 979 + > 980 + <path 981 + fill="none" 982 + stroke="#1297ff" 983 + strokeDasharray={16} 984 + strokeDashoffset={16} 985 + strokeLinecap="round" 986 + strokeLinejoin="round" 987 + strokeWidth={2} 988 + d="M12 3c4.97 0 9 4.03 9 9" 989 + > 990 + <animate 991 + fill="freeze" 992 + attributeName="stroke-dashoffset" 993 + dur="0.2s" 994 + values="16;0" 995 + ></animate> 996 + <animateTransform 997 + attributeName="transform" 998 + dur="1.5s" 999 + repeatCount="indefinite" 1000 + type="rotate" 1001 + values="0 12 12;360 12 12" 1002 + ></animateTransform> 1003 + </path> 1004 + </svg> 1005 + ); 1006 + } 1007 + 1008 + export function MdiRepost(props: SVGProps<SVGSVGElement>) { 1009 + return ( 1010 + <svg 1011 + xmlns="http://www.w3.org/2000/svg" 1012 + width={14} 1013 + height={14} 1014 + viewBox="0 0 24 24" 1015 + {...props} 1016 + > 1017 + <path 1018 + fill="oklch(0.704 0.05 28)" 1019 + d="M17 17H7v-3l-4 4l4 4v-3h12v-6h-2M7 7h10v3l4-4l-4-4v3H5v6h2z" 1020 + ></path> 1021 + </svg> 1022 + ); 1023 + } 1024 + 1025 + export function MdiRepeatVariant(props: SVGProps<SVGSVGElement>) { 1026 + return ( 1027 + <svg 1028 + xmlns="http://www.w3.org/2000/svg" 1029 + width={14} 1030 + height={14} 1031 + viewBox="0 0 24 24" 1032 + {...props} 1033 + > 1034 + <path 1035 + fill="oklch(0.704 0.05 28)" 1036 + d="M6 5.75L10.25 10H7v6h6.5l2 2H7a2 2 0 0 1-2-2v-6H1.75zm12 12.5L13.75 14H17V8h-6.5l-2-2H17a2 2 0 0 1 2 2v6h3.25z" 1037 + ></path> 1038 + </svg> 1039 + ); 1040 + } 1041 + 1042 + export function MdiPlayCircle(props: SVGProps<SVGSVGElement>) { 1043 + return ( 1044 + <svg 1045 + xmlns="http://www.w3.org/2000/svg" 1046 + width={64} 1047 + height={64} 1048 + viewBox="0 0 24 24" 1049 + {...props} 1050 + > 1051 + <path 1052 + fill="#edf2f5" 1053 + d="M10 16.5v-9l6 4.5M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2" 1054 + ></path> 1055 + </svg> 1056 + ); 1057 + } 1058 + 1059 + /* what imported from testfront */ 1060 + import defaultpfp from "~/../public/favicon.png"; 1061 + 1062 + //import Masonry from "@mui/lab/Masonry"; 1063 + import { 1064 + AppBskyActorDefs, 1065 + AppBskyEmbedDefs, 1066 + AppBskyEmbedExternal, 1067 + AppBskyEmbedImages, 1068 + AppBskyEmbedRecord, 1069 + AppBskyEmbedRecordWithMedia, 1070 + AppBskyEmbedVideo, 1071 + AppBskyFeedDefs, 1072 + AppBskyFeedPost, 1073 + AppBskyGraphDefs, 1074 + //AppBskyLabelerDefs, 1075 + //AtUri, 1076 + //ComAtprotoRepoStrongRef, 1077 + ModerationDecision, 1078 + type $Typed, 1079 + type Facet, 1080 + } from "@atproto/api"; 1081 + import type { 1082 + //BlockedPost, 1083 + FeedViewPost, 1084 + //NotFoundPost, 1085 + PostView, 1086 + //ThreadViewPost, 1087 + } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; 1088 + import { useEffect, useRef, useState } from "react"; 1089 + import ReactPlayer from "react-player"; 1090 + import { useAuth } from "~/providers/PassAuthProvider"; 1091 + // import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed"; 1092 + // import type { 1093 + // ViewRecord, 1094 + // ViewNotFound, 1095 + // ViewBlocked, 1096 + // ViewDetached, 1097 + // } from "@atproto/api/dist/client/types/app/bsky/embed/record"; 1098 + //import type { MasonryItemData } from "./onemason/masonry.types"; 1099 + //import { MasonryLayout } from "./onemason/MasonryLayout"; 1100 + // const agent = new AtpAgent({ 1101 + // service: 'https://public.api.bsky.app' 1102 + // }) 1103 + 1104 + const HitSlopButton = ({ 1105 + onClick, 1106 + children, 1107 + style = {}, 1108 + }: { 1109 + onClick?: (e: React.MouseEvent) => void; 1110 + children: React.ReactNode; 1111 + style?: React.CSSProperties; 1112 + }) => ( 1113 + <span 1114 + style={{ position: "relative", display: "inline-block", cursor: "pointer" }} 1115 + > 1116 + <span 1117 + style={{ 1118 + position: "absolute", 1119 + top: -8, 1120 + left: -8, 1121 + right: -8, 1122 + bottom: -8, 1123 + zIndex: 0, 1124 + }} 1125 + onClick={(e) => { 1126 + e.stopPropagation(); 1127 + onClick?.(e); 1128 + }} 1129 + /> 1130 + <span 1131 + style={{ 1132 + ...style, 1133 + position: "relative", 1134 + zIndex: 1, 1135 + pointerEvents: "none", 1136 + }} 1137 + > 1138 + {children} 1139 + </span> 1140 + </span> 1141 + ); 1142 + 1143 + const btnstyle = { 1144 + display: "flex", 1145 + gap: 4, 1146 + cursor: "pointer", 1147 + alignItems: "center", 1148 + fontSize: 14, 1149 + }; 1150 + function randomString(length = 8) { 1151 + const chars = 1152 + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 1153 + return Array.from( 1154 + { length }, 1155 + () => chars[Math.floor(Math.random() * chars.length)], 1156 + ).join(""); 1157 + } 1158 + 1159 + function UniversalPostRenderer({ 1160 + post, 1161 + //setMainItem, 1162 + //isMainItem, 1163 + onPostClick, 1164 + onProfileClick, 1165 + expanded, 1166 + //expanded, 1167 + isQuote, 1168 + //isQuote, 1169 + extraOptionalItemInfo, 1170 + bottomReplyLine, 1171 + topReplyLine, 1172 + salt, 1173 + bottomBorder = true, 1174 + }: { 1175 + post: PostView; 1176 + // optional for now because i havent ported every use to this yet 1177 + // setMainItem?: React.Dispatch< 1178 + // React.SetStateAction<AppBskyFeedDefs.FeedViewPost> 1179 + // >; 1180 + //isMainItem?: boolean; 1181 + onPostClick?: (e: React.MouseEvent) => void; 1182 + onProfileClick?: (e: React.MouseEvent) => void; 1183 + expanded?: boolean; 1184 + isQuote?: boolean; 1185 + extraOptionalItemInfo?: FeedViewPost; 1186 + bottomReplyLine?: boolean; 1187 + topReplyLine?: boolean; 1188 + salt: string; 1189 + bottomBorder?: boolean; 1190 + }) { 1191 + const navigate = useNavigate(); 1192 + const [hasRetweeted, setHasRetweeted] = useState<Boolean>( 1193 + post.viewer?.repost ? true : false, 1194 + ); 1195 + const [hasLiked, setHasLiked] = useState<Boolean>( 1196 + post.viewer?.like ? true : false, 1197 + ); 1198 + const { agent } = useAuth(); 1199 + const [likeUri, setLikeUri] = useState<string | undefined>(post.viewer?.like); 1200 + const [retweetUri, setRetweetUri] = useState<string | undefined>( 1201 + post.viewer?.repost, 1202 + ); 1203 + 1204 + const likeOrUnlikePost = async () => { 1205 + if (!agent) { 1206 + console.error("Agent is null or undefined"); 1207 + return; 1208 + } 1209 + if (hasLiked) { 1210 + if (likeUri) { 1211 + await agent.deleteLike(likeUri); 1212 + setHasLiked(false); 1213 + } 1214 + } else { 1215 + const { uri } = await agent.like(post.uri, post.cid); 1216 + setLikeUri(uri); 1217 + setHasLiked(true); 1218 + } 1219 + }; 1220 + 1221 + const repostOrUnrepostPost = async () => { 1222 + if (!agent) { 1223 + console.error("Agent is null or undefined"); 1224 + return; 1225 + } 1226 + if (hasRetweeted) { 1227 + if (retweetUri) { 1228 + await agent.deleteRepost(retweetUri); 1229 + setHasRetweeted(false); 1230 + } 1231 + } else { 1232 + const { uri } = await agent.repost(post.uri, post.cid); 1233 + setRetweetUri(uri); 1234 + setHasRetweeted(true); 1235 + } 1236 + }; 1237 + 1238 + const isRepost = extraOptionalItemInfo 1239 + ? AppBskyFeedDefs.isReasonRepost(extraOptionalItemInfo.reason) 1240 + ? extraOptionalItemInfo.reason?.by.displayName 1241 + : undefined 1242 + : undefined; 1243 + const isReply = extraOptionalItemInfo 1244 + ? extraOptionalItemInfo.reply 1245 + : undefined; 1246 + 1247 + const emergencySalt = randomString(); 1248 + 1249 + /* fuck you */ 1250 + const isMainItem = false; 1251 + const setMainItem = (any: any) => {}; 1252 + return ( 1253 + <div 1254 + key={salt + "-" + (post.uri || emergencySalt)} 1255 + onClick={ 1256 + isMainItem 1257 + ? onPostClick 1258 + : setMainItem 1259 + ? onPostClick 1260 + ? (e) => { 1261 + setMainItem({ post: post }); 1262 + onPostClick(e); 1263 + } 1264 + : () => { 1265 + setMainItem({ post: post }); 1266 + } 1267 + : undefined 1268 + } 1269 + style={{ 1270 + //border: "1px solid #e1e8ed", 1271 + //borderRadius: 12, 1272 + opacity: "1 !important", 1273 + background: "transparent", 1274 + paddingLeft: isQuote ? 12 : 16, 1275 + paddingRight: isQuote ? 12 : 16, 1276 + //paddingTop: 16, 1277 + paddingTop: isRepost ? 10 : isQuote ? 12 : 16, 1278 + //paddingBottom: bottomReplyLine ? 0 : 16, 1279 + paddingBottom: 0, 1280 + fontFamily: "system-ui, sans-serif", 1281 + //boxShadow: "0 2px 8px rgba(0,0,0,0.04)", 1282 + position: "relative", 1283 + // dont cursor: "pointer", 1284 + borderBottomWidth: bottomBorder ? 1 : 0, 1285 + }} 1286 + className="border-gray-300 dark:border-gray-600" 1287 + > 1288 + {isRepost && ( 1289 + <div 1290 + style={{ 1291 + marginLeft: 36, 1292 + display: "flex", 1293 + borderRadius: 12, 1294 + paddingBottom: "calc(22px - 1rem)", 1295 + fontSize: 14, 1296 + maxHeight: "1rem", 1297 + justifyContent: "flex-start", 1298 + //color: theme.textSecondary, 1299 + gap: 4, 1300 + alignItems: "center", 1301 + }} 1302 + className="text-gray-500 dark:text-gray-400" 1303 + > 1304 + <MdiRepost /> Reposted by {isRepost}{" "} 1305 + </div> 1306 + )} 1307 + {!isQuote && ( 1308 + <div 1309 + style={{ 1310 + opacity: topReplyLine || (isReply && (true || expanded)) ? 0.5 : 0, 1311 + position: "absolute", 1312 + top: 0, 1313 + left: 36, // why 36 ??? 1314 + //left: 16 + (42 / 2), 1315 + width: 2, 1316 + //height: "100%", 1317 + height: isRepost ? "calc(16px + 1rem - 6px)" : 16 - 6, 1318 + // background: theme.textSecondary, 1319 + //opacity: 0.5, 1320 + // no flex here 1321 + }} 1322 + /> 1323 + )} 1324 + <div 1325 + style={{ 1326 + position: "absolute", 1327 + //top: isRepost ? "calc(16px + 1rem)" : 16, 1328 + //left: 16, 1329 + zIndex: 1, 1330 + top: isRepost ? "calc(16px + 1rem)" : isQuote ? 12 : 16, 1331 + left: isQuote ? 12 : 16, 1332 + }} 1333 + onClick={onProfileClick} 1334 + > 1335 + <img 1336 + src={post.author.avatar || defaultpfp} 1337 + alt="avatar" 1338 + // transition={{ 1339 + // type: "spring", 1340 + // stiffness: 260, 1341 + // damping: 20, 1342 + // }} 1343 + style={{ 1344 + borderRadius: "50%", 1345 + marginRight: 12, 1346 + objectFit: "cover", 1347 + //background: theme.border, 1348 + //border: `1px solid ${theme.border}`, 1349 + width: isQuote ? 16 : 42, 1350 + height: isQuote ? 16 : 42, 1351 + }} 1352 + className="border border-gray-300 dark:border-gray-600 bg-gray-300 dark:bg-gray-600" 1353 + /> 1354 + </div> 1355 + <div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}> 1356 + <div 1357 + style={{ 1358 + display: "flex", 1359 + flexDirection: "column", 1360 + alignSelf: "stretch", 1361 + alignItems: "center", 1362 + overflow: "hidden", 1363 + width: expanded || isQuote ? 0 : "auto", 1364 + marginRight: expanded || isQuote ? 0 : 12, 1365 + }} 1366 + > 1367 + {/* dummy for later use */} 1368 + <div style={{ width: 42, height: 42 + 8, minHeight: 42 + 8 }} /> 1369 + {/* reply line !!!! bottomReplyLine */} 1370 + {bottomReplyLine && ( 1371 + <div 1372 + style={{ 1373 + width: 2, 1374 + height: "100%", 1375 + //background: theme.textSecondary, 1376 + opacity: 0.5, 1377 + // no flex here 1378 + }} 1379 + className="text-gray-500 dark:text-gray-400" 1380 + /> 1381 + )} 1382 + {/* <div 1383 + layout 1384 + transition={{ duration: 0.2 }} 1385 + animate={{ height: expanded ? 0 : '100%' }} 1386 + style={{ 1387 + width: 2.4, 1388 + background: theme.border, 1389 + // no flex here 1390 + }} 1391 + /> */} 1392 + </div> 1393 + <div style={{ flex: 1, maxWidth: "100%" }}> 1394 + <div 1395 + style={{ 1396 + display: "flex", 1397 + flexDirection: "row", 1398 + alignItems: "center", 1399 + flexWrap: "nowrap", 1400 + maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1401 + width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1402 + marginLeft: !expanded ? (isQuote ? 26 : 0) : 54, 1403 + marginBottom: !expanded ? 4 : 0, 1404 + }} 1405 + > 1406 + <div 1407 + style={{ 1408 + display: "flex", 1409 + //overflow: "hidden", // hey why is overflow hidden unapplied 1410 + overflow: "hidden", 1411 + textOverflow: "ellipsis", 1412 + flexShrink: 1, 1413 + flexGrow: 1, 1414 + flexBasis: 0, 1415 + width: 0, 1416 + gap: expanded ? 0 : 6, 1417 + alignItems: expanded ? "flex-start" : "center", 1418 + flexDirection: expanded ? "column" : "row", 1419 + height: expanded ? 48 : "1rem", 1420 + }} 1421 + > 1422 + <span 1423 + style={{ 1424 + display: "flex", 1425 + fontWeight: 700, 1426 + fontSize: 16, 1427 + overflow: "hidden", 1428 + textOverflow: "ellipsis", 1429 + whiteSpace: "nowrap", 1430 + flexShrink: 1, 1431 + minWidth: 0, 1432 + gap: 4, 1433 + alignItems: "center", 1434 + //color: theme.text, 1435 + }} 1436 + className="text-gray-900 dark:text-gray-100" 1437 + > 1438 + {/* verified checkmark */} 1439 + {post.author.displayName || post.author.handle}{" "} 1440 + {post.author.verification?.verifiedStatus == "valid" && ( 1441 + <MdiVerified /> 1442 + )} 1443 + </span> 1444 + 1445 + <span 1446 + style={{ 1447 + //color: theme.textSecondary, 1448 + fontSize: 16, 1449 + overflowX: "hidden", 1450 + textOverflow: "ellipsis", 1451 + whiteSpace: "nowrap", 1452 + flexShrink: 1, 1453 + flexGrow: 0, 1454 + minWidth: 0, 1455 + }} 1456 + className="text-gray-500 dark:text-gray-400" 1457 + > 1458 + @{post.author.handle} 1459 + </span> 1460 + </div> 1461 + <div 1462 + style={{ 1463 + display: "flex", 1464 + alignItems: "center", 1465 + height: "1rem", 1466 + }} 1467 + > 1468 + <span 1469 + style={{ 1470 + //color: theme.textSecondary, 1471 + fontSize: 16, 1472 + marginLeft: 8, 1473 + whiteSpace: "nowrap", 1474 + flexShrink: 0, 1475 + maxWidth: "100%", 1476 + }} 1477 + className="text-gray-500 dark:text-gray-400" 1478 + > 1479 + · {/* time placeholder */} 1480 + {shortTimeAgo(post.indexedAt)} 1481 + </span> 1482 + </div> 1483 + </div> 1484 + {/* reply indicator */} 1485 + {false && isReply && ( 1486 + <div 1487 + style={{ 1488 + display: "flex", 1489 + borderRadius: 12, 1490 + paddingBottom: 2, 1491 + fontSize: 14, 1492 + justifyContent: "flex-start", 1493 + //color: theme.textSecondary, 1494 + gap: 4, 1495 + alignItems: "center", 1496 + //marginLeft: 36, 1497 + height: !(expanded || isQuote) && isReply ? "1rem" : 0, 1498 + opacity: !(expanded || isQuote) && isReply ? 1 : 0, 1499 + }} 1500 + className="text-gray-500 dark:text-gray-400" 1501 + > 1502 + <MdiReply /> Reply to some other post lmao 1503 + </div> 1504 + )} 1505 + <div 1506 + style={{ 1507 + fontSize: 16, 1508 + marginBottom: 8, 1509 + whiteSpace: "pre-wrap", 1510 + textAlign: "left", 1511 + overflowWrap: "anywhere", 1512 + wordBreak: "break-word", 1513 + //color: theme.text, 1514 + }} 1515 + className="text-gray-900 dark:text-gray-100" 1516 + > 1517 + {renderTextWithFacets( 1518 + (post.record as { text?: string }).text ?? "", 1519 + (post.record.facets as Facet[]) ?? [], 1520 + )} 1521 + {} 1522 + </div> 1523 + {post.embed ? ( 1524 + <PostEmbeds 1525 + embed={post.embed} 1526 + //moderation={moderation} 1527 + viewContext={PostEmbedViewContext.Feed} 1528 + salt={salt} 1529 + navigate={navigate} 1530 + /> 1531 + ) : null} 1532 + <div style={{ paddingTop: post.embed ? 4 : 0 }}> 1533 + <> 1534 + {expanded && ( 1535 + <div 1536 + style={{ 1537 + overflow: "hidden", 1538 + //color: theme.textSecondary, 1539 + fontSize: 14, 1540 + display: "flex", 1541 + borderBottomStyle: "solid", 1542 + //borderBottomColor: theme.border, 1543 + //background: "#f00", 1544 + // height: "1rem", 1545 + paddingTop: 4, 1546 + paddingBottom: 8, 1547 + borderBottomWidth: 1, 1548 + marginBottom: 8, 1549 + }} // important for height animation 1550 + className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-700" 1551 + > 1552 + {fullDateTimeFormat(post.indexedAt)} 1553 + </div> 1554 + )} 1555 + </> 1556 + {!isQuote && ( 1557 + <div 1558 + style={{ 1559 + display: "flex", 1560 + gap: 32, 1561 + paddingTop: 8, 1562 + //color: theme.textSecondary, 1563 + fontSize: 15, 1564 + justifyContent: "space-between", 1565 + //background: "#0f0", 1566 + }} 1567 + className="text-gray-500 dark:text-gray-400" 1568 + > 1569 + <span style={btnstyle}> 1570 + <MdiCommentOutline /> 1571 + {post.replyCount} 1572 + </span> 1573 + <HitSlopButton 1574 + onClick={() => { 1575 + repostOrUnrepostPost(); 1576 + }} 1577 + style={{ 1578 + ...btnstyle, 1579 + ...(hasRetweeted ? { color: "#5CEFAA" } : {}), 1580 + }} 1581 + > 1582 + {hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />} 1583 + {(post.repostCount || 0) + (hasRetweeted ? 1 : 0)} 1584 + </HitSlopButton> 1585 + <HitSlopButton 1586 + onClick={() => { 1587 + likeOrUnlikePost(); 1588 + }} 1589 + style={{ 1590 + ...btnstyle, 1591 + ...(hasLiked ? { color: "#EC4899" } : {}), 1592 + }} 1593 + > 1594 + {hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />} 1595 + {(post.likeCount || 0) + (hasLiked ? 1 : 0)} 1596 + </HitSlopButton> 1597 + <div style={{ display: "flex", gap: 8 }}> 1598 + <HitSlopButton 1599 + onClick={async (e) => { 1600 + e.stopPropagation(); 1601 + try { 1602 + await navigator.clipboard.writeText( 1603 + "https://bsky.app" + 1604 + "/profile/" + 1605 + post.author.handle + 1606 + "/post/" + 1607 + post.uri.split("/").pop(), 1608 + ); 1609 + } catch {} 1610 + }} 1611 + style={{ 1612 + ...btnstyle, 1613 + }} 1614 + > 1615 + <MdiShareVariant /> 1616 + </HitSlopButton> 1617 + <span style={btnstyle}> 1618 + <MdiMoreHoriz /> 1619 + </span> 1620 + </div> 1621 + </div> 1622 + )} 1623 + </div> 1624 + <div 1625 + style={{ 1626 + //height: bottomReplyLine ? 16 : 0 1627 + height: isQuote ? 12 : 16, 1628 + }} 1629 + /> 1630 + </div> 1631 + </div> 1632 + </div> 1633 + ); 1634 + } 1635 + 1636 + const fullDateTimeFormat = (iso: string) => { 1637 + const date = new Date(iso); 1638 + return date.toLocaleString("en-US", { 1639 + month: "long", 1640 + day: "numeric", 1641 + year: "numeric", 1642 + hour: "numeric", 1643 + minute: "2-digit", 1644 + hour12: true, 1645 + }); 1646 + }; 1647 + const shortTimeAgo = (iso: string) => { 1648 + const diff = Date.now() - new Date(iso).getTime(); 1649 + const mins = Math.floor(diff / 60000); 1650 + if (mins < 1) return "now"; 1651 + if (mins < 60) return `${mins}m`; 1652 + const hrs = Math.floor(mins / 60); 1653 + if (hrs < 24) return `${hrs}h`; 1654 + const days = Math.floor(hrs / 24); 1655 + return `${days}d`; 1656 + }; 1657 + 1658 + // const toAtUri = (url: string) => 1659 + // url 1660 + // .replace("https://bsky.app/profile/", "at://") 1661 + // .replace("/feed/", "/app.bsky.feed.generator/"); 1662 + 1663 + // function PostSizedElipsis() { 1664 + // return ( 1665 + // <div 1666 + // style={{ display: "flex", flexDirection: "row", alignItems: "center" }} 1667 + // > 1668 + // <div 1669 + // style={{ 1670 + // width: 2, 1671 + // height: 40, 1672 + // //background: theme.textSecondary, 1673 + // background: `repeating-linear-gradient(to bottom, var(--color-gray-400) 0px, var(--color-gray-400) 6px, transparent 6px, transparent 10px)`, 1674 + // backgroundSize: "100% 10px", 1675 + // opacity: 0.5, 1676 + // marginLeft: 36, // why 36 ??? 1677 + // }} 1678 + // /> 1679 + // <span 1680 + // style={{ 1681 + // //color: theme.textSecondary, 1682 + // marginLeft: 34, 1683 + // }} 1684 + // className="text-gray-500 dark:text-gray-400" 1685 + // > 1686 + // more posts 1687 + // </span> 1688 + // </div> 1689 + // ); 1690 + // } 1691 + 1692 + type Embed = 1693 + | AppBskyEmbedRecord.View 1694 + | AppBskyEmbedImages.View 1695 + | AppBskyEmbedVideo.View 1696 + | AppBskyEmbedExternal.View 1697 + | AppBskyEmbedRecordWithMedia.View 1698 + | { $type: string; [k: string]: unknown }; 1699 + 1700 + enum PostEmbedViewContext { 1701 + ThreadHighlighted = "ThreadHighlighted", 1702 + Feed = "Feed", 1703 + FeedEmbedRecordWithMedia = "FeedEmbedRecordWithMedia", 1704 + } 1705 + const stopgap = { 1706 + display: "flex", 1707 + justifyContent: "center", 1708 + padding: "32px 12px", 1709 + borderRadius: 12, 1710 + border: "1px solid rgba(161, 170, 174, 0.38)", 1711 + }; 1712 + 1713 + function PostEmbeds({ 1714 + embed, 1715 + moderation, 1716 + onOpen, 1717 + allowNestedQuotes, 1718 + viewContext, 1719 + salt, 1720 + navigate, 1721 + }: { 1722 + embed?: Embed; 1723 + moderation?: ModerationDecision; 1724 + onOpen?: () => void; 1725 + allowNestedQuotes?: boolean; 1726 + viewContext?: PostEmbedViewContext; 1727 + salt: string; 1728 + navigate: ({}: any) => void; 1729 + }) { 1730 + if ( 1731 + AppBskyEmbedRecordWithMedia.isView(embed) && 1732 + AppBskyEmbedRecord.isViewRecord(embed.record.record) && 1733 + AppBskyFeedPost.isRecord(embed.record.record.value) //&& 1734 + //AppBskyFeedPost.validateRecord(embed.record.record.value).success 1735 + ) { 1736 + const post: PostView = { 1737 + $type: "app.bsky.feed.defs#postView", // lmao lies 1738 + uri: embed.record.record.uri, 1739 + cid: embed.record.record.cid, 1740 + author: embed.record.record.author, 1741 + record: embed.record.record.value as { [key: string]: unknown }, 1742 + embed: embed.record.record.embeds 1743 + ? embed.record.record.embeds?.[0] 1744 + : undefined, // quotes handles embeds differently, its an array for some reason 1745 + replyCount: embed.record.record.replyCount, 1746 + repostCount: embed.record.record.repostCount, 1747 + likeCount: embed.record.record.likeCount, 1748 + quoteCount: embed.record.record.quoteCount, 1749 + indexedAt: embed.record.record.indexedAt, 1750 + // we dont have a viewer, so this is a best effort conversion, still requires full query later on 1751 + labels: embed.record.record.labels, 1752 + // neither do we have threadgate. remember to please fetch the full post later 1753 + }; 1754 + return ( 1755 + <div> 1756 + <PostEmbeds 1757 + embed={embed.media} 1758 + moderation={moderation} 1759 + onOpen={onOpen} 1760 + viewContext={viewContext} 1761 + salt={salt} 1762 + navigate={navigate} 1763 + /> 1764 + {/* padding empty div of 8px height */} 1765 + <div style={{ height: 12 }} /> 1766 + {/* stopgap sorry*/} 1767 + <div 1768 + style={{ 1769 + display: "flex", 1770 + flexDirection: "column", 1771 + borderRadius: 12, 1772 + //border: `1px solid ${theme.border}`, 1773 + //boxShadow: theme.cardShadow, 1774 + overflow: "hidden", 1775 + }} 1776 + className="shadow border border-gray-200 dark:border-gray-700" 1777 + > 1778 + <UniversalPostRenderer 1779 + post={post} 1780 + isQuote 1781 + salt={salt} 1782 + onPostClick={(e) => { 1783 + e.stopPropagation(); 1784 + const parsed = parseAtUri(post.uri); 1785 + if (parsed) { 1786 + navigate({ 1787 + to: "/profile/$did/post/$rkey", 1788 + params: { did: parsed.did, rkey: parsed.rkey }, 1789 + }); 1790 + } 1791 + }} 1792 + /> 1793 + </div> 1794 + {/* <QuotePostRenderer 1795 + record={embed.record.record} 1796 + moderation={moderation} 1797 + /> */} 1798 + {/* stopgap sorry */} 1799 + {/* <div style={stopgap}>quote post placeholder</div> */} 1800 + {/* {<MaybeQuoteEmbed 1801 + embed={embed.record} 1802 + onOpen={onOpen} 1803 + viewContext={ 1804 + viewContext === PostEmbedViewContext.Feed 1805 + ? QuoteEmbedViewContext.FeedEmbedRecordWithMedia 1806 + : undefined 1807 + } 1808 + {/* <div style={stopgap}>quote post placeholder</div> */} 1809 + {/* {<MaybeQuoteEmbed 1810 + embed={embed.record} 1811 + onOpen={onOpen} 1812 + viewContext={ 1813 + viewContext === PostEmbedViewContext.Feed 1814 + ? QuoteEmbedViewContext.FeedEmbedRecordWithMedia 1815 + : undefined 1816 + } 1817 + />} */} 1818 + </div> 1819 + ); 1820 + } 1821 + 1822 + if (AppBskyEmbedRecord.isView(embed)) { 1823 + // custom feed embed (i.e. generator view) 1824 + if (AppBskyFeedDefs.isGeneratorView(embed.record)) { 1825 + // stopgap sorry 1826 + return <div style={stopgap}>feedgen placeholder</div>; 1827 + // return ( 1828 + // <div style={{ marginTop: '1rem' }}> 1829 + // <MaybeFeedCard view={embed.record} /> 1830 + // </div> 1831 + // ) 1832 + } 1833 + 1834 + // list embed 1835 + if (AppBskyGraphDefs.isListView(embed.record)) { 1836 + // stopgap sorry 1837 + return <div style={stopgap}>list placeholder</div>; 1838 + // return ( 1839 + // <div style={{ marginTop: '1rem' }}> 1840 + // <MaybeListCard view={embed.record} /> 1841 + // </div> 1842 + // ) 1843 + } 1844 + 1845 + // starter pack embed 1846 + if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) { 1847 + // stopgap sorry 1848 + return <div style={stopgap}>starter pack card placeholder</div>; 1849 + // return ( 1850 + // <div style={{ marginTop: '1rem' }}> 1851 + // <StarterPackCard starterPack={embed.record} /> 1852 + // </div> 1853 + // ) 1854 + } 1855 + 1856 + // quote post 1857 + // = 1858 + // stopgap sorry 1859 + 1860 + if ( 1861 + AppBskyEmbedRecord.isViewRecord(embed.record) && 1862 + AppBskyFeedPost.isRecord(embed.record.value) // && 1863 + //AppBskyFeedPost.validateRecord(embed.record.value).success 1864 + ) { 1865 + const post: PostView = { 1866 + $type: "app.bsky.feed.defs#postView", // lmao lies 1867 + uri: embed.record.uri, 1868 + cid: embed.record.cid, 1869 + author: embed.record.author, 1870 + record: embed.record.value as { [key: string]: unknown }, 1871 + embed: embed.record.embeds ? embed.record.embeds?.[0] : undefined, // quotes handles embeds differently, its an array for some reason 1872 + replyCount: embed.record.replyCount, 1873 + repostCount: embed.record.repostCount, 1874 + likeCount: embed.record.likeCount, 1875 + quoteCount: embed.record.quoteCount, 1876 + indexedAt: embed.record.indexedAt, 1877 + // we dont have a viewer, so this is a best effort conversion, still requires full query later on 1878 + labels: embed.record.labels, 1879 + // neither do we have threadgate. remember to please fetch the full post later 1880 + }; 1881 + 1882 + return ( 1883 + <div 1884 + style={{ 1885 + display: "flex", 1886 + flexDirection: "column", 1887 + borderRadius: 12, 1888 + //border: `1px solid ${theme.border}`, 1889 + //boxShadow: theme.cardShadow, 1890 + overflow: "hidden", 1891 + }} 1892 + className="shadow border border-gray-200 dark:border-gray-700" 1893 + > 1894 + <UniversalPostRenderer 1895 + post={post} 1896 + isQuote 1897 + salt={salt} 1898 + onPostClick={(e) => { 1899 + e.stopPropagation(); 1900 + const parsed = parseAtUri(post.uri); 1901 + if (parsed) { 1902 + navigate({ 1903 + to: "/profile/$did/post/$rkey", 1904 + params: { did: parsed.did, rkey: parsed.rkey }, 1905 + }); 1906 + } 1907 + }} 1908 + /> 1909 + </div> 1910 + ); 1911 + } else { 1912 + return <>sorry</>; 1913 + } 1914 + //return <QuotePostRenderer record={embed.record} moderation={moderation} />; 1915 + 1916 + //return <div style={stopgap}>quote post placeholder</div>; 1917 + // return ( 1918 + // <MaybeQuoteEmbed 1919 + // embed={embed} 1920 + // onOpen={onOpen} 1921 + // allowNestedQuotes={allowNestedQuotes} 1922 + // /> 1923 + // ) 1924 + } 1925 + 1926 + // image embed 1927 + // = 1928 + if (AppBskyEmbedImages.isView(embed)) { 1929 + const { images } = embed; 1930 + 1931 + if (images.length > 0) { 1932 + // const items = embed.images.map(img => ({ 1933 + // uri: img.fullsize, 1934 + // thumbUri: img.thumb, 1935 + // alt: img.alt, 1936 + // dimensions: img.aspectRatio ?? null, 1937 + // })) 1938 + 1939 + if (images.length === 1) { 1940 + const image = images[0]; 1941 + return ( 1942 + <div style={{ marginTop: 0 }}> 1943 + <div 1944 + style={{ 1945 + position: "relative", 1946 + width: "100%", 1947 + aspectRatio: image.aspectRatio 1948 + ? (() => { 1949 + const { width, height } = image.aspectRatio; 1950 + const ratio = width / height; 1951 + return ratio < 0.5 ? "1 / 2" : `${width} / ${height}`; 1952 + })() 1953 + : "1 / 1", // fallback to square 1954 + //backgroundColor: theme.background, // fallback letterboxing color 1955 + borderRadius: 12, 1956 + //border: `1px solid ${theme.border}`, 1957 + overflow: "hidden", 1958 + }} 1959 + className="border border-gray-200 dark:border-gray-700 bg-gray-200 dark:bg-gray-900" 1960 + > 1961 + <img 1962 + src={image.fullsize} 1963 + alt={image.alt} 1964 + style={{ 1965 + width: "100%", 1966 + height: "100%", 1967 + objectFit: "contain", // letterbox or scale to fit 1968 + }} 1969 + /> 1970 + </div> 1971 + </div> 1972 + ); 1973 + } 1974 + // 2 images: side by side, both 1:1, cropped 1975 + if (images.length === 2) { 1976 + return ( 1977 + <div 1978 + style={{ 1979 + display: "flex", 1980 + gap: 4, 1981 + marginTop: 0, 1982 + width: "100%", 1983 + borderRadius: 12, 1984 + overflow: "hidden", 1985 + //border: `1px solid ${theme.border}`, 1986 + }} 1987 + className="border border-gray-200 dark:border-gray-700" 1988 + > 1989 + {images.map((img, i) => ( 1990 + <div 1991 + key={i} 1992 + style={{ flex: 1, aspectRatio: "1 / 1", position: "relative" }} 1993 + > 1994 + <img 1995 + src={img.fullsize} 1996 + alt={img.alt} 1997 + style={{ 1998 + width: "100%", 1999 + height: "100%", 2000 + objectFit: "cover", 2001 + borderRadius: i === 0 ? "12px 0 0 12px" : "0 12px 12px 0", 2002 + }} 2003 + /> 2004 + </div> 2005 + ))} 2006 + </div> 2007 + ); 2008 + } 2009 + 2010 + // 3 images: left is 1:1, right is two stacked 2:1 2011 + if (images.length === 3) { 2012 + return ( 2013 + <div 2014 + style={{ 2015 + display: "flex", 2016 + gap: 4, 2017 + marginTop: 0, 2018 + width: "100%", 2019 + borderRadius: 12, 2020 + overflow: "hidden", 2021 + //border: `1px solid ${theme.border}`, 2022 + // height: 240, // fixed height for cropping 2023 + }} 2024 + className="border border-gray-200 dark:border-gray-700" 2025 + > 2026 + {/* Left: 1:1 */} 2027 + <div 2028 + style={{ flex: 1, aspectRatio: "1 / 1", position: "relative" }} 2029 + > 2030 + <img 2031 + src={images[0].fullsize} 2032 + alt={images[0].alt} 2033 + style={{ 2034 + width: "100%", 2035 + height: "100%", 2036 + objectFit: "cover", 2037 + borderRadius: "12px 0 0 12px", 2038 + }} 2039 + /> 2040 + </div> 2041 + {/* Right: two stacked 2:1 */} 2042 + <div 2043 + style={{ 2044 + flex: 1, 2045 + display: "flex", 2046 + flexDirection: "column", 2047 + gap: 4, 2048 + }} 2049 + > 2050 + {[1, 2].map((i) => ( 2051 + <div 2052 + key={i} 2053 + style={{ 2054 + flex: 1, 2055 + aspectRatio: "2 / 1", 2056 + position: "relative", 2057 + }} 2058 + > 2059 + <img 2060 + src={images[i].fullsize} 2061 + alt={images[i].alt} 2062 + style={{ 2063 + width: "100%", 2064 + height: "100%", 2065 + objectFit: "cover", 2066 + borderRadius: i === 1 ? "0 12px 0 0" : "0 0 12px 0", 2067 + }} 2068 + /> 2069 + </div> 2070 + ))} 2071 + </div> 2072 + </div> 2073 + ); 2074 + } 2075 + 2076 + // 4 images: 2x2 grid, all 3:2 2077 + if (images.length === 4) { 2078 + return ( 2079 + <div 2080 + style={{ 2081 + display: "grid", 2082 + gridTemplateColumns: "1fr 1fr", 2083 + gridTemplateRows: "1fr 1fr", 2084 + gap: 4, 2085 + marginTop: 0, 2086 + width: "100%", 2087 + borderRadius: 12, 2088 + overflow: "hidden", 2089 + //border: `1px solid ${theme.border}`, 2090 + //aspectRatio: "3 / 2", // overall grid aspect 2091 + }} 2092 + className="border border-gray-200 dark:border-gray-700" 2093 + > 2094 + {images.map((img, i) => ( 2095 + <div 2096 + key={i} 2097 + style={{ 2098 + width: "100%", 2099 + height: "100%", 2100 + aspectRatio: "3 / 2", 2101 + position: "relative", 2102 + }} 2103 + > 2104 + <img 2105 + src={img.fullsize} 2106 + alt={img.alt} 2107 + style={{ 2108 + width: "100%", 2109 + height: "100%", 2110 + objectFit: "cover", 2111 + borderRadius: 2112 + i === 0 2113 + ? "12px 0 0 0" 2114 + : i === 1 2115 + ? "0 12px 0 0" 2116 + : i === 2 2117 + ? "0 0 0 12px" 2118 + : "0 0 12px 0", 2119 + }} 2120 + /> 2121 + </div> 2122 + ))} 2123 + </div> 2124 + ); 2125 + } 2126 + 2127 + // stopgap sorry 2128 + return <div style={stopgap}>image count more than one placeholder</div>; 2129 + // return ( 2130 + // <div style={{ marginTop: '1rem' }}> 2131 + // <ImageLayoutGrid 2132 + // images={images} 2133 + // viewContext={viewContext} 2134 + // /> 2135 + // </div> 2136 + // ) 2137 + } 2138 + } 2139 + 2140 + // external link embed 2141 + // = 2142 + if (AppBskyEmbedExternal.isView(embed)) { 2143 + const link = embed.external; 2144 + return ( 2145 + <ExternalLinkEmbed link={link} onOpen={onOpen} style={{ marginTop: 0 }} /> 2146 + ); 2147 + } 2148 + 2149 + // video embed 2150 + // = 2151 + if (AppBskyEmbedVideo.isView(embed)) { 2152 + // hls playlist 2153 + const playlist = embed.playlist; 2154 + return ( 2155 + <SmartHLSPlayer 2156 + url={playlist} 2157 + thumbnail={embed.thumbnail} 2158 + aspect={embed.aspectRatio} 2159 + /> 2160 + ); 2161 + // stopgap sorry 2162 + //return (<div>video</div>) 2163 + // return ( 2164 + // <VideoEmbed 2165 + // embed={embed} 2166 + // crop={ 2167 + // viewContext === PostEmbedViewContext.ThreadHighlighted 2168 + // ? 'none' 2169 + // : viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia 2170 + // ? 'square' 2171 + // : 'constrained' 2172 + // } 2173 + // /> 2174 + // ) 2175 + } 2176 + 2177 + return <div />; 2178 + } 2179 + function getDomain(url: string) { 2180 + try { 2181 + const { hostname } = new URL(url); 2182 + return hostname; 2183 + } catch (e) { 2184 + // In case it's a bare domain like "example.com" 2185 + if (!url.startsWith("http")) { 2186 + try { 2187 + const { hostname } = new URL("http://" + url); 2188 + return hostname; 2189 + } catch { 2190 + return null; 2191 + } 2192 + } 2193 + return null; 2194 + } 2195 + } 2196 + function getByteToCharMap(text: string): number[] { 2197 + const encoder = new TextEncoder(); 2198 + //const utf8 = encoder.encode(text); 2199 + 2200 + const map: number[] = []; 2201 + let byteIndex = 0; 2202 + let charIndex = 0; 2203 + 2204 + for (const char of text) { 2205 + const bytes = encoder.encode(char); 2206 + for (let i = 0; i < bytes.length; i++) { 2207 + map[byteIndex++] = charIndex; 2208 + } 2209 + charIndex++; 2210 + } 2211 + 2212 + return map; 2213 + } 2214 + 2215 + function facetByteRangeToCharRange( 2216 + byteStart: number, 2217 + byteEnd: number, 2218 + byteToCharMap: number[], 2219 + ): [number, number] { 2220 + return [ 2221 + byteToCharMap[byteStart] ?? 0, 2222 + byteToCharMap[byteEnd - 1]! + 1, // inclusive end -> exclusive char end 2223 + ]; 2224 + } 2225 + 2226 + interface FacetRange { 2227 + start: number; 2228 + end: number; 2229 + feature: Facet["features"][number]; 2230 + } 2231 + 2232 + function extractFacetRanges(text: string, facets: Facet[]): FacetRange[] { 2233 + const map = getByteToCharMap(text); 2234 + return facets.map((f) => { 2235 + const [start, end] = facetByteRangeToCharRange( 2236 + f.index.byteStart, 2237 + f.index.byteEnd, 2238 + map, 2239 + ); 2240 + return { start, end, feature: f.features[0] }; 2241 + }); 2242 + } 2243 + function renderTextWithFacets(text: string, facets: Facet[]) { 2244 + const ranges = extractFacetRanges(text, facets).sort( 2245 + (a: any, b: any) => a.start - b.start, 2246 + ); 2247 + 2248 + const result: React.ReactNode[] = []; 2249 + let current = 0; 2250 + 2251 + for (const { start, end, feature } of ranges) { 2252 + if (current < start) { 2253 + result.push(<span key={current}>{text.slice(current, start)}</span>); 2254 + } 2255 + 2256 + const fragment = text.slice(start, end); 2257 + // @ts-ignore 2258 + if (feature.$type === "app.bsky.richtext.facet#link" && feature.uri) { 2259 + result.push( 2260 + <a 2261 + // @ts-ignore 2262 + href={feature.uri} 2263 + key={start} 2264 + className="link" 2265 + style={{ 2266 + textDecoration: "none", 2267 + color: "rgb(29, 122, 242)", 2268 + wordBreak: "break-all", 2269 + }} 2270 + target="_blank" 2271 + rel="noreferrer" 2272 + onClick={(e) => { 2273 + e.stopPropagation(); 2274 + }} 2275 + > 2276 + {fragment} 2277 + </a>, 2278 + ); 2279 + } else if ( 2280 + feature.$type === "app.bsky.richtext.facet#mention" && 2281 + // @ts-ignore 2282 + feature.did 2283 + ) { 2284 + result.push( 2285 + <span 2286 + key={start} 2287 + style={{ color: "rgb(29, 122, 242)" }} 2288 + onClick={(e) => { 2289 + e.stopPropagation(); 2290 + }} 2291 + > 2292 + {fragment} 2293 + </span>, 2294 + ); 2295 + } else if (feature.$type === "app.bsky.richtext.facet#tag") { 2296 + result.push( 2297 + <span 2298 + key={start} 2299 + style={{ color: "rgb(29, 122, 242)" }} 2300 + onClick={(e) => { 2301 + e.stopPropagation(); 2302 + }} 2303 + > 2304 + {fragment} 2305 + </span>, 2306 + ); 2307 + } else { 2308 + result.push(<span key={start}>{fragment}</span>); 2309 + } 2310 + 2311 + current = end; 2312 + } 2313 + 2314 + if (current < text.length) { 2315 + result.push(<span key={current}>{text.slice(current)}</span>); 2316 + } 2317 + 2318 + return result; 2319 + } 2320 + function ExternalLinkEmbed({ 2321 + link, 2322 + onOpen, 2323 + style, 2324 + }: { 2325 + link: AppBskyEmbedExternal.ViewExternal; 2326 + onOpen?: () => void; 2327 + style?: React.CSSProperties; 2328 + }) { 2329 + //const { theme } = useTheme(); 2330 + const { uri, title, description, thumb } = link; 2331 + const thumbAspectRatio = 1.91; 2332 + const titleStyle = { 2333 + fontSize: 16, 2334 + fontWeight: 700, 2335 + marginBottom: 4, 2336 + //color: theme.text, 2337 + wordBreak: "break-word", 2338 + textAlign: "left", 2339 + maxHeight: "4em", // 2 lines * 1.5em line-height 2340 + // stupid shit 2341 + display: "-webkit-box", 2342 + WebkitBoxOrient: "vertical", 2343 + overflow: "hidden", 2344 + WebkitLineClamp: 2, 2345 + }; 2346 + const descriptionStyle = { 2347 + fontSize: 14, 2348 + //color: theme.textSecondary, 2349 + marginBottom: 8, 2350 + wordBreak: "break-word", 2351 + textAlign: "left", 2352 + maxHeight: "5em", // 3 lines * 1.5em line-height 2353 + // stupid shit 2354 + display: "-webkit-box", 2355 + WebkitBoxOrient: "vertical", 2356 + overflow: "hidden", 2357 + WebkitLineClamp: 3, 2358 + }; 2359 + const linkStyle = { 2360 + textDecoration: "none", 2361 + //color: theme.textSecondary, 2362 + wordBreak: "break-all", 2363 + textAlign: "left", 2364 + }; 2365 + const containerStyle = { 2366 + display: "flex", 2367 + flexDirection: "column", 2368 + //backgroundColor: theme.background, 2369 + //background: '#eee', 2370 + borderRadius: 12, 2371 + //border: `1px solid ${theme.border}`, 2372 + //boxShadow: theme.cardShadow, 2373 + maxWidth: "100%", 2374 + overflow: "hidden", 2375 + ...style, 2376 + }; 2377 + return ( 2378 + <a 2379 + href={uri} 2380 + target="_blank" 2381 + rel="noopener noreferrer" 2382 + onClick={(e) => { 2383 + e.stopPropagation(); 2384 + onOpen; 2385 + }} 2386 + /* @ts-ignore */ 2387 + style={linkStyle} 2388 + className="text-gray-500 dark:text-gray-400" 2389 + > 2390 + {/* @ts-ignore ehiaeih */} 2391 + <div 2392 + style={containerStyle as React.CSSProperties} 2393 + className="border border-gray-200 dark:border-gray-700" 2394 + > 2395 + {thumb && ( 2396 + <div 2397 + style={{ 2398 + position: "relative", 2399 + width: "100%", 2400 + aspectRatio: thumbAspectRatio, 2401 + overflow: "hidden", 2402 + borderTopLeftRadius: 12, 2403 + borderTopRightRadius: 12, 2404 + marginBottom: 8, 2405 + //borderBottom: `1px solid ${theme.border}`, 2406 + }} 2407 + className="border-b border-gray-200 dark:border-gray-700" 2408 + > 2409 + <img 2410 + src={thumb} 2411 + alt={description} 2412 + style={{ 2413 + position: "absolute", 2414 + top: 0, 2415 + left: 0, 2416 + width: "100%", 2417 + height: "100%", 2418 + objectFit: "cover", 2419 + }} 2420 + /> 2421 + </div> 2422 + )} 2423 + <div 2424 + style={{ 2425 + paddingBottom: 12, 2426 + paddingLeft: 12, 2427 + paddingRight: 12, 2428 + paddingTop: thumb ? 0 : 12, 2429 + }} 2430 + > 2431 + {/* @ts-ignore */} 2432 + <div style={titleStyle} className="text-gray-900 dark:text-gray-100"> 2433 + {title} 2434 + </div> 2435 + {/* @ts-ignore */} 2436 + <div 2437 + style={descriptionStyle as React.CSSProperties} 2438 + className="text-gray-500 dark:text-gray-400" 2439 + > 2440 + {description} 2441 + </div> 2442 + {/* small 1px divider here */} 2443 + <div 2444 + style={{ 2445 + height: 1, 2446 + //backgroundColor: theme.border, 2447 + marginBottom: 8, 2448 + }} 2449 + className="bg-gray-200 dark:bg-gray-700" 2450 + /> 2451 + <div 2452 + style={{ 2453 + display: "flex", 2454 + alignItems: "center", 2455 + gap: 4, 2456 + }} 2457 + > 2458 + <MdiGlobe /> 2459 + <span 2460 + style={{ 2461 + fontSize: 12, 2462 + //color: theme.textSecondary 2463 + }} 2464 + className="text-gray-500 dark:text-gray-400" 2465 + > 2466 + {getDomain(uri)} 2467 + </span> 2468 + </div> 2469 + </div> 2470 + </div> 2471 + </a> 2472 + ); 2473 + } 2474 + 2475 + const SmartHLSPlayer = ({ 2476 + url, 2477 + thumbnail, 2478 + aspect, 2479 + }: { 2480 + url: string; 2481 + thumbnail?: string; 2482 + aspect?: AppBskyEmbedDefs.AspectRatio; 2483 + }) => { 2484 + const [playing, setPlaying] = useState(false); 2485 + const containerRef = useRef(null); 2486 + 2487 + // pause the player if it goes out of viewport 2488 + useEffect(() => { 2489 + const observer = new IntersectionObserver( 2490 + ([entry]) => { 2491 + if (!entry.isIntersecting && playing) { 2492 + setPlaying(false); 2493 + } 2494 + }, 2495 + { 2496 + root: null, 2497 + threshold: 0.25, 2498 + }, 2499 + ); 2500 + 2501 + if (containerRef.current) { 2502 + observer.observe(containerRef.current); 2503 + } 2504 + 2505 + return () => { 2506 + if (containerRef.current) { 2507 + observer.unobserve(containerRef.current); 2508 + } 2509 + }; 2510 + }, [playing]); 2511 + 2512 + return ( 2513 + <div 2514 + ref={containerRef} 2515 + style={{ 2516 + position: "relative", 2517 + width: "100%", 2518 + maxWidth: 640, 2519 + cursor: "pointer", 2520 + }} 2521 + > 2522 + {!playing && ( 2523 + <> 2524 + <img 2525 + src={thumbnail} 2526 + alt="Video thumbnail" 2527 + style={{ 2528 + width: "100%", 2529 + display: "block", 2530 + aspectRatio: aspect ? aspect?.width / aspect?.height : 16 / 9, 2531 + borderRadius: 12, 2532 + //border: `1px solid ${theme.border}`, 2533 + }} 2534 + className="border border-gray-200 dark:border-gray-700" 2535 + onClick={async (e) => { 2536 + e.stopPropagation(); 2537 + setPlaying(true); 2538 + }} 2539 + /> 2540 + <div 2541 + onClick={async (e) => { 2542 + e.stopPropagation(); 2543 + setPlaying(true); 2544 + }} 2545 + style={{ 2546 + position: "absolute", 2547 + top: "50%", 2548 + left: "50%", 2549 + transform: "translate(-50%, -50%)", 2550 + //fontSize: 48, 2551 + color: "white", 2552 + //textShadow: theme.cardShadow, 2553 + pointerEvents: "none", 2554 + userSelect: "none", 2555 + }} 2556 + className="text-shadow-md" 2557 + > 2558 + {/*▶️*/} 2559 + <MdiPlayCircle /> 2560 + </div> 2561 + </> 2562 + )} 2563 + {playing && ( 2564 + <div 2565 + style={{ 2566 + position: "relative", 2567 + width: "100%", 2568 + borderRadius: 12, 2569 + overflow: "hidden", 2570 + //border: `1px solid ${theme.border}`, 2571 + paddingTop: `${ 2572 + 100 / (aspect ? aspect.width / aspect.height : 16 / 9) 2573 + }%`, // 16:9 = 56.25%, 4:3 = 75% 2574 + }} 2575 + className="border border-gray-200 dark:border-gray-700" 2576 + > 2577 + <ReactPlayer 2578 + src={url} 2579 + playing={true} 2580 + controls={true} 2581 + width="100%" 2582 + height="100%" 2583 + style={{ position: "absolute", top: 0, left: 0 }} 2584 + /> 2585 + {/* <ReactPlayer 2586 + url={url} 2587 + playing={true} 2588 + controls={true} 2589 + width="100%" 2590 + style={{width: "100% !important", aspectRatio: aspect ? aspect?.width/aspect?.height : 16/9}} 2591 + onPause={() => setPlaying(false)} 2592 + onEnded={() => setPlaying(false)} 2593 + /> */} 2594 + </div> 2595 + )} 2596 + </div> 2597 + ); 2598 + };
+5
src/components/UserError.tsx
··· 1 + import { ErrorComponent, ErrorComponentProps } from "@tanstack/react-router"; 2 + 3 + export function UserErrorComponent({ error }: ErrorComponentProps) { 4 + return <ErrorComponent error={error} />; 5 + }
+44
src/logo.svg
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <svg id="Layer_1" 3 + xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3"> 4 + <!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) --> 5 + <defs> 6 + <style> 7 + .st0 { 8 + fill: #9ae7fc; 9 + } 10 + 11 + .st1 { 12 + fill: #61dafb; 13 + } 14 + </style> 15 + </defs> 16 + <g> 17 + <path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/> 18 + <circle class="st1" cx="420.9" cy="296.5" r="45.7"/> 19 + <path class="st1" d="M520.5,78.1"/> 20 + </g> 21 + <circle class="st0" cx="420.8" cy="296.6" r="43"/> 22 + <path class="st1" d="M466.1,296.6c0,25-20.2,45.2-45.2,45.2s-45.2-20.2-45.2-45.2,20.2-45.2,45.2-45.2,45.2,20.2,45.2,45.2ZM386,295.6v-6.3c0-1.1,1.2-5.1,1.8-6.2,1-1.9,2.9-3.5,4.6-4.7l-3.4-3.4c4-3.6,9.4-3.7,13.7-.7,1.9-4.7,6.6-7.1,11.6-6.7l-.8,4.2c5.9.2,13.1,4.1,13.1,10.8s0,.5-.7.7c-1.7.3-3.4-.4-5-.6s-1.2-.4-1.2.3,2.5,4.1,3,5.5,1,3.5.8,5.3c-5.6-.8-10.5-3.2-14.8-6.7.3,2.6,4.1,21.7,5.3,21.9s.8-.6,1-1.1,1.3-6.3,1.3-6.7c0-1-1.7-1.8-2.2-2.8-1.2-2.7,1.3-4.7,3.7-3.3s5.2,6.2,7.5,7.3,13,1.4,14.8,3.3-2.9,4.6-1.5,7.6c6.7-2.6,13.5-3.3,20.6-2.5,3.1-9.7,3.1-20.3-.9-29.8-7.3,0-14.7-3.6-17.2-10.8-2.5-7.2-.7-8.6-1.3-9.3-.8-1-6.3.6-7.4-1.5s.3-1.1-.2-1.4-1.9-.6-2.6-.8c-26-6.4-51.3,15.7-49.7,42.1,0,1.6,1.6,10.3,2.4,11.1s4.8,0,6.3,0,3.7.3,5,.5c2.9.4,7.2,2.4,9.4,2.5s2.4-.8,2.7-2.4c.4-2.6.5-7.4.5-10.1s-1-7.8-1.3-11.6c-.9-.2-.7,0-.9.5-.7,1.3-1.1,3.2-1.9,4.8s-5.2,8.7-5.7,9-.7-.5-.8-.8c-1.6-3.5-2-7.9-1.9-11.8-.9-1-5.4,4.9-6.7,5.3l-.8-.4v-.3h-.2ZM455.6,276.4c1.1-1.2-6-8.9-7.2-10-3-2.7-5.4-4.5-3.5,1.4s5.7,7.8,10.6,8.5h.1ZM410.9,270.1c-.4-.5-6.1,2.9-5.5,4.6,1.9-1.3,5.9-1.7,5.5-4.6ZM400.4,276.4c-.3-2.4-6.3-2.7-7.2-1s1.6,1.4,1.9,1.4c1.8.3,3.5-.6,5.2-.4h.1ZM411.3,276.8c3.8,1.3,6.6,3.6,10.9,3.7s0-3-1.2-3.9c-2.2-1.7-5.1-2.4-7.8-2.4s-1.6-.3-1.4.4c2.8.6,7.3.7,8.4,3.8-2.3-.3-3.9-1.6-6.2-2s-2.5-.5-2.6.3h0ZM420.6,290.3c-.8-5.1-5.7-10.8-10.9-11.6s-1.3-.4-.8.5,4.7,3.2,5.7,4,4.5,4.2,2.1,3.8-8.4-7.8-9.4-6.7c.2.9,1.1,1.9,1.7,2.7,3,3.8,6.9,6.8,11.8,7.4h-.2ZM395.3,279.8c-5,1.1-6.9,6.3-6.7,11,.7.8,5-3.8,5.4-4.5s2.7-4.6,1.1-4-2.9,4.4-4.2,4.6.2-2.1.4-2.5c1.1-1.6,2.9-3.1,4-4.6h0ZM400.4,281.5c-.4-.5-2,1.3-2.3,1.7-2.9,3.9-2.6,10.2-1.5,14.8.8.2.8-.3,1.2-.7,3-3.8,5.5-10.5,4.5-15.4-2.1,3.1-3.1,7.3-3.6,11h-1.3c0-4,1.9-7.7,3-11.4h0ZM426.9,305.9c0-1.7-1.7-1.4-2.5-1.9s-1.3-1.9-3-1.4c1.3,2.1,3,3.2,5.5,3.4h0ZM417.2,308.5c7.6.7,5.5-1.9,1.4-5.5-1.3-.3-1.5,4.5-1.4,5.5ZM437,309.7c-3.5-.3-7.8-2-11.2-2.1s-1.3,0-1.9.7c4,1.3,8.4,1.7,12.1,4l1-2.5h0ZM420.5,312.8c-7.3,0-15.1,3.7-20.4,8.8s-4.8,5.3-4.8,6.2c0,1.8,8.6,6.2,10.5,6.8,12.1,4.8,27.5,3.5,38.2-4.2s3.1-2.7,0-6.2c-5.7-6.6-14.7-11.4-23.4-11.3h-.1ZM398.7,316.9c-1.4-1.4-5-1.9-7-2.1s-5.3-.3-6.9.6l13.9,1.4h0ZM456.9,314.8h-7.4c-.9,0-4.9,1.1-6,1.6s-.8.6,0,.5c2.4,0,5.1-1,7.6-1.3s3.5.2,5.1,0,1.3-.3.6-.8h0Z"/> 23 + <path class="st0" d="M386,295.6l.8.4c1.3-.3,5.8-6.2,6.7-5.3,0,3.9.3,8.3,1.9,11.8s0,1.2.8.8,5.1-7.8,5.7-9,1.3-3.5,1.9-4.8,0-.7.9-.5c.3,3.8,1.2,7.8,1.3,11.6s0,7.5-.5,10.1-1.1,2.4-2.7,2.4-6.5-2.1-9.4-2.5-3.7-.5-5-.5-5.4,1.1-6.3,0-2.2-9.5-2.4-11.1c-1.5-26.4,23.7-48.5,49.7-42.1s2.2.4,2.6.8,0,1,.2,1.4c1.1,2,6.5.5,7.4,1.5s.4,6.9,1.3,9.3c2.5,7.2,10,10.9,17.2,10.8,4,9.4,4,20.1.9,29.8-7.2-.7-13.9,0-20.6,2.5-1.3-3.1,4.1-5.1,1.5-7.6s-11.8-1.9-14.8-3.3-5.4-6.1-7.5-7.3-4.9.6-3.7,3.3,2.1,1.8,2.2,2.8-1,6.2-1.3,6.7-.3,1.3-1,1.1c-1.1-.3-5-19.3-5.3-21.9,4.3,3.5,9.2,5.9,14.8,6.7.2-1.9-.3-3.5-.8-5.3s-3-5.1-3-5.5c0-.8.9-.3,1.2-.3,1.6,0,3.3.8,5,.6s.7.3.7-.7c0-6.6-7.2-10.6-13.1-10.8l.8-4.2c-5.1-.3-9.6,2-11.6,6.7-4.3-3-9.8-3-13.7.7l3.4,3.4c-1.8,1.3-3.5,2.8-4.6,4.7s-1.8,5.1-1.8,6.2v6.6h.2ZM431.6,265c7.8,2.1,8.7-3.5.2-1.3l-.2,1.3ZM432.4,270.9c.3.6,6.4-.4,5.8-2.3s-4.6.6-5.7.6l-.2,1.7h.1ZM434.5,276c.8,1.2,5.7-1.8,5.5-2.7-.4-1.9-6.6,1.2-5.5,2.7ZM442.9,276.4c-.9-.9-5,2.8-4.6,4,.6,2.4,5.7-3,4.6-4ZM445.1,279.9c-.3.2-3.1,4.6-1.5,5s3.5-3.4,3.5-4-1.3-1.3-2-.9h0ZM448.9,287.4c2.1.8,3.8-5.1,2.3-5.5-1.9-.6-2.6,5.1-2.3,5.5ZM457.3,288.6c.5-1.7,1.1-4.7-1-5.5-1,.3-.6,3.9-.6,4.8l.3.5,1.3.2h0Z"/> 24 + <path class="st0" d="M455.6,276.4c-5-.8-9.1-3.6-10.6-8.5s.5-4,3.5-1.4,8.3,8.7,7.2,10h-.1Z"/> 25 + <path class="st0" d="M420.6,290.3c-4.9-.6-8.9-3.6-11.8-7.4s-1.5-1.8-1.7-2.7c1-1,8.5,6.6,9.4,6.7,2.4.4-1.8-3.5-2.1-3.8-1-.8-5.4-3.5-5.7-4-.4-.8.5-.5.8-.5,5.2.8,10.1,6.6,10.9,11.6h.2Z"/> 26 + <path class="st0" d="M400.4,281.5c-1.1,3.7-3,7.3-3,11.4h1.3c.5-3.7,1.5-7.8,3.6-11,1,4.8-1.5,11.6-4.5,15.4s-.4.8-1.2.7c-1.1-4.5-1.3-10.8,1.5-14.8s1.9-2.2,2.3-1.7h0Z"/> 27 + <path class="st0" d="M411.3,276.8c0-.8,2.1-.4,2.6-.3,2.4.4,4,1.7,6.2,2-1.2-3.1-5.7-3.2-8.4-3.8,0-.8.9-.4,1.4-.4,2.8,0,5.6.7,7.8,2.4,2.2,1.7,4,4,1.2,3.9-4.3,0-7.1-2.4-10.9-3.7h0Z"/> 28 + <path class="st0" d="M395.3,279.8c-1.1,1.6-3,3-4,4.6s-1.9,2.8-.4,2.5,2.8-4,4.2-4.6-.9,3.6-1.1,4c-.4.7-4.7,5.2-5.4,4.5-.2-4.6,1.8-9.9,6.7-11h0Z"/> 29 + <path class="st0" d="M437,309.7l-1,2.5c-3.6-2.3-8-2.8-12.1-4,.5-.7,1.1-.7,1.9-.7,3.4,0,7.8,1.8,11.2,2.1h0Z"/> 30 + <path class="st0" d="M417.2,308.5c0-1,0-5.8,1.4-5.5,4,3.5,6.1,6.2-1.4,5.5Z"/> 31 + <path class="st0" d="M400.4,276.4c-1.8-.3-3.5.7-5.2.4s-2.3-.8-1.9-1.4c.8-1.6,6.9-1.4,7.2,1h-.1Z"/> 32 + <path class="st0" d="M410.9,270.1c.4,3-3.6,3.3-5.5,4.6-.6-1.8,5-5.1,5.5-4.6Z"/> 33 + <path class="st0" d="M426.9,305.9c-2.5-.2-4.1-1.3-5.5-3.4,1.7-.4,2,.8,3,1.4s2.6.3,2.5,1.9h0Z"/> 34 + <path class="st1" d="M432.4,270.9l.2-1.7c1.1,0,5.1-2.2,5.7-.6s-5.5,2.9-5.8,2.3h-.1Z"/> 35 + <path class="st1" d="M431.6,265l.2-1.3c8.4-2.1,7.7,3.4-.2,1.3Z"/> 36 + <path class="st1" d="M434.5,276c-1.1-1.5,5.1-4.6,5.5-2.7s-4.6,4-5.5,2.7Z"/> 37 + <path class="st1" d="M442.9,276.4c1.1,1.1-4,6.4-4.6,4s3.7-4.9,4.6-4Z"/> 38 + <path class="st1" d="M445.1,279.9c.7-.4,2.1,0,2,.9s-2.4,4.4-3.5,4,1.3-4.8,1.5-5h0Z"/> 39 + <path class="st1" d="M448.9,287.4c-.3-.3.4-6.1,2.3-5.5,1.4.4-.2,6.2-2.3,5.5Z"/> 40 + <path class="st1" d="M457.3,288.6l-1.3-.2-.3-.5c0-.9-.4-4.6.6-4.8,2.1.8,1.5,3.8,1,5.5h0Z"/> 41 + <path class="st0" d="M420.5,312.8c8.9,0,17.9,4.7,23.4,11.3,5.6,6.6,3.8,3.5,0,6.2-10.7,7.7-26.1,9-38.2,4.2-1.9-.8-10.5-5.1-10.5-6.8s4-5.3,4.8-6.2c5.3-5,13.1-8.6,20.4-8.8h.1Z"/> 42 + <path class="st0" d="M398.7,316.9l-13.9-1.4c1.7-1,5-.8,6.9-.6s5.6.7,7,2.1h0Z"/> 43 + <path class="st0" d="M456.9,314.8c.7.5,0,.8-.6.8-1.6.2-3.5-.2-5.1,0-2.4.3-5.2,1.2-7.6,1.3s-1.1,0,0-.5,5.1-1.6,6-1.6h7.4,0Z"/> 44 + </svg>
+43
src/main.tsx
··· 1 + import { StrictMode } from "react"; 2 + import ReactDOM from "react-dom/client"; 3 + import { RouterProvider, createRouter } from "@tanstack/react-router"; 4 + 5 + // Import the generated route tree 6 + import { routeTree } from "./routeTree.gen"; 7 + 8 + import "~/styles/app.css"; 9 + import reportWebVitals from "./reportWebVitals.ts"; 10 + 11 + // Create a new router instance 12 + const router = createRouter({ 13 + routeTree, 14 + context: {}, 15 + defaultPreload: "intent", 16 + scrollRestoration: true, 17 + defaultStructuralSharing: true, 18 + defaultPreloadStaleTime: 0, 19 + }); 20 + 21 + // Register the router instance for type safety 22 + declare module "@tanstack/react-router" { 23 + interface Register { 24 + router: typeof router; 25 + } 26 + } 27 + 28 + // Render the app 29 + const rootElement = document.getElementById("app"); 30 + if (rootElement && !rootElement.innerHTML) { 31 + const root = ReactDOM.createRoot(rootElement); 32 + root.render( 33 + // double queries annoys me 34 + //<StrictMode> 35 + <RouterProvider router={router} />, 36 + //</StrictMode>, 37 + ); 38 + } 39 + 40 + // If you want to start measuring performance in your app, pass a function 41 + // to log results (for example: reportWebVitals(console.log)) 42 + // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 43 + reportWebVitals();
+149
src/providers/PassAuthProvider.tsx
··· 1 + import React, { createContext, useState, useEffect, useContext } from "react"; 2 + import { AtpAgent, type AtpSessionData } from "@atproto/api"; 3 + 4 + interface AuthContextValue { 5 + agent: AtpAgent | null; 6 + loginStatus: boolean; 7 + login: (user: string, password: string, service?: string) => Promise<void>; 8 + logout: () => Promise<void>; 9 + loading: boolean; 10 + authed: boolean | undefined; 11 + } 12 + 13 + const AuthContext = createContext<AuthContextValue>({} as AuthContextValue); 14 + 15 + export const AuthProvider = ({ children }: { children: React.ReactNode }) => { 16 + const [agent, setAgent] = useState<AtpAgent | null>(null); 17 + const [loginStatus, setLoginStatus] = useState(false); 18 + const [loading, setLoading] = useState(true); 19 + const [increment, setIncrement] = useState(0); 20 + const [authed, setAuthed] = useState<boolean | undefined>(undefined); 21 + 22 + useEffect(() => { 23 + const initialize = async () => { 24 + try { 25 + const service = localStorage.getItem("service"); 26 + // const user = await AsyncStorage.getItem('user'); 27 + // const password = await AsyncStorage.getItem('password'); 28 + const session = localStorage.getItem("sess"); 29 + 30 + if (service && session) { 31 + console.log("Auto-login service is:", service); 32 + const apiAgent = new AtpAgent({ service }); 33 + try { 34 + if (!apiAgent) { 35 + console.log("Agent is null or undefined"); 36 + return; 37 + } 38 + let sess: AtpSessionData = JSON.parse(session); 39 + console.log("resuming session is:", sess); 40 + const { data } = await apiAgent.resumeSession(sess); 41 + console.log("!!!8!!! agent resume session"); 42 + setAgent(apiAgent); 43 + setLoginStatus(true); 44 + setLoading(false); 45 + setAuthed(true); 46 + } catch (e) { 47 + console.log("Failed to resume session" + e); 48 + setLoginStatus(true); 49 + localStorage.removeItem("sess"); 50 + localStorage.removeItem("service"); 51 + const apiAgent = new AtpAgent({ service: "https://api.bsky.app" }); 52 + setAgent(apiAgent); 53 + setLoginStatus(true); 54 + setLoading(false); 55 + setAuthed(false); 56 + return; 57 + } 58 + } else { 59 + const apiAgent = new AtpAgent({ service: "https://api.bsky.app" }); 60 + setAgent(apiAgent); 61 + setLoginStatus(true); 62 + setLoading(false); 63 + setAuthed(false); 64 + } 65 + } catch (e) { 66 + console.log("Failed to auto-login:", e); 67 + } finally { 68 + setLoading(false); 69 + } 70 + }; 71 + 72 + initialize(); 73 + }, [increment]); 74 + 75 + const login = async ( 76 + user: string, 77 + password: string, 78 + service: string = "https://bsky.social", 79 + ) => { 80 + try { 81 + let sessionthing; 82 + const apiAgent = new AtpAgent({ 83 + service: service, 84 + persistSession: (evt, sess) => { 85 + sessionthing = sess; 86 + }, 87 + }); 88 + await apiAgent.login({ identifier: user, password }); 89 + console.log("!!!8!!! agent logged on"); 90 + 91 + localStorage.setItem("service", service); 92 + // await AsyncStorage.setItem('user', user); 93 + // await AsyncStorage.setItem('password', password); 94 + if (sessionthing) { 95 + localStorage.setItem("sess", JSON.stringify(sessionthing)); 96 + } else { 97 + localStorage.setItem("sess", "{}"); 98 + } 99 + 100 + setAgent(apiAgent); 101 + setLoginStatus(true); 102 + setAuthed(true); 103 + } catch (e) { 104 + console.error("Login failed:", e); 105 + } 106 + }; 107 + 108 + const logout = async () => { 109 + if (!agent) { 110 + console.error("Agent is null or undefined"); 111 + return; 112 + } 113 + setLoading(true); 114 + try { 115 + // check if its even in async storage before removing 116 + if (localStorage.getItem("service") && localStorage.getItem("sess")) { 117 + localStorage.removeItem("service"); 118 + localStorage.removeItem("sess"); 119 + } 120 + await agent.logout(); 121 + console.log("!!!8!!! agent logout"); 122 + setLoginStatus(false); 123 + setAuthed(undefined); 124 + await agent.com.atproto.server.deleteSession(); 125 + console.log("!!!8!!! agent deltesession"); 126 + //setAgent(null); 127 + setIncrement(increment + 1); 128 + } catch (e) { 129 + console.error("Logout failed:", e); 130 + } finally { 131 + setLoading(false); 132 + } 133 + }; 134 + 135 + // why the hell are we doing this 136 + /*if (loading) { 137 + return <div><span>Laoding...ae</span></div>; 138 + }*/ 139 + 140 + return ( 141 + <AuthContext.Provider 142 + value={{ agent, loginStatus, login, logout, loading, authed }} 143 + > 144 + {children} 145 + </AuthContext.Provider> 146 + ); 147 + }; 148 + 149 + export const useAuth = () => useContext(AuthContext);
+61
src/providers/PersistentStoreProvider.tsx
··· 1 + import React, { createContext, useContext, useCallback } from "react"; 2 + import { get as idbGet, set as idbSet, del as idbDel } from "idb-keyval"; 3 + 4 + type PersistentValue = { 5 + value: string; 6 + time: number; 7 + }; 8 + 9 + type PersistentStoreContextType = { 10 + get: (key: string) => Promise<PersistentValue | null>; 11 + set: (key: string, value: string) => Promise<void>; 12 + remove: (key: string) => Promise<void>; 13 + }; 14 + 15 + const PersistentStoreContext = createContext<PersistentStoreContextType | null>( 16 + null, 17 + ); 18 + 19 + export const PersistentStoreProvider: React.FC<{ 20 + children: React.ReactNode; 21 + }> = ({ children }) => { 22 + const get = useCallback( 23 + async (key: string): Promise<PersistentValue | null> => { 24 + if (typeof window === "undefined") return null; 25 + const raw = await idbGet(key); 26 + if (!raw) return null; 27 + try { 28 + return JSON.parse(raw) as PersistentValue; 29 + } catch { 30 + return null; 31 + } 32 + }, 33 + [], 34 + ); 35 + 36 + const set = useCallback(async (key: string, value: string) => { 37 + if (typeof window === "undefined") return; 38 + const entry: PersistentValue = { value, time: Date.now() }; 39 + await idbSet(key, JSON.stringify(entry)); 40 + }, []); 41 + 42 + const remove = useCallback(async (key: string) => { 43 + if (typeof window === "undefined") return; 44 + await idbDel(key); 45 + }, []); 46 + 47 + return ( 48 + <PersistentStoreContext.Provider value={{ get, set, remove }}> 49 + {children} 50 + </PersistentStoreContext.Provider> 51 + ); 52 + }; 53 + 54 + export const usePersistentStore = (): PersistentStoreContextType => { 55 + const context = useContext(PersistentStoreContext); 56 + if (!context) 57 + throw new Error( 58 + "usePersistentStore must be used within a PersistentStoreProvider", 59 + ); 60 + return context; 61 + };
+13
src/reportWebVitals.ts
··· 1 + const reportWebVitals = (onPerfEntry?: () => void) => { 2 + if (onPerfEntry && onPerfEntry instanceof Function) { 3 + import("web-vitals").then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => { 4 + onCLS(onPerfEntry); 5 + onINP(onPerfEntry); 6 + onFCP(onPerfEntry); 7 + onLCP(onPerfEntry); 8 + onTTFB(onPerfEntry); 9 + }); 10 + } 11 + }; 12 + 13 + export default reportWebVitals;
+290
src/routeTree.gen.ts
··· 1 + /* eslint-disable */ 2 + 3 + // @ts-nocheck 4 + 5 + // noinspection JSUnusedGlobalSymbols 6 + 7 + // This file was automatically generated by TanStack Router. 8 + // You should NOT make any changes in this file as it will be overwritten. 9 + // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 + 11 + import { Route as rootRouteImport } from './routes/__root' 12 + import { Route as SettingsRouteImport } from './routes/settings' 13 + import { Route as SearchRouteImport } from './routes/search' 14 + import { Route as NotificationsRouteImport } from './routes/notifications' 15 + import { Route as FeedsRouteImport } from './routes/feeds' 16 + import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' 17 + import { Route as IndexRouteImport } from './routes/index' 18 + import { Route as PathlessLayoutNestedLayoutRouteImport } from './routes/_pathlessLayout/_nested-layout' 19 + import { Route as ProfileDidIndexRouteImport } from './routes/profile.$did/index' 20 + import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' 21 + import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' 22 + import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey' 23 + 24 + const SettingsRoute = SettingsRouteImport.update({ 25 + id: '/settings', 26 + path: '/settings', 27 + getParentRoute: () => rootRouteImport, 28 + } as any) 29 + const SearchRoute = SearchRouteImport.update({ 30 + id: '/search', 31 + path: '/search', 32 + getParentRoute: () => rootRouteImport, 33 + } as any) 34 + const NotificationsRoute = NotificationsRouteImport.update({ 35 + id: '/notifications', 36 + path: '/notifications', 37 + getParentRoute: () => rootRouteImport, 38 + } as any) 39 + const FeedsRoute = FeedsRouteImport.update({ 40 + id: '/feeds', 41 + path: '/feeds', 42 + getParentRoute: () => rootRouteImport, 43 + } as any) 44 + const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ 45 + id: '/_pathlessLayout', 46 + getParentRoute: () => rootRouteImport, 47 + } as any) 48 + const IndexRoute = IndexRouteImport.update({ 49 + id: '/', 50 + path: '/', 51 + getParentRoute: () => rootRouteImport, 52 + } as any) 53 + const PathlessLayoutNestedLayoutRoute = 54 + PathlessLayoutNestedLayoutRouteImport.update({ 55 + id: '/_nested-layout', 56 + getParentRoute: () => PathlessLayoutRoute, 57 + } as any) 58 + const ProfileDidIndexRoute = ProfileDidIndexRouteImport.update({ 59 + id: '/profile/$did/', 60 + path: '/profile/$did/', 61 + getParentRoute: () => rootRouteImport, 62 + } as any) 63 + const PathlessLayoutNestedLayoutRouteBRoute = 64 + PathlessLayoutNestedLayoutRouteBRouteImport.update({ 65 + id: '/route-b', 66 + path: '/route-b', 67 + getParentRoute: () => PathlessLayoutNestedLayoutRoute, 68 + } as any) 69 + const PathlessLayoutNestedLayoutRouteARoute = 70 + PathlessLayoutNestedLayoutRouteARouteImport.update({ 71 + id: '/route-a', 72 + path: '/route-a', 73 + getParentRoute: () => PathlessLayoutNestedLayoutRoute, 74 + } as any) 75 + const ProfileDidPostRkeyRoute = ProfileDidPostRkeyRouteImport.update({ 76 + id: '/profile/$did/post/$rkey', 77 + path: '/profile/$did/post/$rkey', 78 + getParentRoute: () => rootRouteImport, 79 + } as any) 80 + 81 + export interface FileRoutesByFullPath { 82 + '/': typeof IndexRoute 83 + '/feeds': typeof FeedsRoute 84 + '/notifications': typeof NotificationsRoute 85 + '/search': typeof SearchRoute 86 + '/settings': typeof SettingsRoute 87 + '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 88 + '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 89 + '/profile/$did': typeof ProfileDidIndexRoute 90 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 91 + } 92 + export interface FileRoutesByTo { 93 + '/': typeof IndexRoute 94 + '/feeds': typeof FeedsRoute 95 + '/notifications': typeof NotificationsRoute 96 + '/search': typeof SearchRoute 97 + '/settings': typeof SettingsRoute 98 + '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 99 + '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 100 + '/profile/$did': typeof ProfileDidIndexRoute 101 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 102 + } 103 + export interface FileRoutesById { 104 + __root__: typeof rootRouteImport 105 + '/': typeof IndexRoute 106 + '/_pathlessLayout': typeof PathlessLayoutRouteWithChildren 107 + '/feeds': typeof FeedsRoute 108 + '/notifications': typeof NotificationsRoute 109 + '/search': typeof SearchRoute 110 + '/settings': typeof SettingsRoute 111 + '/_pathlessLayout/_nested-layout': typeof PathlessLayoutNestedLayoutRouteWithChildren 112 + '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 113 + '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 114 + '/profile/$did/': typeof ProfileDidIndexRoute 115 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 116 + } 117 + export interface FileRouteTypes { 118 + fileRoutesByFullPath: FileRoutesByFullPath 119 + fullPaths: 120 + | '/' 121 + | '/feeds' 122 + | '/notifications' 123 + | '/search' 124 + | '/settings' 125 + | '/route-a' 126 + | '/route-b' 127 + | '/profile/$did' 128 + | '/profile/$did/post/$rkey' 129 + fileRoutesByTo: FileRoutesByTo 130 + to: 131 + | '/' 132 + | '/feeds' 133 + | '/notifications' 134 + | '/search' 135 + | '/settings' 136 + | '/route-a' 137 + | '/route-b' 138 + | '/profile/$did' 139 + | '/profile/$did/post/$rkey' 140 + id: 141 + | '__root__' 142 + | '/' 143 + | '/_pathlessLayout' 144 + | '/feeds' 145 + | '/notifications' 146 + | '/search' 147 + | '/settings' 148 + | '/_pathlessLayout/_nested-layout' 149 + | '/_pathlessLayout/_nested-layout/route-a' 150 + | '/_pathlessLayout/_nested-layout/route-b' 151 + | '/profile/$did/' 152 + | '/profile/$did/post/$rkey' 153 + fileRoutesById: FileRoutesById 154 + } 155 + export interface RootRouteChildren { 156 + IndexRoute: typeof IndexRoute 157 + PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren 158 + FeedsRoute: typeof FeedsRoute 159 + NotificationsRoute: typeof NotificationsRoute 160 + SearchRoute: typeof SearchRoute 161 + SettingsRoute: typeof SettingsRoute 162 + ProfileDidIndexRoute: typeof ProfileDidIndexRoute 163 + ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRoute 164 + } 165 + 166 + declare module '@tanstack/react-router' { 167 + interface FileRoutesByPath { 168 + '/settings': { 169 + id: '/settings' 170 + path: '/settings' 171 + fullPath: '/settings' 172 + preLoaderRoute: typeof SettingsRouteImport 173 + parentRoute: typeof rootRouteImport 174 + } 175 + '/search': { 176 + id: '/search' 177 + path: '/search' 178 + fullPath: '/search' 179 + preLoaderRoute: typeof SearchRouteImport 180 + parentRoute: typeof rootRouteImport 181 + } 182 + '/notifications': { 183 + id: '/notifications' 184 + path: '/notifications' 185 + fullPath: '/notifications' 186 + preLoaderRoute: typeof NotificationsRouteImport 187 + parentRoute: typeof rootRouteImport 188 + } 189 + '/feeds': { 190 + id: '/feeds' 191 + path: '/feeds' 192 + fullPath: '/feeds' 193 + preLoaderRoute: typeof FeedsRouteImport 194 + parentRoute: typeof rootRouteImport 195 + } 196 + '/_pathlessLayout': { 197 + id: '/_pathlessLayout' 198 + path: '' 199 + fullPath: '' 200 + preLoaderRoute: typeof PathlessLayoutRouteImport 201 + parentRoute: typeof rootRouteImport 202 + } 203 + '/': { 204 + id: '/' 205 + path: '/' 206 + fullPath: '/' 207 + preLoaderRoute: typeof IndexRouteImport 208 + parentRoute: typeof rootRouteImport 209 + } 210 + '/_pathlessLayout/_nested-layout': { 211 + id: '/_pathlessLayout/_nested-layout' 212 + path: '' 213 + fullPath: '' 214 + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteImport 215 + parentRoute: typeof PathlessLayoutRoute 216 + } 217 + '/profile/$did/': { 218 + id: '/profile/$did/' 219 + path: '/profile/$did' 220 + fullPath: '/profile/$did' 221 + preLoaderRoute: typeof ProfileDidIndexRouteImport 222 + parentRoute: typeof rootRouteImport 223 + } 224 + '/_pathlessLayout/_nested-layout/route-b': { 225 + id: '/_pathlessLayout/_nested-layout/route-b' 226 + path: '/route-b' 227 + fullPath: '/route-b' 228 + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBRouteImport 229 + parentRoute: typeof PathlessLayoutNestedLayoutRoute 230 + } 231 + '/_pathlessLayout/_nested-layout/route-a': { 232 + id: '/_pathlessLayout/_nested-layout/route-a' 233 + path: '/route-a' 234 + fullPath: '/route-a' 235 + preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteARouteImport 236 + parentRoute: typeof PathlessLayoutNestedLayoutRoute 237 + } 238 + '/profile/$did/post/$rkey': { 239 + id: '/profile/$did/post/$rkey' 240 + path: '/profile/$did/post/$rkey' 241 + fullPath: '/profile/$did/post/$rkey' 242 + preLoaderRoute: typeof ProfileDidPostRkeyRouteImport 243 + parentRoute: typeof rootRouteImport 244 + } 245 + } 246 + } 247 + 248 + interface PathlessLayoutNestedLayoutRouteChildren { 249 + PathlessLayoutNestedLayoutRouteARoute: typeof PathlessLayoutNestedLayoutRouteARoute 250 + PathlessLayoutNestedLayoutRouteBRoute: typeof PathlessLayoutNestedLayoutRouteBRoute 251 + } 252 + 253 + const PathlessLayoutNestedLayoutRouteChildren: PathlessLayoutNestedLayoutRouteChildren = 254 + { 255 + PathlessLayoutNestedLayoutRouteARoute: 256 + PathlessLayoutNestedLayoutRouteARoute, 257 + PathlessLayoutNestedLayoutRouteBRoute: 258 + PathlessLayoutNestedLayoutRouteBRoute, 259 + } 260 + 261 + const PathlessLayoutNestedLayoutRouteWithChildren = 262 + PathlessLayoutNestedLayoutRoute._addFileChildren( 263 + PathlessLayoutNestedLayoutRouteChildren, 264 + ) 265 + 266 + interface PathlessLayoutRouteChildren { 267 + PathlessLayoutNestedLayoutRoute: typeof PathlessLayoutNestedLayoutRouteWithChildren 268 + } 269 + 270 + const PathlessLayoutRouteChildren: PathlessLayoutRouteChildren = { 271 + PathlessLayoutNestedLayoutRoute: PathlessLayoutNestedLayoutRouteWithChildren, 272 + } 273 + 274 + const PathlessLayoutRouteWithChildren = PathlessLayoutRoute._addFileChildren( 275 + PathlessLayoutRouteChildren, 276 + ) 277 + 278 + const rootRouteChildren: RootRouteChildren = { 279 + IndexRoute: IndexRoute, 280 + PathlessLayoutRoute: PathlessLayoutRouteWithChildren, 281 + FeedsRoute: FeedsRoute, 282 + NotificationsRoute: NotificationsRoute, 283 + SearchRoute: SearchRoute, 284 + SettingsRoute: SettingsRoute, 285 + ProfileDidIndexRoute: ProfileDidIndexRoute, 286 + ProfileDidPostRkeyRoute: ProfileDidPostRkeyRoute, 287 + } 288 + export const routeTree = rootRouteImport 289 + ._addFileChildren(rootRouteChildren) 290 + ._addFileTypes<FileRouteTypes>()
+685
src/routes/__root.tsx
··· 1 + /// <reference types="vite/client" /> 2 + 3 + // dont forget to run this 4 + // npx @tanstack/router-cli generate 5 + 6 + import { useState, type SVGProps } from "react"; 7 + import { 8 + HeadContent, 9 + Link, 10 + Outlet, 11 + Scripts, 12 + createRootRoute, 13 + useLocation, 14 + } from "@tanstack/react-router"; 15 + import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; 16 + import * as React from "react"; 17 + import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary"; 18 + import Login from "~/components/Login"; 19 + import { NotFound } from "~/components/NotFound"; 20 + import appCss from "~/styles/app.css?url"; 21 + import { seo } from "~/utils/seo"; 22 + import { AuthProvider, useAuth } from "~/providers/PassAuthProvider"; 23 + import { PersistentStoreProvider } from "~/providers/PersistentStoreProvider"; 24 + import type AtpAgent from "@atproto/api"; 25 + 26 + export const Route = createRootRoute({ 27 + head: () => ({ 28 + meta: [ 29 + { 30 + charSet: "utf-8", 31 + }, 32 + { 33 + name: "viewport", 34 + content: "width=device-width, initial-scale=1", 35 + }, 36 + ...seo({ 37 + title: "Red Dwarf", 38 + description: `Distributed Bluesky Client`, 39 + }), 40 + ], 41 + links: [ 42 + { rel: "stylesheet", href: appCss }, 43 + { 44 + rel: "apple-touch-icon", 45 + sizes: "180x180", 46 + href: "/apple-touch-icon.png", 47 + }, 48 + { 49 + rel: "icon", 50 + type: "image/png", 51 + sizes: "32x32", 52 + href: "/redstar.png?whatwg", 53 + }, 54 + { 55 + rel: "icon", 56 + type: "image/png", 57 + sizes: "16x16", 58 + href: "/redstar.png?whatwg", 59 + }, 60 + { rel: "manifest", href: "/site.webmanifest", color: "#fffff" }, 61 + { rel: "icon", href: "/favicon.ico" }, 62 + ], 63 + }), 64 + errorComponent: (props) => { 65 + return ( 66 + <RootDocument> 67 + <DefaultCatchBoundary {...props} /> 68 + </RootDocument> 69 + ); 70 + }, 71 + notFoundComponent: () => <NotFound />, 72 + component: RootComponent, 73 + }); 74 + 75 + function RootComponent() { 76 + return ( 77 + <AuthProvider> 78 + <PersistentStoreProvider> 79 + <RootDocument> 80 + <Outlet /> 81 + </RootDocument> 82 + </PersistentStoreProvider> 83 + </AuthProvider> 84 + ); 85 + } 86 + 87 + function RootDocument({ children }: { children: React.ReactNode }) { 88 + const location = useLocation(); 89 + const { agent, authed } = useAuth(); 90 + const isHome = location.pathname === "/"; 91 + const isNotifications = location.pathname.startsWith("/notifications"); 92 + const isProfile = location.pathname.startsWith("/profile/"); 93 + 94 + const [postOpen, setPostOpen] = useState(false); 95 + const [postText, setPostText] = useState(""); 96 + const [posting, setPosting] = useState(false); 97 + const [postSuccess, setPostSuccess] = useState(false); 98 + const [postError, setPostError] = useState<string | null>(null); 99 + 100 + async function handlePost() { 101 + if (!agent) return; 102 + setPosting(true); 103 + setPostError(null); 104 + try { 105 + await agent.com.atproto.repo.createRecord({ 106 + collection: "app.bsky.feed.post", 107 + repo: agent.assertDid, 108 + record: { 109 + $type: "app.bsky.feed.post", 110 + text: postText, 111 + createdAt: new Date().toISOString(), 112 + }, 113 + }); 114 + setPostSuccess(true); 115 + setPostText(""); 116 + setTimeout(() => { 117 + setPostSuccess(false); 118 + setPostOpen(false); 119 + }, 1500); 120 + } catch (e: any) { 121 + setPostError(e?.message || "Failed to post"); 122 + } finally { 123 + setPosting(false); 124 + } 125 + } 126 + 127 + return ( 128 + <> 129 + {postOpen && ( 130 + <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"> 131 + <div className="bg-white dark:bg-gray-900 rounded-lg shadow-lg p-6 w-full max-w-md relative"> 132 + <button 133 + className="absolute top-2 right-2 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" 134 + onClick={() => !posting && setPostOpen(false)} 135 + disabled={posting} 136 + aria-label="Close" 137 + > 138 + × 139 + </button> 140 + <h2 className="text-lg font-bold mb-2">Create Post</h2> 141 + {postSuccess ? ( 142 + <div className="flex flex-col items-center justify-center py-8"> 143 + <span className="text-green-500 text-4xl mb-2">✓</span> 144 + <span className="text-green-600">Posted!</span> 145 + </div> 146 + ) : ( 147 + <> 148 + <textarea 149 + className="w-full border rounded p-2 mb-2 dark:bg-gray-800 dark:border-gray-700" 150 + rows={4} 151 + placeholder="What's on your mind?" 152 + value={postText} 153 + onChange={(e) => setPostText(e.target.value)} 154 + disabled={posting} 155 + autoFocus 156 + /> 157 + {postError && ( 158 + <div className="text-red-500 text-sm mb-2">{postError}</div> 159 + )} 160 + <button 161 + className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50" 162 + onClick={handlePost} 163 + disabled={posting || !postText.trim()} 164 + > 165 + {posting ? "Posting..." : "Post"} 166 + </button> 167 + </> 168 + )} 169 + </div> 170 + </div> 171 + )} 172 + 173 + <div className="min-h-screen flex justify-center bg-gray-50 dark:bg-gray-950"> 174 + <nav className="hidden lg:flex h-screen w-[250px] flex-col gap-2 p-4 dark:border-gray-800 sticky top-0 self-start"> 175 + <div className="flex items-center gap-3 mb-4"> 176 + <img src="/redstar.png" alt="Red Dwarf Logo" className="w-8 h-8" /> 177 + <span className="font-extrabold text-2xl tracking-tight text-gray-900 dark:text-gray-100"> 178 + Red Dwarf{" "} 179 + <span className="text-gray-500 dark:text-gray-400 text-sm"> 180 + lite 181 + </span> 182 + </span> 183 + </div> 184 + <Link 185 + to="/" 186 + className={ 187 + `py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ` + 188 + (isHome ? "font-bold" : "") 189 + } 190 + > 191 + {isHome ? ( 192 + <TablerHomeFilled width={28} height={28} /> 193 + ) : ( 194 + <TablerHome width={28} height={28} /> 195 + )} 196 + <span>Home</span> 197 + </Link> 198 + <Link 199 + to="/notifications" 200 + className={ 201 + `py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ` + 202 + (isNotifications ? "font-bold" : "") 203 + } 204 + > 205 + {isNotifications ? ( 206 + <TablerBellFilled width={28} height={28} /> 207 + ) : ( 208 + <TablerBell width={28} height={28} /> 209 + )} 210 + <span>Notifications</span> 211 + </Link> 212 + <Link 213 + to="/feeds" 214 + className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ${ 215 + location.pathname.startsWith("/feeds") ? "font-bold" : "" 216 + }`} 217 + > 218 + {location.pathname.startsWith("/feeds") ? ( 219 + <TablerHashtagFilled width={28} height={28} /> 220 + ) : ( 221 + <TablerHashtag width={28} height={28} /> 222 + )} 223 + <span>Feeds</span> 224 + </Link> 225 + 226 + <Link 227 + to="/search" 228 + className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ${ 229 + location.pathname.startsWith("/search") ? "font-bold" : "" 230 + }`} 231 + > 232 + {location.pathname.startsWith("/search") ? ( 233 + <TablerSearchFilled width={28} height={28} /> 234 + ) : ( 235 + <TablerSearch width={28} height={28} /> 236 + )} 237 + <span>Search</span> 238 + </Link> 239 + <button 240 + className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 w-full text-left ${ 241 + isProfile ? "bg-gray-100 dark:bg-gray-900 font-bold" : "" 242 + }`} 243 + onClick={() => { 244 + if (authed && agent && agent.assertDid) { 245 + window.location.href = `/profile/${agent.assertDid}`; 246 + } 247 + }} 248 + type="button" 249 + > 250 + <TablerUserCircle width={28} height={28} /> 251 + <span>Profile</span> 252 + </button> 253 + <Link 254 + to="/settings" 255 + className={`py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ${ 256 + location.pathname.startsWith("/settings") ? "font-bold" : "" 257 + }`} 258 + > 259 + {location.pathname.startsWith("/settings") ? ( 260 + <IonSettingsSharp width={28} height={28} /> 261 + ) : ( 262 + <IonSettings width={28} height={28} /> 263 + )} 264 + <span>Settings</span> 265 + </Link> 266 + <button 267 + className="mt-4 w-full flex items-center justify-center gap-3 py-3 px-0 mb-3 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 text-xl font-bold rounded-full transition-colors shadow" 268 + onClick={() => setPostOpen(true)} 269 + type="button" 270 + > 271 + <TablerEdit 272 + width={24} 273 + height={24} 274 + className="text-gray-600 dark:text-gray-400" 275 + /> 276 + <span>Post</span> 277 + </button> 278 + <div className="flex-1"></div> 279 + <a 280 + href="https://whey.party/" 281 + target="_blank" 282 + rel="noopener noreferrer" 283 + className="mt-1 text-xs text-gray-400 dark:text-gray-500 text-center hover:underline" 284 + > 285 + made by @whey.party 286 + </a> 287 + <div className="mt-2 text-xs text-gray-400 dark:text-gray-500 text-center"> 288 + powered by{" "} 289 + <a 290 + href="https://microcosm.blue" 291 + target="_blank" 292 + rel="noopener noreferrer" 293 + className="underline hover:text-blue-500" 294 + > 295 + microcosm.blue 296 + </a> 297 + </div> 298 + </nav> 299 + 300 + <button 301 + className="lg:hidden fixed bottom-20 right-6 z-50 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-blue-600 dark:text-blue-400 rounded-full shadow-lg w-16 h-16 flex items-center justify-center border-4 border-white dark:border-gray-950 transition-all" 302 + style={{ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.12)" }} 303 + onClick={() => setPostOpen(true)} 304 + type="button" 305 + aria-label="Create Post" 306 + > 307 + <TablerEdit 308 + width={24} 309 + height={24} 310 + className="text-gray-600 dark:text-gray-400" 311 + /> 312 + </button> 313 + 314 + <main className="w-full max-w-[600px] lg:border-x border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950 pb-16 lg:pb-0"> 315 + <div className="lg:hidden flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950"> 316 + <div className="flex items-center gap-2"> 317 + <img 318 + src="/redstar.png" 319 + alt="Red Dwarf Logo" 320 + className="w-6 h-6" 321 + /> 322 + <span className="font-bold text-lg text-gray-900 dark:text-gray-100"> 323 + Red Dwarf{" "} 324 + <span className="text-gray-500 dark:text-gray-400 text-sm"> 325 + lite 326 + </span> 327 + </span> 328 + </div> 329 + <div className="flex items-center gap-2"> 330 + <Login compact={true} /> 331 + </div> 332 + </div> 333 + 334 + {children} 335 + </main> 336 + 337 + <aside className="hidden lg:flex h-screen w-[250px] sticky top-0 self-start flex-col"> 338 + <Login /> 339 + 340 + <div className="flex-1"></div> 341 + <p className="text-xs text-gray-400 dark:text-gray-500 text-justify mx-4 mb-4"> 342 + Red Dwarf lite is a bluesky client that uses Constellation and 343 + direct PDS queries. Red Dwarf (without the lite) would be a 344 + self-hosted bluesky "instance". Stay tuned for the "without the 345 + lite" version. 346 + </p> 347 + </aside> 348 + </div> 349 + 350 + <nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-950 border-t border-gray-200 dark:border-gray-700 z-40"> 351 + <div className="flex justify-around items-center py-2"> 352 + <Link 353 + to="/" 354 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 355 + isHome 356 + ? "text-gray-900 dark:text-gray-100" 357 + : "text-gray-600 dark:text-gray-400" 358 + }`} 359 + > 360 + {isHome ? ( 361 + <TablerHomeFilled width={24} height={24} /> 362 + ) : ( 363 + <TablerHome width={24} height={24} /> 364 + )} 365 + <span className="text-xs mt-1">Home</span> 366 + </Link> 367 + <Link 368 + to="/search" 369 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 370 + location.pathname.startsWith("/search") 371 + ? "text-gray-900 dark:text-gray-100" 372 + : "text-gray-600 dark:text-gray-400" 373 + }`} 374 + > 375 + {location.pathname.startsWith("/search") ? ( 376 + <TablerSearchFilled width={24} height={24} /> 377 + ) : ( 378 + <TablerSearch width={24} height={24} /> 379 + )} 380 + <span className="text-xs mt-1">Search</span> 381 + </Link> 382 + <Link 383 + to="/notifications" 384 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 385 + isNotifications 386 + ? "text-gray-900 dark:text-gray-100" 387 + : "text-gray-600 dark:text-gray-400" 388 + }`} 389 + > 390 + {isNotifications ? ( 391 + <TablerBellFilled width={24} height={24} /> 392 + ) : ( 393 + <TablerBell width={24} height={24} /> 394 + )} 395 + <span className="text-xs mt-1">Notifications</span> 396 + </Link> 397 + <button 398 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 399 + isProfile 400 + ? "text-gray-900 dark:text-gray-100" 401 + : "text-gray-600 dark:text-gray-400" 402 + }`} 403 + onClick={() => { 404 + if (authed && agent && agent.assertDid) { 405 + window.location.href = `/profile/${agent.assertDid}`; 406 + } 407 + }} 408 + type="button" 409 + > 410 + <TablerUserCircle width={24} height={24} /> 411 + <span className="text-xs mt-1">Profile</span> 412 + </button> 413 + <Link 414 + to="/settings" 415 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 416 + location.pathname.startsWith("/settings") 417 + ? "text-gray-900 dark:text-gray-100" 418 + : "text-gray-600 dark:text-gray-400" 419 + }`} 420 + > 421 + {location.pathname.startsWith("/settings") ? ( 422 + <IonSettingsSharp width={24} height={24} /> 423 + ) : ( 424 + <IonSettings width={24} height={24} /> 425 + )} 426 + <span className="text-xs mt-1">Settings</span> 427 + </Link> 428 + </div> 429 + </nav> 430 + 431 + <TanStackRouterDevtools position="bottom-right" /> 432 + <Scripts /> 433 + </> 434 + ); 435 + } 436 + export function TablerHashtag(props: SVGProps<SVGSVGElement>) { 437 + return ( 438 + <svg 439 + xmlns="http://www.w3.org/2000/svg" 440 + width={24} 441 + height={24} 442 + viewBox="0 0 24 24" 443 + {...props} 444 + > 445 + <path 446 + fill="none" 447 + stroke="currentColor" 448 + strokeLinecap="round" 449 + strokeLinejoin="round" 450 + strokeWidth={2} 451 + d="M5 9h14M5 15h14M11 4L7 20M17 4l-4 16" 452 + ></path> 453 + </svg> 454 + ); 455 + } 456 + 457 + export function TablerHashtagFilled(props: SVGProps<SVGSVGElement>) { 458 + return ( 459 + <svg 460 + xmlns="http://www.w3.org/2000/svg" 461 + width={24} 462 + height={24} 463 + viewBox="0 0 24 24" 464 + {...props} 465 + > 466 + <path 467 + fill="none" 468 + stroke="currentColor" 469 + strokeLinecap="round" 470 + strokeLinejoin="round" 471 + strokeWidth={3} 472 + d="M5 9h14M5 15h14M11 4L7 20M17 4l-4 16" 473 + ></path> 474 + </svg> 475 + ); 476 + } 477 + export function TablerEdit(props: SVGProps<SVGSVGElement>) { 478 + return ( 479 + <svg 480 + xmlns="http://www.w3.org/2000/svg" 481 + width={24} 482 + height={24} 483 + viewBox="0 0 24 24" 484 + className="text-white" 485 + {...props} 486 + > 487 + <g 488 + fill="none" 489 + stroke="currentColor" 490 + strokeLinecap="round" 491 + strokeLinejoin="round" 492 + strokeWidth={2} 493 + > 494 + <path d="M16.475 5.408a2.36 2.36 0 1 1 3.34 3.34L7.5 21H3v-4.5z"></path> 495 + </g> 496 + </svg> 497 + ); 498 + } 499 + export function TablerHome(props: SVGProps<SVGSVGElement>) { 500 + return ( 501 + <svg 502 + xmlns="http://www.w3.org/2000/svg" 503 + width={24} 504 + height={24} 505 + viewBox="0 0 24 24" 506 + className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 507 + {...props} 508 + > 509 + <g 510 + stroke="currentColor" 511 + strokeLinecap="round" 512 + strokeLinejoin="round" 513 + strokeWidth={2} 514 + fill="none" 515 + > 516 + <path d="M5 12H3l9-9l9 9h-2M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7"></path> 517 + <path d="M9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6"></path> 518 + </g> 519 + </svg> 520 + ); 521 + } 522 + export function TablerHomeFilled(props: SVGProps<SVGSVGElement>) { 523 + return ( 524 + <svg 525 + xmlns="http://www.w3.org/2000/svg" 526 + width={24} 527 + height={24} 528 + viewBox="0 0 24 24" 529 + className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 530 + {...props} 531 + > 532 + <path 533 + fill="currentColor" 534 + d="m12.707 2.293l9 9c.63.63.184 1.707-.707 1.707h-1v6a3 3 0 0 1-3 3h-1v-7a3 3 0 0 0-2.824-2.995L13 12h-2a3 3 0 0 0-3 3v7H7a3 3 0 0 1-3-3v-6H3c-.89 0-1.337-1.077-.707-1.707l9-9a1 1 0 0 1 1.414 0M13 14a1 1 0 0 1 1 1v7h-4v-7a1 1 0 0 1 .883-.993L11 14z" 535 + ></path> 536 + </svg> 537 + ); 538 + } 539 + 540 + export function TablerBell(props: SVGProps<SVGSVGElement>) { 541 + return ( 542 + <svg 543 + xmlns="http://www.w3.org/2000/svg" 544 + width={24} 545 + height={24} 546 + viewBox="0 0 24 24" 547 + {...props} 548 + > 549 + <path 550 + className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 551 + stroke="currentColor" 552 + strokeLinecap="round" 553 + strokeLinejoin="round" 554 + strokeWidth={2} 555 + d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3H4a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6M9 17v1a3 3 0 0 0 6 0v-1" 556 + ></path> 557 + </svg> 558 + ); 559 + } 560 + export function TablerBellFilled(props: SVGProps<SVGSVGElement>) { 561 + return ( 562 + <svg 563 + xmlns="http://www.w3.org/2000/svg" 564 + width={24} 565 + height={24} 566 + viewBox="0 0 24 24" 567 + className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 568 + {...props} 569 + > 570 + <path 571 + fill="currentColor" 572 + stroke="currentColor" 573 + d="M14.235 19c.865 0 1.322 1.024.745 1.668A4 4 0 0 1 12 22a4 4 0 0 1-2.98-1.332c-.552-.616-.158-1.579.634-1.661l.11-.006zM12 2c1.358 0 2.506.903 2.875 2.141l.046.171l.008.043a8.01 8.01 0 0 1 4.024 6.069l.028.287L19 11v2.931l.021.136a3 3 0 0 0 1.143 1.847l.167.117l.162.099c.86.487.56 1.766-.377 1.864L20 18H4c-1.028 0-1.387-1.364-.493-1.87a3 3 0 0 0 1.472-2.063L5 13.924l.001-2.97A8 8 0 0 1 8.822 4.5l.248-.146l.01-.043a3 3 0 0 1 2.562-2.29l.182-.017z" 574 + ></path> 575 + </svg> 576 + ); 577 + } 578 + 579 + export function TablerUserCircle(props: SVGProps<SVGSVGElement>) { 580 + return ( 581 + <svg 582 + xmlns="http://www.w3.org/2000/svg" 583 + width={24} 584 + height={24} 585 + viewBox="0 0 24 24" 586 + className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 587 + {...props} 588 + > 589 + <g 590 + fill="none" 591 + stroke="currentColor" 592 + strokeLinecap="round" 593 + strokeLinejoin="round" 594 + strokeWidth={2} 595 + > 596 + <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0"></path> 597 + <path d="M9 10a3 3 0 1 0 6 0a3 3 0 1 0-6 0m-2.832 8.849A4 4 0 0 1 10 16h4a4 4 0 0 1 3.834 2.855"></path> 598 + </g> 599 + </svg> 600 + ); 601 + } 602 + 603 + export function TablerSearch(props: SVGProps<SVGSVGElement>) { 604 + return ( 605 + <svg 606 + xmlns="http://www.w3.org/2000/svg" 607 + width={24} 608 + height={24} 609 + viewBox="0 0 24 24" 610 + //className="text-gray-400 dark:text-gray-500" 611 + {...props} 612 + > 613 + <g 614 + fill="none" 615 + stroke="currentColor" 616 + strokeLinecap="round" 617 + strokeLinejoin="round" 618 + strokeWidth={2} 619 + > 620 + <path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0"></path> 621 + <path d="m21 21l-6-6"></path> 622 + </g> 623 + </svg> 624 + ); 625 + } 626 + export function TablerSearchFilled(props: SVGProps<SVGSVGElement>) { 627 + return ( 628 + <svg 629 + xmlns="http://www.w3.org/2000/svg" 630 + width={24} 631 + height={24} 632 + viewBox="0 0 24 24" 633 + //className="text-gray-400 dark:text-gray-500" 634 + {...props} 635 + > 636 + <g 637 + fill="none" 638 + stroke="currentColor" 639 + strokeLinecap="round" 640 + strokeLinejoin="round" 641 + strokeWidth={3} 642 + > 643 + <path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0"></path> 644 + <path d="m21 21l-6-6"></path> 645 + </g> 646 + </svg> 647 + ); 648 + } 649 + 650 + export function IonSettings(props: SVGProps<SVGSVGElement>) { 651 + return ( 652 + <svg 653 + xmlns="http://www.w3.org/2000/svg" 654 + width={24} 655 + height={24} 656 + viewBox="0 0 512 512" 657 + {...props} 658 + > 659 + <path 660 + fill="none" 661 + stroke="currentColor" 662 + strokeLinecap="round" 663 + strokeLinejoin="round" 664 + strokeWidth={32} 665 + d="M262.29 192.31a64 64 0 1 0 57.4 57.4a64.13 64.13 0 0 0-57.4-57.4M416.39 256a154 154 0 0 1-1.53 20.79l45.21 35.46a10.81 10.81 0 0 1 2.45 13.75l-42.77 74a10.81 10.81 0 0 1-13.14 4.59l-44.9-18.08a16.11 16.11 0 0 0-15.17 1.75A164.5 164.5 0 0 1 325 400.8a15.94 15.94 0 0 0-8.82 12.14l-6.73 47.89a11.08 11.08 0 0 1-10.68 9.17h-85.54a11.11 11.11 0 0 1-10.69-8.87l-6.72-47.82a16.07 16.07 0 0 0-9-12.22a155 155 0 0 1-21.46-12.57a16 16 0 0 0-15.11-1.71l-44.89 18.07a10.81 10.81 0 0 1-13.14-4.58l-42.77-74a10.8 10.8 0 0 1 2.45-13.75l38.21-30a16.05 16.05 0 0 0 6-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 0 0-6.07-13.94l-38.19-30A10.81 10.81 0 0 1 49.48 186l42.77-74a10.81 10.81 0 0 1 13.14-4.59l44.9 18.08a16.11 16.11 0 0 0 15.17-1.75A164.5 164.5 0 0 1 187 111.2a15.94 15.94 0 0 0 8.82-12.14l6.73-47.89A11.08 11.08 0 0 1 213.23 42h85.54a11.11 11.11 0 0 1 10.69 8.87l6.72 47.82a16.07 16.07 0 0 0 9 12.22a155 155 0 0 1 21.46 12.57a16 16 0 0 0 15.11 1.71l44.89-18.07a10.81 10.81 0 0 1 13.14 4.58l42.77 74a10.8 10.8 0 0 1-2.45 13.75l-38.21 30a16.05 16.05 0 0 0-6.05 14.08c.33 4.14.55 8.3.55 12.47" 666 + ></path> 667 + </svg> 668 + ); 669 + } 670 + export function IonSettingsSharp(props: SVGProps<SVGSVGElement>) { 671 + return ( 672 + <svg 673 + xmlns="http://www.w3.org/2000/svg" 674 + width={24} 675 + height={24} 676 + viewBox="0 0 512 512" 677 + {...props} 678 + > 679 + <path 680 + fill="currentColor" 681 + d="M256 176a80 80 0 1 0 80 80a80.24 80.24 0 0 0-80-80m172.72 80a165.5 165.5 0 0 1-1.64 22.34l48.69 38.12a11.59 11.59 0 0 1 2.63 14.78l-46.06 79.52a11.64 11.64 0 0 1-14.14 4.93l-57.25-23a176.6 176.6 0 0 1-38.82 22.67l-8.56 60.78a11.93 11.93 0 0 1-11.51 9.86h-92.12a12 12 0 0 1-11.51-9.53l-8.56-60.78A169.3 169.3 0 0 1 151.05 393L93.8 416a11.64 11.64 0 0 1-14.14-4.92L33.6 331.57a11.59 11.59 0 0 1 2.63-14.78l48.69-38.12A175 175 0 0 1 83.28 256a165.5 165.5 0 0 1 1.64-22.34l-48.69-38.12a11.59 11.59 0 0 1-2.63-14.78l46.06-79.52a11.64 11.64 0 0 1 14.14-4.93l57.25 23a176.6 176.6 0 0 1 38.82-22.67l8.56-60.78A11.93 11.93 0 0 1 209.94 26h92.12a12 12 0 0 1 11.51 9.53l8.56 60.78A169.3 169.3 0 0 1 361 119l57.2-23a11.64 11.64 0 0 1 14.14 4.92l46.06 79.52a11.59 11.59 0 0 1-2.63 14.78l-48.69 38.12a175 175 0 0 1 1.64 22.66" 682 + ></path> 683 + </svg> 684 + ); 685 + }
+16
src/routes/_pathlessLayout.tsx
··· 1 + import { Outlet, createFileRoute } from "@tanstack/react-router"; 2 + 3 + export const Route = createFileRoute("/_pathlessLayout")({ 4 + component: LayoutComponent, 5 + }); 6 + 7 + function LayoutComponent() { 8 + return ( 9 + <div className="p-2"> 10 + <div className="border-b">I'm a layout</div> 11 + <div> 12 + <Outlet /> 13 + </div> 14 + </div> 15 + ); 16 + }
+34
src/routes/_pathlessLayout/_nested-layout.tsx
··· 1 + import { Link, Outlet, createFileRoute } from "@tanstack/react-router"; 2 + 3 + export const Route = createFileRoute("/_pathlessLayout/_nested-layout")({ 4 + component: LayoutComponent, 5 + }); 6 + 7 + function LayoutComponent() { 8 + return ( 9 + <div> 10 + <div>I'm a nested layout</div> 11 + <div className="flex gap-2 border-b"> 12 + <Link 13 + to="/route-a" 14 + activeProps={{ 15 + className: "font-bold", 16 + }} 17 + > 18 + Go to route A 19 + </Link> 20 + <Link 21 + to="/route-b" 22 + activeProps={{ 23 + className: "font-bold", 24 + }} 25 + > 26 + Go to route B 27 + </Link> 28 + </div> 29 + <div> 30 + <Outlet /> 31 + </div> 32 + </div> 33 + ); 34 + }
+10
src/routes/_pathlessLayout/_nested-layout/route-a.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + export const Route = createFileRoute("/_pathlessLayout/_nested-layout/route-a")( 3 + { 4 + component: LayoutAComponent, 5 + }, 6 + ); 7 + 8 + function LayoutAComponent() { 9 + return <div>I'm A!</div>; 10 + }
+10
src/routes/_pathlessLayout/_nested-layout/route-b.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + export const Route = createFileRoute("/_pathlessLayout/_nested-layout/route-b")( 3 + { 4 + component: LayoutBComponent, 5 + }, 6 + ); 7 + 8 + function LayoutBComponent() { 9 + return <div>I'm B!</div>; 10 + }
+9
src/routes/feeds.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + 3 + export const Route = createFileRoute("/feeds")({ 4 + component: Feeds, 5 + }); 6 + 7 + export function Feeds() { 8 + return <div className="p-6">Feeds page (coming soon)</div>; 9 + }
+313
src/routes/index.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + import { 3 + CACHE_TIMEOUT, 4 + cachedGetRecord, 5 + cachedResolveIdentity, 6 + UniversalPostRendererATURILoader, 7 + } from "~/components/UniversalPostRenderer"; 8 + import * as React from "react"; 9 + import { useAuth } from "~/providers/PassAuthProvider"; 10 + import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 11 + 12 + export const Route = createFileRoute("/")({ 13 + component: Home, 14 + }); 15 + 16 + function Home() { 17 + const { 18 + agent, 19 + loginStatus, 20 + login, 21 + logout, 22 + loading: loadering, 23 + authed, 24 + } = useAuth(); 25 + const { get, set } = usePersistentStore(); 26 + const [feed, setFeed] = React.useState<any[]>([]); 27 + const [loading, setLoading] = React.useState(true); 28 + const [error, setError] = React.useState<string | null>(null); 29 + 30 + const [prefs, setPrefs] = React.useState<any>({}); 31 + React.useEffect(() => { 32 + if (!loadering && authed && agent && agent.did) { 33 + const run = async () => { 34 + try { 35 + if (!agent.did) return; 36 + const prefs = await cachedGetPrefs({ 37 + did: agent.did, 38 + agent, 39 + get, 40 + set, 41 + }); 42 + 43 + console.log("alistoffeeds", prefs); 44 + setPrefs(prefs || {}); 45 + } catch (err) { 46 + console.error("alistoffeeds Fetch error in preferences effect:", err); 47 + } 48 + }; 49 + 50 + run(); 51 + } 52 + }, [loadering, authed, agent]); 53 + 54 + const savedFeedsPref = React.useMemo(() => { 55 + if (!prefs?.preferences) return null; 56 + return prefs.preferences.find( 57 + (p: any) => p?.$type === "app.bsky.actor.defs#savedFeedsPrefV2", 58 + ); 59 + }, [prefs]); 60 + 61 + const savedFeeds = savedFeedsPref?.items || []; 62 + 63 + const [selectedFeed, setSelectedFeed] = React.useState<string | null>(null); 64 + 65 + React.useEffect(() => { 66 + const fallbackFeed = 67 + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/wh-hot"; 68 + if (authed) { 69 + if (savedFeeds.length > 0) { 70 + setSelectedFeed((prev) => 71 + prev && savedFeeds.some((f: any) => f.value === prev) 72 + ? prev 73 + : savedFeeds[0].value, 74 + ); 75 + } else { 76 + setSelectedFeed(fallbackFeed); 77 + } 78 + } else { 79 + setSelectedFeed(fallbackFeed); 80 + } 81 + }, [savedFeeds, authed]); 82 + 83 + React.useEffect(() => { 84 + if (loadering || !selectedFeed) return; 85 + 86 + let ignore = false; 87 + 88 + const run = async () => { 89 + setLoading(true); 90 + setError(null); 91 + 92 + try { 93 + if (authed && agent) { 94 + if (!agent.did) return; 95 + 96 + const pdsurl = await cachedResolveIdentity({ 97 + didOrHandle: agent.did, 98 + get, 99 + set, 100 + }); 101 + 102 + const fetchstringcomplex = `${pdsurl.pdsUrl}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${selectedFeed}`; 103 + console.log("fetching feed authed: " + fetchstringcomplex); 104 + 105 + const feeddef = await cachedGetRecord({ 106 + atUri: selectedFeed, 107 + get, 108 + set, 109 + }); 110 + 111 + const feedservicedid = feeddef.value.did; 112 + 113 + const res = await agent.fetchHandler(fetchstringcomplex, { 114 + method: "GET", 115 + headers: { 116 + "atproto-proxy": `${feedservicedid}#bsky_fg`, 117 + "Content-Type": "application/json", 118 + }, 119 + }); 120 + 121 + if (!res.ok) throw new Error("Failed to fetch feed"); 122 + const data = await res.json(); 123 + 124 + if (!ignore) setFeed(data.feed || []); 125 + } else { 126 + console.log("falling back"); 127 + // always use fallback feed for not logged in 128 + const fallbackFeed = 129 + "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"; 130 + // const feeddef = await cachedGetRecord({ 131 + // atUri: fallbackFeed, 132 + // get, 133 + // set, 134 + // }); 135 + 136 + //const feedservicedid = "did:web:discover.bsky.app" //feeddef.did; 137 + const fetchstringsimple = `https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton?feed=${fallbackFeed}`; 138 + console.log("fetching feed unauthed: " + fetchstringsimple); 139 + 140 + const res = await fetch(fetchstringsimple); 141 + if (!res.ok) throw new Error("Failed to fetch feed"); 142 + const data = await res.json(); 143 + 144 + if (!ignore) setFeed(data.feed || []); 145 + } 146 + } catch (e) { 147 + if (!ignore) { 148 + if (e instanceof Error) { 149 + setError(e.message); 150 + } else { 151 + setError("Unknown error"); 152 + } 153 + } 154 + } finally { 155 + if (!ignore) setLoading(false); 156 + } 157 + }; 158 + 159 + run(); 160 + 161 + return () => { 162 + ignore = true; 163 + }; 164 + }, [authed, agent, loadering, selectedFeed, get, set]); 165 + 166 + return ( 167 + <div className="flex flex-col divide-y divide-gray-200 dark:divide-gray-800"> 168 + <div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden scroll-thin"> 169 + {savedFeeds.length > 0 ? ( 170 + savedFeeds.map((item: any, idx: number) => { 171 + const label = item.value.split("/").pop() || item.value; 172 + const isActive = selectedFeed === item.value; 173 + return ( 174 + <button 175 + key={item.value || idx} 176 + className={`px-3 py-1 rounded-full whitespace-nowrap font-medium transition-colors ${ 177 + isActive 178 + ? "bg-gray-600 text-white" 179 + : item.pinned 180 + ? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200" 181 + : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200" 182 + }`} 183 + onClick={() => setSelectedFeed(item.value)} 184 + title={item.value} 185 + > 186 + {label} 187 + {item.pinned && ( 188 + <span className="ml-1 text-xs text-gray-700 dark:text-gray-200"> 189 + 190 + </span> 191 + )} 192 + </button> 193 + ); 194 + }) 195 + ) : ( 196 + <span className="text-xl font-bold ml-2">Home</span> 197 + )} 198 + </div> 199 + {loading && <div className="p-4 text-gray-500">Loading...</div>} 200 + {error && <div className="p-4 text-red-500">{error}</div>} 201 + {!loading && !error && feed.length === 0 && ( 202 + <div className="p-4 text-gray-500">No posts found.</div> 203 + )} 204 + {feed.map((item, i) => ( 205 + <UniversalPostRendererATURILoader 206 + key={item.post || i} 207 + atUri={item.post} 208 + /> 209 + ))} 210 + </div> 211 + ); 212 + } 213 + 214 + export async function cachedResolveDIDWEBDOC({ 215 + didweb, 216 + cacheTimeout = CACHE_TIMEOUT, 217 + get, 218 + set, 219 + }: { 220 + didweb: string; 221 + cacheTimeout?: number; 222 + get: (key: string) => any; 223 + set: (key: string, value: string) => void; 224 + }): Promise<any> { 225 + const isDidInput = didweb.startsWith("did:web:"); 226 + const cacheKey = `didwebdoc:${didweb}`; 227 + const now = Date.now(); 228 + const cached = get(cacheKey); 229 + if ( 230 + cached && 231 + cached.value && 232 + cached.time && 233 + now - cached.time < cacheTimeout 234 + ) { 235 + try { 236 + return JSON.parse(cached.value); 237 + } catch {} 238 + } 239 + const url = `https://free-fly-24.deno.dev/resolve-did-web?did=${encodeURIComponent( 240 + didweb, 241 + )}`; 242 + const res = await fetch(url); 243 + if (!res.ok) throw new Error("Failed to resolve didwebdoc"); 244 + const data = await res.json(); 245 + set(cacheKey, JSON.stringify(data)); 246 + if (!isDidInput && data.did) { 247 + set(`didwebdoc:${data.did}`, JSON.stringify(data)); 248 + } 249 + return data; 250 + } 251 + 252 + export async function cachedGetPrefs({ 253 + did, 254 + agent, 255 + get, 256 + set, 257 + cacheTimeout = CACHE_TIMEOUT, 258 + }: { 259 + did: string; 260 + agent: any; // or type properly if available 261 + get: (key: string) => any; 262 + set: (key: string, value: string) => void; 263 + cacheTimeout?: number; 264 + }): Promise<any> { 265 + const cacheKey = `prefs:${did}`; 266 + const cached = get(cacheKey); 267 + const now = Date.now(); 268 + 269 + if ( 270 + cached && 271 + cached.value && 272 + cached.time && 273 + now - cached.time < cacheTimeout 274 + ) { 275 + try { 276 + return JSON.parse(cached.value); 277 + } catch { 278 + // fall through to fetch 279 + } 280 + } 281 + 282 + const resolved = await cachedResolveIdentity({ 283 + didOrHandle: did, 284 + get, 285 + set, 286 + }); 287 + 288 + if (!resolved?.pdsUrl) throw new Error("Missing resolved PDS info"); 289 + 290 + const fetchUrl = `${resolved.pdsUrl}/xrpc/app.bsky.actor.getPreferences`; 291 + 292 + const res = await agent.fetchHandler(fetchUrl, { 293 + method: "GET", 294 + headers: { 295 + "Content-Type": "application/json", 296 + }, 297 + }); 298 + 299 + if (!res.ok) throw new Error(`Failed to fetch preferences: ${res.status}`); 300 + 301 + const text = await res.text(); 302 + 303 + let data: any; 304 + try { 305 + data = JSON.parse(text); 306 + } catch (err) { 307 + console.error("Failed to parse preferences JSON:", err); 308 + throw err; 309 + } 310 + 311 + set(cacheKey, JSON.stringify(data)); 312 + return data; 313 + }
+171
src/routes/notifications.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + import React, { useEffect, useState, useRef } from "react"; 3 + import { useAuth } from "~/providers/PassAuthProvider"; 4 + import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 5 + 6 + const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 7 + 8 + export const Route = createFileRoute("/notifications")({ 9 + component: NotificationsComponent, 10 + }); 11 + 12 + function NotificationsComponent() { 13 + console.log("NotificationsComponent render"); 14 + const { agent, authed, loading: authLoading } = useAuth(); 15 + const { get, set } = usePersistentStore(); 16 + const [did, setDid] = useState<string | null>(null); 17 + const [resolving, setResolving] = useState(false); 18 + const [error, setError] = useState<string | null>(null); 19 + const [responses, setResponses] = useState<any[]>([null, null, null]); 20 + const [loading, setLoading] = useState(false); 21 + const inputRef = useRef<HTMLInputElement>(null); 22 + 23 + useEffect(() => { 24 + if (authLoading) return; 25 + if (authed && agent && agent.assertDid) { 26 + setDid(agent.assertDid); 27 + } 28 + }, [authed, agent, authLoading]); 29 + 30 + async function handleSubmit() { 31 + console.log("handleSubmit called"); 32 + setError(null); 33 + setResponses([null, null, null]); 34 + const value = inputRef.current?.value?.trim() || ""; 35 + if (!value) return; 36 + if (value.startsWith("did:")) { 37 + setDid(value); 38 + setError(null); 39 + return; 40 + } 41 + setResolving(true); 42 + const cacheKey = `handleDid:${value}`; 43 + const now = Date.now(); 44 + const cached = await get(cacheKey); 45 + if ( 46 + cached && 47 + cached.value && 48 + cached.time && 49 + now - cached.time < HANDLE_DID_CACHE_TIMEOUT 50 + ) { 51 + try { 52 + const data = JSON.parse(cached.value); 53 + setDid(data.did); 54 + setResolving(false); 55 + return; 56 + } catch {} 57 + } 58 + try { 59 + const url = `https://free-fly-24.deno.dev/?handle=${encodeURIComponent(value)}`; 60 + const res = await fetch(url); 61 + if (!res.ok) throw new Error("Failed to resolve handle"); 62 + const data = await res.json(); 63 + set(cacheKey, JSON.stringify(data)); 64 + setDid(data.did); 65 + } catch (e: any) { 66 + setError("Failed to resolve handle: " + (e?.message || e)); 67 + } finally { 68 + setResolving(false); 69 + } 70 + } 71 + 72 + useEffect(() => { 73 + if (!did) return; 74 + setLoading(true); 75 + setError(null); 76 + const urls = [ 77 + `https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet%23mention].did`, 78 + `https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[].features[app.bsky.richtext.facet%23mention].did`, 79 + `https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.graph.follow&path=.subject`, 80 + ]; 81 + let ignore = false; 82 + Promise.all( 83 + urls.map(async (url) => { 84 + try { 85 + const r = await fetch(url); 86 + if (!r.ok) throw new Error("Failed to fetch"); 87 + const text = await r.text(); 88 + if (!text) return null; 89 + try { 90 + return JSON.parse(text); 91 + } catch { 92 + return null; 93 + } 94 + } catch (e: any) { 95 + return { error: e?.message || String(e) }; 96 + } 97 + }), 98 + ) 99 + .then((results) => { 100 + if (!ignore) setResponses(results); 101 + }) 102 + .catch((e) => { 103 + if (!ignore) 104 + setError("Failed to fetch notifications: " + (e?.message || e)); 105 + }) 106 + .finally(() => { 107 + if (!ignore) setLoading(false); 108 + }); 109 + return () => { 110 + ignore = true; 111 + }; 112 + }, [did]); 113 + 114 + return ( 115 + <div className="flex flex-col divide-y divide-gray-200 dark:divide-gray-800"> 116 + <div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-800"> 117 + <span className="text-xl font-bold ml-2">Notifications</span> 118 + {!authed && ( 119 + <div className="flex items-center gap-2"> 120 + <input 121 + type="text" 122 + placeholder="Enter handle or DID" 123 + ref={inputRef} 124 + className="ml-4 px-2 py-1 rounded border border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100" 125 + style={{ minWidth: 220 }} 126 + disabled={resolving} 127 + /> 128 + <button 129 + type="button" 130 + className="px-3 py-1 rounded bg-blue-600 text-white font-semibold disabled:opacity-50" 131 + disabled={resolving} 132 + onClick={handleSubmit} 133 + > 134 + {resolving ? "Resolving..." : "Submit"} 135 + </button> 136 + </div> 137 + )} 138 + </div> 139 + {error && <div className="p-4 text-red-500">{error}</div>} 140 + {loading && ( 141 + <div className="p-4 text-gray-500">Loading notifications...</div> 142 + )} 143 + {!loading && 144 + !error && 145 + responses.map((resp, i) => ( 146 + <div key={i} className="p-4"> 147 + <div className="font-bold mb-2">Query {i + 1}</div> 148 + {!resp || 149 + (typeof resp === "object" && Object.keys(resp).length === 0) || 150 + (Array.isArray(resp) && resp.length === 0) ? ( 151 + <div className="text-gray-500">No notifications found.</div> 152 + ) : ( 153 + <pre 154 + style={{ 155 + background: "#222", 156 + color: "#eee", 157 + borderRadius: 8, 158 + padding: 12, 159 + fontSize: 13, 160 + overflowX: "auto", 161 + }} 162 + > 163 + {JSON.stringify(resp, null, 2)} 164 + </pre> 165 + )} 166 + </div> 167 + ))} 168 + {/* <div className="p-4"> yo this project sucks, ill remake it some other time, like cmon inputting anything into the textbox makes it break. ive warned you</div> */} 169 + </div> 170 + ); 171 + }
+442
src/routes/profile.$did/index.tsx
··· 1 + import { createFileRoute, Link } from "@tanstack/react-router"; 2 + import React from "react"; 3 + import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; 4 + import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 5 + 6 + const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 7 + const CACHE_TIMEOUT = 5 * 60 * 1000; // 5 minutes 8 + 9 + export const Route = createFileRoute("/profile/$did/")({ 10 + component: ProfileComponent, 11 + }); 12 + 13 + function ProfileComponent() { 14 + const { did } = Route.useParams(); 15 + const { get, set } = usePersistentStore(); 16 + const [resolvedDid, setResolvedDid] = React.useState<string | null>(null); 17 + const [resolvedHandle, setResolvedHandle] = React.useState<string | null>( 18 + null, 19 + ); 20 + const [loading, setLoading] = React.useState(false); 21 + const [error, setError] = React.useState<string | null>(null); 22 + const [profile, setProfile] = React.useState<any>(null); 23 + const [posts, setPosts] = React.useState<any[]>([]); 24 + const [postsLoading, setPostsLoading] = React.useState(false); 25 + const [cursor, setCursor] = React.useState<string | null>(null); 26 + const [hasMore, setHasMore] = React.useState(true); 27 + const [postsCached, setPostsCached] = React.useState(false); 28 + 29 + React.useEffect(() => { 30 + let ignore = false; 31 + async function resolveDidIfNeeded() { 32 + if (!did) { 33 + setResolvedDid(null); 34 + setResolvedHandle(null); 35 + return; 36 + } 37 + if (did.startsWith("did:")) { 38 + setResolvedDid(did); 39 + setLoading(true); 40 + setError(null); 41 + const cacheKey = `handleDid:${did}`; 42 + const now = Date.now(); 43 + const cached = await get(cacheKey); 44 + if ( 45 + cached && 46 + cached.value && 47 + cached.time && 48 + now - cached.time < HANDLE_DID_CACHE_TIMEOUT 49 + ) { 50 + try { 51 + const data = JSON.parse(cached.value); 52 + if (!ignore) { 53 + setResolvedDid(data.did); 54 + setResolvedHandle(data.handle || null); 55 + } 56 + setLoading(false); 57 + return; 58 + } catch {} 59 + } 60 + try { 61 + const url = `https://free-fly-24.deno.dev/?did=${encodeURIComponent(did)}`; 62 + const res = await fetch(url); 63 + if (!res.ok) throw new Error("Failed to resolve DID"); 64 + const data = await res.json(); 65 + set(cacheKey, JSON.stringify(data)); 66 + if (!ignore) { 67 + setResolvedDid(data.did); 68 + setResolvedHandle(data.handle || null); 69 + } 70 + } catch (e: any) { 71 + if (!ignore) 72 + setError("Failed to resolve handle: " + (e?.message || e)); 73 + } finally { 74 + setLoading(false); 75 + } 76 + return; 77 + } 78 + setLoading(true); 79 + setError(null); 80 + const cacheKey = `handleDid:${did}`; 81 + const now = Date.now(); 82 + const cached = await get(cacheKey); 83 + if ( 84 + cached && 85 + cached.value && 86 + cached.time && 87 + now - cached.time < HANDLE_DID_CACHE_TIMEOUT 88 + ) { 89 + try { 90 + const data = JSON.parse(cached.value); 91 + if (!ignore) { 92 + setResolvedDid(data.did); 93 + setResolvedHandle(data.handle || did); 94 + } 95 + setLoading(false); 96 + return; 97 + } catch {} 98 + } 99 + try { 100 + const url = `https://free-fly-24.deno.dev/?handle=${encodeURIComponent(did)}`; 101 + const res = await fetch(url); 102 + if (!res.ok) throw new Error("Failed to resolve handle"); 103 + const data = await res.json(); 104 + set(cacheKey, JSON.stringify(data)); 105 + if (!ignore) { 106 + setResolvedDid(data.did); 107 + setResolvedHandle(data.handle || did); 108 + } 109 + } catch (e: any) { 110 + if (!ignore) setError("Failed to resolve handle: " + (e?.message || e)); 111 + } finally { 112 + setLoading(false); 113 + } 114 + } 115 + resolveDidIfNeeded(); 116 + return () => { 117 + ignore = true; 118 + }; 119 + }, [did, get, set]); 120 + 121 + React.useEffect(() => { 122 + if (!resolvedDid) return; 123 + let ignore = false; 124 + async function fetchProfile() { 125 + const cacheKey = `profile:${resolvedDid}`; 126 + const now = Date.now(); 127 + const cached = await get(cacheKey); 128 + if ( 129 + cached && 130 + cached.value && 131 + cached.time && 132 + now - cached.time < CACHE_TIMEOUT 133 + ) { 134 + try { 135 + if (!ignore) setProfile(JSON.parse(cached.value)); 136 + return; 137 + } catch {} 138 + } 139 + try { 140 + if (!resolvedDid) return; 141 + let resolvedRaw = await get(`handleDid:${resolvedDid}`); 142 + let resolved: any = null; 143 + if ( 144 + resolvedRaw && 145 + resolvedRaw.value && 146 + resolvedRaw.time && 147 + now - resolvedRaw.time < HANDLE_DID_CACHE_TIMEOUT 148 + ) { 149 + try { 150 + resolved = JSON.parse(resolvedRaw.value); 151 + } catch { 152 + resolved = null; 153 + } 154 + } else { 155 + const url = `https://free-fly-24.deno.dev/?did=${encodeURIComponent(resolvedDid)}`; 156 + const res = await fetch(url); 157 + if (!res.ok) throw new Error("Failed to resolve DID"); 158 + resolved = await res.json(); 159 + set(`handleDid:${resolvedDid}`, JSON.stringify(resolved)); 160 + } 161 + if (!resolved || !resolved.pdsUrl) 162 + throw new Error("DID resolution failed or missing pdsUrl"); 163 + 164 + const profileUrl = `${resolved.pdsUrl}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(resolvedDid)}&collection=app.bsky.actor.profile&rkey=self`; 165 + const profileRes = await fetch(profileUrl); 166 + if (!profileRes.ok) throw new Error("Failed to fetch profile"); 167 + const profileData = await profileRes.json(); 168 + if (!ignore) { 169 + setProfile(profileData); 170 + set(cacheKey, JSON.stringify(profileData)); 171 + } 172 + } catch (e: any) { 173 + if (!ignore) setError("Failed to fetch profile: " + (e?.message || e)); 174 + } 175 + } 176 + fetchProfile(); 177 + return () => { 178 + ignore = true; 179 + }; 180 + }, [resolvedDid, get, set]); 181 + 182 + React.useEffect(() => { 183 + if (!resolvedDid) return; 184 + let ignore = false; 185 + async function fetchPosts() { 186 + setPostsLoading(true); 187 + setPostsCached(false); 188 + try { 189 + if (!resolvedDid) return; 190 + let resolvedRaw = await get(`handleDid:${resolvedDid}`); 191 + let resolved: any = null; 192 + const now = Date.now(); 193 + if ( 194 + resolvedRaw && 195 + resolvedRaw.value && 196 + resolvedRaw.time && 197 + now - resolvedRaw.time < HANDLE_DID_CACHE_TIMEOUT 198 + ) { 199 + try { 200 + resolved = JSON.parse(resolvedRaw.value); 201 + } catch { 202 + resolved = null; 203 + } 204 + } else { 205 + const url = `https://free-fly-24.deno.dev/?did=${encodeURIComponent(resolvedDid)}`; 206 + const res = await fetch(url); 207 + if (!res.ok) throw new Error("Failed to resolve DID"); 208 + resolved = await res.json(); 209 + set(`handleDid:${resolvedDid}`, JSON.stringify(resolved)); 210 + } 211 + if (!resolved || !resolved.pdsUrl) 212 + throw new Error("DID resolution failed or missing pdsUrl"); 213 + 214 + const postsUrl = `${resolved.pdsUrl}/xrpc/com.atproto.repo.listRecords?repo=${resolvedDid}&collection=app.bsky.feed.post${cursor && false ? `&cursor=${cursor}` : ""}&limit=20`; 215 + const postsRes = await fetch(postsUrl); 216 + if (!postsRes.ok) throw new Error("Failed to fetch posts"); 217 + const postsData = await postsRes.json(); 218 + 219 + if (postsData.records) { 220 + await Promise.all( 221 + postsData.records.map(async (post: any) => { 222 + if (post.uri && post.value) { 223 + const postCacheKey = `record:${post.uri}`; 224 + console.log( 225 + "caching post", 226 + postCacheKey, 227 + JSON.stringify(post, null, 2), 228 + ); 229 + await set(postCacheKey, JSON.stringify(post)); 230 + } 231 + }), 232 + ); 233 + } 234 + 235 + if (!ignore) { 236 + setPosts((prev) => 237 + cursor ? [...prev, ...postsData.records] : postsData.records, 238 + ); 239 + setCursor(postsData.cursor || null); 240 + setHasMore(postsData.records.length === 20); 241 + setPostsCached(true); 242 + } 243 + } catch (e: any) { 244 + if (!ignore) setError("Failed to fetch posts: " + (e?.message || e)); 245 + } finally { 246 + if (!ignore) setPostsLoading(false); 247 + } 248 + } 249 + fetchPosts(); 250 + return () => { 251 + ignore = true; 252 + }; 253 + }, [resolvedDid, cursor, get, set]); 254 + 255 + function getAvatarUrl(profile: any) { 256 + const link = profile?.value?.avatar?.ref?.["$link"]; 257 + if (!link || !resolvedDid) return null; 258 + return `https://cdn.bsky.app/img/avatar/plain/${resolvedDid}/${link}@jpeg`; 259 + } 260 + function getBannerUrl(profile: any) { 261 + const link = profile?.value?.banner?.ref?.["$link"]; 262 + if (!link || !resolvedDid) return null; 263 + return `https://cdn.bsky.app/img/banner/plain/${resolvedDid}/${link}@jpeg`; 264 + } 265 + 266 + const displayName = 267 + profile?.value?.displayName || 268 + (resolvedHandle ? `@${resolvedHandle}` : did); 269 + let handle: string; 270 + if (resolvedHandle) { 271 + handle = `@${resolvedHandle}`; 272 + } else if (did && !did.startsWith("did:")) { 273 + handle = `@${did}`; 274 + } else { 275 + handle = resolvedDid || did; 276 + } 277 + const description = profile?.value?.description || ""; 278 + 279 + if (!did) return <div>Invalid profile</div>; 280 + if (loading) return <div>Resolving handle...</div>; 281 + if (error) return <div style={{ color: "red" }}>{error}</div>; 282 + if (!resolvedDid) return <div>Invalid profile</div>; 283 + 284 + return ( 285 + <> 286 + <div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700"> 287 + <Link 288 + to=".." 289 + className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 290 + onClick={(e) => { 291 + e.preventDefault(); 292 + window.history.length > 1 293 + ? window.history.back() 294 + : window.location.assign("/"); 295 + }} 296 + aria-label="Go back" 297 + > 298 + 299 + </Link> 300 + <span className="text-xl font-bold ml-2">Profile</span> 301 + </div> 302 + 303 + {/* Profile Header */} 304 + <div 305 + style={{ 306 + width: "100%", 307 + maxWidth: 600, 308 + margin: "0 auto", 309 + boxShadow: "0 2px 12px #0002", 310 + padding: 0, 311 + color: "#eee", 312 + fontFamily: "system-ui, sans-serif", 313 + // marginTop: 20, 314 + //background: '#181a20', 315 + borderRadius: 16, 316 + overflow: "hidden", 317 + position: "relative", 318 + }} 319 + className="bg-gray-200 dark:bg-gray-900" 320 + > 321 + {/* Banner */} 322 + <div 323 + style={{ 324 + width: "100%", 325 + height: 160, 326 + background: `#222 url(${getBannerUrl(profile)}) center/cover no-repeat`, 327 + position: "relative", 328 + }} 329 + /> 330 + {/* Avatar (PFP) */} 331 + <div 332 + style={{ 333 + position: "absolute", 334 + left: "50%", 335 + top: 120, 336 + transform: "translateX(-50%)", 337 + zIndex: 2, 338 + borderRadius: "50%", 339 + border: "4px solid #181a20", 340 + boxShadow: "0 2px 8px #0006", 341 + background: "#222", 342 + }} 343 + > 344 + <img 345 + src={getAvatarUrl(profile) || "/favicon.png"} 346 + alt="avatar" 347 + style={{ 348 + width: 112, 349 + height: 112, 350 + borderRadius: "50%", 351 + objectFit: "cover", 352 + display: "block", 353 + }} 354 + /> 355 + </div> 356 + {/* Info Card */} 357 + <div 358 + style={{ 359 + marginTop: 72, 360 + padding: "0 24px 24px 24px", 361 + textAlign: "center", 362 + }} 363 + > 364 + <div style={{ fontWeight: 700, fontSize: 24, marginBottom: 4 }}> 365 + {displayName} 366 + </div> 367 + <div style={{ color: "#aaa", fontSize: 16, marginBottom: 12 }}> 368 + {handle} 369 + </div> 370 + {description && ( 371 + <div 372 + style={{ 373 + fontSize: 16, 374 + lineHeight: 1.5, 375 + color: "#ddd", 376 + marginBottom: 20, 377 + }} 378 + > 379 + {description} 380 + </div> 381 + )} 382 + {!profile && !error && ( 383 + <div style={{ color: "#888", padding: 16 }}>Loading profile...</div> 384 + )} 385 + </div> 386 + </div> 387 + 388 + {/* Posts */} 389 + <div style={{ maxWidth: 600, margin: "0px auto 0", padding: 0 }}> 390 + <div 391 + className="text-gray-500 dark:text-gray-400 text-sm font-bold" 392 + style={{ 393 + fontSize: 18, 394 + margin: "12px 16px 12px 16px", 395 + fontWeight: 600, 396 + }} 397 + > 398 + Posts 399 + </div> 400 + <div style={{ display: "flex", flexDirection: "column", gap: 0 }}> 401 + {postsCached && 402 + posts.map((post) => { 403 + return ( 404 + <UniversalPostRendererATURILoader 405 + key={post.uri} 406 + atUri={post.uri} 407 + /> 408 + ); 409 + })} 410 + </div> 411 + {postsLoading && ( 412 + <div style={{ color: "#888", padding: 16, textAlign: "center" }}> 413 + Loading posts... 414 + </div> 415 + )} 416 + {hasMore && !postsLoading && ( 417 + <button 418 + onClick={() => setCursor(cursor)} 419 + style={{ 420 + width: "100%", 421 + padding: 12, 422 + background: "#222", 423 + color: "#eee", 424 + border: "none", 425 + borderRadius: 8, 426 + cursor: "pointer", 427 + fontSize: 16, 428 + marginTop: 16, 429 + }} 430 + > 431 + Load More Posts 432 + </button> 433 + )} 434 + {posts.length === 0 && !postsLoading && !error && ( 435 + <div style={{ color: "#888", padding: 16, textAlign: "center" }}> 436 + No posts found 437 + </div> 438 + )} 439 + </div> 440 + </> 441 + ); 442 + }
+165
src/routes/profile.$did/post.$rkey.tsx
··· 1 + import { createFileRoute, Link } from "@tanstack/react-router"; 2 + import React from "react"; 3 + import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; 4 + import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 5 + 6 + const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 7 + 8 + export const Route = createFileRoute("/profile/$did/post/$rkey")({ 9 + component: RouterWrapper, 10 + }); 11 + 12 + function RouterWrapper() { 13 + const { did, rkey } = Route.useParams(); 14 + 15 + return ( 16 + <ProfilePostComponent 17 + key={`/profile/${did}/post/${rkey}`} 18 + did={did} 19 + rkey={rkey} 20 + /> 21 + ); 22 + } 23 + 24 + function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) { 25 + const { get, set } = usePersistentStore(); 26 + const [resolvedDid, setResolvedDid] = React.useState<string | null>(null); 27 + const [loading, setLoading] = React.useState(false); 28 + const [error, setError] = React.useState<string | null>(null); 29 + const [replies, setReplies] = React.useState<any[]>([]); 30 + 31 + React.useEffect(() => { 32 + let ignore = false; 33 + async function resolveDidIfNeeded() { 34 + if (!did) { 35 + setResolvedDid(null); 36 + return; 37 + } 38 + if (did.startsWith("did:")) { 39 + setResolvedDid(did); 40 + return; 41 + } 42 + setLoading(true); 43 + setError(null); 44 + const cacheKey = `handleDid:${did}`; 45 + const now = Date.now(); 46 + const cached = await get(cacheKey); // <-- await here 47 + if ( 48 + cached && 49 + cached.value && 50 + cached.time && 51 + now - cached.time < HANDLE_DID_CACHE_TIMEOUT 52 + ) { 53 + try { 54 + const data = JSON.parse(cached.value); 55 + if (!ignore) setResolvedDid(data.did); 56 + setLoading(false); 57 + return; 58 + } catch {} 59 + } 60 + try { 61 + const url = `https://free-fly-24.deno.dev/?handle=${encodeURIComponent(did)}`; 62 + const res = await fetch(url); 63 + if (!res.ok) throw new Error("Failed to resolve handle"); 64 + const data = await res.json(); 65 + await set(cacheKey, JSON.stringify(data)); // <-- await here 66 + if (!ignore) setResolvedDid(data.did); 67 + } catch (e: any) { 68 + if (!ignore) setError("Failed to resolve handle: " + (e?.message || e)); 69 + } finally { 70 + setLoading(false); 71 + } 72 + } 73 + resolveDidIfNeeded(); 74 + return () => { 75 + ignore = true; 76 + }; 77 + }, [did, get, set]); 78 + 79 + const atUri = 80 + resolvedDid && rkey 81 + ? `at://${decodeURIComponent(resolvedDid)}/app.bsky.feed.post/${rkey}` 82 + : ""; 83 + 84 + const handleConstellation = React.useCallback((data: any) => {}, []); 85 + 86 + React.useEffect(() => { 87 + if (!atUri) return; 88 + let ignore = false; 89 + async function fetchReplies() { 90 + try { 91 + const url = `https://constellation.microcosm.blue/links?target=${encodeURIComponent(atUri)}&collection=app.bsky.feed.post&path=.reply.parent.uri`; 92 + const res = await fetch(url); 93 + if (!res.ok) throw new Error("Failed to fetch replies"); 94 + const data = await res.json(); 95 + if (!ignore && data.linking_records) { 96 + setReplies(data.linking_records.slice(0, 50)); 97 + } 98 + } catch (e) { 99 + if (!ignore) setReplies([]); 100 + } 101 + } 102 + fetchReplies(); 103 + return () => { 104 + ignore = true; 105 + }; 106 + }, [atUri]); 107 + 108 + if (!did || !rkey) return <div>Invalid post URI</div>; 109 + if (loading) return <div>Resolving handle...</div>; 110 + if (error) return <div style={{ color: "red" }}>{error}</div>; 111 + if (!atUri) return <div>Invalid post URI</div>; 112 + 113 + console.log("atUri", atUri); 114 + 115 + return ( 116 + <> 117 + <div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700"> 118 + <Link 119 + to=".." 120 + className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 121 + onClick={(e) => { 122 + e.preventDefault(); 123 + window.history.length > 1 124 + ? window.history.back() 125 + : window.location.assign("/"); 126 + }} 127 + aria-label="Go back" 128 + > 129 + 130 + </Link> 131 + <span className="text-xl font-bold ml-2">Post</span> 132 + </div> 133 + <UniversalPostRendererATURILoader 134 + atUri={atUri} 135 + onConstellation={handleConstellation} 136 + detailed={true} 137 + /> 138 + {replies.length > 0 && ( 139 + <div style={{ maxWidth: 600, margin: "0px auto 0", padding: 0 }}> 140 + <div 141 + className="text-gray-500 dark:text-gray-400 text-sm font-bold" 142 + style={{ 143 + fontSize: 18, 144 + margin: "12px 16px 12px 16px", 145 + fontWeight: 600, 146 + }} 147 + > 148 + Replies 149 + </div> 150 + <div style={{ display: "flex", flexDirection: "column", gap: 0 }}> 151 + {replies.map((reply, i) => { 152 + const replyAtUri = `at://${reply.did}/app.bsky.feed.post/${reply.rkey}`; 153 + return ( 154 + <UniversalPostRendererATURILoader 155 + key={replyAtUri} 156 + atUri={replyAtUri} 157 + /> 158 + ); 159 + })} 160 + </div> 161 + </div> 162 + )} 163 + </> 164 + ); 165 + }
+9
src/routes/search.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + 3 + export const Route = createFileRoute("/search")({ 4 + component: Search, 5 + }); 6 + 7 + export function Search() { 8 + return <div className="p-6">Search page (coming soon)</div>; 9 + }
+9
src/routes/settings.tsx
··· 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + 3 + export const Route = createFileRoute("/settings")({ 4 + component: Settings, 5 + }); 6 + 7 + export function Settings() { 8 + return <div className="p-6">Settings page (coming soon)</div>; 9 + }
+63
src/styles/app.css
··· 1 + @import "tailwindcss"; 2 + 3 + /* @theme { 4 + --color-gray-50: oklch(0.984 0.005 220.000); 5 + --color-gray-100: oklch(0.968 0.010 220.000); 6 + --color-gray-200: oklch(0.929 0.020 222.000); 7 + --color-gray-300: oklch(0.869 0.035 220.000); 8 + --color-gray-400: oklch(0.704 0.060 218.000); 9 + --color-gray-500: oklch(0.554 0.070 218.000); 10 + --color-gray-600: oklch(0.446 0.065 218.000); 11 + --color-gray-700: oklch(0.372 0.060 218.000); 12 + --color-gray-800: oklch(0.279 0.055 220.000); 13 + --color-gray-900: oklch(0.208 0.050 222.000); 14 + --color-gray-950: oklch(0.129 0.050 222.000); 15 + } */ 16 + 17 + @theme { 18 + --color-gray-50: oklch(0.984 0.012 28); 19 + --color-gray-100: oklch(0.968 0.017 28); 20 + --color-gray-200: oklch(0.929 0.025 28); 21 + --color-gray-300: oklch(0.869 0.035 28); 22 + --color-gray-400: oklch(0.704 0.05 28); 23 + --color-gray-500: oklch(0.554 0.06 28); 24 + --color-gray-600: oklch(0.446 0.058 28); 25 + --color-gray-700: oklch(0.372 0.058 28); 26 + --color-gray-800: oklch(0.279 0.055 28); 27 + --color-gray-900: oklch(0.208 0.055 28); 28 + --color-gray-950: oklch(0.129 0.055 28); 29 + } 30 + 31 + @layer base { 32 + html { 33 + color-scheme: light dark; 34 + } 35 + 36 + * { 37 + @apply border-gray-200 dark:border-gray-800; 38 + } 39 + 40 + html, 41 + body { 42 + @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; 43 + } 44 + 45 + .using-mouse * { 46 + outline: none !important; 47 + } 48 + } 49 + 50 + @media (width >= 64rem /* 1024px */) { 51 + html, 52 + body { 53 + scrollbar-gutter: stable both-edges !important; 54 + } 55 + } 56 + .scroll-thin { 57 + scrollbar-width: thin; 58 + /*scrollbar-gutter: stable both-edges !important;*/ 59 + } 60 + 61 + .scroll-none { 62 + scrollbar-width: none; 63 + }
+33
src/utils/seo.ts
··· 1 + export const seo = ({ 2 + title, 3 + description, 4 + keywords, 5 + image, 6 + }: { 7 + title: string; 8 + description?: string; 9 + image?: string; 10 + keywords?: string; 11 + }) => { 12 + const tags = [ 13 + { title }, 14 + { name: "description", content: description }, 15 + { name: "keywords", content: keywords }, 16 + { name: "twitter:title", content: title }, 17 + { name: "twitter:description", content: description }, 18 + { name: "twitter:creator", content: "@tannerlinsley" }, 19 + { name: "twitter:site", content: "@tannerlinsley" }, 20 + { name: "og:type", content: "website" }, 21 + { name: "og:title", content: title }, 22 + { name: "og:description", content: description }, 23 + ...(image 24 + ? [ 25 + { name: "twitter:image", content: image }, 26 + { name: "twitter:card", content: "summary_large_image" }, 27 + { name: "og:image", content: image }, 28 + ] 29 + : []), 30 + ]; 31 + 32 + return tags; 33 + };
+5
src/utils/users.tsx
··· 1 + export type User = { 2 + id: number; 3 + name: string; 4 + email: string; 5 + };
+29
tsconfig.json
··· 1 + { 2 + "include": ["**/*.ts", "**/*.tsx"], 3 + "compilerOptions": { 4 + "target": "ES2022", 5 + "jsx": "react-jsx", 6 + "module": "ESNext", 7 + "lib": ["ES2022", "DOM", "DOM.Iterable"], 8 + "types": ["vite/client"], 9 + 10 + /* Bundler mode */ 11 + "moduleResolution": "bundler", 12 + "allowImportingTsExtensions": true, 13 + "verbatimModuleSyntax": true, 14 + "noEmit": true, 15 + 16 + /* Linting */ 17 + "skipLibCheck": true, 18 + "strict": true, 19 + "noUnusedLocals": true, 20 + "noUnusedParameters": true, 21 + "noFallthroughCasesInSwitch": true, 22 + "noUncheckedSideEffectImports": true, 23 + "baseUrl": ".", 24 + "paths": { 25 + "~/*": ["./src/*"], 26 + "@/*": ["./src/*"] 27 + } 28 + } 29 + }
+25
vite.config.ts
··· 1 + import { defineConfig } from "vite"; 2 + import viteReact from "@vitejs/plugin-react"; 3 + import tailwindcss from "@tailwindcss/vite"; 4 + 5 + import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; 6 + import { resolve } from "node:path"; 7 + 8 + // https://vitejs.dev/config/ 9 + export default defineConfig({ 10 + plugins: [ 11 + TanStackRouterVite({ autoCodeSplitting: true }), 12 + viteReact(), 13 + tailwindcss(), 14 + ], 15 + // test: { 16 + // globals: true, 17 + // environment: 'jsdom', 18 + // }, 19 + resolve: { 20 + alias: { 21 + "@": resolve(__dirname, "./src"), 22 + "~": resolve(__dirname, "./src"), 23 + }, 24 + }, 25 + });