A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.

Compare changes

Choose any two refs to compare.

+3 -1
.gitignore
··· 6 6 yarn-error.log* 7 7 pnpm-debug.log* 8 8 lerna-debug.log* 9 - 9 + demo 10 10 node_modules 11 11 dist 12 12 lib-dist ··· 24 24 *.njsproj 25 25 *.sln 26 26 *.sw? 27 + 28 + *.tsbuildinfo
+22
.npmignore
··· 1 + # Demo and development files 2 + demo/ 3 + src/ 4 + index.html 5 + 6 + # Build configuration 7 + vite.config.ts 8 + vite.config.d.ts 9 + tsconfig.app.json 10 + tsconfig.node.json 11 + eslint.config.js 12 + tsconfig.lib.tsbuildinfo 13 + 14 + # Dependencies 15 + node_modules/ 16 + package-lock.json 17 + bun.lock 18 + 19 + CLAUDE.md 20 + 21 + # Output directory 22 + lib/
+42
.tangled/workflows/upload-demo-to-wisp.yml
··· 1 + when: 2 + - event: ['push'] 3 + branch: ['main'] 4 + - event: ['manual'] 5 + engine: 'nixery' 6 + clone: 7 + skip: false 8 + depth: 1 9 + submodules: false 10 + dependencies: 11 + nixpkgs: 12 + - nodejs 13 + - coreutils 14 + - curl 15 + github:NixOS/nixpkgs/nixpkgs-unstable: 16 + - bun 17 + 18 + environment: 19 + SITE_PATH: 'demo' 20 + SITE_NAME: 'atproto-ui' 21 + WISP_HANDLE: 'ana.pds.nkp.pet' 22 + 23 + steps: 24 + - name: build demo 25 + command: | 26 + export PATH="$HOME/.nix-profile/bin:$PATH" 27 + 28 + # regenerate lockfile, https://github.com/npm/cli/pull/8184 makes rolldown not install 29 + rm package-lock.json bun.lock 30 + bun install 31 + 32 + # run directly with bun because of shebang issues in nix 33 + BUILD_TARGET=demo bun node_modules/.bin/vite build 34 + - name: upload to wisp 35 + command: | 36 + curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli 37 + chmod +x wisp-cli 38 + ./wisp-cli \ 39 + "$WISP_HANDLE" \ 40 + --path "$SITE_PATH" \ 41 + --site "$SITE_NAME" \ 42 + --password "$WISP_APP_PASSWORD"
+701
CLAUDE.md
··· 1 + # AtReact Hooks Deep Dive 2 + 3 + ## Overview 4 + The AtReact hooks system provides a robust, cache-optimized layer for fetching AT Protocol data. All hooks follow React best practices with proper cleanup, cancellation, and stable references. 5 + 6 + --- 7 + 8 + ## Core Architecture Principles 9 + 10 + ### 1. **Three-Tier Caching Strategy** 11 + All data flows through three cache layers: 12 + - **DidCache** - DID documents, handle mappings, PDS endpoints 13 + - **BlobCache** - Media/image blobs with reference counting 14 + - **RecordCache** - AT Protocol records with deduplication 15 + 16 + ### 2. **Concurrent Request Deduplication** 17 + When multiple components request the same data, only one network request is made. Uses reference counting to manage in-flight requests. 18 + 19 + ### 3. **Stable Reference Pattern** 20 + Caches use memoized snapshots to prevent unnecessary re-renders: 21 + ```typescript 22 + // Only creates new snapshot if data actually changed 23 + if (existing && existing.did === did && existing.handle === handle) { 24 + return toSnapshot(existing); // Reuse existing 25 + } 26 + ``` 27 + 28 + ### 4. **Three-Tier Fallback for Bluesky** 29 + For `app.bsky.*` collections: 30 + 1. Try Bluesky appview API (fastest, public) 31 + 2. Fall back to Slingshot (microcosm service) 32 + 3. Finally query PDS directly 33 + 34 + --- 35 + 36 + ## Hook Catalog 37 + 38 + ## 1. `useDidResolution` 39 + **Purpose:** Resolves handles to DIDs or fetches DID documents 40 + 41 + ### Key Features: 42 + - **Bidirectional:** Works with handles OR DIDs 43 + - **Smart Caching:** Only fetches if not in cache 44 + - **Dual Resolution Paths:** 45 + - Handle โ†’ DID: Uses Slingshot first, then appview 46 + - DID โ†’ Document: Fetches full DID document for handle extraction 47 + 48 + ### State Flow: 49 + ```typescript 50 + Input: "alice.bsky.social" or "did:plc:xxx" 51 + โ†“ 52 + Check didCache 53 + โ†“ 54 + If handle: ensureHandle(resolver, handle) โ†’ DID 55 + If DID: ensureDidDoc(resolver, did) โ†’ DID doc + handle from alsoKnownAs 56 + โ†“ 57 + Return: { did, handle, loading, error } 58 + ``` 59 + 60 + ### Critical Implementation Details: 61 + - **Normalizes input** to lowercase for handles 62 + - **Memoizes input** to prevent effect re-runs 63 + - **Stabilizes error references** - only updates if message changes 64 + - **Cleanup:** Cancellation token prevents stale updates 65 + 66 + --- 67 + 68 + ## 2. `usePdsEndpoint` 69 + **Purpose:** Discovers the PDS endpoint for a DID 70 + 71 + ### Key Features: 72 + - **Depends on DID resolution** (implicit dependency) 73 + - **Extracts from DID document** if already cached 74 + - **Lazy fetching** - only when endpoint not in cache 75 + 76 + ### State Flow: 77 + ```typescript 78 + Input: DID 79 + โ†“ 80 + Check didCache.getByDid(did).pdsEndpoint 81 + โ†“ 82 + If missing: ensurePdsEndpoint(resolver, did) 83 + โ”œโ”€ Tries to get from existing DID doc 84 + โ””โ”€ Falls back to resolver.pdsEndpointForDid() 85 + โ†“ 86 + Return: { endpoint, loading, error } 87 + ``` 88 + 89 + ### Service Discovery: 90 + Looks for `AtprotoPersonalDataServer` service in DID document: 91 + ```json 92 + { 93 + "service": [{ 94 + "type": "AtprotoPersonalDataServer", 95 + "serviceEndpoint": "https://pds.example.com" 96 + }] 97 + } 98 + ``` 99 + 100 + --- 101 + 102 + ## 3. `useAtProtoRecord` 103 + **Purpose:** Fetches a single AT Protocol record with smart routing 104 + 105 + ### Key Features: 106 + - **Collection-aware routing:** Bluesky vs other protocols 107 + - **RecordCache deduplication:** Multiple components = one fetch 108 + - **Cleanup with reference counting** 109 + 110 + ### State Flow: 111 + ```typescript 112 + Input: { did, collection, rkey } 113 + โ†“ 114 + If collection.startsWith("app.bsky."): 115 + โ””โ”€ useBlueskyAppview() โ†’ Three-tier fallback 116 + Else: 117 + โ”œโ”€ useDidResolution(did) 118 + โ”œโ”€ usePdsEndpoint(resolved.did) 119 + โ””โ”€ recordCache.ensure() โ†’ Fetch from PDS 120 + โ†“ 121 + Return: { record, loading, error } 122 + ``` 123 + 124 + ### RecordCache Deduplication: 125 + ```typescript 126 + // First component calling this 127 + const { promise, release } = recordCache.ensure(did, collection, rkey, loader) 128 + // refCount = 1 129 + 130 + // Second component calling same record 131 + const { promise, release } = recordCache.ensure(...) // Same promise! 132 + // refCount = 2 133 + 134 + // On cleanup, both call release() 135 + // Only aborts when refCount reaches 0 136 + ``` 137 + 138 + --- 139 + 140 + ## 4. `useBlueskyAppview` 141 + **Purpose:** Fetches Bluesky records with appview optimization 142 + 143 + ### Key Features: 144 + - **Collection-aware endpoints:** 145 + - `app.bsky.actor.profile` โ†’ `app.bsky.actor.getProfile` 146 + - `app.bsky.feed.post` โ†’ `app.bsky.feed.getPostThread` 147 + - **CDN URL extraction:** Parses CDN URLs to extract CIDs 148 + - **Atomic state updates:** Uses reducer for complex state 149 + 150 + ### Three-Tier Fallback with Source Tracking: 151 + ```typescript 152 + async function fetchWithFallback() { 153 + // Tier 1: Appview (if endpoint mapped) 154 + try { 155 + const result = await fetchFromAppview(did, collection, rkey); 156 + return { record: result, source: "appview" }; 157 + } catch {} 158 + 159 + // Tier 2: Slingshot 160 + try { 161 + const result = await fetchFromSlingshot(did, collection, rkey); 162 + return { record: result, source: "slingshot" }; 163 + } catch {} 164 + 165 + // Tier 3: PDS 166 + try { 167 + const result = await fetchFromPds(did, collection, rkey); 168 + return { record: result, source: "pds" }; 169 + } catch {} 170 + 171 + // All tiers failed - provide helpful error for banned Bluesky accounts 172 + if (pdsEndpoint.includes('.bsky.network')) { 173 + throw new Error('Record unavailable. The Bluesky PDS may be unreachable or the account may be banned.'); 174 + } 175 + 176 + throw new Error('Failed to fetch record from all sources'); 177 + } 178 + ``` 179 + 180 + The `source` field in the result accurately indicates which tier successfully fetched the data, enabling debugging and analytics. 181 + 182 + ### CDN URL Handling: 183 + Appview returns CDN URLs like: 184 + ``` 185 + https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg 186 + ``` 187 + 188 + Hook extracts CID (`bafkreixxx`) and creates standard Blob object: 189 + ```typescript 190 + { 191 + $type: "blob", 192 + ref: { $link: "bafkreixxx" }, 193 + mimeType: "image/jpeg", 194 + size: 0, 195 + cdnUrl: "https://cdn.bsky.app/..." // Preserved for fast rendering 196 + } 197 + ``` 198 + 199 + ### Reducer Pattern: 200 + ```typescript 201 + type Action = 202 + | { type: "SET_LOADING"; loading: boolean } 203 + | { type: "SET_SUCCESS"; record: T; source: "appview" | "slingshot" | "pds" } 204 + | { type: "SET_ERROR"; error: Error } 205 + | { type: "RESET" }; 206 + 207 + // Atomic state updates, no race conditions 208 + dispatch({ type: "SET_SUCCESS", record, source }); 209 + ``` 210 + 211 + --- 212 + 213 + ## 5. `useLatestRecord` 214 + **Purpose:** Fetches the most recent record from a collection 215 + 216 + ### Key Features: 217 + - **Timestamp validation:** Skips records before 2023 (pre-ATProto) 218 + - **PDS-only:** Slingshot doesn't support `listRecords` 219 + - **Smart fetching:** Gets 3 records to handle invalid timestamps 220 + 221 + ### State Flow: 222 + ```typescript 223 + Input: { did, collection } 224 + โ†“ 225 + useDidResolution(did) 226 + usePdsEndpoint(did) 227 + โ†“ 228 + callListRecords(endpoint, did, collection, limit: 3) 229 + โ†“ 230 + Filter: isValidTimestamp(record) โ†’ year >= 2023 231 + โ†“ 232 + Return first valid record: { record, rkey, loading, error, empty } 233 + ``` 234 + 235 + ### Timestamp Validation: 236 + ```typescript 237 + function isValidTimestamp(record: unknown): boolean { 238 + const timestamp = record.createdAt || record.indexedAt; 239 + if (!timestamp) return true; // No timestamp, assume valid 240 + 241 + const date = new Date(timestamp); 242 + return date.getFullYear() >= 2023; // ATProto created in 2023 243 + } 244 + ``` 245 + 246 + --- 247 + 248 + ## 6. `usePaginatedRecords` 249 + **Purpose:** Cursor-based pagination with prefetching 250 + 251 + ### Key Features: 252 + - **Dual fetching modes:** 253 + - Author feed (appview) - for Bluesky posts with filters 254 + - Direct PDS - for all other collections 255 + - **Smart prefetching:** Loads next page in background 256 + - **Invalid timestamp filtering:** Same as `useLatestRecord` 257 + - **Request sequencing:** Prevents race conditions with `requestSeq` 258 + 259 + ### State Management: 260 + ```typescript 261 + // Pages stored as array 262 + pages: [ 263 + { records: [...], cursor: "abc" }, // page 0 264 + { records: [...], cursor: "def" }, // page 1 265 + { records: [...], cursor: undefined } // page 2 (last) 266 + ] 267 + pageIndex: 1 // Currently viewing page 1 268 + ``` 269 + 270 + ### Prefetch Logic: 271 + ```typescript 272 + useEffect(() => { 273 + const cursor = pages[pageIndex]?.cursor; 274 + if (!cursor || pages[pageIndex + 1]) return; // No cursor or already loaded 275 + 276 + // Prefetch next page in background 277 + fetchPage(identity, cursor, pageIndex + 1, "prefetch"); 278 + }, [pageIndex, pages]); 279 + ``` 280 + 281 + ### Author Feed vs PDS: 282 + ```typescript 283 + if (preferAuthorFeed && collection === "app.bsky.feed.post") { 284 + // Use app.bsky.feed.getAuthorFeed 285 + const res = await callAppviewRpc("app.bsky.feed.getAuthorFeed", { 286 + actor: handle || did, 287 + filter: "posts_with_media", // Optional filter 288 + includePins: true 289 + }); 290 + } else { 291 + // Use com.atproto.repo.listRecords 292 + const res = await callListRecords(pdsEndpoint, did, collection, limit); 293 + } 294 + ``` 295 + 296 + ### Race Condition Prevention: 297 + ```typescript 298 + const requestSeq = useRef(0); 299 + 300 + // On identity change 301 + resetState(); 302 + requestSeq.current += 1; // Invalidate in-flight requests 303 + 304 + // In fetch callback 305 + const token = requestSeq.current; 306 + // ... do async work ... 307 + if (token !== requestSeq.current) return; // Stale request, abort 308 + ``` 309 + 310 + --- 311 + 312 + ## 7. `useBlob` 313 + **Purpose:** Fetches and caches media blobs with object URL management 314 + 315 + ### Key Features: 316 + - **Automatic cleanup:** Revokes object URLs on unmount 317 + - **BlobCache deduplication:** Same blob = one fetch 318 + - **Reference counting:** Safe concurrent access 319 + 320 + ### State Flow: 321 + ```typescript 322 + Input: { did, cid } 323 + โ†“ 324 + useDidResolution(did) 325 + usePdsEndpoint(did) 326 + โ†“ 327 + Check blobCache.get(did, cid) 328 + โ†“ 329 + If missing: blobCache.ensure() โ†’ Fetch from PDS 330 + โ”œโ”€ GET /xrpc/com.atproto.sync.getBlob?did={did}&cid={cid} 331 + โ””โ”€ Store in cache 332 + โ†“ 333 + Create object URL: URL.createObjectURL(blob) 334 + โ†“ 335 + Return: { url, loading, error } 336 + โ†“ 337 + Cleanup: URL.revokeObjectURL(url) 338 + ``` 339 + 340 + ### Object URL Management: 341 + ```typescript 342 + const objectUrlRef = useRef<string>(); 343 + 344 + // On successful fetch 345 + const nextUrl = URL.createObjectURL(blob); 346 + const prevUrl = objectUrlRef.current; 347 + objectUrlRef.current = nextUrl; 348 + if (prevUrl) URL.revokeObjectURL(prevUrl); // Clean up old URL 349 + 350 + // On unmount 351 + useEffect(() => () => { 352 + if (objectUrlRef.current) { 353 + URL.revokeObjectURL(objectUrlRef.current); 354 + } 355 + }, []); 356 + ``` 357 + 358 + --- 359 + 360 + ## 8. `useBlueskyProfile` 361 + **Purpose:** Wrapper around `useBlueskyAppview` for profile records 362 + 363 + ### Key Features: 364 + - **Simplified interface:** Just pass DID 365 + - **Type conversion:** Converts ProfileRecord to BlueskyProfileData 366 + - **CID extraction:** Extracts avatar/banner CIDs from blobs 367 + 368 + ### Implementation: 369 + ```typescript 370 + export function useBlueskyProfile(did: string | undefined) { 371 + const { record, loading, error } = useBlueskyAppview<ProfileRecord>({ 372 + did, 373 + collection: "app.bsky.actor.profile", 374 + rkey: "self", 375 + }); 376 + 377 + const data = record ? { 378 + did: did || "", 379 + handle: "", // Populated by caller 380 + displayName: record.displayName, 381 + description: record.description, 382 + avatar: extractCidFromBlob(record.avatar), 383 + banner: extractCidFromBlob(record.banner), 384 + createdAt: record.createdAt, 385 + } : undefined; 386 + 387 + return { data, loading, error }; 388 + } 389 + ``` 390 + 391 + --- 392 + 393 + ## 9. `useBacklinks` 394 + **Purpose:** Fetches backlinks from Microcosm Constellation API 395 + 396 + ### Key Features: 397 + - **Specialized use case:** Tangled stars, etc. 398 + - **Abort controller:** Cancels in-flight requests 399 + - **Refetch support:** Manual refresh capability 400 + 401 + ### State Flow: 402 + ```typescript 403 + Input: { subject: "at://did:plc:xxx/sh.tangled.repo/yyy", source: "sh.tangled.feed.star:subject" } 404 + โ†“ 405 + GET https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks 406 + ?subject={subject}&source={source}&limit={limit} 407 + โ†“ 408 + Return: { backlinks: [...], total, loading, error, refetch } 409 + ``` 410 + 411 + --- 412 + 413 + ## 10. `useRepoLanguages` 414 + **Purpose:** Fetches language statistics from Tangled knot server 415 + 416 + ### Key Features: 417 + - **Branch fallback:** Tries "main", then "master" 418 + - **Knot server query:** For repository analysis 419 + 420 + ### State Flow: 421 + ```typescript 422 + Input: { knot: "knot.gaze.systems", did, repoName, branch } 423 + โ†“ 424 + GET https://{knot}/xrpc/sh.tangled.repo.languages 425 + ?repo={did}/{repoName}&ref={branch} 426 + โ†“ 427 + If 404: Try fallback branch 428 + โ†“ 429 + Return: { data: { languages: {...} }, loading, error } 430 + ``` 431 + 432 + --- 433 + 434 + ## Cache Implementation Deep Dive 435 + 436 + ### DidCache 437 + **Purpose:** Cache DID documents, handle mappings, PDS endpoints 438 + 439 + ```typescript 440 + class DidCache { 441 + private byHandle = new Map<string, DidCacheEntry>(); 442 + private byDid = new Map<string, DidCacheEntry>(); 443 + private handlePromises = new Map<string, Promise<...>>(); 444 + private docPromises = new Map<string, Promise<...>>(); 445 + private pdsPromises = new Map<string, Promise<...>>(); 446 + 447 + // Memoized snapshots prevent re-renders 448 + private toSnapshot(entry): DidCacheSnapshot { 449 + if (entry.snapshot) return entry.snapshot; // Reuse 450 + entry.snapshot = { did, handle, doc, pdsEndpoint }; 451 + return entry.snapshot; 452 + } 453 + } 454 + ``` 455 + 456 + **Key methods:** 457 + - `getByHandle(handle)` - Instant cache lookup 458 + - `getByDid(did)` - Instant cache lookup 459 + - `ensureHandle(resolver, handle)` - Deduplicated resolution 460 + - `ensureDidDoc(resolver, did)` - Deduplicated doc fetch 461 + - `ensurePdsEndpoint(resolver, did)` - Deduplicated PDS discovery 462 + 463 + **Snapshot stability:** 464 + ```typescript 465 + memoize(entry) { 466 + const existing = this.byDid.get(did); 467 + 468 + // Data unchanged? Reuse snapshot (same reference) 469 + if (existing && existing.did === did && 470 + existing.handle === handle && ...) { 471 + return toSnapshot(existing); // Prevents re-render! 472 + } 473 + 474 + // Data changed, create new entry 475 + const merged = { did, handle, doc, pdsEndpoint, snapshot: undefined }; 476 + this.byDid.set(did, merged); 477 + return toSnapshot(merged); 478 + } 479 + ``` 480 + 481 + ### BlobCache 482 + **Purpose:** Cache media blobs with reference counting 483 + 484 + ```typescript 485 + class BlobCache { 486 + private store = new Map<string, BlobCacheEntry>(); 487 + private inFlight = new Map<string, InFlightBlobEntry>(); 488 + 489 + ensure(did, cid, loader) { 490 + // Already cached? 491 + const cached = this.get(did, cid); 492 + if (cached) return { promise: Promise.resolve(cached), release: noop }; 493 + 494 + // In-flight request? 495 + const existing = this.inFlight.get(key); 496 + if (existing) { 497 + existing.refCount++; // Multiple consumers 498 + return { promise: existing.promise, release: () => this.release(key) }; 499 + } 500 + 501 + // New request 502 + const { promise, abort } = loader(); 503 + this.inFlight.set(key, { promise, abort, refCount: 1 }); 504 + return { promise, release: () => this.release(key) }; 505 + } 506 + 507 + private release(key) { 508 + const entry = this.inFlight.get(key); 509 + entry.refCount--; 510 + if (entry.refCount <= 0) { 511 + this.inFlight.delete(key); 512 + entry.abort(); // Cancel fetch 513 + } 514 + } 515 + } 516 + ``` 517 + 518 + ### RecordCache 519 + **Purpose:** Cache AT Protocol records with deduplication 520 + 521 + Identical structure to BlobCache but for record data. 522 + 523 + --- 524 + 525 + ## Common Patterns 526 + 527 + ### 1. Cancellation Pattern 528 + ```typescript 529 + useEffect(() => { 530 + let cancelled = false; 531 + 532 + const assignState = (next) => { 533 + if (cancelled) return; // Don't update unmounted component 534 + setState(prev => ({ ...prev, ...next })); 535 + }; 536 + 537 + // ... async work ... 538 + 539 + return () => { 540 + cancelled = true; // Mark as cancelled 541 + release?.(); // Decrement refCount 542 + }; 543 + }, [deps]); 544 + ``` 545 + 546 + ### 2. Error Stabilization Pattern 547 + ```typescript 548 + setError(prevError => 549 + prevError?.message === newError.message 550 + ? prevError // Reuse same reference 551 + : newError // New error 552 + ); 553 + ``` 554 + 555 + ### 3. Identity Tracking Pattern 556 + ```typescript 557 + const identityRef = useRef<string>(); 558 + const identity = did && endpoint ? `${did}::${endpoint}` : undefined; 559 + 560 + useEffect(() => { 561 + if (identityRef.current !== identity) { 562 + identityRef.current = identity; 563 + resetState(); // Clear stale data 564 + } 565 + // ... 566 + }, [identity]); 567 + ``` 568 + 569 + ### 4. Dual-Mode Resolution 570 + ```typescript 571 + const isDid = input.startsWith("did:"); 572 + const normalizedHandle = !isDid ? input.toLowerCase() : undefined; 573 + 574 + // Different code paths 575 + if (isDid) { 576 + snapshot = await didCache.ensureDidDoc(resolver, input); 577 + } else { 578 + snapshot = await didCache.ensureHandle(resolver, normalizedHandle); 579 + } 580 + ``` 581 + 582 + --- 583 + 584 + ## Performance Optimizations 585 + 586 + ### 1. **Memoized Snapshots** 587 + Caches return stable references when data unchanged โ†’ prevents re-renders 588 + 589 + ### 2. **Reference Counting** 590 + Multiple components requesting same data share one fetch 591 + 592 + ### 3. **Prefetching** 593 + `usePaginatedRecords` loads next page in background 594 + 595 + ### 4. **CDN URLs** 596 + Bluesky appview returns CDN URLs โ†’ skip blob fetching for images 597 + 598 + ### 5. **Smart Routing** 599 + Bluesky collections use fast appview โ†’ non-Bluesky goes direct to PDS 600 + 601 + ### 6. **Request Deduplication** 602 + In-flight request maps prevent duplicate fetches 603 + 604 + ### 7. **Timestamp Validation** 605 + Skip invalid records early (before 2023) โ†’ fewer wasted cycles 606 + 607 + --- 608 + 609 + ## Error Handling Strategy 610 + 611 + ### 1. **Fallback Chains** 612 + Never fail on first attempt โ†’ try multiple sources 613 + 614 + ### 2. **Graceful Degradation** 615 + ```typescript 616 + // Slingshot failed? Try appview 617 + try { 618 + return await fetchFromSlingshot(); 619 + } catch (slingshotError) { 620 + try { 621 + return await fetchFromAppview(); 622 + } catch (appviewError) { 623 + // Combine errors for better debugging 624 + throw new Error(`${appviewError.message}; Slingshot: ${slingshotError.message}`); 625 + } 626 + } 627 + ``` 628 + 629 + ### 3. **Component Isolation** 630 + Errors in one component don't crash others (via error boundaries recommended) 631 + 632 + ### 4. **Abort Handling** 633 + ```typescript 634 + try { 635 + await fetch(url, { signal }); 636 + } catch (err) { 637 + if (err.name === "AbortError") return; // Expected, ignore 638 + throw err; 639 + } 640 + ``` 641 + 642 + ### 5. **Banned Bluesky Account Detection** 643 + When all three tiers fail and the PDS is a `.bsky.network` endpoint, provide a helpful error: 644 + ```typescript 645 + // All tiers failed - check if it's a banned Bluesky account 646 + if (pdsEndpoint.includes('.bsky.network')) { 647 + throw new Error( 648 + 'Record unavailable. The Bluesky PDS may be unreachable or the account may be banned.' 649 + ); 650 + } 651 + ``` 652 + 653 + This helps users understand why data is unavailable instead of showing generic fetch errors. Applies to both `useBlueskyAppview` and `useAtProtoRecord` hooks. 654 + 655 + --- 656 + 657 + ## Testing Considerations 658 + 659 + ### Key scenarios to test: 660 + 1. **Concurrent requests:** Multiple components requesting same data 661 + 2. **Race conditions:** Component unmounting mid-fetch 662 + 3. **Cache invalidation:** Identity changes during fetch 663 + 4. **Error fallbacks:** Slingshot down โ†’ appview works 664 + 5. **Timestamp filtering:** Records before 2023 skipped 665 + 6. **Reference counting:** Proper cleanup on unmount 666 + 7. **Prefetching:** Background loads don't interfere with active loads 667 + 668 + --- 669 + 670 + ## Common Gotchas 671 + 672 + ### 1. **React Rules of Hooks** 673 + All hooks called unconditionally, even if results not used: 674 + ```typescript 675 + // Always call, conditionally use results 676 + const blueskyResult = useBlueskyAppview({ 677 + did: isBlueskyCollection ? handleOrDid : undefined, // Pass undefined to skip 678 + collection: isBlueskyCollection ? collection : undefined, 679 + rkey: isBlueskyCollection ? rkey : undefined, 680 + }); 681 + ``` 682 + 683 + ### 2. **Cleanup Order Matters** 684 + ```typescript 685 + return () => { 686 + cancelled = true; // 1. Prevent state updates 687 + release?.(); // 2. Decrement refCount 688 + revokeObjectURL(...); // 3. Free resources 689 + }; 690 + ``` 691 + 692 + ### 3. **Snapshot Reuse** 693 + Don't modify cached snapshots! They're shared across components. 694 + 695 + ### 4. **CDN URL Extraction** 696 + Bluesky CDN URLs must be parsed carefully: 697 + ``` 698 + https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg 699 + ^^^^^^^^^^^^ ^^^^^^ 700 + DID CID 701 + ```
+181 -77
README.md
··· 1 1 # atproto-ui 2 2 3 - atproto-ui is a component library and set of hooks for rendering records from the AT Protocol (Bluesky, Leaflet, and friends) in React applications. It handles DID resolution, PDS endpoint discovery, and record fetching so you can focus on UI. [Live demo](https://wisp.place/s/ana.pds.nkp.pet/ATComponents/). 3 + A React component library for rendering AT Protocol records (Bluesky, Leaflet, Tangled, and more). Handles DID resolution, PDS discovery, and record fetching automatically as well as caching these so multiple components can render quickly. [Live demo](https://atproto-ui.wisp.place). 4 + 5 + This project is mostly a wrapper on the extremely amazing work [Mary](https://mary.my.id/) has done with [atcute](https://tangled.org/@mary.my.id/atcute), please support it. I have to give thanks to [phil](https://bsky.app/profile/bad-example.com) for microcosm and slingshot. Incredible services being given for free that is responsible for why the components fetch data so quickly. 4 6 5 7 ## Screenshots 6 8 ··· 9 11 10 12 ## Features 11 13 12 - - Drop-in components for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledString`, etc.). 13 - - Hooks and helpers for composing your own renderers for your own applications, (PRs welcome!) 14 - - Built on the lightweight [`@atcute/*`](https://github.com/atcute) clients. 14 + - **Drop-in components** for common record types (`BlueskyPost`, `BlueskyProfile`, `TangledRepo`, `LeafletDocument`) 15 + - **Prefetch support** - Pass data directly to skip API calls (perfect for SSR/caching) 16 + - **Caching** - Blobs, DIDs, and records are cached so components which use the same ones can render even quicker 17 + - **Customizable theming** - Override CSS variables to match your app's design 18 + - **Composable hooks** - Build custom renderers with protocol primitives 19 + - Built on lightweight [`@atcute/*`](https://tangled.org/@mary.my.id/atcute) clients 15 20 16 21 ## Installation 17 22 ··· 19 24 npm install atproto-ui 20 25 ``` 21 26 22 - ## Quick start 23 - 24 - 1. Wrap your app (once) with the `AtProtoProvider`. 25 - 2. Drop any of the ready-made components inside that provider. 26 - 3. Use the hooks to prefetch handles, blobs, or latest records when you want to control the render flow yourself. 27 + ## Quick Start 27 28 28 29 ```tsx 29 - import { AtProtoProvider, BlueskyPost } from 'atproto-ui'; 30 + import { AtProtoProvider, BlueskyPost, LeafletDocument } from "atproto-ui"; 31 + import "atproto-ui/styles.css"; 30 32 31 33 export function App() { 32 - return ( 33 - <AtProtoProvider> 34 - <BlueskyPost did="did:plc:example" rkey="3k2aexample" /> 35 - {/* you can use handles in the components as well. */} 36 - <LeafletDocument did="nekomimi.pet" rkey="3m2seagm2222c" /> 37 - </AtProtoProvider> 38 - ); 34 + return ( 35 + <AtProtoProvider> 36 + <BlueskyPost did="did:plc:example" rkey="3k2aexample" /> 37 + {/* You can use handles too */} 38 + <LeafletDocument did="nekomimi.pet" rkey="3m2seagm2222c" /> 39 + </AtProtoProvider> 40 + ); 39 41 } 40 42 ``` 41 43 42 - ### Available building blocks 44 + **Note:** The library automatically imports the CSS when you import any component. If you prefer to import it explicitly (e.g., for better IDE support or control over load order), you can use `import "atproto-ui/styles.css"`. 45 + 46 + ## Theming 47 + 48 + Components use CSS variables for theming. By default, they respond to system dark mode preferences, or you can set a theme explicitly: 49 + 50 + ```tsx 51 + // Set theme via data attribute on document element 52 + document.documentElement.setAttribute("data-theme", "dark"); // or "light" 53 + 54 + // For system preference (default) 55 + document.documentElement.removeAttribute("data-theme"); 56 + ``` 57 + 58 + ### Available CSS Variables 59 + 60 + ```css 61 + --atproto-color-bg 62 + --atproto-color-bg-elevated 63 + --atproto-color-text 64 + --atproto-color-text-secondary 65 + --atproto-color-border 66 + --atproto-color-link 67 + /* ...and more, check out lib/styles.css */ 68 + ``` 69 + 70 + ### Override Component Theme 71 + 72 + Wrap any component in a div with custom CSS variables to override its appearance: 43 73 44 - | Component / Hook | What it does | 45 - | --- | --- | 46 - | `AtProtoProvider` | Configures PLC directory (defaults to `https://plc.directory`) and shares protocol clients via React context. | 47 - | `BlueskyProfile` | Renders a profile card for a DID/handle. Accepts `fallback`, `loadingIndicator`, `renderer`, and `colorScheme`. | 48 - | `BlueskyPost` / `BlueskyQuotePost` | Shows a single Bluesky post, with quotation support, custom renderer overrides, and the same loading/fallback knobs. | 49 - | `BlueskyPostList` | Lists the latest posts with built-in pagination (defaults: 5 per page, pagination controls on). | 50 - | `TangledString` | Renders a Tangled string (gist-like record) with optional renderer overrides. | 51 - | `LeafletDocument` | Displays long-form Leaflet documents with blocks, theme support, and renderer overrides. | 52 - | `useDidResolution`, `useLatestRecord`, `usePaginatedRecords`, โ€ฆ | Hook-level access to records if you want to own the markup or prefill components. | 74 + ```tsx 75 + import { AtProtoStyles } from "atproto-ui"; 53 76 54 - All components accept a `colorScheme` of `'light' | 'dark' | 'system'` so they can blend into your design. They also accept `fallback` and `loadingIndicator` props to control what renders before or during network work, and most expose a `renderer` override when you need total control of the final markup. 77 + <div style={{ 78 + '--atproto-color-bg': '#f0f0f0', 79 + '--atproto-color-text': '#000', 80 + '--atproto-color-link': '#0066cc', 81 + } satisfies AtProtoStyles}> 82 + <BlueskyPost did="..." rkey="..." /> 83 + </div> 84 + ``` 55 85 56 - ### Prefill components with the latest record 86 + ## Prefetched Data 57 87 58 - `useLatestRecord` gives you the most recent record for any collection along with its `rkey`. You can use that key to pre-populate components like `BlueskyPost`, `LeafletDocument`, or `TangledString`. 88 + All components accept a `record` prop. When provided, the component uses your data immediately without making network requests. Perfect for SSR, caching, or when you've already fetched data. 59 89 60 90 ```tsx 61 - import { useLatestRecord, BlueskyPost } from 'atproto-ui'; 62 - import type { FeedPostRecord } from 'atproto-ui'; 91 + import { BlueskyPost, useLatestRecord } from "atproto-ui"; 92 + import type { FeedPostRecord } from "atproto-ui"; 63 93 64 - const LatestBlueskyPost: React.FC<{ did: string }> = ({ did }) => { 65 - const { rkey, loading, error, empty } = useLatestRecord<FeedPostRecord>(did, 'app.bsky.feed.post'); 94 + const MyComponent: React.FC<{ did: string }> = ({ did }) => { 95 + // Fetch the latest post using the hook 96 + const { record, rkey, loading } = useLatestRecord<FeedPostRecord>( 97 + did, 98 + "app.bsky.feed.post" 99 + ); 66 100 67 - if (loading) return <p>Fetching latest postโ€ฆ</p>; 68 - if (error) return <p>Could not load: {error.message}</p>; 69 - if (empty || !rkey) return <p>No posts yet.</p>; 101 + if (loading) return <p>Loadingโ€ฆ</p>; 102 + if (!record || !rkey) return <p>No posts found.</p>; 70 103 71 - return ( 72 - <BlueskyPost 73 - did={did} 74 - rkey={rkey} 75 - colorScheme="system" 76 - /> 77 - ); 104 + // Pass the fetched record directlyโ€”BlueskyPost won't re-fetch it 105 + return <BlueskyPost did={did} rkey={rkey} record={record} />; 78 106 }; 79 107 ``` 80 108 81 - The same pattern works for other components: swap the collection NSID and the component you render once you have an `rkey`. 109 + All components support prefetched data: 82 110 83 111 ```tsx 84 - const LatestLeafletDocument: React.FC<{ did: string }> = ({ did }) => { 85 - const { rkey } = useLatestRecord(did, 'pub.leaflet.document'); 86 - return rkey ? <LeafletDocument did={did} rkey={rkey} colorScheme="light" /> : null; 87 - }; 112 + <BlueskyProfile did={did} record={profileRecord} /> 113 + <TangledString did={did} rkey={rkey} record={stringRecord} /> 114 + <LeafletDocument did={did} rkey={rkey} record={documentRecord} /> 88 115 ``` 89 116 90 - ## Compose your own component 117 + ### Using atcute Directly 91 118 92 - The helpers let you stitch together custom experiences without reimplementing protocol plumbing. The example below pulls a creatorโ€™s latest post and renders a minimal summary: 119 + Use atcute directly to construct records and pass them to componentsโ€”fully compatible! 93 120 94 121 ```tsx 95 - import { useLatestRecord, useColorScheme, AtProtoRecord } from 'atproto-ui'; 96 - import type { FeedPostRecord } from 'atproto-ui'; 122 + import { Client, simpleFetchHandler, ok } from '@atcute/client'; 123 + import type { AppBskyFeedPost } from '@atcute/bluesky'; 124 + import { BlueskyPost } from 'atproto-ui'; 125 + 126 + // Create atcute client 127 + const client = new Client({ 128 + handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 129 + }); 130 + 131 + // Fetch a record 132 + const data = await ok( 133 + client.get('com.atproto.repo.getRecord', { 134 + params: { 135 + repo: 'did:plc:ttdrpj45ibqunmfhdsb4zdwq', 136 + collection: 'app.bsky.feed.post', 137 + rkey: '3m45rq4sjes2h' 138 + } 139 + }) 140 + ); 141 + 142 + const record = data.value as AppBskyFeedPost.Main; 143 + 144 + // Pass atcute record directly to component! 145 + <BlueskyPost 146 + did="did:plc:ttdrpj45ibqunmfhdsb4zdwq" 147 + rkey="3m45rq4sjes2h" 148 + record={record} 149 + /> 150 + ``` 151 + 152 + ## API Reference 153 + 154 + ### Components 97 155 98 - const LatestPostSummary: React.FC<{ did: string }> = ({ did }) => { 99 - const scheme = useColorScheme('system'); 100 - const { rkey, loading, error } = useLatestRecord<FeedPostRecord>(did, 'app.bsky.feed.post'); 156 + | Component | Description | 157 + |-----------|-------------| 158 + | `AtProtoProvider` | Context provider for sharing protocol clients. Optional `plcDirectory` prop. | 159 + | `AtProtoRecord` | Core component for fetching/rendering any AT Protocol record. Accepts `record` prop. | 160 + | `BlueskyProfile` | Profile card for a DID/handle. Accepts `record`, `fallback`, `loadingIndicator`, `renderer`. | 161 + | `BlueskyPost` | Single Bluesky post. Accepts `record`, `iconPlacement`, custom renderers. | 162 + | `BlueskyQuotePost` | Post with quoted post support. Accepts `record`. | 163 + | `BlueskyPostList` | Paginated list of posts (default: 5 per page). | 164 + | `TangledString` | Tangled string (code snippet) renderer. Accepts `record`. | 165 + | `LeafletDocument` | Long-form document with blocks. Accepts `record`, `publicationRecord`. | 166 + 167 + ### Hooks 168 + 169 + | Hook | Returns | 170 + |------|---------| 171 + | `useDidResolution(did)` | `{ did, handle, loading, error }` | 172 + | `useLatestRecord(did, collection)` | `{ record, rkey, loading, error, empty }` | 173 + | `usePaginatedRecords(options)` | `{ records, loading, hasNext, loadNext, ... }` | 174 + | `useBlob(did, cid)` | `{ url, loading, error }` | 175 + | `useAtProtoRecord(did, collection, rkey)` | `{ record, loading, error }` | 176 + 177 + ## Advanced Usage 178 + 179 + ### Using Hooks for Custom Logic 180 + 181 + ```tsx 182 + import { useLatestRecord, BlueskyPost } from "atproto-ui"; 183 + import type { FeedPostRecord } from "atproto-ui"; 184 + 185 + const LatestBlueskyPost: React.FC<{ did: string }> = ({ did }) => { 186 + const { record, rkey, loading, error, empty } = useLatestRecord<FeedPostRecord>( 187 + did, 188 + "app.bsky.feed.post", 189 + ); 101 190 102 - if (loading) return <span>Loadingโ€ฆ</span>; 103 - if (error || !rkey) return <span>No post yet.</span>; 191 + if (loading) return <p>Fetching latest postโ€ฆ</p>; 192 + if (error) return <p>Could not load: {error.message}</p>; 193 + if (empty || !record || !rkey) return <p>No posts yet.</p>; 104 194 105 - return ( 106 - <AtProtoRecord<FeedPostRecord> 107 - did={did} 108 - collection="app.bsky.feed.post" 109 - rkey={rkey} 110 - renderer={({ record }) => ( 111 - <article data-color-scheme={scheme}> 112 - <strong>{record?.text ?? 'Empty post'}</strong> 113 - </article> 114 - )} 115 - /> 116 - ); 195 + // Pass both record and rkeyโ€”no additional API call needed 196 + return <BlueskyPost did={did} rkey={rkey} record={record} colorScheme="system" />; 117 197 }; 118 198 ``` 119 199 120 - There is a [demo](https://wisp.place/s/ana.pds.nkp.pet/ATComponents) where you can see the components in live action. 200 + ### Custom Renderer 121 201 122 - ## Running the demo locally 202 + Use `AtProtoRecord` with a custom renderer for full control: 203 + 204 + ```tsx 205 + import { AtProtoRecord } from "atproto-ui"; 206 + import type { FeedPostRecord } from "atproto-ui"; 207 + 208 + <AtProtoRecord<FeedPostRecord> 209 + did={did} 210 + collection="app.bsky.feed.post" 211 + rkey={rkey} 212 + renderer={({ record, loading, error }) => ( 213 + <article> 214 + <strong>{record?.text ?? "Empty post"}</strong> 215 + </article> 216 + )} 217 + /> 218 + ``` 219 + 220 + ## Demo 221 + 222 + Check out the [live demo](https://atproto-ui.wisp.place/) to see all components in action. 223 + 224 + ### Running Locally 123 225 124 226 ```bash 125 227 npm install 126 228 npm run dev 127 229 ``` 128 230 129 - Then open the printed Vite URL and try entering a Bluesky handle to see the components in action. 231 + ## Contributing 130 232 131 - ## Next steps 233 + Contributions are welcome! Open an issue or PR for: 234 + - New record type support (e.g., Grain.social photos) 235 + - Improved documentation 236 + - Bug fixes or feature requests 132 237 133 - - Expand renderer coverage (e.g., Grain.social photos). 134 - - Expand documentation with TypeScript API references and theming guidelines. 238 + ## License 135 239 136 - Contributions and ideas are welcomeโ€”feel free to open an issue or PR! 240 + MIT
+697
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "atproto-ui", 7 + "dependencies": { 8 + "@atcute/atproto": "^3.1.7", 9 + "@atcute/bluesky": "^3.2.3", 10 + "@atcute/client": "^4.0.3", 11 + "@atcute/identity-resolver": "^1.1.3", 12 + "@atcute/tangled": "^1.0.10", 13 + }, 14 + "devDependencies": { 15 + "@eslint/js": "^9.36.0", 16 + "@microsoft/api-extractor": "^7.53.1", 17 + "@types/node": "^24.6.0", 18 + "@types/react": "^19.1.16", 19 + "@types/react-dom": "^19.1.9", 20 + "@vitejs/plugin-react": "^5.0.4", 21 + "eslint": "^9.36.0", 22 + "eslint-plugin-react-hooks": "^5.2.0", 23 + "eslint-plugin-react-refresh": "^0.4.22", 24 + "globals": "^16.4.0", 25 + "react": "^19.1.1", 26 + "react-dom": "^19.1.1", 27 + "rollup-plugin-typescript2": "^0.36.0", 28 + "typescript": "~5.9.3", 29 + "typescript-eslint": "^8.45.0", 30 + "unplugin-dts": "^1.0.0-beta.6", 31 + "vite": "npm:rolldown-vite@7.1.14", 32 + }, 33 + "peerDependencies": { 34 + "react": "^18.2.0 || ^19.0.0", 35 + "react-dom": "^18.2.0 || ^19.0.0", 36 + }, 37 + "optionalPeers": [ 38 + "react-dom", 39 + ], 40 + }, 41 + }, 42 + "packages": { 43 + "@atcute/atproto": ["@atcute/atproto@3.1.9", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w=="], 44 + 45 + "@atcute/bluesky": ["@atcute/bluesky@3.2.11", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.5" } }, "sha512-AboS6y4t+zaxIq7E4noue10csSpIuk/Uwo30/l6GgGBDPXrd7STw8Yb5nGZQP+TdG/uC8/c2mm7UnY65SDOh6A=="], 46 + 47 + "@atcute/client": ["@atcute/client@4.1.0", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.5" } }, "sha512-AYhSu3RSDA2VDkVGOmad320NRbUUUf5pCFWJcOzlk25YC/4kyzmMFfpzhf1jjjEcY+anNBXGGhav/kKB1evggQ=="], 48 + 49 + "@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="], 50 + 51 + "@atcute/identity-resolver": ["@atcute/identity-resolver@1.1.4", "", { "dependencies": { "@atcute/lexicons": "^1.2.2", "@atcute/util-fetch": "^1.0.3", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA=="], 52 + 53 + "@atcute/lexicons": ["@atcute/lexicons@1.2.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "esm-env": "^1.2.2" } }, "sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q=="], 54 + 55 + "@atcute/tangled": ["@atcute/tangled@1.0.12", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.3" } }, "sha512-JKA5sOhd8SLhDFhY+PKHqLLytQBBKSiwcaEzfYUJBeyfvqXFPNNAwvRbe3VST4IQ3izoOu3O0R9/b1mjL45UzA=="], 56 + 57 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.4", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg=="], 58 + 59 + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], 60 + 61 + "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], 62 + 63 + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], 64 + 65 + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], 66 + 67 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], 68 + 69 + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], 70 + 71 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], 72 + 73 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], 74 + 75 + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], 76 + 77 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], 78 + 79 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], 80 + 81 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], 82 + 83 + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], 84 + 85 + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], 86 + 87 + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], 88 + 89 + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], 90 + 91 + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], 92 + 93 + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], 94 + 95 + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], 96 + 97 + "@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="], 98 + 99 + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], 100 + 101 + "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], 102 + 103 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], 104 + 105 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], 106 + 107 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 108 + 109 + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], 110 + 111 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 112 + 113 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 114 + 115 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], 116 + 117 + "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], 118 + 119 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 120 + 121 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 122 + 123 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 124 + 125 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 126 + 127 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 128 + 129 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 130 + 131 + "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], 132 + 133 + "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], 134 + 135 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 136 + 137 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], 138 + 139 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 140 + 141 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 142 + 143 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 144 + 145 + "@microsoft/api-extractor": ["@microsoft/api-extractor@7.55.1", "", { "dependencies": { "@microsoft/api-extractor-model": "7.32.1", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.0", "@rushstack/node-core-library": "5.19.0", "@rushstack/rig-package": "0.6.0", "@rushstack/terminal": "0.19.4", "@rushstack/ts-command-line": "5.1.4", "diff": "~8.0.2", "lodash": "~4.17.15", "minimatch": "10.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-l8Z+8qrLkZFM3HM95Dbpqs6G39fpCa7O5p8A7AkA6hSevxkgwsOlLrEuPv0ADOyj5dI1Af5WVDiwpKG/ya5G3w=="], 146 + 147 + "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.32.1", "", { "dependencies": { "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.0", "@rushstack/node-core-library": "5.19.0" } }, "sha512-u4yJytMYiUAnhcNQcZDTh/tVtlrzKlyKrQnLOV+4Qr/5gV+cpufWzCYAB1Q23URFqD6z2RoL2UYncM9xJVGNKA=="], 148 + 149 + "@microsoft/tsdoc": ["@microsoft/tsdoc@0.16.0", "", {}, "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA=="], 150 + 151 + "@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.0", "", { "dependencies": { "@microsoft/tsdoc": "0.16.0", "ajv": "~8.12.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw=="], 152 + 153 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], 154 + 155 + "@oxc-project/runtime": ["@oxc-project/runtime@0.92.0", "", {}, "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw=="], 156 + 157 + "@oxc-project/types": ["@oxc-project/types@0.93.0", "", {}, "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg=="], 158 + 159 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.41", "", { "os": "android", "cpu": "arm64" }, "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ=="], 160 + 161 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw=="], 162 + 163 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ=="], 164 + 165 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.41", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g=="], 166 + 167 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm" }, "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA=="], 168 + 169 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg=="], 170 + 171 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg=="], 172 + 173 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g=="], 174 + 175 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q=="], 176 + 177 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.41", "", { "os": "none", "cpu": "arm64" }, "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg=="], 178 + 179 + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.41", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ=="], 180 + 181 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "arm64" }, "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg=="], 182 + 183 + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "ia32" }, "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw=="], 184 + 185 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "x64" }, "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg=="], 186 + 187 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="], 188 + 189 + "@rollup/pluginutils": ["@rollup/pluginutils@4.2.1", "", { "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" } }, "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ=="], 190 + 191 + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="], 192 + 193 + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="], 194 + 195 + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="], 196 + 197 + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="], 198 + 199 + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="], 200 + 201 + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="], 202 + 203 + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="], 204 + 205 + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="], 206 + 207 + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="], 208 + 209 + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="], 210 + 211 + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="], 212 + 213 + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="], 214 + 215 + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g=="], 216 + 217 + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="], 218 + 219 + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="], 220 + 221 + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="], 222 + 223 + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q=="], 224 + 225 + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="], 226 + 227 + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw=="], 228 + 229 + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="], 230 + 231 + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="], 232 + 233 + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="], 234 + 235 + "@rushstack/node-core-library": ["@rushstack/node-core-library@5.19.0", "", { "dependencies": { "ajv": "~8.13.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-BxAopbeWBvNJ6VGiUL+5lbJXywTdsnMeOS8j57Cn/xY10r6sV/gbsTlfYKjzVCUBZATX2eRzJHSMCchsMTGN6A=="], 236 + 237 + "@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.1.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA=="], 238 + 239 + "@rushstack/rig-package": ["@rushstack/rig-package@0.6.0", "", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw=="], 240 + 241 + "@rushstack/terminal": ["@rushstack/terminal@0.19.4", "", { "dependencies": { "@rushstack/node-core-library": "5.19.0", "@rushstack/problem-matcher": "0.1.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-f4XQk02CrKfrMgyOfhYd3qWI944dLC21S4I/LUhrlAP23GTMDNG6EK5effQtFkISwUKCgD9vMBrJZaPSUquxWQ=="], 242 + 243 + "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.1.4", "", { "dependencies": { "@rushstack/terminal": "0.19.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-H0I6VdJ6sOUbktDFpP2VW5N29w8v4hRoNZOQz02vtEi6ZTYL1Ju8u+TcFiFawUDrUsx/5MQTUhd79uwZZVwVlA=="], 244 + 245 + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], 246 + 247 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 248 + 249 + "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], 250 + 251 + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], 252 + 253 + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], 254 + 255 + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], 256 + 257 + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], 258 + 259 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 260 + 261 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 262 + 263 + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], 264 + 265 + "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], 266 + 267 + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], 268 + 269 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="], 270 + 271 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA=="], 272 + 273 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.1", "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w=="], 274 + 275 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="], 276 + 277 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw=="], 278 + 279 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg=="], 280 + 281 + "@typescript-eslint/types": ["@typescript-eslint/types@8.48.1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="], 282 + 283 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.1", "@typescript-eslint/tsconfig-utils": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg=="], 284 + 285 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA=="], 286 + 287 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="], 288 + 289 + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.47", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA=="], 290 + 291 + "@volar/language-core": ["@volar/language-core@2.4.26", "", { "dependencies": { "@volar/source-map": "2.4.26" } }, "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A=="], 292 + 293 + "@volar/source-map": ["@volar/source-map@2.4.26", "", {}, "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw=="], 294 + 295 + "@volar/typescript": ["@volar/typescript@2.4.26", "", { "dependencies": { "@volar/language-core": "2.4.26", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA=="], 296 + 297 + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 298 + 299 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 300 + 301 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 302 + 303 + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], 304 + 305 + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], 306 + 307 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 308 + 309 + "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], 310 + 311 + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], 312 + 313 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 314 + 315 + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="], 316 + 317 + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 318 + 319 + "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], 320 + 321 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 322 + 323 + "caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="], 324 + 325 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 326 + 327 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 328 + 329 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 330 + 331 + "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], 332 + 333 + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], 334 + 335 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 336 + 337 + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], 338 + 339 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 340 + 341 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 342 + 343 + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], 344 + 345 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 346 + 347 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 348 + 349 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 350 + 351 + "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], 352 + 353 + "electron-to-chromium": ["electron-to-chromium@1.5.263", "", {}, "sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg=="], 354 + 355 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 356 + 357 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 358 + 359 + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], 360 + 361 + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], 362 + 363 + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="], 364 + 365 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 366 + 367 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 368 + 369 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 370 + 371 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 372 + 373 + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 374 + 375 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 376 + 377 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 378 + 379 + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 380 + 381 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 382 + 383 + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], 384 + 385 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 386 + 387 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 388 + 389 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 390 + 391 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 392 + 393 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 394 + 395 + "find-cache-dir": ["find-cache-dir@3.3.2", "", { "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" } }, "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig=="], 396 + 397 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 398 + 399 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 400 + 401 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 402 + 403 + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], 404 + 405 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 406 + 407 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 408 + 409 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 410 + 411 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 412 + 413 + "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], 414 + 415 + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 416 + 417 + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 418 + 419 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 420 + 421 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 422 + 423 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 424 + 425 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 426 + 427 + "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], 428 + 429 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 430 + 431 + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 432 + 433 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 434 + 435 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 436 + 437 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 438 + 439 + "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], 440 + 441 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 442 + 443 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 444 + 445 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 446 + 447 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 448 + 449 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 450 + 451 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 452 + 453 + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 454 + 455 + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], 456 + 457 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 458 + 459 + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], 460 + 461 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 462 + 463 + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], 464 + 465 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], 466 + 467 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], 468 + 469 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], 470 + 471 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], 472 + 473 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], 474 + 475 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], 476 + 477 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], 478 + 479 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], 480 + 481 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], 482 + 483 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], 484 + 485 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], 486 + 487 + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], 488 + 489 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 490 + 491 + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], 492 + 493 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 494 + 495 + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], 496 + 497 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 498 + 499 + "make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="], 500 + 501 + "minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], 502 + 503 + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], 504 + 505 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 506 + 507 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 508 + 509 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 510 + 511 + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], 512 + 513 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 514 + 515 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 516 + 517 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 518 + 519 + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], 520 + 521 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 522 + 523 + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], 524 + 525 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 526 + 527 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 528 + 529 + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 530 + 531 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 532 + 533 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 534 + 535 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 536 + 537 + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], 538 + 539 + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], 540 + 541 + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], 542 + 543 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 544 + 545 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 546 + 547 + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], 548 + 549 + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], 550 + 551 + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], 552 + 553 + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], 554 + 555 + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], 556 + 557 + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], 558 + 559 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 560 + 561 + "rolldown": ["rolldown@1.0.0-beta.41", "", { "dependencies": { "@oxc-project/types": "=0.93.0", "@rolldown/pluginutils": "1.0.0-beta.41", "ansis": "=4.2.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-x64": "1.0.0-beta.41", "@rolldown/binding-freebsd-x64": "1.0.0-beta.41", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.41", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.41", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.41", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.41", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.41" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg=="], 562 + 563 + "rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.3", "@rollup/rollup-android-arm64": "4.53.3", "@rollup/rollup-darwin-arm64": "4.53.3", "@rollup/rollup-darwin-x64": "4.53.3", "@rollup/rollup-freebsd-arm64": "4.53.3", "@rollup/rollup-freebsd-x64": "4.53.3", "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", "@rollup/rollup-linux-arm-musleabihf": "4.53.3", "@rollup/rollup-linux-arm64-gnu": "4.53.3", "@rollup/rollup-linux-arm64-musl": "4.53.3", "@rollup/rollup-linux-loong64-gnu": "4.53.3", "@rollup/rollup-linux-ppc64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-gnu": "4.53.3", "@rollup/rollup-linux-riscv64-musl": "4.53.3", "@rollup/rollup-linux-s390x-gnu": "4.53.3", "@rollup/rollup-linux-x64-gnu": "4.53.3", "@rollup/rollup-linux-x64-musl": "4.53.3", "@rollup/rollup-openharmony-arm64": "4.53.3", "@rollup/rollup-win32-arm64-msvc": "4.53.3", "@rollup/rollup-win32-ia32-msvc": "4.53.3", "@rollup/rollup-win32-x64-gnu": "4.53.3", "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="], 564 + 565 + "rollup-plugin-typescript2": ["rollup-plugin-typescript2@0.36.0", "", { "dependencies": { "@rollup/pluginutils": "^4.1.2", "find-cache-dir": "^3.3.2", "fs-extra": "^10.0.0", "semver": "^7.5.4", "tslib": "^2.6.2" }, "peerDependencies": { "rollup": ">=1.26.3", "typescript": ">=2.4.0" } }, "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw=="], 566 + 567 + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], 568 + 569 + "semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], 570 + 571 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 572 + 573 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 574 + 575 + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 576 + 577 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 578 + 579 + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], 580 + 581 + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], 582 + 583 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 584 + 585 + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], 586 + 587 + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 588 + 589 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 590 + 591 + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 592 + 593 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 594 + 595 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 596 + 597 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 598 + 599 + "typescript-eslint": ["typescript-eslint@8.48.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="], 600 + 601 + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], 602 + 603 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 604 + 605 + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], 606 + 607 + "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], 608 + 609 + "unplugin-dts": ["unplugin-dts@1.0.0-beta.6", "", { "dependencies": { "@rollup/pluginutils": "^5.1.4", "@volar/typescript": "^2.4.17", "compare-versions": "^6.1.1", "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^1.1.1", "magic-string": "^0.30.17", "unplugin": "^2.3.2" }, "peerDependencies": { "@microsoft/api-extractor": ">=7", "@rspack/core": "^1", "@vue/language-core": "~3.0.1", "esbuild": "*", "rolldown": "*", "rollup": ">=3", "typescript": ">=4", "vite": ">=3", "webpack": "^4 || ^5" }, "optionalPeers": ["@microsoft/api-extractor", "@rspack/core", "@vue/language-core", "esbuild", "rolldown", "rollup", "vite", "webpack"] }, "sha512-+xbFv5aVFtLZFNBAKI4+kXmd2h+T42/AaP8Bsp0YP/je/uOTN94Ame2Xt3e9isZS+Z7/hrLCLbsVJh+saqFMfQ=="], 610 + 611 + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], 612 + 613 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 614 + 615 + "vite": ["rolldown-vite@7.1.14", "", { "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.41", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw=="], 616 + 617 + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], 618 + 619 + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], 620 + 621 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 622 + 623 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 624 + 625 + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], 626 + 627 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 628 + 629 + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 630 + 631 + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 632 + 633 + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 634 + 635 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 636 + 637 + "@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 638 + 639 + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 640 + 641 + "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 642 + 643 + "@microsoft/api-extractor/typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], 644 + 645 + "@microsoft/tsdoc-config/ajv": ["ajv@8.12.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA=="], 646 + 647 + "@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 648 + 649 + "@rushstack/node-core-library/ajv": ["ajv@8.13.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="], 650 + 651 + "@rushstack/node-core-library/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], 652 + 653 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 654 + 655 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 656 + 657 + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 658 + 659 + "ajv-formats/ajv": ["ajv@8.13.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="], 660 + 661 + "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 662 + 663 + "eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 664 + 665 + "js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 666 + 667 + "make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 668 + 669 + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], 670 + 671 + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], 672 + 673 + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.41", "", {}, "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw=="], 674 + 675 + "rollup-plugin-typescript2/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 676 + 677 + "unplugin-dts/@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], 678 + 679 + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 680 + 681 + "@microsoft/tsdoc-config/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 682 + 683 + "@rushstack/node-core-library/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 684 + 685 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 686 + 687 + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 688 + 689 + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], 690 + 691 + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], 692 + 693 + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], 694 + 695 + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], 696 + } 697 + }
+36 -32
lib/components/BlueskyIcon.tsx
··· 1 - import React from 'react'; 1 + import React from "react"; 2 2 3 3 /** 4 4 * Configuration for the `BlueskyIcon` component. 5 5 */ 6 6 export interface BlueskyIconProps { 7 - /** 8 - * Pixel dimensions applied to both the width and height of the SVG element. 9 - * Defaults to `16`. 10 - */ 11 - size?: number; 12 - /** 13 - * Hex, RGB, or any valid CSS color string used to fill the icon path. 14 - * Defaults to the standard Bluesky blue `#1185fe`. 15 - */ 16 - color?: string; 17 - /** 18 - * Accessible title that will be exposed via `aria-label` for screen readers. 19 - * Defaults to `'Bluesky'`. 20 - */ 21 - title?: string; 7 + /** 8 + * Pixel dimensions applied to both the width and height of the SVG element. 9 + * Defaults to `16`. 10 + */ 11 + size?: number; 12 + /** 13 + * Hex, RGB, or any valid CSS color string used to fill the icon path. 14 + * Defaults to the standard Bluesky blue `#1185fe`. 15 + */ 16 + color?: string; 17 + /** 18 + * Accessible title that will be exposed via `aria-label` for screen readers. 19 + * Defaults to `'Bluesky'`. 20 + */ 21 + title?: string; 22 22 } 23 23 24 24 /** ··· 29 29 * @param title - Accessible label exposed via `aria-label`. 30 30 * @returns A JSX `<svg>` element suitable for inline usage. 31 31 */ 32 - export const BlueskyIcon: React.FC<BlueskyIconProps> = ({ size = 16, color = '#1185fe', title = 'Bluesky' }) => ( 33 - <svg 34 - xmlns="http://www.w3.org/2000/svg" 35 - width={size} 36 - height={size} 37 - viewBox="0 0 16 16" 38 - role="img" 39 - aria-label={title} 40 - focusable="false" 41 - style={{ display: 'block' }} 42 - > 43 - <path 44 - fill={color} 45 - d="M3.468 1.948C5.303 3.325 7.276 6.118 8 7.616c.725-1.498 2.698-4.29 4.532-5.668C13.855.955 16 .186 16 2.632c0 .489-.28 4.105-.444 4.692-.572 2.04-2.653 2.561-4.504 2.246 3.236.551 4.06 2.375 2.281 4.2-3.376 3.464-4.852-.87-5.23-1.98-.07-.204-.103-.3-.103-.218 0-.081-.033.014-.102.218-.379 1.11-1.855 5.444-5.231 1.98-1.778-1.825-.955-3.65 2.28-4.2-1.85.315-3.932-.205-4.503-2.246C.28 6.737 0 3.12 0 2.632 0 .186 2.145.955 3.468 1.948" 46 - /> 47 - </svg> 32 + export const BlueskyIcon: React.FC<BlueskyIconProps> = ({ 33 + size = 16, 34 + color = "#1185fe", 35 + title = "Bluesky", 36 + }) => ( 37 + <svg 38 + xmlns="http://www.w3.org/2000/svg" 39 + width={size} 40 + height={size} 41 + viewBox="0 0 16 16" 42 + role="img" 43 + aria-label={title} 44 + focusable="false" 45 + style={{ display: "block" }} 46 + > 47 + <path 48 + fill={color} 49 + d="M3.468 1.948C5.303 3.325 7.276 6.118 8 7.616c.725-1.498 2.698-4.29 4.532-5.668C13.855.955 16 .186 16 2.632c0 .489-.28 4.105-.444 4.692-.572 2.04-2.653 2.561-4.504 2.246 3.236.551 4.06 2.375 2.281 4.2-3.376 3.464-4.852-.87-5.23-1.98-.07-.204-.103-.3-.103-.218 0-.081-.033.014-.102.218-.379 1.11-1.855 5.444-5.231 1.98-1.778-1.825-.955-3.65 2.28-4.2-1.85.315-3.932-.205-4.503-2.246C.28 6.737 0 3.12 0 2.632 0 .186 2.145.955 3.468 1.948" 50 + /> 51 + </svg> 48 52 ); 49 53 50 54 export default BlueskyIcon;
+416 -137
lib/components/BlueskyPost.tsx
··· 1 - import React, { useMemo } from 'react'; 2 - import { AtProtoRecord } from '../core/AtProtoRecord'; 3 - import { BlueskyPostRenderer } from '../renderers/BlueskyPostRenderer'; 4 - import type { FeedPostRecord, ProfileRecord } from '../types/bluesky'; 5 - import { useDidResolution } from '../hooks/useDidResolution'; 6 - import { useAtProtoRecord } from '../hooks/useAtProtoRecord'; 7 - import { useBlob } from '../hooks/useBlob'; 8 - import { BLUESKY_PROFILE_COLLECTION } from './BlueskyProfile'; 9 - import { getAvatarCid } from '../utils/profile'; 10 - import { formatDidForLabel } from '../utils/at-uri'; 1 + import React, { useMemo } from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { BlueskyPostRenderer } from "../renderers/BlueskyPostRenderer"; 4 + import type { FeedPostRecord, ProfileRecord } from "../types/bluesky"; 5 + import { useDidResolution } from "../hooks/useDidResolution"; 6 + import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 7 + import { useBlob } from "../hooks/useBlob"; 8 + import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile"; 9 + import { getAvatarCid } from "../utils/profile"; 10 + import { formatDidForLabel, parseAtUri } from "../utils/at-uri"; 11 + import { isBlobWithCdn } from "../utils/blob"; 11 12 12 13 /** 13 14 * Props for rendering a single Bluesky post with optional customization hooks. 14 15 */ 15 16 export interface BlueskyPostProps { 16 - /** 17 - * Decentralized identifier for the repository that owns the post. 18 - */ 19 - did: string; 20 - /** 21 - * Record key identifying the specific post within the collection. 22 - */ 23 - rkey: string; 24 - /** 25 - * Custom renderer component that receives resolved post data and status flags. 26 - */ 27 - renderer?: React.ComponentType<BlueskyPostRendererInjectedProps>; 28 - /** 29 - * React node shown while the post query has not yet produced data or an error. 30 - */ 31 - fallback?: React.ReactNode; 32 - /** 33 - * React node displayed while the post fetch is actively loading. 34 - */ 35 - loadingIndicator?: React.ReactNode; 36 - /** 37 - * Preferred color scheme to pass through to renderers. 38 - */ 39 - colorScheme?: 'light' | 'dark' | 'system'; 40 - /** 41 - * Whether the default renderer should show the Bluesky icon. 42 - * Defaults to `true`. 43 - */ 44 - showIcon?: boolean; 45 - /** 46 - * Placement strategy for the icon when it is rendered. 47 - * Defaults to `'timestamp'`. 48 - */ 49 - iconPlacement?: 'cardBottomRight' | 'timestamp' | 'linkInline'; 17 + /** 18 + * Decentralized identifier for the repository that owns the post. 19 + */ 20 + did: string; 21 + /** 22 + * Record key identifying the specific post within the collection. 23 + */ 24 + rkey: string; 25 + /** 26 + * Prefetched post record. When provided, skips fetching the post from the network. 27 + * Note: Profile and avatar data will still be fetched unless a custom renderer is used. 28 + */ 29 + record?: FeedPostRecord; 30 + /** 31 + * Custom renderer component that receives resolved post data and status flags. 32 + */ 33 + renderer?: React.ComponentType<BlueskyPostRendererInjectedProps>; 34 + /** 35 + * React node shown while the post query has not yet produced data or an error. 36 + */ 37 + fallback?: React.ReactNode; 38 + /** 39 + * React node displayed while the post fetch is actively loading. 40 + */ 41 + loadingIndicator?: React.ReactNode; 42 + 43 + /** 44 + * Whether the default renderer should show the Bluesky icon. 45 + * Defaults to `true`. 46 + */ 47 + showIcon?: boolean; 48 + /** 49 + * Placement strategy for the icon when it is rendered. 50 + * Defaults to `'timestamp'`. 51 + */ 52 + iconPlacement?: "cardBottomRight" | "timestamp" | "linkInline"; 53 + /** 54 + * Controls whether to show the parent post if this post is a reply. 55 + * Defaults to `false`. 56 + */ 57 + showParent?: boolean; 58 + /** 59 + * Controls whether to recursively show all parent posts to the root. 60 + * Only applies when `showParent` is `true`. Defaults to `false`. 61 + */ 62 + recursiveParent?: boolean; 50 63 } 51 64 52 65 /** 53 66 * Values injected by `BlueskyPost` into a downstream renderer component. 54 67 */ 55 68 export type BlueskyPostRendererInjectedProps = { 56 - /** 57 - * Resolved record payload for the post. 58 - */ 59 - record: FeedPostRecord; 60 - /** 61 - * `true` while network operations are in-flight. 62 - */ 63 - loading: boolean; 64 - /** 65 - * Error encountered during loading, if any. 66 - */ 67 - error?: Error; 68 - /** 69 - * The author's public handle derived from the DID. 70 - */ 71 - authorHandle: string; 72 - /** 73 - * The DID that owns the post record. 74 - */ 75 - authorDid: string; 76 - /** 77 - * Resolved URL for the author's avatar blob, if available. 78 - */ 79 - avatarUrl?: string; 80 - /** 81 - * Preferred color scheme bubbled down to children. 82 - */ 83 - colorScheme?: 'light' | 'dark' | 'system'; 84 - /** 85 - * Placement strategy for the Bluesky icon. 86 - */ 87 - iconPlacement?: 'cardBottomRight' | 'timestamp' | 'linkInline'; 88 - /** 89 - * Controls whether the icon should render at all. 90 - */ 91 - showIcon?: boolean; 92 - /** 93 - * Fully qualified AT URI of the post, when resolvable. 94 - */ 95 - atUri?: string; 96 - /** 97 - * Optional override for the rendered embed contents. 98 - */ 99 - embed?: React.ReactNode; 69 + /** 70 + * Resolved record payload for the post. 71 + */ 72 + record: FeedPostRecord; 73 + /** 74 + * `true` while network operations are in-flight. 75 + */ 76 + loading: boolean; 77 + /** 78 + * Error encountered during loading, if any. 79 + */ 80 + error?: Error; 81 + /** 82 + * The author's public handle derived from the DID. 83 + */ 84 + authorHandle: string; 85 + /** 86 + * The author's display name from their profile. 87 + */ 88 + authorDisplayName?: string; 89 + /** 90 + * The DID that owns the post record. 91 + */ 92 + authorDid: string; 93 + /** 94 + * Resolved URL for the author's avatar blob, if available. 95 + */ 96 + avatarUrl?: string; 97 + 98 + /** 99 + * Placement strategy for the Bluesky icon. 100 + */ 101 + iconPlacement?: "cardBottomRight" | "timestamp" | "linkInline"; 102 + /** 103 + * Controls whether the icon should render at all. 104 + */ 105 + showIcon?: boolean; 106 + /** 107 + * Fully qualified AT URI of the post, when resolvable. 108 + */ 109 + atUri?: string; 110 + /** 111 + * Optional override for the rendered embed contents. 112 + */ 113 + embed?: React.ReactNode; 114 + /** 115 + * Whether this post is part of a thread. 116 + */ 117 + isInThread?: boolean; 118 + /** 119 + * Depth of this post in a thread (0 = root, 1 = first reply, etc.). 120 + */ 121 + threadDepth?: number; 122 + /** 123 + * Whether to show border even when in thread context. 124 + */ 125 + showThreadBorder?: boolean; 100 126 }; 101 127 102 - /** NSID for the canonical Bluesky feed post collection. */ 103 - export const BLUESKY_POST_COLLECTION = 'app.bsky.feed.post'; 128 + export const BLUESKY_POST_COLLECTION = "app.bsky.feed.post"; 129 + 130 + const threadContainerStyle: React.CSSProperties = { 131 + display: "flex", 132 + flexDirection: "column", 133 + maxWidth: "600px", 134 + width: "100%", 135 + background: "var(--atproto-color-bg)", 136 + position: "relative", 137 + borderRadius: "12px", 138 + overflow: "hidden" 139 + }; 140 + 141 + const parentPostStyle: React.CSSProperties = { 142 + position: "relative", 143 + }; 144 + 145 + const replyPostStyle: React.CSSProperties = { 146 + position: "relative", 147 + }; 148 + 149 + const loadingStyle: React.CSSProperties = { 150 + padding: "24px 18px", 151 + fontSize: "14px", 152 + textAlign: "center", 153 + color: "var(--atproto-color-text-secondary)", 154 + }; 104 155 105 156 /** 106 157 * Fetches a Bluesky feed post, resolves metadata such as author handle and avatar, ··· 108 159 * 109 160 * @param did - DID of the repository that stores the post. 110 161 * @param rkey - Record key for the post within the feed collection. 162 + * @param record - Prefetched record for the post. 111 163 * @param renderer - Optional renderer component to override the default. 112 164 * @param fallback - Node rendered before the first fetch attempt resolves. 113 165 * @param loadingIndicator - Node rendered while the post is loading. 114 - * @param colorScheme - Preferred color scheme forwarded to downstream components. 115 166 * @param showIcon - Controls whether the Bluesky icon should render alongside the post. Defaults to `true`. 116 167 * @param iconPlacement - Determines where the icon is positioned in the rendered post. Defaults to `'timestamp'`. 117 168 * @returns A component that renders loading/fallback states and the resolved post. 118 169 */ 119 - export const BlueskyPost: React.FC<BlueskyPostProps> = ({ did: handleOrDid, rkey, renderer, fallback, loadingIndicator, colorScheme, showIcon = true, iconPlacement = 'timestamp' }) => { 120 - const { did: resolvedDid, handle, loading: resolvingIdentity, error: resolutionError } = useDidResolution(handleOrDid); 121 - const repoIdentifier = resolvedDid ?? handleOrDid; 122 - const { record: profile } = useAtProtoRecord<ProfileRecord>({ did: repoIdentifier, collection: BLUESKY_PROFILE_COLLECTION, rkey: 'self' }); 123 - const avatarCid = getAvatarCid(profile); 170 + export const BlueskyPost: React.FC<BlueskyPostProps> = React.memo( 171 + ({ 172 + did: handleOrDid, 173 + rkey, 174 + record, 175 + renderer, 176 + fallback, 177 + loadingIndicator, 178 + showIcon = true, 179 + iconPlacement = "timestamp", 180 + showParent = false, 181 + recursiveParent = false, 182 + }) => { 183 + const { 184 + did: resolvedDid, 185 + handle, 186 + loading: resolvingIdentity, 187 + error: resolutionError, 188 + } = useDidResolution(handleOrDid); 189 + const repoIdentifier = resolvedDid ?? handleOrDid; 190 + const { record: profile } = useAtProtoRecord<ProfileRecord>({ 191 + did: repoIdentifier, 192 + collection: BLUESKY_PROFILE_COLLECTION, 193 + rkey: "self", 194 + }); 195 + const avatar = profile?.avatar; 196 + const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined; 197 + const avatarCid = avatarCdnUrl ? undefined : getAvatarCid(profile); 198 + const authorDisplayName = profile?.displayName; 199 + 200 + const { 201 + record: fetchedRecord, 202 + loading: currentLoading, 203 + error: currentError, 204 + } = useAtProtoRecord<FeedPostRecord>({ 205 + did: showParent && !record ? repoIdentifier : "", 206 + collection: showParent && !record ? BLUESKY_POST_COLLECTION : "", 207 + rkey: showParent && !record ? rkey : "", 208 + }); 209 + 210 + const currentRecord = record ?? fetchedRecord; 211 + 212 + const parentUri = currentRecord?.reply?.parent?.uri; 213 + const parsedParentUri = parentUri ? parseAtUri(parentUri) : null; 214 + const parentDid = parsedParentUri?.did; 215 + const parentRkey = parsedParentUri?.rkey; 124 216 125 - const Comp: React.ComponentType<BlueskyPostRendererInjectedProps> = renderer ?? ((props) => <BlueskyPostRenderer {...props} />); 217 + const { 218 + record: parentRecord, 219 + loading: parentLoading, 220 + error: parentError, 221 + } = useAtProtoRecord<FeedPostRecord>({ 222 + did: showParent && parentDid ? parentDid : "", 223 + collection: showParent && parentDid ? BLUESKY_POST_COLLECTION : "", 224 + rkey: showParent && parentRkey ? parentRkey : "", 225 + }); 126 226 127 - const displayHandle = handle ?? (handleOrDid.startsWith('did:') ? undefined : handleOrDid); 128 - const authorHandle = displayHandle ?? formatDidForLabel(resolvedDid ?? handleOrDid); 129 - const atUri = resolvedDid ? `at://${resolvedDid}/${BLUESKY_POST_COLLECTION}/${rkey}` : undefined; 227 + const Comp: React.ComponentType<BlueskyPostRendererInjectedProps> = 228 + useMemo( 229 + () => 230 + renderer ?? ((props) => <BlueskyPostRenderer {...props} />), 231 + [renderer], 232 + ); 130 233 131 - const Wrapped = useMemo(() => { 132 - const WrappedComponent: React.FC<{ record: FeedPostRecord; loading: boolean; error?: Error }> = (props) => { 133 - const { url: avatarUrl } = useBlob(repoIdentifier, avatarCid); 134 - return ( 135 - <Comp 136 - {...props} 137 - authorHandle={authorHandle} 138 - authorDid={repoIdentifier} 139 - avatarUrl={avatarUrl} 140 - colorScheme={colorScheme} 141 - iconPlacement={iconPlacement} 142 - showIcon={showIcon} 143 - atUri={atUri} 144 - /> 145 - ); 146 - }; 147 - WrappedComponent.displayName = 'BlueskyPostWrappedRenderer'; 148 - return WrappedComponent; 149 - }, [Comp, repoIdentifier, avatarCid, authorHandle, colorScheme, iconPlacement, showIcon, atUri]); 234 + const displayHandle = 235 + handle ?? 236 + (handleOrDid.startsWith("did:") ? undefined : handleOrDid); 237 + const authorHandle = 238 + displayHandle ?? formatDidForLabel(resolvedDid ?? handleOrDid); 239 + const atUri = resolvedDid 240 + ? `at://${resolvedDid}/${BLUESKY_POST_COLLECTION}/${rkey}` 241 + : undefined; 150 242 151 - if (!displayHandle && resolvingIdentity) { 152 - return <div style={{ padding: 8 }}>Resolving handleโ€ฆ</div>; 153 - } 154 - if (!displayHandle && resolutionError) { 155 - return <div style={{ padding: 8, color: 'crimson' }}>Could not resolve handle.</div>; 156 - } 243 + const Wrapped = useMemo(() => { 244 + const WrappedComponent: React.FC<{ 245 + record: FeedPostRecord; 246 + loading: boolean; 247 + error?: Error; 248 + }> = (props) => { 249 + const { url: avatarUrlFromBlob } = useBlob( 250 + repoIdentifier, 251 + avatarCid, 252 + ); 253 + const avatarUrl = avatarCdnUrl || avatarUrlFromBlob; 254 + return ( 255 + <Comp 256 + {...props} 257 + authorHandle={authorHandle} 258 + authorDisplayName={authorDisplayName} 259 + authorDid={repoIdentifier} 260 + avatarUrl={avatarUrl} 261 + iconPlacement={iconPlacement} 262 + showIcon={showIcon} 263 + atUri={atUri} 264 + isInThread 265 + threadDepth={showParent ? 1 : 0} 266 + showThreadBorder={!showParent && !!props.record?.reply?.parent} 267 + /> 268 + ); 269 + }; 270 + WrappedComponent.displayName = "BlueskyPostWrappedRenderer"; 271 + return WrappedComponent; 272 + }, [ 273 + Comp, 274 + repoIdentifier, 275 + avatarCid, 276 + avatarCdnUrl, 277 + authorHandle, 278 + authorDisplayName, 279 + iconPlacement, 280 + showIcon, 281 + atUri, 282 + showParent, 283 + ]); 157 284 158 - return ( 159 - <AtProtoRecord<FeedPostRecord> 160 - did={repoIdentifier} 161 - collection={BLUESKY_POST_COLLECTION} 162 - rkey={rkey} 163 - renderer={Wrapped} 164 - fallback={fallback} 165 - loadingIndicator={loadingIndicator} 166 - /> 167 - ); 168 - }; 285 + const WrappedWithoutIcon = useMemo(() => { 286 + const WrappedComponent: React.FC<{ 287 + record: FeedPostRecord; 288 + loading: boolean; 289 + error?: Error; 290 + }> = (props) => { 291 + const { url: avatarUrlFromBlob } = useBlob( 292 + repoIdentifier, 293 + avatarCid, 294 + ); 295 + const avatarUrl = avatarCdnUrl || avatarUrlFromBlob; 296 + return ( 297 + <Comp 298 + {...props} 299 + authorHandle={authorHandle} 300 + authorDisplayName={authorDisplayName} 301 + authorDid={repoIdentifier} 302 + avatarUrl={avatarUrl} 303 + iconPlacement={iconPlacement} 304 + showIcon={false} 305 + atUri={atUri} 306 + isInThread 307 + threadDepth={showParent ? 1 : 0} 308 + showThreadBorder={!showParent && !!props.record?.reply?.parent} 309 + /> 310 + ); 311 + }; 312 + WrappedComponent.displayName = "BlueskyPostWrappedRendererWithoutIcon"; 313 + return WrappedComponent; 314 + }, [ 315 + Comp, 316 + repoIdentifier, 317 + avatarCid, 318 + avatarCdnUrl, 319 + authorHandle, 320 + authorDisplayName, 321 + iconPlacement, 322 + atUri, 323 + showParent, 324 + ]); 169 325 170 - export default BlueskyPost; 326 + if (!displayHandle && resolvingIdentity) { 327 + return <div style={{ padding: 8 }}>Resolving handleโ€ฆ</div>; 328 + } 329 + if (!displayHandle && resolutionError) { 330 + return ( 331 + <div style={{ padding: 8, color: "crimson" }}> 332 + Could not resolve handle. 333 + </div> 334 + ); 335 + } 336 + 337 + const renderMainPost = (mainRecord?: FeedPostRecord) => { 338 + if (mainRecord !== undefined) { 339 + return ( 340 + <AtProtoRecord<FeedPostRecord> 341 + record={mainRecord} 342 + renderer={Wrapped} 343 + fallback={fallback} 344 + loadingIndicator={loadingIndicator} 345 + /> 346 + ); 347 + } 348 + 349 + return ( 350 + <AtProtoRecord<FeedPostRecord> 351 + did={repoIdentifier} 352 + collection={BLUESKY_POST_COLLECTION} 353 + rkey={rkey} 354 + renderer={Wrapped} 355 + fallback={fallback} 356 + loadingIndicator={loadingIndicator} 357 + /> 358 + ); 359 + }; 360 + 361 + const renderMainPostWithoutIcon = (mainRecord?: FeedPostRecord) => { 362 + if (mainRecord !== undefined) { 363 + return ( 364 + <AtProtoRecord<FeedPostRecord> 365 + record={mainRecord} 366 + renderer={WrappedWithoutIcon} 367 + fallback={fallback} 368 + loadingIndicator={loadingIndicator} 369 + /> 370 + ); 371 + } 372 + 373 + return ( 374 + <AtProtoRecord<FeedPostRecord> 375 + did={repoIdentifier} 376 + collection={BLUESKY_POST_COLLECTION} 377 + rkey={rkey} 378 + renderer={WrappedWithoutIcon} 379 + fallback={fallback} 380 + loadingIndicator={loadingIndicator} 381 + /> 382 + ); 383 + }; 384 + 385 + if (showParent) { 386 + if (currentLoading || (parentLoading && !parentRecord)) { 387 + return ( 388 + <div style={threadContainerStyle}> 389 + <div style={loadingStyle}>Loading threadโ€ฆ</div> 390 + </div> 391 + ); 392 + } 393 + 394 + if (currentError) { 395 + return ( 396 + <div style={{ padding: 8, color: "crimson" }}> 397 + Failed to load post. 398 + </div> 399 + ); 400 + } 401 + 402 + if (!parentDid || !parentRkey) { 403 + return renderMainPost(record); 404 + } 405 + 406 + if (parentError) { 407 + return ( 408 + <div style={{ padding: 8, color: "crimson" }}> 409 + Failed to load parent post. 410 + </div> 411 + ); 412 + } 413 + 414 + return ( 415 + <div style={threadContainerStyle}> 416 + <div style={parentPostStyle}> 417 + {recursiveParent && parentRecord?.reply?.parent?.uri ? ( 418 + <BlueskyPost 419 + did={parentDid} 420 + rkey={parentRkey} 421 + record={parentRecord} 422 + showParent={true} 423 + recursiveParent={true} 424 + showIcon={showIcon} 425 + iconPlacement={iconPlacement} 426 + /> 427 + ) : ( 428 + <BlueskyPost 429 + did={parentDid} 430 + rkey={parentRkey} 431 + record={parentRecord} 432 + showIcon={showIcon} 433 + iconPlacement={iconPlacement} 434 + /> 435 + )} 436 + </div> 437 + 438 + <div style={replyPostStyle}> 439 + {renderMainPostWithoutIcon(record || currentRecord)} 440 + </div> 441 + </div> 442 + ); 443 + } 444 + 445 + return renderMainPost(record); 446 + }, 447 + ); 448 + 449 + export default BlueskyPost;
+660 -392
lib/components/BlueskyPostList.tsx
··· 1 - import React, { useMemo } from 'react'; 2 - import { usePaginatedRecords } from '../hooks/usePaginatedRecords'; 3 - import { useColorScheme } from '../hooks/useColorScheme'; 4 - import type { FeedPostRecord } from '../types/bluesky'; 5 - import { useDidResolution } from '../hooks/useDidResolution'; 6 - import { BlueskyIcon } from './BlueskyIcon'; 1 + import React, { useMemo } from "react"; 2 + import { 3 + usePaginatedRecords, 4 + type AuthorFeedReason, 5 + type ReplyParentInfo, 6 + } from "../hooks/usePaginatedRecords"; 7 + import type { FeedPostRecord, ProfileRecord } from "../types/bluesky"; 8 + import { useDidResolution } from "../hooks/useDidResolution"; 9 + import { BlueskyIcon } from "./BlueskyIcon"; 10 + import { parseAtUri } from "../utils/at-uri"; 11 + import { useAtProto } from "../providers/AtProtoProvider"; 12 + import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 13 + import { useBlob } from "../hooks/useBlob"; 14 + import { getAvatarCid } from "../utils/profile"; 15 + import { isBlobWithCdn } from "../utils/blob"; 16 + import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile"; 17 + import { RichText as BlueskyRichText } from "./RichText"; 7 18 8 19 /** 9 20 * Options for rendering a paginated list of Bluesky posts. 10 21 */ 11 22 export interface BlueskyPostListProps { 12 - /** 13 - * DID whose feed posts should be fetched. 14 - */ 15 - did: string; 16 - /** 17 - * Maximum number of records to list per page. Defaults to `5`. 18 - */ 19 - limit?: number; 20 - /** 21 - * Enables pagination controls when `true`. Defaults to `true`. 22 - */ 23 - enablePagination?: boolean; 24 - /** 25 - * Preferred color scheme passed through to styling helpers. 26 - * Defaults to `'system'` which follows the OS preference. 27 - */ 28 - colorScheme?: 'light' | 'dark' | 'system'; 23 + /** 24 + * DID whose feed posts should be fetched. 25 + */ 26 + did: string; 27 + /** 28 + * Maximum number of records to list per page. Defaults to `5`. 29 + */ 30 + limit?: number; 31 + /** 32 + * Enables pagination controls when `true`. Defaults to `true`. 33 + */ 34 + enablePagination?: boolean; 29 35 } 30 36 31 37 /** ··· 34 40 * @param did - DID whose posts should be displayed. 35 41 * @param limit - Maximum number of posts per page. Default `5`. 36 42 * @param enablePagination - Whether pagination controls should render. Default `true`. 37 - * @param colorScheme - Preferred color scheme used for styling. Default `'system'`. 38 43 * @returns A card-like list element with loading, empty, and error handling. 39 44 */ 40 - export const BlueskyPostList: React.FC<BlueskyPostListProps> = ({ did, limit = 5, enablePagination = true, colorScheme = 'system' }) => { 41 - const scheme = useColorScheme(colorScheme); 42 - const palette: ListPalette = scheme === 'dark' ? darkPalette : lightPalette; 43 - const { handle: resolvedHandle, did: resolvedDid } = useDidResolution(did); 44 - const actorLabel = resolvedHandle ?? formatDid(did); 45 - const actorPath = resolvedHandle ?? resolvedDid ?? did; 45 + export const BlueskyPostList: React.FC<BlueskyPostListProps> = React.memo(({ 46 + did, 47 + limit = 5, 48 + enablePagination = true, 49 + }) => { 50 + const { handle: resolvedHandle, did: resolvedDid } = useDidResolution(did); 51 + const actorLabel = resolvedHandle ?? formatDid(did); 52 + const actorPath = resolvedHandle ?? resolvedDid ?? did; 46 53 47 - const { records, loading, error, hasNext, hasPrev, loadNext, loadPrev, pageIndex, pagesCount } = usePaginatedRecords<FeedPostRecord>({ 48 - did, 49 - collection: 'app.bsky.feed.post', 50 - limit, 51 - preferAuthorFeed: true, 52 - authorFeedActor: actorPath 53 - }); 54 + const { 55 + records, 56 + loading, 57 + error, 58 + hasNext, 59 + hasPrev, 60 + loadNext, 61 + loadPrev, 62 + pageIndex, 63 + pagesCount, 64 + } = usePaginatedRecords<FeedPostRecord>({ 65 + did, 66 + collection: "app.bsky.feed.post", 67 + limit, 68 + preferAuthorFeed: true, 69 + authorFeedActor: actorPath, 70 + }); 54 71 55 - const pageLabel = useMemo(() => { 56 - const knownTotal = Math.max(pageIndex + 1, pagesCount); 57 - if (!enablePagination) return undefined; 58 - if (hasNext && knownTotal === pageIndex + 1) return `${pageIndex + 1}/โ€ฆ`; 59 - return `${pageIndex + 1}/${knownTotal}`; 60 - }, [enablePagination, hasNext, pageIndex, pagesCount]); 72 + const pageLabel = useMemo(() => { 73 + const knownTotal = Math.max(pageIndex + 1, pagesCount); 74 + if (!enablePagination) return undefined; 75 + if (hasNext && knownTotal === pageIndex + 1) 76 + return `${pageIndex + 1}/โ€ฆ`; 77 + return `${pageIndex + 1}/${knownTotal}`; 78 + }, [enablePagination, hasNext, pageIndex, pagesCount]); 61 79 62 - if (error) return <div style={{ padding: 8, color: 'crimson' }}>Failed to load posts.</div>; 80 + if (error) 81 + return ( 82 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 83 + Failed to load posts. 84 + </div> 85 + ); 63 86 64 - return ( 65 - <div style={{ ...listStyles.card, ...palette.card }}> 66 - <div style={{ ...listStyles.header, ...palette.header }}> 67 - <div style={listStyles.headerInfo}> 68 - <div style={listStyles.headerIcon}> 69 - <BlueskyIcon size={20} /> 70 - </div> 71 - <div style={listStyles.headerText}> 72 - <span style={listStyles.title}>Latest Posts</span> 73 - <span style={{ ...listStyles.subtitle, ...palette.subtitle }}>@{actorLabel}</span> 74 - </div> 75 - </div> 76 - {pageLabel && <span style={{ ...listStyles.pageMeta, ...palette.pageMeta }}>{pageLabel}</span>} 77 - </div> 78 - <div style={listStyles.items}> 79 - {loading && records.length === 0 && <div style={{ ...listStyles.empty, ...palette.empty }}>Loading postsโ€ฆ</div>} 80 - {records.map((record, idx) => ( 81 - <ListRow 82 - key={record.rkey} 83 - record={record.value} 84 - rkey={record.rkey} 85 - did={actorPath} 86 - palette={palette} 87 - hasDivider={idx < records.length - 1} 88 - /> 89 - ))} 90 - {!loading && records.length === 0 && <div style={{ ...listStyles.empty, ...palette.empty }}>No posts found.</div>} 91 - </div> 92 - {enablePagination && ( 93 - <div style={{ ...listStyles.footer, ...palette.footer }}> 94 - <button 95 - type="button" 96 - style={{ 97 - ...listStyles.navButton, 98 - ...palette.navButton, 99 - cursor: hasPrev ? 'pointer' : 'not-allowed', 100 - opacity: hasPrev ? 1 : 0.5 101 - }} 102 - onClick={loadPrev} 103 - disabled={!hasPrev} 104 - > 105 - โ€น Prev 106 - </button> 107 - <div style={listStyles.pageChips}> 108 - <span style={{ ...listStyles.pageChipActive, ...palette.pageChipActive }}>{pageIndex + 1}</span> 109 - {(hasNext || pagesCount > pageIndex + 1) && ( 110 - <span style={{ ...listStyles.pageChip, ...palette.pageChip }}>{pageIndex + 2}</span> 111 - )} 112 - </div> 113 - <button 114 - type="button" 115 - style={{ 116 - ...listStyles.navButton, 117 - ...palette.navButton, 118 - cursor: hasNext ? 'pointer' : 'not-allowed', 119 - opacity: hasNext ? 1 : 0.5 120 - }} 121 - onClick={loadNext} 122 - disabled={!hasNext} 123 - > 124 - Next โ€บ 125 - </button> 126 - </div> 127 - )} 128 - {loading && records.length > 0 && <div style={{ ...listStyles.loadingBar, ...palette.loadingBar }}>Updatingโ€ฆ</div>} 129 - </div> 130 - ); 131 - }; 87 + return ( 88 + <div style={{ ...listStyles.card, background: `var(--atproto-color-bg)`, borderWidth: "1px", borderStyle: "solid", borderColor: `var(--atproto-color-border)` }}> 89 + <div style={{ ...listStyles.header, background: `var(--atproto-color-bg-elevated)`, color: `var(--atproto-color-text)` }}> 90 + <div style={listStyles.headerInfo}> 91 + <div style={listStyles.headerIcon}> 92 + <BlueskyIcon size={20} /> 93 + </div> 94 + <div style={listStyles.headerText}> 95 + <span style={listStyles.title}>Latest Posts</span> 96 + <span 97 + style={{ 98 + ...listStyles.subtitle, 99 + color: `var(--atproto-color-text-secondary)`, 100 + }} 101 + > 102 + @{actorLabel} 103 + </span> 104 + </div> 105 + </div> 106 + {pageLabel && ( 107 + <span 108 + style={{ ...listStyles.pageMeta, color: `var(--atproto-color-text-secondary)` }} 109 + > 110 + {pageLabel} 111 + </span> 112 + )} 113 + </div> 114 + <div style={listStyles.items}> 115 + {loading && records.length === 0 && ( 116 + <div style={{ ...listStyles.empty, color: `var(--atproto-color-text-secondary)` }}> 117 + Loading postsโ€ฆ 118 + </div> 119 + )} 120 + {records.map((record, idx) => ( 121 + <ListRow 122 + key={record.rkey} 123 + record={record.value} 124 + rkey={record.rkey} 125 + did={actorPath} 126 + uri={record.uri} 127 + reason={record.reason} 128 + replyParent={record.replyParent} 129 + hasDivider={idx < records.length - 1} 130 + /> 131 + ))} 132 + {!loading && records.length === 0 && ( 133 + <div style={{ ...listStyles.empty, color: `var(--atproto-color-text-secondary)` }}> 134 + No posts found. 135 + </div> 136 + )} 137 + </div> 138 + {enablePagination && ( 139 + <div style={{ ...listStyles.footer, borderTopColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 140 + <button 141 + type="button" 142 + style={{ 143 + ...listStyles.pageButton, 144 + background: `var(--atproto-color-button-bg)`, 145 + color: `var(--atproto-color-button-text)`, 146 + cursor: hasPrev ? "pointer" : "not-allowed", 147 + opacity: hasPrev ? 1 : 0.5, 148 + }} 149 + onClick={loadPrev} 150 + disabled={!hasPrev} 151 + > 152 + โ€น Prev 153 + </button> 154 + <div style={listStyles.pageChips}> 155 + <span 156 + style={{ 157 + ...listStyles.pageChipActive, 158 + color: `var(--atproto-color-button-text)`, 159 + background: `var(--atproto-color-button-bg)`, 160 + borderWidth: "1px", 161 + borderStyle: "solid", 162 + borderColor: `var(--atproto-color-button-bg)`, 163 + }} 164 + > 165 + {pageIndex + 1} 166 + </span> 167 + {(hasNext || pagesCount > pageIndex + 1) && ( 168 + <span 169 + style={{ 170 + ...listStyles.pageChip, 171 + color: `var(--atproto-color-text-secondary)`, 172 + borderWidth: "1px", 173 + borderStyle: "solid", 174 + borderColor: `var(--atproto-color-border)`, 175 + background: `var(--atproto-color-bg)`, 176 + }} 177 + > 178 + {pageIndex + 2} 179 + </span> 180 + )} 181 + </div> 182 + <button 183 + type="button" 184 + style={{ 185 + ...listStyles.pageButton, 186 + background: `var(--atproto-color-button-bg)`, 187 + color: `var(--atproto-color-button-text)`, 188 + cursor: hasNext ? "pointer" : "not-allowed", 189 + opacity: hasNext ? 1 : 0.5, 190 + }} 191 + onClick={loadNext} 192 + disabled={!hasNext} 193 + > 194 + Next โ€บ 195 + </button> 196 + </div> 197 + )} 198 + {loading && records.length > 0 && ( 199 + <div 200 + style={{ ...listStyles.loadingBar, background: `var(--atproto-color-bg-elevated)`, color: `var(--atproto-color-text-secondary)` }} 201 + > 202 + Updatingโ€ฆ 203 + </div> 204 + )} 205 + </div> 206 + ); 207 + }); 132 208 133 209 interface ListRowProps { 134 - record: FeedPostRecord; 135 - rkey: string; 136 - did: string; 137 - palette: ListPalette; 138 - hasDivider: boolean; 210 + record: FeedPostRecord; 211 + rkey: string; 212 + did: string; 213 + uri?: string; 214 + reason?: AuthorFeedReason; 215 + replyParent?: ReplyParentInfo; 216 + hasDivider: boolean; 139 217 } 140 218 141 - const ListRow: React.FC<ListRowProps> = ({ record, rkey, did, palette, hasDivider }) => { 142 - const text = record.text?.trim() ?? ''; 143 - const relative = record.createdAt ? formatRelativeTime(record.createdAt) : undefined; 144 - const absolute = record.createdAt ? new Date(record.createdAt).toLocaleString() : undefined; 145 - const href = `https://bsky.app/profile/${did}/post/${rkey}`; 219 + const ListRow: React.FC<ListRowProps> = ({ 220 + record, 221 + rkey, 222 + did, 223 + uri, 224 + reason, 225 + replyParent, 226 + hasDivider, 227 + }) => { 228 + const { blueskyAppBaseUrl } = useAtProto(); 229 + const text = record.text?.trim() ?? ""; 230 + const relative = record.createdAt 231 + ? formatRelativeTime(record.createdAt) 232 + : undefined; 233 + const absolute = record.createdAt 234 + ? new Date(record.createdAt).toLocaleString() 235 + : undefined; 236 + 237 + // Parse the URI to get the actual post's DID and rkey 238 + const parsedUri = uri ? parseAtUri(uri) : undefined; 239 + const postDid = parsedUri?.did ?? did; 240 + const postRkey = parsedUri?.rkey ?? rkey; 241 + const href = `${blueskyAppBaseUrl}/profile/${postDid}/post/${postRkey}`; 242 + 243 + // Author profile and avatar 244 + const { handle: authorHandle } = useDidResolution(postDid); 245 + const { record: authorProfile } = useAtProtoRecord<ProfileRecord>({ 246 + did: postDid, 247 + collection: BLUESKY_PROFILE_COLLECTION, 248 + rkey: "self", 249 + }); 250 + const authorDisplayName = authorProfile?.displayName; 251 + const authorAvatar = authorProfile?.avatar; 252 + const authorAvatarCdnUrl = isBlobWithCdn(authorAvatar) ? authorAvatar.cdnUrl : undefined; 253 + const authorAvatarCid = authorAvatarCdnUrl ? undefined : getAvatarCid(authorProfile); 254 + const { url: authorAvatarUrl } = useBlob( 255 + postDid, 256 + authorAvatarCid, 257 + ); 258 + const finalAuthorAvatarUrl = authorAvatarCdnUrl ?? authorAvatarUrl; 259 + 260 + // Repost metadata 261 + const isRepost = reason?.$type === "app.bsky.feed.defs#reasonRepost"; 262 + const reposterDid = reason?.by?.did; 263 + const { handle: reposterHandle } = useDidResolution(reposterDid); 264 + const { record: reposterProfile } = useAtProtoRecord<ProfileRecord>({ 265 + did: reposterDid, 266 + collection: BLUESKY_PROFILE_COLLECTION, 267 + rkey: "self", 268 + }); 269 + const reposterDisplayName = reposterProfile?.displayName; 270 + const reposterAvatar = reposterProfile?.avatar; 271 + const reposterAvatarCdnUrl = isBlobWithCdn(reposterAvatar) ? reposterAvatar.cdnUrl : undefined; 272 + const reposterAvatarCid = reposterAvatarCdnUrl ? undefined : getAvatarCid(reposterProfile); 273 + const { url: reposterAvatarUrl } = useBlob( 274 + reposterDid, 275 + reposterAvatarCid, 276 + ); 277 + const finalReposterAvatarUrl = reposterAvatarCdnUrl ?? reposterAvatarUrl; 278 + 279 + // Reply metadata 280 + const parentUri = replyParent?.uri ?? record.reply?.parent?.uri; 281 + const parentDid = replyParent?.author?.did ?? (parentUri ? parseAtUri(parentUri)?.did : undefined); 282 + const { handle: parentHandle } = useDidResolution( 283 + replyParent?.author?.handle ? undefined : parentDid, 284 + ); 285 + const { record: parentProfile } = useAtProtoRecord<ProfileRecord>({ 286 + did: parentDid, 287 + collection: BLUESKY_PROFILE_COLLECTION, 288 + rkey: "self", 289 + }); 290 + const parentAvatar = parentProfile?.avatar; 291 + const parentAvatarCdnUrl = isBlobWithCdn(parentAvatar) ? parentAvatar.cdnUrl : undefined; 292 + const parentAvatarCid = parentAvatarCdnUrl ? undefined : getAvatarCid(parentProfile); 293 + const { url: parentAvatarUrl } = useBlob( 294 + parentDid, 295 + parentAvatarCid, 296 + ); 297 + const finalParentAvatarUrl = parentAvatarCdnUrl ?? parentAvatarUrl; 298 + 299 + const isReply = !!parentUri; 300 + const replyTargetHandle = replyParent?.author?.handle ?? parentHandle; 301 + 302 + const postPreview = text.slice(0, 100); 303 + const ariaLabel = text 304 + ? `Post by ${authorDisplayName ?? authorHandle ?? did}: ${postPreview}${text.length > 100 ? "..." : ""}` 305 + : `Post by ${authorDisplayName ?? authorHandle ?? did}`; 306 + 307 + return ( 308 + <div 309 + style={{ 310 + ...listStyles.rowContainer, 311 + borderBottom: hasDivider ? `1px solid var(--atproto-color-border)` : "none", 312 + }} 313 + > 314 + {isRepost && ( 315 + <div style={listStyles.repostIndicator}> 316 + {finalReposterAvatarUrl && ( 317 + <img 318 + src={finalReposterAvatarUrl} 319 + alt="" 320 + style={listStyles.repostAvatar} 321 + /> 322 + )} 323 + <svg 324 + width="16" 325 + height="16" 326 + viewBox="0 0 16 16" 327 + fill="none" 328 + style={{ flexShrink: 0 }} 329 + > 330 + <path 331 + d="M5.5 3.5L3 6L5.5 8.5M3 6H10C11.1046 6 12 6.89543 12 8V8.5M10.5 12.5L13 10L10.5 7.5M13 10H6C4.89543 10 4 9.10457 4 8V7.5" 332 + stroke="var(--atproto-color-text-secondary)" 333 + strokeWidth="1.5" 334 + strokeLinecap="round" 335 + strokeLinejoin="round" 336 + /> 337 + </svg> 338 + <span style={{ ...listStyles.repostText, color: "var(--atproto-color-text-secondary)" }}> 339 + {reposterDisplayName ?? reposterHandle ?? "Someone"} reposted 340 + </span> 341 + </div> 342 + )} 343 + 344 + {isReply && ( 345 + <div style={listStyles.replyIndicator}> 346 + <svg 347 + width="14" 348 + height="14" 349 + viewBox="0 0 14 14" 350 + fill="none" 351 + style={{ flexShrink: 0 }} 352 + > 353 + <path 354 + d="M11 7H3M3 7L7 3M3 7L7 11" 355 + stroke="#1185FE" 356 + strokeWidth="1.5" 357 + strokeLinecap="round" 358 + strokeLinejoin="round" 359 + /> 360 + </svg> 361 + <span style={{ ...listStyles.replyText, color: "var(--atproto-color-text-secondary)" }}> 362 + Replying to 363 + </span> 364 + {finalParentAvatarUrl && ( 365 + <img 366 + src={finalParentAvatarUrl} 367 + alt="" 368 + style={listStyles.replyAvatar} 369 + /> 370 + )} 371 + <span style={{ color: "#1185FE", fontWeight: 600 }}> 372 + @{replyTargetHandle ?? formatDid(parentDid ?? "")} 373 + </span> 374 + </div> 375 + )} 376 + 377 + <div style={listStyles.postContent}> 378 + <div style={listStyles.avatarContainer}> 379 + {finalAuthorAvatarUrl ? ( 380 + <img 381 + src={finalAuthorAvatarUrl} 382 + alt={authorDisplayName ?? authorHandle ?? "User avatar"} 383 + style={listStyles.avatar} 384 + /> 385 + ) : ( 386 + <div style={listStyles.avatarPlaceholder}> 387 + {(authorDisplayName ?? authorHandle ?? "?")[0].toUpperCase()} 388 + </div> 389 + )} 390 + </div> 391 + 392 + <div style={listStyles.postMain}> 393 + <div style={listStyles.postHeader}> 394 + <a 395 + href={`${blueskyAppBaseUrl}/profile/${postDid}`} 396 + target="_blank" 397 + rel="noopener noreferrer" 398 + style={{ ...listStyles.authorName, color: "var(--atproto-color-text)" }} 399 + onClick={(e) => e.stopPropagation()} 400 + > 401 + {authorDisplayName ?? authorHandle ?? formatDid(postDid)} 402 + </a> 403 + <span style={{ ...listStyles.authorHandle, color: "var(--atproto-color-text-secondary)" }}> 404 + @{authorHandle ?? formatDid(postDid)} 405 + </span> 406 + <span style={{ ...listStyles.separator, color: "var(--atproto-color-text-secondary)" }}>ยท</span> 407 + <span 408 + style={{ ...listStyles.timestamp, color: "var(--atproto-color-text-secondary)" }} 409 + title={absolute} 410 + > 411 + {relative} 412 + </span> 413 + </div> 146 414 147 - return ( 148 - <a href={href} target="_blank" rel="noopener noreferrer" style={{ ...listStyles.row, ...palette.row, borderBottom: hasDivider ? `1px solid ${palette.divider}` : 'none' }}> 149 - {relative && ( 150 - <span style={{ ...listStyles.rowTime, ...palette.rowTime }} title={absolute}> 151 - {relative} 152 - </span> 153 - )} 154 - {text && <p style={{ ...listStyles.rowBody, ...palette.rowBody }}>{text}</p>} 155 - {!text && <p style={{ ...listStyles.rowBody, ...palette.rowBody, fontStyle: 'italic' }}>No text content.</p>} 156 - </a> 157 - ); 415 + <a 416 + href={href} 417 + target="_blank" 418 + rel="noopener noreferrer" 419 + aria-label={ariaLabel} 420 + style={{ ...listStyles.postLink, color: "var(--atproto-color-text)" }} 421 + > 422 + {text && ( 423 + <p style={listStyles.postText}> 424 + <BlueskyRichText text={text} facets={record.facets} /> 425 + </p> 426 + )} 427 + {!text && ( 428 + <p style={{ ...listStyles.postText, fontStyle: "italic", color: "var(--atproto-color-text-secondary)" }}> 429 + No text content 430 + </p> 431 + )} 432 + </a> 433 + </div> 434 + </div> 435 + </div> 436 + ); 158 437 }; 159 438 160 439 function formatDid(did: string) { 161 - return did.replace(/^did:(plc:)?/, ''); 440 + return did.replace(/^did:(plc:)?/, ""); 162 441 } 163 442 164 443 function formatRelativeTime(iso: string): string { 165 - const date = new Date(iso); 166 - const diffSeconds = (date.getTime() - Date.now()) / 1000; 167 - const absSeconds = Math.abs(diffSeconds); 168 - const thresholds: Array<{ limit: number; unit: Intl.RelativeTimeFormatUnit; divisor: number }> = [ 169 - { limit: 60, unit: 'second', divisor: 1 }, 170 - { limit: 3600, unit: 'minute', divisor: 60 }, 171 - { limit: 86400, unit: 'hour', divisor: 3600 }, 172 - { limit: 604800, unit: 'day', divisor: 86400 }, 173 - { limit: 2629800, unit: 'week', divisor: 604800 }, 174 - { limit: 31557600, unit: 'month', divisor: 2629800 }, 175 - { limit: Infinity, unit: 'year', divisor: 31557600 } 176 - ]; 177 - const threshold = thresholds.find(t => absSeconds < t.limit) ?? thresholds[thresholds.length - 1]; 178 - const value = diffSeconds / threshold.divisor; 179 - const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }); 180 - return rtf.format(Math.round(value), threshold.unit); 444 + const date = new Date(iso); 445 + const diffSeconds = (date.getTime() - Date.now()) / 1000; 446 + const absSeconds = Math.abs(diffSeconds); 447 + const thresholds: Array<{ 448 + limit: number; 449 + unit: Intl.RelativeTimeFormatUnit; 450 + divisor: number; 451 + }> = [ 452 + { limit: 60, unit: "second", divisor: 1 }, 453 + { limit: 3600, unit: "minute", divisor: 60 }, 454 + { limit: 86400, unit: "hour", divisor: 3600 }, 455 + { limit: 604800, unit: "day", divisor: 86400 }, 456 + { limit: 2629800, unit: "week", divisor: 604800 }, 457 + { limit: 31557600, unit: "month", divisor: 2629800 }, 458 + { limit: Infinity, unit: "year", divisor: 31557600 }, 459 + ]; 460 + const threshold = 461 + thresholds.find((t) => absSeconds < t.limit) ?? 462 + thresholds[thresholds.length - 1]; 463 + const value = diffSeconds / threshold.divisor; 464 + const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); 465 + return rtf.format(Math.round(value), threshold.unit); 181 466 } 182 467 183 - interface ListPalette { 184 - card: { background: string; borderColor: string }; 185 - header: { borderBottomColor: string; color: string }; 186 - pageMeta: { color: string }; 187 - subtitle: { color: string }; 188 - empty: { color: string }; 189 - row: { color: string }; 190 - rowTime: { color: string }; 191 - rowBody: { color: string }; 192 - divider: string; 193 - footer: { borderTopColor: string; color: string }; 194 - navButton: { color: string; background: string }; 195 - pageChip: { color: string; borderColor: string; background: string }; 196 - pageChipActive: { color: string; background: string; borderColor: string }; 197 - loadingBar: { color: string }; 198 - } 199 468 200 469 const listStyles = { 201 - card: { 202 - borderRadius: 16, 203 - border: '1px solid transparent', 204 - boxShadow: '0 8px 18px -12px rgba(15, 23, 42, 0.25)', 205 - overflow: 'hidden', 206 - display: 'flex', 207 - flexDirection: 'column' 208 - } satisfies React.CSSProperties, 209 - header: { 210 - display: 'flex', 211 - alignItems: 'center', 212 - justifyContent: 'space-between', 213 - padding: '14px 18px', 214 - fontSize: 14, 215 - fontWeight: 500, 216 - borderBottom: '1px solid transparent' 217 - } satisfies React.CSSProperties, 218 - headerInfo: { 219 - display: 'flex', 220 - alignItems: 'center', 221 - gap: 12 222 - } satisfies React.CSSProperties, 223 - headerIcon: { 224 - width: 28, 225 - height: 28, 226 - display: 'flex', 227 - alignItems: 'center', 228 - justifyContent: 'center', 229 - //background: 'rgba(17, 133, 254, 0.14)', 230 - borderRadius: '50%' 231 - } satisfies React.CSSProperties, 232 - headerText: { 233 - display: 'flex', 234 - flexDirection: 'column', 235 - gap: 2 236 - } satisfies React.CSSProperties, 237 - title: { 238 - fontSize: 15, 239 - fontWeight: 600 240 - } satisfies React.CSSProperties, 241 - subtitle: { 242 - fontSize: 12, 243 - fontWeight: 500 244 - } satisfies React.CSSProperties, 245 - pageMeta: { 246 - fontSize: 12 247 - } satisfies React.CSSProperties, 248 - items: { 249 - display: 'flex', 250 - flexDirection: 'column' 251 - } satisfies React.CSSProperties, 252 - empty: { 253 - padding: '24px 18px', 254 - fontSize: 13, 255 - textAlign: 'center' 256 - } satisfies React.CSSProperties, 257 - row: { 258 - padding: '18px', 259 - textDecoration: 'none', 260 - display: 'flex', 261 - flexDirection: 'column', 262 - gap: 6, 263 - transition: 'background-color 120ms ease' 264 - } satisfies React.CSSProperties, 265 - rowHeader: { 266 - display: 'flex', 267 - gap: 6, 268 - alignItems: 'baseline', 269 - fontSize: 13 270 - } satisfies React.CSSProperties, 271 - rowTime: { 272 - fontSize: 12, 273 - fontWeight: 500 274 - } satisfies React.CSSProperties, 275 - rowBody: { 276 - margin: 0, 277 - whiteSpace: 'pre-wrap', 278 - fontSize: 14, 279 - lineHeight: 1.45 280 - } satisfies React.CSSProperties, 281 - footer: { 282 - display: 'flex', 283 - alignItems: 'center', 284 - justifyContent: 'space-between', 285 - padding: '12px 18px', 286 - borderTop: '1px solid transparent', 287 - fontSize: 13 288 - } satisfies React.CSSProperties, 289 - navButton: { 290 - border: 'none', 291 - borderRadius: 999, 292 - padding: '6px 12px', 293 - fontSize: 13, 294 - fontWeight: 500, 295 - background: 'transparent', 296 - display: 'flex', 297 - alignItems: 'center', 298 - gap: 4, 299 - transition: 'background-color 120ms ease' 300 - } satisfies React.CSSProperties, 301 - pageChips: { 302 - display: 'flex', 303 - gap: 6, 304 - alignItems: 'center' 305 - } satisfies React.CSSProperties, 306 - pageChip: { 307 - padding: '4px 10px', 308 - borderRadius: 999, 309 - fontSize: 13, 310 - border: '1px solid transparent' 311 - } satisfies React.CSSProperties, 312 - pageChipActive: { 313 - padding: '4px 10px', 314 - borderRadius: 999, 315 - fontSize: 13, 316 - fontWeight: 600, 317 - border: '1px solid transparent' 318 - } satisfies React.CSSProperties, 319 - loadingBar: { 320 - padding: '4px 18px 14px', 321 - fontSize: 12, 322 - textAlign: 'right', 323 - color: '#64748b' 324 - } satisfies React.CSSProperties 325 - }; 326 - 327 - const lightPalette: ListPalette = { 328 - card: { 329 - background: '#ffffff', 330 - borderColor: '#e2e8f0' 331 - }, 332 - header: { 333 - borderBottomColor: '#e2e8f0', 334 - color: '#0f172a' 335 - }, 336 - pageMeta: { 337 - color: '#64748b' 338 - }, 339 - subtitle: { 340 - color: '#475569' 341 - }, 342 - empty: { 343 - color: '#64748b' 344 - }, 345 - row: { 346 - color: '#0f172a' 347 - }, 348 - rowTime: { 349 - color: '#94a3b8' 350 - }, 351 - rowBody: { 352 - color: '#0f172a' 353 - }, 354 - divider: '#e2e8f0', 355 - footer: { 356 - borderTopColor: '#e2e8f0', 357 - color: '#0f172a' 358 - }, 359 - navButton: { 360 - color: '#0f172a', 361 - background: '#f1f5f9' 362 - }, 363 - pageChip: { 364 - color: '#475569', 365 - borderColor: '#e2e8f0', 366 - background: '#ffffff' 367 - }, 368 - pageChipActive: { 369 - color: '#ffffff', 370 - background: '#0f172a', 371 - borderColor: '#0f172a' 372 - }, 373 - loadingBar: { 374 - color: '#64748b' 375 - } 376 - }; 377 - 378 - const darkPalette: ListPalette = { 379 - card: { 380 - background: '#0f172a', 381 - borderColor: '#1e293b' 382 - }, 383 - header: { 384 - borderBottomColor: '#1e293b', 385 - color: '#e2e8f0' 386 - }, 387 - pageMeta: { 388 - color: '#94a3b8' 389 - }, 390 - subtitle: { 391 - color: '#94a3b8' 392 - }, 393 - empty: { 394 - color: '#94a3b8' 395 - }, 396 - row: { 397 - color: '#e2e8f0' 398 - }, 399 - rowTime: { 400 - color: '#94a3b8' 401 - }, 402 - rowBody: { 403 - color: '#e2e8f0' 404 - }, 405 - divider: '#1e293b', 406 - footer: { 407 - borderTopColor: '#1e293b', 408 - color: '#e2e8f0' 409 - }, 410 - navButton: { 411 - color: '#e2e8f0', 412 - background: '#111c31' 413 - }, 414 - pageChip: { 415 - color: '#cbd5f5', 416 - borderColor: '#1e293b', 417 - background: '#0f172a' 418 - }, 419 - pageChipActive: { 420 - color: '#0f172a', 421 - background: '#38bdf8', 422 - borderColor: '#38bdf8' 423 - }, 424 - loadingBar: { 425 - color: '#94a3b8' 426 - } 470 + card: { 471 + borderRadius: 16, 472 + borderWidth: "1px", 473 + borderStyle: "solid", 474 + borderColor: "transparent", 475 + boxShadow: "0 8px 18px -12px rgba(15, 23, 42, 0.25)", 476 + overflow: "hidden", 477 + display: "flex", 478 + flexDirection: "column", 479 + } satisfies React.CSSProperties, 480 + header: { 481 + display: "flex", 482 + alignItems: "center", 483 + justifyContent: "space-between", 484 + padding: "14px 18px", 485 + fontSize: 14, 486 + fontWeight: 500, 487 + borderBottom: "1px solid transparent", 488 + } satisfies React.CSSProperties, 489 + headerInfo: { 490 + display: "flex", 491 + alignItems: "center", 492 + gap: 12, 493 + } satisfies React.CSSProperties, 494 + headerIcon: { 495 + width: 28, 496 + height: 28, 497 + display: "flex", 498 + alignItems: "center", 499 + justifyContent: "center", 500 + borderRadius: "50%", 501 + } satisfies React.CSSProperties, 502 + headerText: { 503 + display: "flex", 504 + flexDirection: "column", 505 + gap: 2, 506 + } satisfies React.CSSProperties, 507 + title: { 508 + fontSize: 15, 509 + fontWeight: 600, 510 + } satisfies React.CSSProperties, 511 + subtitle: { 512 + fontSize: 12, 513 + fontWeight: 500, 514 + } satisfies React.CSSProperties, 515 + pageMeta: { 516 + fontSize: 12, 517 + } satisfies React.CSSProperties, 518 + items: { 519 + display: "flex", 520 + flexDirection: "column", 521 + } satisfies React.CSSProperties, 522 + empty: { 523 + padding: "24px 18px", 524 + fontSize: 13, 525 + textAlign: "center", 526 + } satisfies React.CSSProperties, 527 + rowContainer: { 528 + padding: "16px", 529 + display: "flex", 530 + flexDirection: "column", 531 + gap: 8, 532 + transition: "background-color 120ms ease", 533 + position: "relative", 534 + } satisfies React.CSSProperties, 535 + repostIndicator: { 536 + display: "flex", 537 + alignItems: "center", 538 + gap: 8, 539 + fontSize: 13, 540 + fontWeight: 500, 541 + paddingLeft: 8, 542 + marginBottom: 4, 543 + } satisfies React.CSSProperties, 544 + repostAvatar: { 545 + width: 16, 546 + height: 16, 547 + borderRadius: "50%", 548 + objectFit: "cover", 549 + } satisfies React.CSSProperties, 550 + repostText: { 551 + fontSize: 13, 552 + fontWeight: 500, 553 + } satisfies React.CSSProperties, 554 + replyIndicator: { 555 + display: "flex", 556 + alignItems: "center", 557 + gap: 8, 558 + fontSize: 13, 559 + fontWeight: 500, 560 + paddingLeft: 8, 561 + marginBottom: 4, 562 + } satisfies React.CSSProperties, 563 + replyAvatar: { 564 + width: 16, 565 + height: 16, 566 + borderRadius: "50%", 567 + objectFit: "cover", 568 + } satisfies React.CSSProperties, 569 + replyText: { 570 + fontSize: 13, 571 + fontWeight: 500, 572 + } satisfies React.CSSProperties, 573 + postContent: { 574 + display: "flex", 575 + gap: 12, 576 + } satisfies React.CSSProperties, 577 + avatarContainer: { 578 + flexShrink: 0, 579 + } satisfies React.CSSProperties, 580 + avatar: { 581 + width: 48, 582 + height: 48, 583 + borderRadius: "50%", 584 + objectFit: "cover", 585 + } satisfies React.CSSProperties, 586 + avatarPlaceholder: { 587 + width: 48, 588 + height: 48, 589 + borderRadius: "50%", 590 + background: "var(--atproto-color-bg-elevated)", 591 + color: "var(--atproto-color-text-secondary)", 592 + display: "flex", 593 + alignItems: "center", 594 + justifyContent: "center", 595 + fontSize: 18, 596 + fontWeight: 600, 597 + } satisfies React.CSSProperties, 598 + postMain: { 599 + flex: 1, 600 + minWidth: 0, 601 + display: "flex", 602 + flexDirection: "column", 603 + gap: 6, 604 + } satisfies React.CSSProperties, 605 + postHeader: { 606 + display: "flex", 607 + alignItems: "baseline", 608 + gap: 6, 609 + flexWrap: "wrap", 610 + } satisfies React.CSSProperties, 611 + authorName: { 612 + fontWeight: 700, 613 + fontSize: 15, 614 + textDecoration: "none", 615 + maxWidth: "200px", 616 + overflow: "hidden", 617 + textOverflow: "ellipsis", 618 + whiteSpace: "nowrap", 619 + } satisfies React.CSSProperties, 620 + authorHandle: { 621 + fontSize: 15, 622 + fontWeight: 400, 623 + maxWidth: "150px", 624 + overflow: "hidden", 625 + textOverflow: "ellipsis", 626 + whiteSpace: "nowrap", 627 + } satisfies React.CSSProperties, 628 + separator: { 629 + fontSize: 15, 630 + fontWeight: 400, 631 + } satisfies React.CSSProperties, 632 + timestamp: { 633 + fontSize: 15, 634 + fontWeight: 400, 635 + } satisfies React.CSSProperties, 636 + postLink: { 637 + textDecoration: "none", 638 + display: "block", 639 + } satisfies React.CSSProperties, 640 + postText: { 641 + margin: 0, 642 + whiteSpace: "pre-wrap", 643 + fontSize: 15, 644 + lineHeight: 1.5, 645 + wordBreak: "break-word", 646 + } satisfies React.CSSProperties, 647 + footer: { 648 + display: "flex", 649 + alignItems: "center", 650 + justifyContent: "space-between", 651 + padding: "12px 18px", 652 + borderTop: "1px solid transparent", 653 + fontSize: 13, 654 + } satisfies React.CSSProperties, 655 + pageChips: { 656 + display: "flex", 657 + gap: 6, 658 + alignItems: "center", 659 + } satisfies React.CSSProperties, 660 + pageChip: { 661 + padding: "4px 10px", 662 + borderRadius: 999, 663 + fontSize: 13, 664 + borderWidth: "1px", 665 + borderStyle: "solid", 666 + borderColor: "transparent", 667 + } satisfies React.CSSProperties, 668 + pageChipActive: { 669 + padding: "4px 10px", 670 + borderRadius: 999, 671 + fontSize: 13, 672 + fontWeight: 600, 673 + borderWidth: "1px", 674 + borderStyle: "solid", 675 + borderColor: "transparent", 676 + } satisfies React.CSSProperties, 677 + pageButton: { 678 + border: "none", 679 + borderRadius: 999, 680 + padding: "6px 12px", 681 + fontSize: 13, 682 + fontWeight: 500, 683 + background: "transparent", 684 + display: "flex", 685 + alignItems: "center", 686 + gap: 4, 687 + transition: "background-color 120ms ease", 688 + } satisfies React.CSSProperties, 689 + loadingBar: { 690 + padding: "4px 18px 14px", 691 + fontSize: 12, 692 + textAlign: "right", 693 + color: "#64748b", 694 + } satisfies React.CSSProperties, 427 695 }; 428 696 429 697 export default BlueskyPostList;
+129 -88
lib/components/BlueskyProfile.tsx
··· 1 - import React from 'react'; 2 - import { AtProtoRecord } from '../core/AtProtoRecord'; 3 - import { BlueskyProfileRenderer } from '../renderers/BlueskyProfileRenderer'; 4 - import type { ProfileRecord } from '../types/bluesky'; 5 - import { useBlob } from '../hooks/useBlob'; 6 - import { getAvatarCid } from '../utils/profile'; 7 - import { useDidResolution } from '../hooks/useDidResolution'; 8 - import { formatDidForLabel } from '../utils/at-uri'; 1 + import React from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { BlueskyProfileRenderer } from "../renderers/BlueskyProfileRenderer"; 4 + import type { ProfileRecord } from "../types/bluesky"; 5 + import { useBlob } from "../hooks/useBlob"; 6 + import { getAvatarCid } from "../utils/profile"; 7 + import { useDidResolution } from "../hooks/useDidResolution"; 8 + import { formatDidForLabel } from "../utils/at-uri"; 9 + import { isBlobWithCdn } from "../utils/blob"; 9 10 10 11 /** 11 12 * Props used to render a Bluesky actor profile record. 12 13 */ 13 14 export interface BlueskyProfileProps { 14 - /** 15 - * DID of the target actor whose profile should be loaded. 16 - */ 17 - did: string; 18 - /** 19 - * Record key within the profile collection. Typically `'self'`. 20 - */ 21 - rkey?: string; 22 - /** 23 - * Optional renderer override for custom presentation. 24 - */ 25 - renderer?: React.ComponentType<BlueskyProfileRendererInjectedProps>; 26 - /** 27 - * Fallback node shown before a request begins yielding data. 28 - */ 29 - fallback?: React.ReactNode; 30 - /** 31 - * Loading indicator shown during in-flight fetches. 32 - */ 33 - loadingIndicator?: React.ReactNode; 34 - /** 35 - * Pre-resolved handle to display when available externally. 36 - */ 37 - handle?: string; 38 - /** 39 - * Preferred color scheme forwarded to renderer implementations. 40 - */ 41 - colorScheme?: 'light' | 'dark' | 'system'; 15 + /** 16 + * DID of the target actor whose profile should be loaded. 17 + */ 18 + did: string; 19 + /** 20 + * Record key within the profile collection. Typically `'self'`. 21 + * Optional when `record` is provided. 22 + */ 23 + rkey?: string; 24 + /** 25 + * Prefetched profile record. When provided, skips fetching the profile from the network. 26 + */ 27 + record?: ProfileRecord; 28 + /** 29 + * Optional renderer override for custom presentation. 30 + */ 31 + renderer?: React.ComponentType<BlueskyProfileRendererInjectedProps>; 32 + /** 33 + * Fallback node shown before a request begins yielding data. 34 + */ 35 + fallback?: React.ReactNode; 36 + /** 37 + * Loading indicator shown during in-flight fetches. 38 + */ 39 + loadingIndicator?: React.ReactNode; 40 + /** 41 + * Pre-resolved handle to display when available externally. 42 + */ 43 + handle?: string; 44 + 42 45 } 43 46 44 47 /** 45 48 * Props injected into custom profile renderer implementations. 46 49 */ 47 50 export type BlueskyProfileRendererInjectedProps = { 48 - /** 49 - * Loaded profile record value. 50 - */ 51 - record: ProfileRecord; 52 - /** 53 - * Indicates whether the record is currently being fetched. 54 - */ 55 - loading: boolean; 56 - /** 57 - * Any error encountered while fetching the profile. 58 - */ 59 - error?: Error; 60 - /** 61 - * DID associated with the profile. 62 - */ 63 - did: string; 64 - /** 65 - * Human-readable handle for the DID, when known. 66 - */ 67 - handle?: string; 68 - /** 69 - * Blob URL for the user's avatar, when available. 70 - */ 71 - avatarUrl?: string; 72 - /** 73 - * Preferred color scheme for theming downstream components. 74 - */ 75 - colorScheme?: 'light' | 'dark' | 'system'; 51 + /** 52 + * Loaded profile record value. 53 + */ 54 + record: ProfileRecord; 55 + /** 56 + * Indicates whether the record is currently being fetched. 57 + */ 58 + loading: boolean; 59 + /** 60 + * Any error encountered while fetching the profile. 61 + */ 62 + error?: Error; 63 + /** 64 + * DID associated with the profile. 65 + */ 66 + did: string; 67 + /** 68 + * Human-readable handle for the DID, when known. 69 + */ 70 + handle?: string; 71 + /** 72 + * Blob URL for the user's avatar, when available. 73 + */ 74 + avatarUrl?: string; 75 + 76 76 }; 77 77 78 78 /** NSID for the canonical Bluesky profile collection. */ 79 - export const BLUESKY_PROFILE_COLLECTION = 'app.bsky.actor.profile'; 79 + export const BLUESKY_PROFILE_COLLECTION = "app.bsky.actor.profile"; 80 80 81 81 /** 82 82 * Fetches and renders a Bluesky actor profile, optionally injecting custom presentation ··· 88 88 * @param fallback - Node rendered prior to loading state initialization. 89 89 * @param loadingIndicator - Node rendered while the profile request is in-flight. 90 90 * @param handle - Optional pre-resolved handle to display. 91 - * @param colorScheme - Preferred color scheme forwarded to the renderer. 92 91 * @returns A rendered profile component with loading/error states handled. 93 92 */ 94 - export const BlueskyProfile: React.FC<BlueskyProfileProps> = ({ did: handleOrDid, rkey = 'self', renderer, fallback, loadingIndicator, handle, colorScheme }) => { 95 - const Component: React.ComponentType<BlueskyProfileRendererInjectedProps> = renderer ?? ((props) => <BlueskyProfileRenderer {...props} />); 96 - const { did, handle: resolvedHandle } = useDidResolution(handleOrDid); 97 - const repoIdentifier = did ?? handleOrDid; 98 - const effectiveHandle = handle ?? resolvedHandle ?? (handleOrDid.startsWith('did:') ? formatDidForLabel(repoIdentifier) : handleOrDid); 93 + export const BlueskyProfile: React.FC<BlueskyProfileProps> = React.memo(({ 94 + did: handleOrDid, 95 + rkey = "self", 96 + record, 97 + renderer, 98 + fallback, 99 + loadingIndicator, 100 + handle, 101 + }) => { 102 + const Component: React.ComponentType<BlueskyProfileRendererInjectedProps> = 103 + renderer ?? ((props) => <BlueskyProfileRenderer {...props} />); 104 + const { did, handle: resolvedHandle } = useDidResolution(handleOrDid); 105 + const repoIdentifier = did ?? handleOrDid; 106 + const effectiveHandle = 107 + handle ?? 108 + resolvedHandle ?? 109 + (handleOrDid.startsWith("did:") 110 + ? formatDidForLabel(repoIdentifier) 111 + : handleOrDid); 99 112 100 - const Wrapped: React.FC<{ record: ProfileRecord; loading: boolean; error?: Error }> = (props) => { 101 - const avatarCid = getAvatarCid(props.record); 102 - const { url: avatarUrl } = useBlob(repoIdentifier, avatarCid); 103 - return <Component {...props} did={repoIdentifier} handle={effectiveHandle} avatarUrl={avatarUrl} colorScheme={colorScheme} />; 104 - }; 105 - return ( 106 - <AtProtoRecord<ProfileRecord> 107 - did={repoIdentifier} 108 - collection={BLUESKY_PROFILE_COLLECTION} 109 - rkey={rkey} 110 - renderer={Wrapped} 111 - fallback={fallback} 112 - loadingIndicator={loadingIndicator} 113 - /> 114 - ); 115 - }; 113 + const Wrapped: React.FC<{ 114 + record: ProfileRecord; 115 + loading: boolean; 116 + error?: Error; 117 + }> = (props) => { 118 + // Check if the avatar has a CDN URL from the appview (preferred) 119 + const avatar = props.record?.avatar; 120 + const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined; 121 + const avatarCid = avatarCdnUrl ? undefined : getAvatarCid(props.record); 122 + const { url: avatarUrlFromBlob } = useBlob(repoIdentifier, avatarCid); 123 + const avatarUrl = avatarCdnUrl || avatarUrlFromBlob; 124 + 125 + return ( 126 + <Component 127 + {...props} 128 + did={repoIdentifier} 129 + handle={effectiveHandle} 130 + avatarUrl={avatarUrl} 131 + /> 132 + ); 133 + }; 116 134 117 - export default BlueskyProfile; 135 + if (record !== undefined) { 136 + return ( 137 + <AtProtoRecord<ProfileRecord> 138 + record={record} 139 + renderer={Wrapped} 140 + fallback={fallback} 141 + loadingIndicator={loadingIndicator} 142 + /> 143 + ); 144 + } 145 + 146 + return ( 147 + <AtProtoRecord<ProfileRecord> 148 + did={repoIdentifier} 149 + collection={BLUESKY_PROFILE_COLLECTION} 150 + rkey={rkey} 151 + renderer={Wrapped} 152 + fallback={fallback} 153 + loadingIndicator={loadingIndicator} 154 + /> 155 + ); 156 + }); 157 + 158 + export default BlueskyProfile;
+99 -84
lib/components/BlueskyQuotePost.tsx
··· 1 - import React, { memo, useMemo, type NamedExoticComponent } from 'react'; 2 - import { BlueskyPost, type BlueskyPostRendererInjectedProps, BLUESKY_POST_COLLECTION } from './BlueskyPost'; 3 - import { BlueskyPostRenderer } from '../renderers/BlueskyPostRenderer'; 4 - import { parseAtUri } from '../utils/at-uri'; 1 + import React, { memo, useMemo, type NamedExoticComponent } from "react"; 2 + import { 3 + BlueskyPost, 4 + type BlueskyPostRendererInjectedProps, 5 + BLUESKY_POST_COLLECTION, 6 + } from "./BlueskyPost"; 7 + import { BlueskyPostRenderer } from "../renderers/BlueskyPostRenderer"; 8 + import { parseAtUri } from "../utils/at-uri"; 5 9 6 10 /** 7 11 * Props for rendering a Bluesky post that quotes another Bluesky post. 8 12 */ 9 13 export interface BlueskyQuotePostProps { 10 - /** 11 - * DID of the repository that owns the parent post. 12 - */ 13 - did: string; 14 - /** 15 - * Record key of the parent post. 16 - */ 17 - rkey: string; 18 - /** 19 - * Preferred color scheme propagated to nested renders. 20 - */ 21 - colorScheme?: 'light' | 'dark' | 'system'; 22 - /** 23 - * Custom renderer override applied to the parent post. 24 - */ 25 - renderer?: React.ComponentType<BlueskyPostRendererInjectedProps>; 26 - /** 27 - * Fallback content rendered before any request completes. 28 - */ 29 - fallback?: React.ReactNode; 30 - /** 31 - * Loading indicator rendered while the parent post is resolving. 32 - */ 33 - loadingIndicator?: React.ReactNode; 34 - /** 35 - * Controls whether the Bluesky icon is shown. Defaults to `true`. 36 - */ 37 - showIcon?: boolean; 38 - /** 39 - * Placement for the Bluesky icon. Defaults to `'timestamp'`. 40 - */ 41 - iconPlacement?: 'cardBottomRight' | 'timestamp' | 'linkInline'; 14 + /** 15 + * DID of the repository that owns the parent post. 16 + */ 17 + did: string; 18 + /** 19 + * Record key of the parent post. 20 + */ 21 + rkey: string; 22 + /** 23 + * Custom renderer override applied to the parent post. 24 + */ 25 + renderer?: React.ComponentType<BlueskyPostRendererInjectedProps>; 26 + /** 27 + * Fallback content rendered before any request completes. 28 + */ 29 + fallback?: React.ReactNode; 30 + /** 31 + * Loading indicator rendered while the parent post is resolving. 32 + */ 33 + loadingIndicator?: React.ReactNode; 34 + /** 35 + * Controls whether the Bluesky icon is shown. Defaults to `true`. 36 + */ 37 + showIcon?: boolean; 38 + /** 39 + * Placement for the Bluesky icon. Defaults to `'timestamp'`. 40 + */ 41 + iconPlacement?: "cardBottomRight" | "timestamp" | "linkInline"; 42 42 } 43 43 44 44 /** ··· 46 46 * 47 47 * @param did - DID that owns the quoted parent post. 48 48 * @param rkey - Record key identifying the parent post. 49 - * @param colorScheme - Preferred color scheme for both parent and quoted posts. 50 49 * @param renderer - Optional renderer override applied to the parent post. 51 50 * @param fallback - Node rendered before parent post data loads. 52 51 * @param loadingIndicator - Node rendered while the parent post request is in-flight. ··· 54 53 * @param iconPlacement - Placement location for the icon. Defaults to `'timestamp'`. 55 54 * @returns A `BlueskyPost` element configured with an augmented renderer. 56 55 */ 57 - const BlueskyQuotePostComponent: React.FC<BlueskyQuotePostProps> = ({ did, rkey, colorScheme, renderer, fallback, loadingIndicator, showIcon = true, iconPlacement = 'timestamp' }) => { 58 - const BaseRenderer = renderer ?? BlueskyPostRenderer; 59 - const Renderer = useMemo(() => { 60 - const QuoteRenderer: React.FC<BlueskyPostRendererInjectedProps> = (props) => { 61 - const resolvedColorScheme = props.colorScheme ?? colorScheme; 62 - const embedSource = props.record.embed as QuoteRecordEmbed | undefined; 63 - const embedNode = useMemo( 64 - () => createQuoteEmbed(embedSource, resolvedColorScheme), 65 - [embedSource, resolvedColorScheme] 66 - ); 67 - return <BaseRenderer {...props} embed={embedNode} />; 68 - }; 69 - QuoteRenderer.displayName = 'BlueskyQuotePostRenderer'; 70 - const MemoizedQuoteRenderer = memo(QuoteRenderer); 71 - MemoizedQuoteRenderer.displayName = 'BlueskyQuotePostRenderer'; 72 - return MemoizedQuoteRenderer; 73 - }, [BaseRenderer, colorScheme]); 56 + const BlueskyQuotePostComponent: React.FC<BlueskyQuotePostProps> = ({ 57 + did, 58 + rkey, 59 + renderer, 60 + fallback, 61 + loadingIndicator, 62 + showIcon = true, 63 + iconPlacement = "timestamp", 64 + }) => { 65 + const BaseRenderer = renderer ?? BlueskyPostRenderer; 66 + const Renderer = useMemo(() => { 67 + const QuoteRenderer: React.FC<BlueskyPostRendererInjectedProps> = ( 68 + props, 69 + ) => { 70 + const embedSource = props.record.embed as 71 + | QuoteRecordEmbed 72 + | undefined; 73 + const embedNode = useMemo( 74 + () => createQuoteEmbed(embedSource), 75 + [embedSource], 76 + ); 77 + return <BaseRenderer isQuotePost={true} {...props} embed={embedNode} />; 78 + }; 79 + QuoteRenderer.displayName = "BlueskyQuotePostRenderer"; 80 + const MemoizedQuoteRenderer = memo(QuoteRenderer); 81 + MemoizedQuoteRenderer.displayName = "BlueskyQuotePostRenderer"; 82 + return MemoizedQuoteRenderer; 83 + }, [BaseRenderer]); 74 84 75 - return ( 76 - <BlueskyPost 77 - did={did} 78 - rkey={rkey} 79 - colorScheme={colorScheme} 80 - renderer={Renderer} 81 - fallback={fallback} 82 - loadingIndicator={loadingIndicator} 83 - showIcon={showIcon} 84 - iconPlacement={iconPlacement} 85 - /> 86 - ); 85 + return ( 86 + <BlueskyPost 87 + did={did} 88 + rkey={rkey} 89 + renderer={Renderer} 90 + fallback={fallback} 91 + loadingIndicator={loadingIndicator} 92 + showIcon={showIcon} 93 + iconPlacement={iconPlacement} 94 + /> 95 + ); 87 96 }; 88 97 89 - BlueskyQuotePostComponent.displayName = 'BlueskyQuotePost'; 98 + BlueskyQuotePostComponent.displayName = "BlueskyQuotePost"; 90 99 91 - export const BlueskyQuotePost: NamedExoticComponent<BlueskyQuotePostProps> = memo(BlueskyQuotePostComponent); 92 - BlueskyQuotePost.displayName = 'BlueskyQuotePost'; 100 + export const BlueskyQuotePost: NamedExoticComponent<BlueskyQuotePostProps> = 101 + memo(BlueskyQuotePostComponent); 102 + BlueskyQuotePost.displayName = "BlueskyQuotePost"; 93 103 94 104 /** 95 105 * Builds the quoted post embed node when the parent record contains a record embed. 96 106 * 97 107 * @param embed - Embed payload containing a possible quote reference. 98 - * @param colorScheme - Desired visual theme for the nested quote. 99 108 * @returns A nested `BlueskyPost` or `null` if no compatible embed exists. 100 109 */ 101 110 type QuoteRecordEmbed = { $type?: string; record?: { uri?: string } }; 102 111 103 - function createQuoteEmbed(embed: QuoteRecordEmbed | undefined, colorScheme?: 'light' | 'dark' | 'system') { 104 - if (!embed || embed.$type !== 'app.bsky.embed.record') return null; 105 - const quoted = embed.record; 106 - const quotedUri = quoted?.uri; 107 - const parsed = parseAtUri(quotedUri); 108 - if (!parsed || parsed.collection !== BLUESKY_POST_COLLECTION) return null; 109 - return ( 110 - <div style={quoteWrapperStyle}> 111 - <BlueskyPost did={parsed.did} rkey={parsed.rkey} colorScheme={colorScheme} showIcon={false} /> 112 - </div> 113 - ); 112 + function createQuoteEmbed( 113 + embed: QuoteRecordEmbed | undefined, 114 + ) { 115 + if (!embed || embed.$type !== "app.bsky.embed.record") return null; 116 + const quoted = embed.record; 117 + const quotedUri = quoted?.uri; 118 + const parsed = parseAtUri(quotedUri); 119 + if (!parsed || parsed.collection !== BLUESKY_POST_COLLECTION) return null; 120 + return ( 121 + <div style={quoteWrapperStyle}> 122 + <BlueskyPost 123 + did={parsed.did} 124 + rkey={parsed.rkey} 125 + showIcon={false} 126 + /> 127 + </div> 128 + ); 114 129 } 115 130 116 131 const quoteWrapperStyle: React.CSSProperties = { 117 - display: 'flex', 118 - flexDirection: 'column', 119 - gap: 8 132 + display: "flex", 133 + flexDirection: "column", 134 + gap: 8, 120 135 }; 121 136 122 137 export default BlueskyQuotePost;
-116
lib/components/ColorSchemeToggle.tsx
··· 1 - import React from 'react'; 2 - import type { ColorSchemePreference } from '../hooks/useColorScheme'; 3 - 4 - /** 5 - * Props for the `ColorSchemeToggle` segmented control. 6 - */ 7 - export interface ColorSchemeToggleProps { 8 - /** 9 - * Current color scheme preference selection. 10 - */ 11 - value: ColorSchemePreference; 12 - /** 13 - * Change handler invoked when the user selects a new scheme. 14 - */ 15 - onChange: (value: ColorSchemePreference) => void; 16 - /** 17 - * Theme used to style the control itself; defaults to `'light'`. 18 - */ 19 - scheme?: 'light' | 'dark'; 20 - } 21 - 22 - const options: Array<{ label: string; value: ColorSchemePreference; description: string }> = [ 23 - { label: 'System', value: 'system', description: 'Follow OS preference' }, 24 - { label: 'Light', value: 'light', description: 'Always light mode' }, 25 - { label: 'Dark', value: 'dark', description: 'Always dark mode' } 26 - ]; 27 - 28 - /** 29 - * A button group that lets users choose between light, dark, or system color modes. 30 - * 31 - * @param value - Current scheme selection displayed as active. 32 - * @param onChange - Callback fired when a new option is selected. 33 - * @param scheme - Theme used to style the control itself. Defaults to `'light'`. 34 - * @returns A fully keyboard-accessible toggle rendered as a radio group. 35 - */ 36 - export const ColorSchemeToggle: React.FC<ColorSchemeToggleProps> = ({ value, onChange, scheme = 'light' }) => { 37 - const palette = scheme === 'dark' ? darkTheme : lightTheme; 38 - 39 - return ( 40 - <div aria-label="Color scheme" role="radiogroup" style={{ ...containerStyle, ...palette.container }}> 41 - {options.map(option => { 42 - const isActive = option.value === value; 43 - const activeStyles = isActive ? palette.active : undefined; 44 - return ( 45 - <button 46 - key={option.value} 47 - role="radio" 48 - aria-checked={isActive} 49 - type="button" 50 - onClick={() => onChange(option.value)} 51 - style={{ 52 - ...buttonStyle, 53 - ...palette.button, 54 - ...(activeStyles ?? {}) 55 - }} 56 - title={option.description} 57 - > 58 - {option.label} 59 - </button> 60 - ); 61 - })} 62 - </div> 63 - ); 64 - }; 65 - 66 - const containerStyle: React.CSSProperties = { 67 - display: 'inline-flex', 68 - borderRadius: 999, 69 - padding: 4, 70 - gap: 4, 71 - border: '1px solid transparent', 72 - background: '#f8fafc' 73 - }; 74 - 75 - const buttonStyle: React.CSSProperties = { 76 - border: '1px solid transparent', 77 - borderRadius: 999, 78 - padding: '4px 12px', 79 - fontSize: 12, 80 - fontWeight: 500, 81 - cursor: 'pointer', 82 - background: 'transparent', 83 - transition: 'background-color 160ms ease, border-color 160ms ease, color 160ms ease' 84 - }; 85 - 86 - const lightTheme = { 87 - container: { 88 - borderColor: '#e2e8f0', 89 - background: 'rgba(241, 245, 249, 0.8)' 90 - }, 91 - button: { 92 - color: '#334155' 93 - }, 94 - active: { 95 - background: '#2563eb', 96 - borderColor: '#2563eb', 97 - color: '#f8fafc' 98 - } 99 - } satisfies Record<string, React.CSSProperties>; 100 - 101 - const darkTheme = { 102 - container: { 103 - borderColor: '#2e3540ff', 104 - background: 'rgba(30, 38, 49, 0.6)' 105 - }, 106 - button: { 107 - color: '#e2e8f0' 108 - }, 109 - active: { 110 - background: '#38bdf8', 111 - borderColor: '#38bdf8', 112 - color: '#020617' 113 - } 114 - } satisfies Record<string, React.CSSProperties>; 115 - 116 - export default ColorSchemeToggle;
+143
lib/components/CurrentlyPlaying.tsx
··· 1 + import React from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { CurrentlyPlayingRenderer } from "../renderers/CurrentlyPlayingRenderer"; 4 + import { useDidResolution } from "../hooks/useDidResolution"; 5 + import type { TealActorStatusRecord } from "../types/teal"; 6 + 7 + /** 8 + * Props for rendering teal.fm currently playing status. 9 + */ 10 + export interface CurrentlyPlayingProps { 11 + /** DID of the user whose currently playing status to display. */ 12 + did: string; 13 + /** Record key within the `fm.teal.alpha.actor.status` collection (usually "self"). */ 14 + rkey?: string; 15 + /** Prefetched teal.fm status record. When provided, skips fetching from the network. */ 16 + record?: TealActorStatusRecord; 17 + /** Optional renderer override for custom presentation. */ 18 + renderer?: React.ComponentType<CurrentlyPlayingRendererInjectedProps>; 19 + /** Fallback node displayed before loading begins. */ 20 + fallback?: React.ReactNode; 21 + /** Indicator node shown while data is loading. */ 22 + loadingIndicator?: React.ReactNode; 23 + /** Preferred color scheme for theming. */ 24 + colorScheme?: "light" | "dark" | "system"; 25 + /** Auto-refresh music data and album art. When true, refreshes every 15 seconds. Defaults to true. */ 26 + autoRefresh?: boolean; 27 + /** Refresh interval in milliseconds. Defaults to 15000 (15 seconds). Only used when autoRefresh is true. */ 28 + refreshInterval?: number; 29 + } 30 + 31 + /** 32 + * Values injected into custom currently playing renderer implementations. 33 + */ 34 + export type CurrentlyPlayingRendererInjectedProps = { 35 + /** Loaded teal.fm status record value. */ 36 + record: TealActorStatusRecord; 37 + /** Indicates whether the record is currently loading. */ 38 + loading: boolean; 39 + /** Fetch error, if any. */ 40 + error?: Error; 41 + /** Preferred color scheme for downstream components. */ 42 + colorScheme?: "light" | "dark" | "system"; 43 + /** DID associated with the record. */ 44 + did: string; 45 + /** Record key for the status. */ 46 + rkey: string; 47 + /** Label to display. */ 48 + label?: string; 49 + /** Handle to display in not listening state */ 50 + handle?: string; 51 + }; 52 + 53 + /** NSID for teal.fm actor status records. */ 54 + export const CURRENTLY_PLAYING_COLLECTION = "fm.teal.alpha.actor.status"; 55 + 56 + /** 57 + * Compares two teal.fm status records to determine if the track has changed. 58 + * Used to prevent unnecessary re-renders during auto-refresh when the same track is still playing. 59 + */ 60 + const compareTealRecords = ( 61 + prev: TealActorStatusRecord | undefined, 62 + next: TealActorStatusRecord | undefined 63 + ): boolean => { 64 + if (!prev || !next) return prev === next; 65 + 66 + const prevTrack = prev.item.trackName; 67 + const nextTrack = next.item.trackName; 68 + const prevArtist = prev.item.artists[0]?.artistName; 69 + const nextArtist = next.item.artists[0]?.artistName; 70 + 71 + return prevTrack === nextTrack && prevArtist === nextArtist; 72 + }; 73 + 74 + /** 75 + * Displays the currently playing track from teal.fm with auto-refresh. 76 + * 77 + * @param did - DID whose currently playing status should be fetched. 78 + * @param rkey - Record key within the teal.fm status collection (defaults to "self"). 79 + * @param renderer - Optional component override that will receive injected props. 80 + * @param fallback - Node rendered before the first load begins. 81 + * @param loadingIndicator - Node rendered while the status is loading. 82 + * @param colorScheme - Preferred color scheme for theming the renderer. 83 + * @param autoRefresh - When true (default), refreshes the record every 15 seconds (or custom interval). 84 + * @param refreshInterval - Custom refresh interval in milliseconds. Defaults to 15000 (15 seconds). 85 + * @returns A JSX subtree representing the currently playing track with loading states handled. 86 + */ 87 + export const CurrentlyPlaying: React.FC<CurrentlyPlayingProps> = React.memo(({ 88 + did, 89 + rkey = "self", 90 + record, 91 + renderer, 92 + fallback, 93 + loadingIndicator, 94 + colorScheme, 95 + autoRefresh = true, 96 + refreshInterval = 15000, 97 + }) => { 98 + // Resolve handle from DID 99 + const { handle } = useDidResolution(did); 100 + 101 + const Comp: React.ComponentType<CurrentlyPlayingRendererInjectedProps> = 102 + renderer ?? ((props) => <CurrentlyPlayingRenderer {...props} />); 103 + const Wrapped: React.FC<{ 104 + record: TealActorStatusRecord; 105 + loading: boolean; 106 + error?: Error; 107 + }> = (props) => ( 108 + <Comp 109 + {...props} 110 + colorScheme={colorScheme} 111 + did={did} 112 + rkey={rkey} 113 + label="CURRENTLY PLAYING" 114 + handle={handle} 115 + /> 116 + ); 117 + 118 + if (record !== undefined) { 119 + return ( 120 + <AtProtoRecord<TealActorStatusRecord> 121 + record={record} 122 + renderer={Wrapped} 123 + fallback={fallback} 124 + loadingIndicator={loadingIndicator} 125 + /> 126 + ); 127 + } 128 + 129 + return ( 130 + <AtProtoRecord<TealActorStatusRecord> 131 + did={did} 132 + collection={CURRENTLY_PLAYING_COLLECTION} 133 + rkey={rkey} 134 + renderer={Wrapped} 135 + fallback={fallback} 136 + loadingIndicator={loadingIndicator} 137 + refreshInterval={autoRefresh ? refreshInterval : undefined} 138 + compareRecords={compareTealRecords} 139 + /> 140 + ); 141 + }); 142 + 143 + export default CurrentlyPlaying;
+327
lib/components/GrainGallery.tsx
··· 1 + import React, { useMemo, useEffect, useState } from "react"; 2 + import { GrainGalleryRenderer, type GrainGalleryPhoto } from "../renderers/GrainGalleryRenderer"; 3 + import type { GrainGalleryRecord, GrainGalleryItemRecord, GrainPhotoRecord } from "../types/grain"; 4 + import type { ProfileRecord } from "../types/bluesky"; 5 + import { useDidResolution } from "../hooks/useDidResolution"; 6 + import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 7 + import { useBacklinks } from "../hooks/useBacklinks"; 8 + import { useBlob } from "../hooks/useBlob"; 9 + import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile"; 10 + import { getAvatarCid } from "../utils/profile"; 11 + import { formatDidForLabel, parseAtUri } from "../utils/at-uri"; 12 + import { isBlobWithCdn } from "../utils/blob"; 13 + import { createAtprotoClient } from "../utils/atproto-client"; 14 + 15 + /** 16 + * Props for rendering a grain.social gallery. 17 + */ 18 + export interface GrainGalleryProps { 19 + /** 20 + * Decentralized identifier for the repository that owns the gallery. 21 + */ 22 + did: string; 23 + /** 24 + * Record key identifying the specific gallery within the collection. 25 + */ 26 + rkey: string; 27 + /** 28 + * Prefetched gallery record. When provided, skips fetching the gallery from the network. 29 + */ 30 + record?: GrainGalleryRecord; 31 + /** 32 + * Custom renderer component that receives resolved gallery data and status flags. 33 + */ 34 + renderer?: React.ComponentType<GrainGalleryRendererInjectedProps>; 35 + /** 36 + * React node shown while the gallery query has not yet produced data or an error. 37 + */ 38 + fallback?: React.ReactNode; 39 + /** 40 + * React node displayed while the gallery fetch is actively loading. 41 + */ 42 + loadingIndicator?: React.ReactNode; 43 + /** 44 + * Constellation API base URL for fetching backlinks. 45 + */ 46 + constellationBaseUrl?: string; 47 + } 48 + 49 + /** 50 + * Values injected by `GrainGallery` into a downstream renderer component. 51 + */ 52 + export type GrainGalleryRendererInjectedProps = { 53 + /** 54 + * Resolved gallery record 55 + */ 56 + gallery: GrainGalleryRecord; 57 + /** 58 + * Array of photos in the gallery with their records and metadata 59 + */ 60 + photos: GrainGalleryPhoto[]; 61 + /** 62 + * `true` while network operations are in-flight. 63 + */ 64 + loading: boolean; 65 + /** 66 + * Error encountered during loading, if any. 67 + */ 68 + error?: Error; 69 + /** 70 + * The author's public handle derived from the DID. 71 + */ 72 + authorHandle?: string; 73 + /** 74 + * The author's display name from their profile. 75 + */ 76 + authorDisplayName?: string; 77 + /** 78 + * Resolved URL for the author's avatar blob, if available. 79 + */ 80 + avatarUrl?: string; 81 + }; 82 + 83 + export const GRAIN_GALLERY_COLLECTION = "social.grain.gallery"; 84 + export const GRAIN_GALLERY_ITEM_COLLECTION = "social.grain.gallery.item"; 85 + export const GRAIN_PHOTO_COLLECTION = "social.grain.photo"; 86 + 87 + /** 88 + * Fetches a grain.social gallery, resolves all photos via constellation backlinks, 89 + * and renders them in a grid layout. 90 + * 91 + * @param did - DID of the repository that stores the gallery. 92 + * @param rkey - Record key for the gallery. 93 + * @param record - Prefetched gallery record. 94 + * @param renderer - Optional renderer component to override the default. 95 + * @param fallback - Node rendered before the first fetch attempt resolves. 96 + * @param loadingIndicator - Node rendered while the gallery is loading. 97 + * @param constellationBaseUrl - Constellation API base URL. 98 + * @returns A component that renders loading/fallback states and the resolved gallery. 99 + */ 100 + export const GrainGallery: React.FC<GrainGalleryProps> = React.memo( 101 + ({ 102 + did: handleOrDid, 103 + rkey, 104 + record, 105 + renderer, 106 + fallback, 107 + loadingIndicator, 108 + constellationBaseUrl, 109 + }) => { 110 + const { 111 + did: resolvedDid, 112 + handle, 113 + loading: resolvingIdentity, 114 + error: resolutionError, 115 + } = useDidResolution(handleOrDid); 116 + 117 + const repoIdentifier = resolvedDid ?? handleOrDid; 118 + 119 + // Fetch author profile 120 + const { record: profile } = useAtProtoRecord<ProfileRecord>({ 121 + did: repoIdentifier, 122 + collection: BLUESKY_PROFILE_COLLECTION, 123 + rkey: "self", 124 + }); 125 + const avatar = profile?.avatar; 126 + const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined; 127 + const avatarCid = avatarCdnUrl ? undefined : getAvatarCid(profile); 128 + const authorDisplayName = profile?.displayName; 129 + const { url: avatarUrlFromBlob } = useBlob(repoIdentifier, avatarCid); 130 + const avatarUrl = avatarCdnUrl || avatarUrlFromBlob; 131 + 132 + // Fetch gallery record 133 + const { 134 + record: fetchedGallery, 135 + loading: galleryLoading, 136 + error: galleryError, 137 + } = useAtProtoRecord<GrainGalleryRecord>({ 138 + did: record ? "" : repoIdentifier, 139 + collection: record ? "" : GRAIN_GALLERY_COLLECTION, 140 + rkey: record ? "" : rkey, 141 + }); 142 + 143 + const galleryRecord = record ?? fetchedGallery; 144 + const galleryUri = resolvedDid 145 + ? `at://${resolvedDid}/${GRAIN_GALLERY_COLLECTION}/${rkey}` 146 + : undefined; 147 + 148 + // Fetch backlinks to get gallery items 149 + const { 150 + backlinks, 151 + loading: backlinksLoading, 152 + error: backlinksError, 153 + } = useBacklinks({ 154 + subject: galleryUri || "", 155 + source: `${GRAIN_GALLERY_ITEM_COLLECTION}:gallery`, 156 + enabled: !!galleryUri && !!galleryRecord, 157 + constellationBaseUrl, 158 + }); 159 + 160 + // Fetch all gallery item records and photo records 161 + const [photos, setPhotos] = useState<GrainGalleryPhoto[]>([]); 162 + const [photosLoading, setPhotosLoading] = useState(false); 163 + const [photosError, setPhotosError] = useState<Error | undefined>(undefined); 164 + 165 + useEffect(() => { 166 + if (!backlinks || backlinks.length === 0) { 167 + setPhotos([]); 168 + return; 169 + } 170 + 171 + let cancelled = false; 172 + setPhotosLoading(true); 173 + setPhotosError(undefined); 174 + 175 + (async () => { 176 + try { 177 + const photoPromises = backlinks.map(async (backlink) => { 178 + // Create client for gallery item DID (uses slingshot + PDS fallback) 179 + const { rpc: galleryItemClient } = await createAtprotoClient({ 180 + did: backlink.did, 181 + }); 182 + 183 + // Fetch gallery item record 184 + const galleryItemRes = await ( 185 + galleryItemClient as unknown as { 186 + get: ( 187 + nsid: string, 188 + opts: { 189 + params: { 190 + repo: string; 191 + collection: string; 192 + rkey: string; 193 + }; 194 + }, 195 + ) => Promise<{ ok: boolean; data: { value: GrainGalleryItemRecord } }>; 196 + } 197 + ).get("com.atproto.repo.getRecord", { 198 + params: { 199 + repo: backlink.did, 200 + collection: GRAIN_GALLERY_ITEM_COLLECTION, 201 + rkey: backlink.rkey, 202 + }, 203 + }); 204 + 205 + if (!galleryItemRes.ok) return null; 206 + 207 + const galleryItem = galleryItemRes.data.value; 208 + 209 + // Parse photo URI 210 + const photoUri = parseAtUri(galleryItem.item); 211 + if (!photoUri) return null; 212 + 213 + // Create client for photo DID (uses slingshot + PDS fallback) 214 + const { rpc: photoClient } = await createAtprotoClient({ 215 + did: photoUri.did, 216 + }); 217 + 218 + // Fetch photo record 219 + const photoRes = await ( 220 + photoClient as unknown as { 221 + get: ( 222 + nsid: string, 223 + opts: { 224 + params: { 225 + repo: string; 226 + collection: string; 227 + rkey: string; 228 + }; 229 + }, 230 + ) => Promise<{ ok: boolean; data: { value: GrainPhotoRecord } }>; 231 + } 232 + ).get("com.atproto.repo.getRecord", { 233 + params: { 234 + repo: photoUri.did, 235 + collection: photoUri.collection, 236 + rkey: photoUri.rkey, 237 + }, 238 + }); 239 + 240 + if (!photoRes.ok) return null; 241 + 242 + const photoRecord = photoRes.data.value; 243 + 244 + return { 245 + record: photoRecord, 246 + did: photoUri.did, 247 + rkey: photoUri.rkey, 248 + position: galleryItem.position, 249 + } as GrainGalleryPhoto; 250 + }); 251 + 252 + const resolvedPhotos = await Promise.all(photoPromises); 253 + const validPhotos = resolvedPhotos.filter((p): p is NonNullable<typeof p> => p !== null) as GrainGalleryPhoto[]; 254 + 255 + if (!cancelled) { 256 + setPhotos(validPhotos); 257 + setPhotosLoading(false); 258 + } 259 + } catch (err) { 260 + if (!cancelled) { 261 + setPhotosError(err instanceof Error ? err : new Error("Failed to fetch photos")); 262 + setPhotosLoading(false); 263 + } 264 + } 265 + })(); 266 + 267 + return () => { 268 + cancelled = true; 269 + }; 270 + }, [backlinks]); 271 + 272 + const Comp: React.ComponentType<GrainGalleryRendererInjectedProps> = 273 + useMemo( 274 + () => 275 + renderer ?? ((props) => <GrainGalleryRenderer {...props} />), 276 + [renderer], 277 + ); 278 + 279 + const displayHandle = 280 + handle ?? 281 + (handleOrDid.startsWith("did:") ? undefined : handleOrDid); 282 + const authorHandle = 283 + displayHandle ?? formatDidForLabel(resolvedDid ?? handleOrDid); 284 + 285 + if (!displayHandle && resolvingIdentity) { 286 + return loadingIndicator || <div role="status" aria-live="polite" style={{ padding: 8 }}>Resolving handleโ€ฆ</div>; 287 + } 288 + if (!displayHandle && resolutionError) { 289 + return ( 290 + <div style={{ padding: 8, color: "crimson" }}> 291 + Could not resolve handle. 292 + </div> 293 + ); 294 + } 295 + 296 + if (galleryError || backlinksError || photosError) { 297 + return ( 298 + <div style={{ padding: 8, color: "crimson" }}> 299 + Failed to load gallery. 300 + </div> 301 + ); 302 + } 303 + 304 + if (!galleryRecord && galleryLoading) { 305 + return loadingIndicator || <div style={{ padding: 8 }}>Loading galleryโ€ฆ</div>; 306 + } 307 + 308 + if (!galleryRecord) { 309 + return fallback || <div style={{ padding: 8 }}>Gallery not found.</div>; 310 + } 311 + 312 + const loading = galleryLoading || backlinksLoading || photosLoading; 313 + 314 + return ( 315 + <Comp 316 + gallery={galleryRecord} 317 + photos={photos} 318 + loading={loading} 319 + authorHandle={authorHandle} 320 + authorDisplayName={authorDisplayName} 321 + avatarUrl={avatarUrl} 322 + /> 323 + ); 324 + }, 325 + ); 326 + 327 + export default GrainGallery;
+165
lib/components/LastPlayed.tsx
··· 1 + import React, { useMemo } from "react"; 2 + import { useLatestRecord } from "../hooks/useLatestRecord"; 3 + import { useDidResolution } from "../hooks/useDidResolution"; 4 + import { CurrentlyPlayingRenderer } from "../renderers/CurrentlyPlayingRenderer"; 5 + import type { TealFeedPlayRecord, TealActorStatusRecord } from "../types/teal"; 6 + 7 + /** 8 + * Props for rendering the last played track from teal.fm feed. 9 + */ 10 + export interface LastPlayedProps { 11 + /** DID of the user whose last played track to display. */ 12 + did: string; 13 + /** Optional renderer override for custom presentation. */ 14 + renderer?: React.ComponentType<LastPlayedRendererInjectedProps>; 15 + /** Fallback node displayed before loading begins. */ 16 + fallback?: React.ReactNode; 17 + /** Indicator node shown while data is loading. */ 18 + loadingIndicator?: React.ReactNode; 19 + /** Preferred color scheme for theming. */ 20 + colorScheme?: "light" | "dark" | "system"; 21 + /** Auto-refresh music data and album art. Defaults to false for last played. */ 22 + autoRefresh?: boolean; 23 + /** Refresh interval in milliseconds. Defaults to 60000 (60 seconds). */ 24 + refreshInterval?: number; 25 + } 26 + 27 + /** 28 + * Values injected into custom last played renderer implementations. 29 + */ 30 + export type LastPlayedRendererInjectedProps = { 31 + /** Loaded teal.fm feed play record value. */ 32 + record: TealActorStatusRecord; 33 + /** Indicates whether the record is currently loading. */ 34 + loading: boolean; 35 + /** Fetch error, if any. */ 36 + error?: Error; 37 + /** Preferred color scheme for downstream components. */ 38 + colorScheme?: "light" | "dark" | "system"; 39 + /** DID associated with the record. */ 40 + did: string; 41 + /** Record key for the play record. */ 42 + rkey: string; 43 + /** Handle to display in not listening state */ 44 + handle?: string; 45 + }; 46 + 47 + /** NSID for teal.fm feed play records. */ 48 + export const LAST_PLAYED_COLLECTION = "fm.teal.alpha.feed.play"; 49 + 50 + /** 51 + * Displays the last played track from teal.fm feed. 52 + * 53 + * @param did - DID whose last played track should be fetched. 54 + * @param renderer - Optional component override that will receive injected props. 55 + * @param fallback - Node rendered before the first load begins. 56 + * @param loadingIndicator - Node rendered while the data is loading. 57 + * @param colorScheme - Preferred color scheme for theming the renderer. 58 + * @param autoRefresh - When true, refreshes album art and streaming platform links at the specified interval. Defaults to false. 59 + * @param refreshInterval - Refresh interval in milliseconds. Defaults to 60000 (60 seconds). 60 + * @returns A JSX subtree representing the last played track with loading states handled. 61 + */ 62 + export const LastPlayed: React.FC<LastPlayedProps> = React.memo(({ 63 + did, 64 + renderer, 65 + fallback, 66 + loadingIndicator, 67 + colorScheme, 68 + autoRefresh = false, 69 + refreshInterval = 60000, 70 + }) => { 71 + // Resolve handle from DID 72 + const { handle } = useDidResolution(did); 73 + 74 + // Auto-refresh key for refetching teal.fm record 75 + const [refreshKey, setRefreshKey] = React.useState(0); 76 + 77 + // Auto-refresh interval 78 + React.useEffect(() => { 79 + if (!autoRefresh) return; 80 + 81 + const interval = setInterval(() => { 82 + setRefreshKey((prev) => prev + 1); 83 + }, refreshInterval); 84 + 85 + return () => clearInterval(interval); 86 + }, [autoRefresh, refreshInterval]); 87 + 88 + const { record, rkey, loading, error, empty } = useLatestRecord<TealFeedPlayRecord>( 89 + did, 90 + LAST_PLAYED_COLLECTION, 91 + refreshKey, 92 + ); 93 + 94 + // Normalize TealFeedPlayRecord to match TealActorStatusRecord structure 95 + // Use useMemo to prevent creating new object on every render 96 + // MUST be called before any conditional returns (Rules of Hooks) 97 + const normalizedRecord = useMemo(() => { 98 + if (!record) return null; 99 + 100 + return { 101 + $type: "fm.teal.alpha.actor.status" as const, 102 + item: { 103 + artists: record.artists, 104 + originUrl: record.originUrl, 105 + trackName: record.trackName, 106 + playedTime: record.playedTime, 107 + releaseName: record.releaseName, 108 + recordingMbId: record.recordingMbId, 109 + releaseMbId: record.releaseMbId, 110 + submissionClientAgent: record.submissionClientAgent, 111 + musicServiceBaseDomain: record.musicServiceBaseDomain, 112 + isrc: record.isrc, 113 + duration: record.duration, 114 + }, 115 + time: new Date(record.playedTime).getTime().toString(), 116 + expiry: undefined, 117 + }; 118 + }, [record]); 119 + 120 + const Comp = renderer ?? CurrentlyPlayingRenderer; 121 + 122 + // Now handle conditional returns after all hooks 123 + if (error) { 124 + return ( 125 + <div style={{ padding: 8, color: "var(--atproto-color-error)" }}> 126 + Failed to load last played track. 127 + </div> 128 + ); 129 + } 130 + 131 + if (loading && !record) { 132 + return loadingIndicator ? ( 133 + <>{loadingIndicator}</> 134 + ) : ( 135 + <div style={{ padding: 8, color: "var(--atproto-color-text-secondary)" }}> 136 + Loadingโ€ฆ 137 + </div> 138 + ); 139 + } 140 + 141 + if (empty || !record || !normalizedRecord) { 142 + return fallback ? ( 143 + <>{fallback}</> 144 + ) : ( 145 + <div style={{ padding: 8, color: "var(--atproto-color-text-secondary)" }}> 146 + No plays found. 147 + </div> 148 + ); 149 + } 150 + 151 + return ( 152 + <Comp 153 + record={normalizedRecord} 154 + loading={loading} 155 + error={error} 156 + colorScheme={colorScheme} 157 + did={did} 158 + rkey={rkey || "unknown"} 159 + label="LAST PLAYED" 160 + handle={handle} 161 + /> 162 + ); 163 + }); 164 + 165 + export default LastPlayed;
+125 -76
lib/components/LeafletDocument.tsx
··· 1 - import React, { useMemo } from 'react'; 2 - import { AtProtoRecord } from '../core/AtProtoRecord'; 3 - import { LeafletDocumentRenderer, type LeafletDocumentRendererProps } from '../renderers/LeafletDocumentRenderer'; 4 - import type { LeafletDocumentRecord, LeafletPublicationRecord } from '../types/leaflet'; 5 - import type { ColorSchemePreference } from '../hooks/useColorScheme'; 6 - import { parseAtUri, toBlueskyPostUrl, leafletRkeyUrl, normalizeLeafletBasePath } from '../utils/at-uri'; 7 - import { useAtProtoRecord } from '../hooks/useAtProtoRecord'; 1 + import React, { useMemo } from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { 4 + LeafletDocumentRenderer, 5 + type LeafletDocumentRendererProps, 6 + } from "../renderers/LeafletDocumentRenderer"; 7 + import type { 8 + LeafletDocumentRecord, 9 + LeafletPublicationRecord, 10 + } from "../types/leaflet"; 11 + import { 12 + parseAtUri, 13 + toBlueskyPostUrl, 14 + leafletRkeyUrl, 15 + normalizeLeafletBasePath, 16 + } from "../utils/at-uri"; 17 + import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 8 18 9 19 /** 10 20 * Props for rendering a Leaflet document record. 11 21 */ 12 22 export interface LeafletDocumentProps { 13 - /** 14 - * DID of the Leaflet publisher. 15 - */ 16 - did: string; 17 - /** 18 - * Record key of the document within the Leaflet collection. 19 - */ 20 - rkey: string; 21 - /** 22 - * Optional custom renderer for advanced layouts. 23 - */ 24 - renderer?: React.ComponentType<LeafletDocumentRendererInjectedProps>; 25 - /** 26 - * React node rendered before data begins loading. 27 - */ 28 - fallback?: React.ReactNode; 29 - /** 30 - * Indicator rendered while data is being fetched from the PDS. 31 - */ 32 - loadingIndicator?: React.ReactNode; 33 - /** 34 - * Preferred color scheme to forward to the renderer. 35 - */ 36 - colorScheme?: ColorSchemePreference; 23 + /** 24 + * DID of the Leaflet publisher. 25 + */ 26 + did: string; 27 + /** 28 + * Record key of the document within the Leaflet collection. 29 + */ 30 + rkey: string; 31 + /** 32 + * Prefetched Leaflet document record. When provided, skips fetching from the network. 33 + */ 34 + record?: LeafletDocumentRecord; 35 + /** 36 + * Optional custom renderer for advanced layouts. 37 + */ 38 + renderer?: React.ComponentType<LeafletDocumentRendererInjectedProps>; 39 + /** 40 + * React node rendered before data begins loading. 41 + */ 42 + fallback?: React.ReactNode; 43 + /** 44 + * Indicator rendered while data is being fetched from the PDS. 45 + */ 46 + loadingIndicator?: React.ReactNode; 37 47 } 38 48 39 49 /** ··· 42 52 export type LeafletDocumentRendererInjectedProps = LeafletDocumentRendererProps; 43 53 44 54 /** NSID for Leaflet document records. */ 45 - export const LEAFLET_DOCUMENT_COLLECTION = 'pub.leaflet.document'; 55 + export const LEAFLET_DOCUMENT_COLLECTION = "pub.leaflet.document"; 46 56 47 57 /** 48 58 * Loads a Leaflet document along with its associated publication record and renders it ··· 56 66 * @param colorScheme - Preferred color scheme forwarded to the renderer. 57 67 * @returns A JSX subtree that renders a Leaflet document with contextual metadata. 58 68 */ 59 - export const LeafletDocument: React.FC<LeafletDocumentProps> = ({ did, rkey, renderer, fallback, loadingIndicator, colorScheme }) => { 60 - const Comp: React.ComponentType<LeafletDocumentRendererInjectedProps> = renderer ?? ((props) => <LeafletDocumentRenderer {...props} />); 69 + export const LeafletDocument: React.FC<LeafletDocumentProps> = React.memo(({ 70 + did, 71 + rkey, 72 + record, 73 + renderer, 74 + fallback, 75 + loadingIndicator, 76 + }) => { 77 + const Comp: React.ComponentType<LeafletDocumentRendererInjectedProps> = 78 + renderer ?? ((props) => <LeafletDocumentRenderer {...props} />); 79 + 80 + const Wrapped: React.FC<{ 81 + record: LeafletDocumentRecord; 82 + loading: boolean; 83 + error?: Error; 84 + }> = (props) => { 85 + const publicationUri = useMemo( 86 + () => parseAtUri(props.record.publication), 87 + [props.record.publication], 88 + ); 89 + const { record: publicationRecord } = 90 + useAtProtoRecord<LeafletPublicationRecord>({ 91 + did: publicationUri?.did, 92 + collection: 93 + publicationUri?.collection ?? "pub.leaflet.publication", 94 + rkey: publicationUri?.rkey ?? "", 95 + }); 96 + const publicationBaseUrl = normalizeLeafletBasePath( 97 + publicationRecord?.base_path, 98 + ); 99 + const canonicalUrl = resolveCanonicalUrl( 100 + props.record, 101 + did, 102 + rkey, 103 + publicationRecord?.base_path, 104 + ); 105 + return ( 106 + <Comp 107 + {...props} 108 + did={did} 109 + rkey={rkey} 110 + canonicalUrl={canonicalUrl} 111 + publicationBaseUrl={publicationBaseUrl} 112 + publicationRecord={publicationRecord} 113 + /> 114 + ); 115 + }; 61 116 62 - const Wrapped: React.FC<{ record: LeafletDocumentRecord; loading: boolean; error?: Error }> = (props) => { 63 - const publicationUri = useMemo(() => parseAtUri(props.record.publication), [props.record.publication]); 64 - const { record: publicationRecord } = useAtProtoRecord<LeafletPublicationRecord>({ 65 - did: publicationUri?.did, 66 - collection: publicationUri?.collection ?? 'pub.leaflet.publication', 67 - rkey: publicationUri?.rkey ?? '' 68 - }); 69 - const publicationBaseUrl = normalizeLeafletBasePath(publicationRecord?.base_path); 70 - const canonicalUrl = resolveCanonicalUrl(props.record, did, rkey, publicationRecord?.base_path); 71 - return ( 72 - <Comp 73 - {...props} 74 - colorScheme={colorScheme} 75 - did={did} 76 - rkey={rkey} 77 - canonicalUrl={canonicalUrl} 78 - publicationBaseUrl={publicationBaseUrl} 79 - publicationRecord={publicationRecord} 80 - /> 81 - ); 82 - }; 117 + if (record !== undefined) { 118 + return ( 119 + <AtProtoRecord<LeafletDocumentRecord> 120 + record={record} 121 + renderer={Wrapped} 122 + fallback={fallback} 123 + loadingIndicator={loadingIndicator} 124 + /> 125 + ); 126 + } 83 127 84 - return ( 85 - <AtProtoRecord<LeafletDocumentRecord> 86 - did={did} 87 - collection={LEAFLET_DOCUMENT_COLLECTION} 88 - rkey={rkey} 89 - renderer={Wrapped} 90 - fallback={fallback} 91 - loadingIndicator={loadingIndicator} 92 - /> 93 - ); 94 - }; 128 + return ( 129 + <AtProtoRecord<LeafletDocumentRecord> 130 + did={did} 131 + collection={LEAFLET_DOCUMENT_COLLECTION} 132 + rkey={rkey} 133 + renderer={Wrapped} 134 + fallback={fallback} 135 + loadingIndicator={loadingIndicator} 136 + /> 137 + ); 138 + }); 95 139 96 140 /** 97 141 * Determines the best canonical URL to expose for a Leaflet document. ··· 102 146 * @param publicationBasePath - Optional base path configured by the publication. 103 147 * @returns A URL to use for canonical links. 104 148 */ 105 - function resolveCanonicalUrl(record: LeafletDocumentRecord, did: string, rkey: string, publicationBasePath?: string): string { 106 - const publicationUrl = leafletRkeyUrl(publicationBasePath, rkey); 107 - if (publicationUrl) return publicationUrl; 108 - const postUri = record.postRef?.uri; 109 - if (postUri) { 110 - const parsed = parseAtUri(postUri); 111 - const href = parsed ? toBlueskyPostUrl(parsed) : undefined; 112 - if (href) return href; 113 - } 114 - return `at://${encodeURIComponent(did)}/${LEAFLET_DOCUMENT_COLLECTION}/${encodeURIComponent(rkey)}`; 149 + function resolveCanonicalUrl( 150 + record: LeafletDocumentRecord, 151 + did: string, 152 + rkey: string, 153 + publicationBasePath?: string, 154 + ): string { 155 + const publicationUrl = leafletRkeyUrl(publicationBasePath, rkey); 156 + if (publicationUrl) return publicationUrl; 157 + const postUri = record.postRef?.uri; 158 + if (postUri) { 159 + const parsed = parseAtUri(postUri); 160 + const href = parsed ? toBlueskyPostUrl(parsed) : undefined; 161 + if (href) return href; 162 + } 163 + return `at://${encodeURIComponent(did)}/${LEAFLET_DOCUMENT_COLLECTION}/${encodeURIComponent(rkey)}`; 115 164 } 116 165 117 166 export default LeafletDocument;
+125
lib/components/RichText.tsx
··· 1 + import React from "react"; 2 + import type { AppBskyRichtextFacet } from "@atcute/bluesky"; 3 + import { createTextSegments, type TextSegment } from "../utils/richtext"; 4 + import { useAtProto } from "../providers/AtProtoProvider"; 5 + 6 + export interface RichTextProps { 7 + text: string; 8 + facets?: AppBskyRichtextFacet.Main[]; 9 + style?: React.CSSProperties; 10 + } 11 + 12 + /** 13 + * RichText component that renders text with facets (mentions, links, hashtags). 14 + * Properly handles byte offsets and multi-byte characters. 15 + */ 16 + export const RichText: React.FC<RichTextProps> = ({ text, facets, style }) => { 17 + const { blueskyAppBaseUrl } = useAtProto(); 18 + const segments = createTextSegments(text, facets); 19 + 20 + return ( 21 + <span style={style}> 22 + {segments.map((segment, idx) => ( 23 + <RichTextSegment key={idx} segment={segment} blueskyAppBaseUrl={blueskyAppBaseUrl} /> 24 + ))} 25 + </span> 26 + ); 27 + }; 28 + 29 + interface RichTextSegmentProps { 30 + segment: TextSegment; 31 + blueskyAppBaseUrl: string; 32 + } 33 + 34 + const RichTextSegment: React.FC<RichTextSegmentProps> = ({ segment, blueskyAppBaseUrl }) => { 35 + if (!segment.facet) { 36 + return <>{segment.text}</>; 37 + } 38 + 39 + // Find the first feature in the facet 40 + const feature = segment.facet.features?.[0]; 41 + if (!feature) { 42 + return <>{segment.text}</>; 43 + } 44 + 45 + const featureType = (feature as { $type?: string }).$type; 46 + 47 + // Render based on feature type 48 + switch (featureType) { 49 + case "app.bsky.richtext.facet#link": { 50 + const linkFeature = feature as AppBskyRichtextFacet.Link; 51 + return ( 52 + <a 53 + href={linkFeature.uri} 54 + target="_blank" 55 + rel="noopener noreferrer" 56 + style={{ 57 + color: "var(--atproto-color-link)", 58 + textDecoration: "none", 59 + }} 60 + onMouseEnter={(e) => { 61 + e.currentTarget.style.textDecoration = "underline"; 62 + }} 63 + onMouseLeave={(e) => { 64 + e.currentTarget.style.textDecoration = "none"; 65 + }} 66 + > 67 + {segment.text} 68 + </a> 69 + ); 70 + } 71 + 72 + case "app.bsky.richtext.facet#mention": { 73 + const mentionFeature = feature as AppBskyRichtextFacet.Mention; 74 + const profileUrl = `${blueskyAppBaseUrl}/profile/${mentionFeature.did}`; 75 + return ( 76 + <a 77 + href={profileUrl} 78 + target="_blank" 79 + rel="noopener noreferrer" 80 + style={{ 81 + color: "var(--atproto-color-link)", 82 + textDecoration: "none", 83 + }} 84 + onMouseEnter={(e) => { 85 + e.currentTarget.style.textDecoration = "underline"; 86 + }} 87 + onMouseLeave={(e) => { 88 + e.currentTarget.style.textDecoration = "none"; 89 + }} 90 + > 91 + {segment.text} 92 + </a> 93 + ); 94 + } 95 + 96 + case "app.bsky.richtext.facet#tag": { 97 + const tagFeature = feature as AppBskyRichtextFacet.Tag; 98 + const tagUrl = `${blueskyAppBaseUrl}/hashtag/${encodeURIComponent(tagFeature.tag)}`; 99 + return ( 100 + <a 101 + href={tagUrl} 102 + target="_blank" 103 + rel="noopener noreferrer" 104 + style={{ 105 + color: "var(--atproto-color-link)", 106 + textDecoration: "none", 107 + }} 108 + onMouseEnter={(e) => { 109 + e.currentTarget.style.textDecoration = "underline"; 110 + }} 111 + onMouseLeave={(e) => { 112 + e.currentTarget.style.textDecoration = "none"; 113 + }} 114 + > 115 + {segment.text} 116 + </a> 117 + ); 118 + } 119 + 120 + default: 121 + return <>{segment.text}</>; 122 + } 123 + }; 124 + 125 + export default RichText;
+648
lib/components/SongHistoryList.tsx
··· 1 + import React, { useState, useEffect, useMemo } from "react"; 2 + import { usePaginatedRecords } from "../hooks/usePaginatedRecords"; 3 + import { useDidResolution } from "../hooks/useDidResolution"; 4 + import type { TealFeedPlayRecord } from "../types/teal"; 5 + 6 + /** 7 + * Options for rendering a paginated list of song history from teal.fm. 8 + */ 9 + export interface SongHistoryListProps { 10 + /** 11 + * DID whose song history should be fetched. 12 + */ 13 + did: string; 14 + /** 15 + * Maximum number of records to list per page. Defaults to `6`. 16 + */ 17 + limit?: number; 18 + /** 19 + * Enables pagination controls when `true`. Defaults to `true`. 20 + */ 21 + enablePagination?: boolean; 22 + } 23 + 24 + interface SonglinkResponse { 25 + linksByPlatform: { 26 + [platform: string]: { 27 + url: string; 28 + entityUniqueId: string; 29 + }; 30 + }; 31 + entitiesByUniqueId: { 32 + [id: string]: { 33 + thumbnailUrl?: string; 34 + title?: string; 35 + artistName?: string; 36 + }; 37 + }; 38 + entityUniqueId?: string; 39 + } 40 + 41 + /** 42 + * Fetches a user's song history from teal.fm and renders them with album art focus. 43 + * 44 + * @param did - DID whose song history should be displayed. 45 + * @param limit - Maximum number of songs per page. Default `6`. 46 + * @param enablePagination - Whether pagination controls should render. Default `true`. 47 + * @returns A card-like list element with loading, empty, and error handling. 48 + */ 49 + export const SongHistoryList: React.FC<SongHistoryListProps> = React.memo(({ 50 + did, 51 + limit = 6, 52 + enablePagination = true, 53 + }) => { 54 + const { handle: resolvedHandle } = useDidResolution(did); 55 + const actorLabel = resolvedHandle ?? formatDid(did); 56 + 57 + const { 58 + records, 59 + loading, 60 + error, 61 + hasNext, 62 + hasPrev, 63 + loadNext, 64 + loadPrev, 65 + pageIndex, 66 + pagesCount, 67 + } = usePaginatedRecords<TealFeedPlayRecord>({ 68 + did, 69 + collection: "fm.teal.alpha.feed.play", 70 + limit, 71 + }); 72 + 73 + const pageLabel = useMemo(() => { 74 + const knownTotal = Math.max(pageIndex + 1, pagesCount); 75 + if (!enablePagination) return undefined; 76 + if (hasNext && knownTotal === pageIndex + 1) 77 + return `${pageIndex + 1}/โ€ฆ`; 78 + return `${pageIndex + 1}/${knownTotal}`; 79 + }, [enablePagination, hasNext, pageIndex, pagesCount]); 80 + 81 + if (error) 82 + return ( 83 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 84 + Failed to load song history. 85 + </div> 86 + ); 87 + 88 + return ( 89 + <div style={{ ...listStyles.card, background: `var(--atproto-color-bg)`, borderWidth: "1px", borderStyle: "solid", borderColor: `var(--atproto-color-border)` }}> 90 + <div style={{ ...listStyles.header, background: `var(--atproto-color-bg-elevated)`, color: `var(--atproto-color-text)` }}> 91 + <div style={listStyles.headerInfo}> 92 + <div style={listStyles.headerIcon}> 93 + <svg 94 + width="24" 95 + height="24" 96 + viewBox="0 0 24 24" 97 + fill="none" 98 + stroke="currentColor" 99 + strokeWidth="2" 100 + strokeLinecap="round" 101 + strokeLinejoin="round" 102 + > 103 + <path d="M9 18V5l12-2v13" /> 104 + <circle cx="6" cy="18" r="3" /> 105 + <circle cx="18" cy="16" r="3" /> 106 + </svg> 107 + </div> 108 + <div style={listStyles.headerText}> 109 + <span style={listStyles.title}>Listening History</span> 110 + <span 111 + style={{ 112 + ...listStyles.subtitle, 113 + color: `var(--atproto-color-text-secondary)`, 114 + }} 115 + > 116 + @{actorLabel} 117 + </span> 118 + </div> 119 + </div> 120 + {pageLabel && ( 121 + <span 122 + style={{ ...listStyles.pageMeta, color: `var(--atproto-color-text-secondary)` }} 123 + > 124 + {pageLabel} 125 + </span> 126 + )} 127 + </div> 128 + <div style={listStyles.items}> 129 + {loading && records.length === 0 && ( 130 + <div style={{ ...listStyles.empty, color: `var(--atproto-color-text-secondary)` }}> 131 + Loading songsโ€ฆ 132 + </div> 133 + )} 134 + {records.map((record, idx) => ( 135 + <SongRow 136 + key={`${record.rkey}-${record.value.playedTime}`} 137 + record={record.value} 138 + hasDivider={idx < records.length - 1} 139 + /> 140 + ))} 141 + {!loading && records.length === 0 && ( 142 + <div style={{ ...listStyles.empty, color: `var(--atproto-color-text-secondary)` }}> 143 + No songs found. 144 + </div> 145 + )} 146 + </div> 147 + {enablePagination && ( 148 + <div style={{ ...listStyles.footer, borderTopColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 149 + <button 150 + type="button" 151 + style={{ 152 + ...listStyles.pageButton, 153 + background: `var(--atproto-color-button-bg)`, 154 + color: `var(--atproto-color-button-text)`, 155 + cursor: hasPrev ? "pointer" : "not-allowed", 156 + opacity: hasPrev ? 1 : 0.5, 157 + }} 158 + onClick={loadPrev} 159 + disabled={!hasPrev} 160 + > 161 + โ€น Prev 162 + </button> 163 + <div style={listStyles.pageChips}> 164 + <span 165 + style={{ 166 + ...listStyles.pageChipActive, 167 + color: `var(--atproto-color-button-text)`, 168 + background: `var(--atproto-color-button-bg)`, 169 + borderWidth: "1px", 170 + borderStyle: "solid", 171 + borderColor: `var(--atproto-color-button-bg)`, 172 + }} 173 + > 174 + {pageIndex + 1} 175 + </span> 176 + {(hasNext || pagesCount > pageIndex + 1) && ( 177 + <span 178 + style={{ 179 + ...listStyles.pageChip, 180 + color: `var(--atproto-color-text-secondary)`, 181 + borderWidth: "1px", 182 + borderStyle: "solid", 183 + borderColor: `var(--atproto-color-border)`, 184 + background: `var(--atproto-color-bg)`, 185 + }} 186 + > 187 + {pageIndex + 2} 188 + </span> 189 + )} 190 + </div> 191 + <button 192 + type="button" 193 + style={{ 194 + ...listStyles.pageButton, 195 + background: `var(--atproto-color-button-bg)`, 196 + color: `var(--atproto-color-button-text)`, 197 + cursor: hasNext ? "pointer" : "not-allowed", 198 + opacity: hasNext ? 1 : 0.5, 199 + }} 200 + onClick={loadNext} 201 + disabled={!hasNext} 202 + > 203 + Next โ€บ 204 + </button> 205 + </div> 206 + )} 207 + {loading && records.length > 0 && ( 208 + <div 209 + style={{ ...listStyles.loadingBar, background: `var(--atproto-color-bg-elevated)`, color: `var(--atproto-color-text-secondary)` }} 210 + > 211 + Updatingโ€ฆ 212 + </div> 213 + )} 214 + </div> 215 + ); 216 + }); 217 + 218 + interface SongRowProps { 219 + record: TealFeedPlayRecord; 220 + hasDivider: boolean; 221 + } 222 + 223 + const SongRow: React.FC<SongRowProps> = ({ record, hasDivider }) => { 224 + const [albumArt, setAlbumArt] = useState<string | undefined>(undefined); 225 + const [artLoading, setArtLoading] = useState(true); 226 + 227 + const artistNames = record.artists.map((a) => a.artistName).join(", "); 228 + const relative = record.playedTime 229 + ? formatRelativeTime(record.playedTime) 230 + : undefined; 231 + const absolute = record.playedTime 232 + ? new Date(record.playedTime).toLocaleString() 233 + : undefined; 234 + 235 + useEffect(() => { 236 + let cancelled = false; 237 + setArtLoading(true); 238 + setAlbumArt(undefined); 239 + 240 + const fetchAlbumArt = async () => { 241 + try { 242 + // Try ISRC first 243 + if (record.isrc) { 244 + const response = await fetch( 245 + `https://api.song.link/v1-alpha.1/links?platform=isrc&type=song&id=${encodeURIComponent(record.isrc)}&songIfSingle=true` 246 + ); 247 + if (cancelled) return; 248 + if (response.ok) { 249 + const data: SonglinkResponse = await response.json(); 250 + const entityId = data.entityUniqueId; 251 + const entity = entityId ? data.entitiesByUniqueId?.[entityId] : undefined; 252 + if (entity?.thumbnailUrl) { 253 + setAlbumArt(entity.thumbnailUrl); 254 + setArtLoading(false); 255 + return; 256 + } 257 + } 258 + } 259 + 260 + // Fallback to iTunes search 261 + const iTunesSearchUrl = `https://itunes.apple.com/search?term=${encodeURIComponent( 262 + `${record.trackName} ${artistNames}` 263 + )}&media=music&entity=song&limit=1`; 264 + 265 + const iTunesResponse = await fetch(iTunesSearchUrl); 266 + if (cancelled) return; 267 + 268 + if (iTunesResponse.ok) { 269 + const iTunesData = await iTunesResponse.json(); 270 + if (iTunesData.results && iTunesData.results.length > 0) { 271 + const match = iTunesData.results[0]; 272 + const artworkUrl = match.artworkUrl100?.replace('100x100', '600x600') || match.artworkUrl100; 273 + if (artworkUrl) { 274 + setAlbumArt(artworkUrl); 275 + } 276 + } 277 + } 278 + setArtLoading(false); 279 + } catch (err) { 280 + console.error(`Failed to fetch album art for "${record.trackName}":`, err); 281 + setArtLoading(false); 282 + } 283 + }; 284 + 285 + fetchAlbumArt(); 286 + 287 + return () => { 288 + cancelled = true; 289 + }; 290 + }, [record.trackName, artistNames, record.isrc]); 291 + 292 + return ( 293 + <div 294 + style={{ 295 + ...listStyles.row, 296 + color: `var(--atproto-color-text)`, 297 + borderBottom: hasDivider 298 + ? `1px solid var(--atproto-color-border)` 299 + : "none", 300 + }} 301 + > 302 + {/* Album Art - Large and prominent */} 303 + <div style={listStyles.albumArtContainer}> 304 + {artLoading ? ( 305 + <div style={listStyles.albumArtPlaceholder}> 306 + <div style={listStyles.loadingSpinner} /> 307 + </div> 308 + ) : albumArt ? ( 309 + <img 310 + src={albumArt} 311 + alt={`${record.releaseName || "Album"} cover`} 312 + style={listStyles.albumArt} 313 + onError={(e) => { 314 + e.currentTarget.style.display = "none"; 315 + const parent = e.currentTarget.parentElement; 316 + if (parent) { 317 + const placeholder = document.createElement("div"); 318 + Object.assign(placeholder.style, listStyles.albumArtPlaceholder); 319 + placeholder.innerHTML = ` 320 + <svg 321 + width="48" 322 + height="48" 323 + viewBox="0 0 24 24" 324 + fill="none" 325 + stroke="currentColor" 326 + stroke-width="1.5" 327 + > 328 + <circle cx="12" cy="12" r="10" /> 329 + <circle cx="12" cy="12" r="3" /> 330 + <path d="M12 2v3M12 19v3M2 12h3M19 12h3" /> 331 + </svg> 332 + `; 333 + parent.appendChild(placeholder); 334 + } 335 + }} 336 + /> 337 + ) : ( 338 + <div style={listStyles.albumArtPlaceholder}> 339 + <svg 340 + width="48" 341 + height="48" 342 + viewBox="0 0 24 24" 343 + fill="none" 344 + stroke="currentColor" 345 + strokeWidth="1.5" 346 + > 347 + <circle cx="12" cy="12" r="10" /> 348 + <circle cx="12" cy="12" r="3" /> 349 + <path d="M12 2v3M12 19v3M2 12h3M19 12h3" /> 350 + </svg> 351 + </div> 352 + )} 353 + </div> 354 + 355 + {/* Song Info */} 356 + <div style={listStyles.songInfo}> 357 + <div style={listStyles.trackName}>{record.trackName}</div> 358 + <div style={{ ...listStyles.artistName, color: `var(--atproto-color-text-secondary)` }}> 359 + {artistNames} 360 + </div> 361 + {record.releaseName && ( 362 + <div style={{ ...listStyles.releaseName, color: `var(--atproto-color-text-secondary)` }}> 363 + {record.releaseName} 364 + </div> 365 + )} 366 + {relative && ( 367 + <div 368 + style={{ ...listStyles.playedTime, color: `var(--atproto-color-text-secondary)` }} 369 + title={absolute} 370 + > 371 + {relative} 372 + </div> 373 + )} 374 + </div> 375 + 376 + {/* External Link */} 377 + {record.originUrl && ( 378 + <a 379 + href={record.originUrl} 380 + target="_blank" 381 + rel="noopener noreferrer" 382 + style={listStyles.externalLink} 383 + title="Listen on streaming service" 384 + aria-label={`Listen to ${record.trackName} by ${artistNames}`} 385 + > 386 + <svg 387 + width="20" 388 + height="20" 389 + viewBox="0 0 24 24" 390 + fill="none" 391 + stroke="currentColor" 392 + strokeWidth="2" 393 + strokeLinecap="round" 394 + strokeLinejoin="round" 395 + > 396 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 397 + <polyline points="15 3 21 3 21 9" /> 398 + <line x1="10" y1="14" x2="21" y2="3" /> 399 + </svg> 400 + </a> 401 + )} 402 + </div> 403 + ); 404 + }; 405 + 406 + function formatDid(did: string) { 407 + return did.replace(/^did:(plc:)?/, ""); 408 + } 409 + 410 + function formatRelativeTime(iso: string): string { 411 + const date = new Date(iso); 412 + const diffSeconds = (date.getTime() - Date.now()) / 1000; 413 + const absSeconds = Math.abs(diffSeconds); 414 + const thresholds: Array<{ 415 + limit: number; 416 + unit: Intl.RelativeTimeFormatUnit; 417 + divisor: number; 418 + }> = [ 419 + { limit: 60, unit: "second", divisor: 1 }, 420 + { limit: 3600, unit: "minute", divisor: 60 }, 421 + { limit: 86400, unit: "hour", divisor: 3600 }, 422 + { limit: 604800, unit: "day", divisor: 86400 }, 423 + { limit: 2629800, unit: "week", divisor: 604800 }, 424 + { limit: 31557600, unit: "month", divisor: 2629800 }, 425 + { limit: Infinity, unit: "year", divisor: 31557600 }, 426 + ]; 427 + const threshold = 428 + thresholds.find((t) => absSeconds < t.limit) ?? 429 + thresholds[thresholds.length - 1]; 430 + const value = diffSeconds / threshold.divisor; 431 + const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); 432 + return rtf.format(Math.round(value), threshold.unit); 433 + } 434 + 435 + const listStyles = { 436 + card: { 437 + borderRadius: 16, 438 + borderWidth: "1px", 439 + borderStyle: "solid", 440 + borderColor: "transparent", 441 + boxShadow: "0 8px 18px -12px rgba(15, 23, 42, 0.25)", 442 + overflow: "hidden", 443 + display: "flex", 444 + flexDirection: "column", 445 + } satisfies React.CSSProperties, 446 + header: { 447 + display: "flex", 448 + alignItems: "center", 449 + justifyContent: "space-between", 450 + padding: "14px 18px", 451 + fontSize: 14, 452 + fontWeight: 500, 453 + borderBottom: "1px solid var(--atproto-color-border)", 454 + } satisfies React.CSSProperties, 455 + headerInfo: { 456 + display: "flex", 457 + alignItems: "center", 458 + gap: 12, 459 + } satisfies React.CSSProperties, 460 + headerIcon: { 461 + width: 28, 462 + height: 28, 463 + display: "flex", 464 + alignItems: "center", 465 + justifyContent: "center", 466 + borderRadius: "50%", 467 + color: "var(--atproto-color-text)", 468 + } satisfies React.CSSProperties, 469 + headerText: { 470 + display: "flex", 471 + flexDirection: "column", 472 + gap: 2, 473 + } satisfies React.CSSProperties, 474 + title: { 475 + fontSize: 15, 476 + fontWeight: 600, 477 + } satisfies React.CSSProperties, 478 + subtitle: { 479 + fontSize: 12, 480 + fontWeight: 500, 481 + } satisfies React.CSSProperties, 482 + pageMeta: { 483 + fontSize: 12, 484 + } satisfies React.CSSProperties, 485 + items: { 486 + display: "flex", 487 + flexDirection: "column", 488 + } satisfies React.CSSProperties, 489 + empty: { 490 + padding: "24px 18px", 491 + fontSize: 13, 492 + textAlign: "center", 493 + } satisfies React.CSSProperties, 494 + row: { 495 + padding: "18px", 496 + display: "flex", 497 + gap: 16, 498 + alignItems: "center", 499 + transition: "background-color 120ms ease", 500 + position: "relative", 501 + } satisfies React.CSSProperties, 502 + albumArtContainer: { 503 + width: 96, 504 + height: 96, 505 + flexShrink: 0, 506 + borderRadius: 8, 507 + overflow: "hidden", 508 + boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)", 509 + } satisfies React.CSSProperties, 510 + albumArt: { 511 + width: "100%", 512 + height: "100%", 513 + objectFit: "cover", 514 + display: "block", 515 + } satisfies React.CSSProperties, 516 + albumArtPlaceholder: { 517 + width: "100%", 518 + height: "100%", 519 + display: "flex", 520 + alignItems: "center", 521 + justifyContent: "center", 522 + background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", 523 + color: "rgba(255, 255, 255, 0.6)", 524 + } satisfies React.CSSProperties, 525 + loadingSpinner: { 526 + width: 28, 527 + height: 28, 528 + border: "3px solid rgba(255, 255, 255, 0.3)", 529 + borderTop: "3px solid rgba(255, 255, 255, 0.9)", 530 + borderRadius: "50%", 531 + animation: "spin 1s linear infinite", 532 + } satisfies React.CSSProperties, 533 + songInfo: { 534 + flex: 1, 535 + display: "flex", 536 + flexDirection: "column", 537 + gap: 4, 538 + minWidth: 0, 539 + } satisfies React.CSSProperties, 540 + trackName: { 541 + fontSize: 16, 542 + fontWeight: 600, 543 + lineHeight: 1.3, 544 + color: "var(--atproto-color-text)", 545 + overflow: "hidden", 546 + textOverflow: "ellipsis", 547 + whiteSpace: "nowrap", 548 + } satisfies React.CSSProperties, 549 + artistName: { 550 + fontSize: 14, 551 + fontWeight: 500, 552 + overflow: "hidden", 553 + textOverflow: "ellipsis", 554 + whiteSpace: "nowrap", 555 + } satisfies React.CSSProperties, 556 + releaseName: { 557 + fontSize: 13, 558 + overflow: "hidden", 559 + textOverflow: "ellipsis", 560 + whiteSpace: "nowrap", 561 + } satisfies React.CSSProperties, 562 + playedTime: { 563 + fontSize: 12, 564 + fontWeight: 500, 565 + marginTop: 2, 566 + } satisfies React.CSSProperties, 567 + externalLink: { 568 + flexShrink: 0, 569 + width: 36, 570 + height: 36, 571 + display: "flex", 572 + alignItems: "center", 573 + justifyContent: "center", 574 + borderRadius: "50%", 575 + background: "var(--atproto-color-bg-elevated)", 576 + border: "1px solid var(--atproto-color-border)", 577 + color: "var(--atproto-color-text-secondary)", 578 + cursor: "pointer", 579 + transition: "all 0.2s ease", 580 + textDecoration: "none", 581 + } satisfies React.CSSProperties, 582 + footer: { 583 + display: "flex", 584 + alignItems: "center", 585 + justifyContent: "space-between", 586 + padding: "12px 18px", 587 + borderTop: "1px solid transparent", 588 + fontSize: 13, 589 + } satisfies React.CSSProperties, 590 + pageChips: { 591 + display: "flex", 592 + gap: 6, 593 + alignItems: "center", 594 + } satisfies React.CSSProperties, 595 + pageChip: { 596 + padding: "4px 10px", 597 + borderRadius: 999, 598 + fontSize: 13, 599 + borderWidth: "1px", 600 + borderStyle: "solid", 601 + borderColor: "transparent", 602 + } satisfies React.CSSProperties, 603 + pageChipActive: { 604 + padding: "4px 10px", 605 + borderRadius: 999, 606 + fontSize: 13, 607 + fontWeight: 600, 608 + borderWidth: "1px", 609 + borderStyle: "solid", 610 + borderColor: "transparent", 611 + } satisfies React.CSSProperties, 612 + pageButton: { 613 + border: "none", 614 + borderRadius: 999, 615 + padding: "6px 12px", 616 + fontSize: 13, 617 + fontWeight: 500, 618 + background: "transparent", 619 + display: "flex", 620 + alignItems: "center", 621 + gap: 4, 622 + transition: "background-color 120ms ease", 623 + } satisfies React.CSSProperties, 624 + loadingBar: { 625 + padding: "4px 18px 14px", 626 + fontSize: 12, 627 + textAlign: "right", 628 + color: "#64748b", 629 + } satisfies React.CSSProperties, 630 + }; 631 + 632 + // Add keyframes and hover styles 633 + if (typeof document !== "undefined") { 634 + const styleId = "song-history-styles"; 635 + if (!document.getElementById(styleId)) { 636 + const styleElement = document.createElement("style"); 637 + styleElement.id = styleId; 638 + styleElement.textContent = ` 639 + @keyframes spin { 640 + 0% { transform: rotate(0deg); } 641 + 100% { transform: rotate(360deg); } 642 + } 643 + `; 644 + document.head.appendChild(styleElement); 645 + } 646 + } 647 + 648 + export default SongHistoryList;
+131
lib/components/TangledRepo.tsx
··· 1 + import React from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { TangledRepoRenderer } from "../renderers/TangledRepoRenderer"; 4 + import type { TangledRepoRecord } from "../types/tangled"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 6 + 7 + /** 8 + * Props for rendering Tangled Repo records. 9 + */ 10 + export interface TangledRepoProps { 11 + /** DID of the repository that stores the repo record. */ 12 + did: string; 13 + /** Record key within the `sh.tangled.repo` collection. */ 14 + rkey: string; 15 + /** Prefetched Tangled Repo record. When provided, skips fetching from the network. */ 16 + record?: TangledRepoRecord; 17 + /** Optional renderer override for custom presentation. */ 18 + renderer?: React.ComponentType<TangledRepoRendererInjectedProps>; 19 + /** Fallback node displayed before loading begins. */ 20 + fallback?: React.ReactNode; 21 + /** Indicator node shown while data is loading. */ 22 + loadingIndicator?: React.ReactNode; 23 + /** Preferred color scheme for theming. */ 24 + colorScheme?: "light" | "dark" | "system"; 25 + /** Whether to show star count from backlinks. Defaults to true. */ 26 + showStarCount?: boolean; 27 + /** Branch to query for language information. Defaults to trying "main", then "master". */ 28 + branch?: string; 29 + /** Prefetched language names (e.g., ['TypeScript', 'React']). When provided, skips fetching languages from the knot server. */ 30 + languages?: string[]; 31 + } 32 + 33 + /** 34 + * Values injected into custom Tangled Repo renderer implementations. 35 + */ 36 + export type TangledRepoRendererInjectedProps = { 37 + /** Loaded Tangled Repo record value. */ 38 + record: TangledRepoRecord; 39 + /** Indicates whether the record is currently loading. */ 40 + loading: boolean; 41 + /** Fetch error, if any. */ 42 + error?: Error; 43 + /** Preferred color scheme for downstream components. */ 44 + colorScheme?: "light" | "dark" | "system"; 45 + /** DID associated with the record. */ 46 + did: string; 47 + /** Record key for the repo. */ 48 + rkey: string; 49 + /** Canonical external URL for linking to the repo. */ 50 + canonicalUrl: string; 51 + /** Whether to show star count from backlinks. */ 52 + showStarCount?: boolean; 53 + /** Branch to query for language information. */ 54 + branch?: string; 55 + /** Prefetched language names. */ 56 + languages?: string[]; 57 + }; 58 + 59 + /** NSID for Tangled Repo records. */ 60 + export const TANGLED_REPO_COLLECTION = "sh.tangled.repo"; 61 + 62 + /** 63 + * Resolves a Tangled Repo record and renders it with optional overrides while computing a canonical link. 64 + * 65 + * @param did - DID whose Tangled Repo should be fetched. 66 + * @param rkey - Record key within the Tangled Repo collection. 67 + * @param renderer - Optional component override that will receive injected props. 68 + * @param fallback - Node rendered before the first load begins. 69 + * @param loadingIndicator - Node rendered while the Tangled Repo is loading. 70 + * @param colorScheme - Preferred color scheme for theming the renderer. 71 + * @param showStarCount - Whether to show star count from backlinks. Defaults to true. 72 + * @param branch - Branch to query for language information. Defaults to trying "main", then "master". 73 + * @param languages - Prefetched language names (e.g., ['TypeScript', 'React']). When provided, skips fetching languages from the knot server. 74 + * @returns A JSX subtree representing the Tangled Repo record with loading states handled. 75 + */ 76 + export const TangledRepo: React.FC<TangledRepoProps> = React.memo(({ 77 + did, 78 + rkey, 79 + record, 80 + renderer, 81 + fallback, 82 + loadingIndicator, 83 + colorScheme, 84 + showStarCount = true, 85 + branch, 86 + languages, 87 + }) => { 88 + const { tangledBaseUrl } = useAtProto(); 89 + const Comp: React.ComponentType<TangledRepoRendererInjectedProps> = 90 + renderer ?? ((props) => <TangledRepoRenderer {...props} />); 91 + const Wrapped: React.FC<{ 92 + record: TangledRepoRecord; 93 + loading: boolean; 94 + error?: Error; 95 + }> = (props) => ( 96 + <Comp 97 + {...props} 98 + colorScheme={colorScheme} 99 + did={did} 100 + rkey={rkey} 101 + canonicalUrl={`${tangledBaseUrl}/${did}/${encodeURIComponent(props.record.name)}`} 102 + showStarCount={showStarCount} 103 + branch={branch} 104 + languages={languages} 105 + /> 106 + ); 107 + 108 + if (record !== undefined) { 109 + return ( 110 + <AtProtoRecord<TangledRepoRecord> 111 + record={record} 112 + renderer={Wrapped} 113 + fallback={fallback} 114 + loadingIndicator={loadingIndicator} 115 + /> 116 + ); 117 + } 118 + 119 + return ( 120 + <AtProtoRecord<TangledRepoRecord> 121 + did={did} 122 + collection={TANGLED_REPO_COLLECTION} 123 + rkey={rkey} 124 + renderer={Wrapped} 125 + fallback={fallback} 126 + loadingIndicator={loadingIndicator} 127 + /> 128 + ); 129 + }); 130 + 131 + export default TangledRepo;
+41 -12
lib/components/TangledString.tsx
··· 1 - import React from 'react'; 2 - import { AtProtoRecord } from '../core/AtProtoRecord'; 3 - import { TangledStringRenderer } from '../renderers/TangledStringRenderer'; 4 - import type { TangledStringRecord } from '../renderers/TangledStringRenderer'; 1 + import React from "react"; 2 + import { AtProtoRecord } from "../core/AtProtoRecord"; 3 + import { TangledStringRenderer } from "../renderers/TangledStringRenderer"; 4 + import type { TangledStringRecord } from "../types/tangled"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 5 6 6 7 /** 7 8 * Props for rendering Tangled String records. ··· 11 12 did: string; 12 13 /** Record key within the `sh.tangled.string` collection. */ 13 14 rkey: string; 15 + /** Prefetched Tangled String record. When provided, skips fetching from the network. */ 16 + record?: TangledStringRecord; 14 17 /** Optional renderer override for custom presentation. */ 15 18 renderer?: React.ComponentType<TangledStringRendererInjectedProps>; 16 19 /** Fallback node displayed before loading begins. */ ··· 18 21 /** Indicator node shown while data is loading. */ 19 22 loadingIndicator?: React.ReactNode; 20 23 /** Preferred color scheme for theming. */ 21 - colorScheme?: 'light' | 'dark' | 'system'; 24 + colorScheme?: "light" | "dark" | "system"; 22 25 } 23 26 24 27 /** ··· 32 35 /** Fetch error, if any. */ 33 36 error?: Error; 34 37 /** Preferred color scheme for downstream components. */ 35 - colorScheme?: 'light' | 'dark' | 'system'; 38 + colorScheme?: "light" | "dark" | "system"; 36 39 /** DID associated with the record. */ 37 40 did: string; 38 41 /** Record key for the string. */ ··· 42 45 }; 43 46 44 47 /** NSID for Tangled String records. */ 45 - export const TANGLED_COLLECTION = 'sh.tangled.string'; 48 + export const TANGLED_COLLECTION = "sh.tangled.string"; 46 49 47 50 /** 48 51 * Resolves a Tangled String record and renders it with optional overrides while computing a canonical link. ··· 55 58 * @param colorScheme - Preferred color scheme for theming the renderer. 56 59 * @returns A JSX subtree representing the Tangled String record with loading states handled. 57 60 */ 58 - export const TangledString: React.FC<TangledStringProps> = ({ did, rkey, renderer, fallback, loadingIndicator, colorScheme }) => { 59 - const Comp: React.ComponentType<TangledStringRendererInjectedProps> = renderer ?? ((props) => <TangledStringRenderer {...props} />); 60 - const Wrapped: React.FC<{ record: TangledStringRecord; loading: boolean; error?: Error }> = (props) => ( 61 + export const TangledString: React.FC<TangledStringProps> = React.memo(({ 62 + did, 63 + rkey, 64 + record, 65 + renderer, 66 + fallback, 67 + loadingIndicator, 68 + colorScheme, 69 + }) => { 70 + const { tangledBaseUrl } = useAtProto(); 71 + const Comp: React.ComponentType<TangledStringRendererInjectedProps> = 72 + renderer ?? ((props) => <TangledStringRenderer {...props} />); 73 + const Wrapped: React.FC<{ 74 + record: TangledStringRecord; 75 + loading: boolean; 76 + error?: Error; 77 + }> = (props) => ( 61 78 <Comp 62 79 {...props} 63 80 colorScheme={colorScheme} 64 81 did={did} 65 82 rkey={rkey} 66 - canonicalUrl={`https://tangled.org/strings/${did}/${encodeURIComponent(rkey)}`} 83 + canonicalUrl={`${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`} 67 84 /> 68 85 ); 86 + 87 + if (record !== undefined) { 88 + return ( 89 + <AtProtoRecord<TangledStringRecord> 90 + record={record} 91 + renderer={Wrapped} 92 + fallback={fallback} 93 + loadingIndicator={loadingIndicator} 94 + /> 95 + ); 96 + } 97 + 69 98 return ( 70 99 <AtProtoRecord<TangledStringRecord> 71 100 did={did} ··· 76 105 loadingIndicator={loadingIndicator} 77 106 /> 78 107 ); 79 - }; 108 + }); 80 109 81 110 export default TangledString;
+157 -14
lib/core/AtProtoRecord.tsx
··· 1 - import React from 'react'; 2 - import { useAtProtoRecord } from '../hooks/useAtProtoRecord'; 1 + import React, { useState, useEffect, useRef } from "react"; 2 + import { useAtProtoRecord } from "../hooks/useAtProtoRecord"; 3 3 4 + /** 5 + * Common rendering customization props for AT Protocol records. 6 + */ 4 7 interface AtProtoRecordRenderProps<T> { 5 - renderer?: React.ComponentType<{ record: T; loading: boolean; error?: Error }>; 8 + /** Custom renderer component that receives the fetched record and loading state. */ 9 + renderer?: React.ComponentType<{ 10 + record: T; 11 + loading: boolean; 12 + error?: Error; 13 + }>; 14 + /** React node displayed when no record is available (after error or before load). */ 6 15 fallback?: React.ReactNode; 16 + /** React node shown while the record is being fetched. */ 7 17 loadingIndicator?: React.ReactNode; 18 + /** Auto-refresh interval in milliseconds. When set, the record will be refetched at this interval. */ 19 + refreshInterval?: number; 20 + /** Comparison function to determine if a record has changed. Used to prevent unnecessary re-renders during auto-refresh. */ 21 + compareRecords?: (prev: T | undefined, next: T | undefined) => boolean; 8 22 } 9 23 24 + /** 25 + * Props for fetching an AT Protocol record from the network. 26 + */ 10 27 type AtProtoRecordFetchProps<T> = AtProtoRecordRenderProps<T> & { 28 + /** Repository DID that owns the record. */ 11 29 did: string; 30 + /** NSID collection containing the record. */ 12 31 collection: string; 32 + /** Record key identifying the specific record. */ 13 33 rkey: string; 34 + /** Must be undefined when fetching (discriminates the union type). */ 14 35 record?: undefined; 15 36 }; 16 37 38 + /** 39 + * Props for rendering a prefetched AT Protocol record. 40 + */ 17 41 type AtProtoRecordProvidedRecordProps<T> = AtProtoRecordRenderProps<T> & { 42 + /** Prefetched record value to render (skips network fetch). */ 18 43 record: T; 44 + /** Optional DID for context (not used for fetching). */ 19 45 did?: string; 46 + /** Optional collection for context (not used for fetching). */ 20 47 collection?: string; 48 + /** Optional rkey for context (not used for fetching). */ 21 49 rkey?: string; 22 50 }; 23 51 24 - export type AtProtoRecordProps<T = unknown> = AtProtoRecordFetchProps<T> | AtProtoRecordProvidedRecordProps<T>; 52 + /** 53 + * Union type for AT Protocol record props - supports both fetching and prefetched records. 54 + */ 55 + export type AtProtoRecordProps<T = unknown> = 56 + | AtProtoRecordFetchProps<T> 57 + | AtProtoRecordProvidedRecordProps<T>; 25 58 59 + /** 60 + * Core component for fetching and rendering AT Protocol records with customizable presentation. 61 + * 62 + * Supports two modes: 63 + * 1. **Fetch mode**: Provide `did`, `collection`, and `rkey` to fetch the record from the network 64 + * 2. **Prefetch mode**: Provide a `record` directly to skip fetching (useful for SSR/caching) 65 + * 66 + * When no custom renderer is provided, displays the record as formatted JSON. 67 + * 68 + * **Auto-refresh**: Set `refreshInterval` to automatically refetch the record at the specified interval. 69 + * The component intelligently avoids re-rendering if the record hasn't changed (using `compareRecords`). 70 + * 71 + * @example 72 + * ```tsx 73 + * // Fetch mode - retrieves record from network 74 + * <AtProtoRecord 75 + * did="did:plc:example" 76 + * collection="app.bsky.feed.post" 77 + * rkey="3k2aexample" 78 + * renderer={MyCustomRenderer} 79 + * /> 80 + * ``` 81 + * 82 + * @example 83 + * ```tsx 84 + * // Prefetch mode - uses provided record 85 + * <AtProtoRecord 86 + * record={myPrefetchedRecord} 87 + * renderer={MyCustomRenderer} 88 + * /> 89 + * ``` 90 + * 91 + * @example 92 + * ```tsx 93 + * // Auto-refresh mode - refetches every 15 seconds 94 + * <AtProtoRecord 95 + * did="did:plc:example" 96 + * collection="fm.teal.alpha.actor.status" 97 + * rkey="self" 98 + * refreshInterval={15000} 99 + * compareRecords={(prev, next) => JSON.stringify(prev) === JSON.stringify(next)} 100 + * renderer={MyCustomRenderer} 101 + * /> 102 + * ``` 103 + * 104 + * @param props - Either fetch props (did/collection/rkey) or prefetch props (record). 105 + * @returns A rendered AT Protocol record with loading/error states handled. 106 + */ 26 107 export function AtProtoRecord<T = unknown>(props: AtProtoRecordProps<T>) { 27 - const { renderer: Renderer, fallback = null, loadingIndicator = 'Loadingโ€ฆ' } = props; 28 - const hasProvidedRecord = 'record' in props; 108 + const { 109 + renderer: Renderer, 110 + fallback = null, 111 + loadingIndicator = "Loadingโ€ฆ", 112 + refreshInterval, 113 + compareRecords, 114 + } = props; 115 + const hasProvidedRecord = "record" in props; 29 116 const providedRecord = hasProvidedRecord ? props.record : undefined; 30 117 31 - const { record: fetchedRecord, error, loading } = useAtProtoRecord<T>({ 32 - did: hasProvidedRecord ? undefined : props.did, 33 - collection: hasProvidedRecord ? undefined : props.collection, 34 - rkey: hasProvidedRecord ? undefined : props.rkey, 118 + // Extract fetch props for logging 119 + const fetchDid = hasProvidedRecord ? undefined : (props as any).did; 120 + const fetchCollection = hasProvidedRecord ? undefined : (props as any).collection; 121 + const fetchRkey = hasProvidedRecord ? undefined : (props as any).rkey; 122 + 123 + // State for managing auto-refresh 124 + const [refreshKey, setRefreshKey] = useState(0); 125 + const [stableRecord, setStableRecord] = useState<T | undefined>(providedRecord); 126 + const previousRecordRef = useRef<T | undefined>(providedRecord); 127 + 128 + // Auto-refresh interval 129 + useEffect(() => { 130 + if (!refreshInterval || hasProvidedRecord) return; 131 + 132 + const interval = setInterval(() => { 133 + setRefreshKey((prev) => prev + 1); 134 + }, refreshInterval); 135 + 136 + return () => clearInterval(interval); 137 + }, [refreshInterval, hasProvidedRecord, fetchCollection, fetchDid]); 138 + 139 + const { 140 + record: fetchedRecord, 141 + error, 142 + loading, 143 + } = useAtProtoRecord<T>({ 144 + did: fetchDid, 145 + collection: fetchCollection, 146 + rkey: fetchRkey, 147 + bypassCache: !!refreshInterval && refreshKey > 0, // Bypass cache on auto-refresh (but not initial load) 148 + _refreshKey: refreshKey, // Force hook to re-run 35 149 }); 36 150 37 - const record = providedRecord ?? fetchedRecord; 38 - const isLoading = loading && !providedRecord; 151 + // Determine which record to use 152 + const currentRecord = providedRecord ?? fetchedRecord; 153 + 154 + // Handle record changes with optional comparison 155 + useEffect(() => { 156 + if (!currentRecord) return; 157 + 158 + const hasChanged = compareRecords 159 + ? !compareRecords(previousRecordRef.current, currentRecord) 160 + : previousRecordRef.current !== currentRecord; 161 + 162 + if (hasChanged) { 163 + setStableRecord(currentRecord); 164 + previousRecordRef.current = currentRecord; 165 + } 166 + }, [currentRecord, compareRecords]); 167 + 168 + const record = stableRecord; 169 + const isLoading = loading && !providedRecord && !stableRecord; 39 170 40 171 if (error && !record) return <>{fallback}</>; 41 172 if (!record) return <>{isLoading ? loadingIndicator : fallback}</>; 42 - if (Renderer) return <Renderer record={record} loading={isLoading} error={error} />; 43 - return <pre style={{ fontSize: 12, padding: 8, background: '#f5f5f5', overflow: 'auto' }}>{JSON.stringify(record, null, 2)}</pre>; 173 + if (Renderer) 174 + return <Renderer record={record} loading={isLoading} error={error} />; 175 + return ( 176 + <pre 177 + style={{ 178 + fontSize: 12, 179 + padding: 8, 180 + background: "#f5f5f5", 181 + overflow: "auto", 182 + }} 183 + > 184 + {JSON.stringify(record, null, 2)} 185 + </pre> 186 + ); 44 187 }
+268 -67
lib/hooks/useAtProtoRecord.ts
··· 1 - import { useEffect, useState } from 'react'; 2 - import { useDidResolution } from './useDidResolution'; 3 - import { usePdsEndpoint } from './usePdsEndpoint'; 4 - import { createAtprotoClient } from '../utils/atproto-client'; 1 + import { useEffect, useState, useRef } from "react"; 2 + import { useDidResolution } from "./useDidResolution"; 3 + import { usePdsEndpoint } from "./usePdsEndpoint"; 4 + import { createAtprotoClient } from "../utils/atproto-client"; 5 + import { useBlueskyAppview } from "./useBlueskyAppview"; 6 + import { useAtProto } from "../providers/AtProtoProvider"; 5 7 6 8 /** 7 9 * Identifier trio required to address an AT Protocol record. 8 10 */ 9 11 export interface AtProtoRecordKey { 10 - /** Repository DID (or handle prior to resolution) containing the record. */ 11 - did?: string; 12 - /** NSID collection in which the record resides. */ 13 - collection?: string; 14 - /** Record key string uniquely identifying the record within the collection. */ 15 - rkey?: string; 12 + /** Repository DID (or handle prior to resolution) containing the record. */ 13 + did?: string; 14 + /** NSID collection in which the record resides. */ 15 + collection?: string; 16 + /** Record key string uniquely identifying the record within the collection. */ 17 + rkey?: string; 18 + /** Force bypass cache and refetch from network. Useful for auto-refresh scenarios. */ 19 + bypassCache?: boolean; 20 + /** Internal refresh trigger - changes to this value force a refetch. */ 21 + _refreshKey?: number; 16 22 } 17 23 18 24 /** 19 25 * Loading state returned by {@link useAtProtoRecord}. 20 26 */ 21 27 export interface AtProtoRecordState<T = unknown> { 22 - /** Resolved record value when fetch succeeds. */ 23 - record?: T; 24 - /** Error thrown while loading, if any. */ 25 - error?: Error; 26 - /** Indicates whether the hook is in a loading state. */ 27 - loading: boolean; 28 + /** Resolved record value when fetch succeeds. */ 29 + record?: T; 30 + /** Error thrown while loading, if any. */ 31 + error?: Error; 32 + /** Indicates whether the hook is in a loading state. */ 33 + loading: boolean; 28 34 } 29 35 30 36 /** 31 37 * React hook that fetches a single AT Protocol record and tracks loading/error state. 38 + * 39 + * For Bluesky collections (app.bsky.*), uses a three-tier fallback strategy: 40 + * 1. Try Bluesky appview API first 41 + * 2. Fall back to Slingshot getRecord 42 + * 3. Finally query the PDS directly 43 + * 44 + * For other collections, queries the PDS directly (with Slingshot fallback via the client handler). 32 45 * 33 46 * @param did - DID (or handle before resolution) that owns the record. 34 47 * @param collection - NSID collection from which to fetch the record. 35 48 * @param rkey - Record key identifying the record within the collection. 49 + * @param bypassCache - Force bypass cache and refetch from network. Useful for auto-refresh scenarios. 50 + * @param _refreshKey - Internal parameter used to trigger refetches. 36 51 * @returns {AtProtoRecordState<T>} Object containing the resolved record, any error, and a loading flag. 37 52 */ 38 - export function useAtProtoRecord<T = unknown>({ did: handleOrDid, collection, rkey }: AtProtoRecordKey): AtProtoRecordState<T> { 39 - const { did, error: didError, loading: resolvingDid } = useDidResolution(handleOrDid); 40 - const { endpoint, error: endpointError, loading: resolvingEndpoint } = usePdsEndpoint(did); 41 - const [state, setState] = useState<AtProtoRecordState<T>>({ loading: !!(handleOrDid && collection && rkey) }); 53 + export function useAtProtoRecord<T = unknown>({ 54 + did: handleOrDid, 55 + collection, 56 + rkey, 57 + bypassCache = false, 58 + _refreshKey = 0, 59 + }: AtProtoRecordKey): AtProtoRecordState<T> { 60 + const { recordCache } = useAtProto(); 61 + const isBlueskyCollection = collection?.startsWith("app.bsky."); 62 + 63 + // Always call all hooks (React rules) - conditionally use results 64 + const blueskyResult = useBlueskyAppview<T>({ 65 + did: isBlueskyCollection ? handleOrDid : undefined, 66 + collection: isBlueskyCollection ? collection : undefined, 67 + rkey: isBlueskyCollection ? rkey : undefined, 68 + }); 69 + 70 + const { 71 + did, 72 + error: didError, 73 + loading: resolvingDid, 74 + } = useDidResolution(handleOrDid); 75 + const { 76 + endpoint, 77 + error: endpointError, 78 + loading: resolvingEndpoint, 79 + } = usePdsEndpoint(did); 80 + const [state, setState] = useState<AtProtoRecordState<T>>({ 81 + loading: !!(handleOrDid && collection && rkey), 82 + }); 83 + 84 + const releaseRef = useRef<(() => void) | undefined>(undefined); 85 + 86 + useEffect(() => { 87 + let cancelled = false; 88 + 89 + const assignState = (next: Partial<AtProtoRecordState<T>>) => { 90 + if (cancelled) return; 91 + setState((prev) => ({ ...prev, ...next })); 92 + }; 93 + 94 + if (!handleOrDid || !collection || !rkey) { 95 + assignState({ 96 + loading: false, 97 + record: undefined, 98 + error: undefined, 99 + }); 100 + return () => { 101 + cancelled = true; 102 + if (releaseRef.current) { 103 + releaseRef.current(); 104 + releaseRef.current = undefined; 105 + } 106 + }; 107 + } 108 + 109 + if (didError) { 110 + assignState({ loading: false, error: didError }); 111 + return () => { 112 + cancelled = true; 113 + if (releaseRef.current) { 114 + releaseRef.current(); 115 + releaseRef.current = undefined; 116 + } 117 + }; 118 + } 119 + 120 + if (endpointError) { 121 + assignState({ loading: false, error: endpointError }); 122 + return () => { 123 + cancelled = true; 124 + if (releaseRef.current) { 125 + releaseRef.current(); 126 + releaseRef.current = undefined; 127 + } 128 + }; 129 + } 130 + 131 + if (resolvingDid || resolvingEndpoint || !did || !endpoint) { 132 + assignState({ loading: true, error: undefined }); 133 + return () => { 134 + cancelled = true; 135 + if (releaseRef.current) { 136 + releaseRef.current(); 137 + releaseRef.current = undefined; 138 + } 139 + }; 140 + } 141 + 142 + assignState({ loading: true, error: undefined, record: undefined }); 143 + 144 + // Bypass cache if requested (for auto-refresh scenarios) 145 + if (bypassCache) { 146 + assignState({ loading: true, error: undefined }); 147 + 148 + // Skip cache and fetch directly 149 + const controller = new AbortController(); 150 + 151 + const fetchPromise = (async () => { 152 + try { 153 + const { rpc } = await createAtprotoClient({ 154 + service: endpoint, 155 + }); 156 + const res = await ( 157 + rpc as unknown as { 158 + get: ( 159 + nsid: string, 160 + opts: { 161 + params: { 162 + repo: string; 163 + collection: string; 164 + rkey: string; 165 + }; 166 + }, 167 + ) => Promise<{ ok: boolean; data: { value: T } }>; 168 + } 169 + ).get("com.atproto.repo.getRecord", { 170 + params: { repo: did, collection, rkey }, 171 + }); 172 + if (!res.ok) throw new Error("Failed to load record"); 173 + return (res.data as { value: T }).value; 174 + } catch (err) { 175 + // Provide helpful error for banned/unreachable Bluesky PDSes 176 + if (endpoint.includes('.bsky.network')) { 177 + throw new Error( 178 + `Record unavailable. The Bluesky PDS (${endpoint}) may be unreachable or the account may be banned.` 179 + ); 180 + } 181 + throw err; 182 + } 183 + })(); 42 184 43 - useEffect(() => { 44 - let cancelled = false; 185 + fetchPromise 186 + .then((record) => { 187 + if (!cancelled) { 188 + assignState({ record, loading: false }); 189 + } 190 + }) 191 + .catch((e) => { 192 + if (!cancelled) { 193 + const err = e instanceof Error ? e : new Error(String(e)); 194 + assignState({ error: err, loading: false }); 195 + } 196 + }); 45 197 46 - const assignState = (next: Partial<AtProtoRecordState<T>>) => { 47 - if (cancelled) return; 48 - setState(prev => ({ ...prev, ...next })); 49 - }; 198 + return () => { 199 + cancelled = true; 200 + controller.abort(); 201 + }; 202 + } 50 203 51 - if (!handleOrDid || !collection || !rkey) { 52 - assignState({ loading: false, record: undefined, error: undefined }); 53 - return () => { cancelled = true; }; 54 - } 204 + // Use recordCache.ensure for deduplication and caching 205 + const { promise, release } = recordCache.ensure<T>( 206 + did, 207 + collection, 208 + rkey, 209 + () => { 210 + const controller = new AbortController(); 55 211 56 - if (didError) { 57 - assignState({ loading: false, error: didError }); 58 - return () => { cancelled = true; }; 59 - } 212 + const fetchPromise = (async () => { 213 + try { 214 + const { rpc } = await createAtprotoClient({ 215 + service: endpoint, 216 + }); 217 + const res = await ( 218 + rpc as unknown as { 219 + get: ( 220 + nsid: string, 221 + opts: { 222 + params: { 223 + repo: string; 224 + collection: string; 225 + rkey: string; 226 + }; 227 + }, 228 + ) => Promise<{ ok: boolean; data: { value: T } }>; 229 + } 230 + ).get("com.atproto.repo.getRecord", { 231 + params: { repo: did, collection, rkey }, 232 + }); 233 + if (!res.ok) throw new Error("Failed to load record"); 234 + return (res.data as { value: T }).value; 235 + } catch (err) { 236 + // Provide helpful error for banned/unreachable Bluesky PDSes 237 + if (endpoint.includes('.bsky.network')) { 238 + throw new Error( 239 + `Record unavailable. The Bluesky PDS (${endpoint}) may be unreachable or the account may be banned.` 240 + ); 241 + } 242 + throw err; 243 + } 244 + })(); 60 245 61 - if (endpointError) { 62 - assignState({ loading: false, error: endpointError }); 63 - return () => { cancelled = true; }; 64 - } 246 + return { 247 + promise: fetchPromise, 248 + abort: () => controller.abort(), 249 + }; 250 + } 251 + ); 65 252 66 - if (resolvingDid || resolvingEndpoint || !did || !endpoint) { 67 - assignState({ loading: true, error: undefined }); 68 - return () => { cancelled = true; }; 69 - } 253 + releaseRef.current = release; 70 254 71 - assignState({ loading: true, error: undefined, record: undefined }); 255 + promise 256 + .then((record) => { 257 + if (!cancelled) { 258 + assignState({ record, loading: false }); 259 + } 260 + }) 261 + .catch((e) => { 262 + if (!cancelled) { 263 + const err = e instanceof Error ? e : new Error(String(e)); 264 + assignState({ error: err, loading: false }); 265 + } 266 + }); 72 267 73 - (async () => { 74 - try { 75 - const { rpc } = await createAtprotoClient({ service: endpoint }); 76 - const res = await (rpc as unknown as { 77 - get: ( 78 - nsid: string, 79 - opts: { params: { repo: string; collection: string; rkey: string } } 80 - ) => Promise<{ ok: boolean; data: { value: T } }>; 81 - }).get('com.atproto.repo.getRecord', { 82 - params: { repo: did, collection, rkey } 83 - }); 84 - if (!res.ok) throw new Error('Failed to load record'); 85 - const record = (res.data as { value: T }).value; 86 - assignState({ record, loading: false }); 87 - } catch (e) { 88 - const err = e instanceof Error ? e : new Error(String(e)); 89 - assignState({ error: err, loading: false }); 90 - } 91 - })(); 268 + return () => { 269 + cancelled = true; 270 + if (releaseRef.current) { 271 + releaseRef.current(); 272 + releaseRef.current = undefined; 273 + } 274 + }; 275 + }, [ 276 + handleOrDid, 277 + did, 278 + endpoint, 279 + collection, 280 + rkey, 281 + resolvingDid, 282 + resolvingEndpoint, 283 + didError, 284 + endpointError, 285 + recordCache, 286 + bypassCache, 287 + _refreshKey, 288 + ]); 92 289 93 - return () => { 94 - cancelled = true; 95 - }; 96 - }, [handleOrDid, did, endpoint, collection, rkey, resolvingDid, resolvingEndpoint, didError, endpointError]); 290 + // Return Bluesky result for app.bsky.* collections 291 + if (isBlueskyCollection) { 292 + return { 293 + record: blueskyResult.record, 294 + error: blueskyResult.error, 295 + loading: blueskyResult.loading, 296 + }; 297 + } 97 298 98 - return state; 299 + return state; 99 300 }
+163
lib/hooks/useBacklinks.ts
··· 1 + import { useEffect, useState, useCallback, useRef } from "react"; 2 + 3 + /** 4 + * Individual backlink record returned by Microcosm Constellation. 5 + */ 6 + export interface BacklinkRecord { 7 + /** DID of the author who created the backlink. */ 8 + did: string; 9 + /** Collection type of the backlink record (e.g., "sh.tangled.feed.star"). */ 10 + collection: string; 11 + /** Record key of the backlink. */ 12 + rkey: string; 13 + } 14 + 15 + /** 16 + * Response from Microcosm Constellation API. 17 + */ 18 + export interface BacklinksResponse { 19 + /** Total count of backlinks. */ 20 + total: number; 21 + /** Array of backlink records. */ 22 + records: BacklinkRecord[]; 23 + /** Cursor for pagination (optional). */ 24 + cursor?: string; 25 + } 26 + 27 + /** 28 + * Parameters for fetching backlinks. 29 + */ 30 + export interface UseBacklinksParams { 31 + /** The AT-URI subject to get backlinks for (e.g., "at://did:plc:xxx/sh.tangled.repo/yyy"). */ 32 + subject: string; 33 + /** The source collection and path (e.g., "sh.tangled.feed.star:subject"). */ 34 + source: string; 35 + /** Maximum number of results to fetch (default: 16, max: 100). */ 36 + limit?: number; 37 + /** Base URL for the Microcosm Constellation API. */ 38 + constellationBaseUrl?: string; 39 + /** Whether to automatically fetch backlinks on mount. */ 40 + enabled?: boolean; 41 + } 42 + 43 + const DEFAULT_CONSTELLATION = "https://constellation.microcosm.blue"; 44 + 45 + /** 46 + * Hook to fetch backlinks from Microcosm Constellation API. 47 + * 48 + * Backlinks are records that reference another record. For example, 49 + * `sh.tangled.feed.star` records are backlinks to `sh.tangled.repo` records, 50 + * representing users who have starred a repository. 51 + * 52 + * @param params - Configuration for fetching backlinks 53 + * @returns Object containing backlinks data, loading state, error, and refetch function 54 + * 55 + * @example 56 + * ```tsx 57 + * const { backlinks, loading, error, count } = useBacklinks({ 58 + * subject: "at://did:plc:example/sh.tangled.repo/3k2aexample", 59 + * source: "sh.tangled.feed.star:subject", 60 + * }); 61 + * ``` 62 + */ 63 + export function useBacklinks({ 64 + subject, 65 + source, 66 + limit = 16, 67 + constellationBaseUrl = DEFAULT_CONSTELLATION, 68 + enabled = true, 69 + }: UseBacklinksParams) { 70 + const [backlinks, setBacklinks] = useState<BacklinkRecord[]>([]); 71 + const [total, setTotal] = useState(0); 72 + const [loading, setLoading] = useState(false); 73 + const [error, setError] = useState<Error | undefined>(undefined); 74 + const [cursor, setCursor] = useState<string | undefined>(undefined); 75 + const abortControllerRef = useRef<AbortController | null>(null); 76 + 77 + const fetchBacklinks = useCallback( 78 + async (signal?: AbortSignal) => { 79 + if (!subject || !source || !enabled) return; 80 + 81 + try { 82 + setLoading(true); 83 + setError(undefined); 84 + 85 + const baseUrl = constellationBaseUrl.endsWith("/") 86 + ? constellationBaseUrl.slice(0, -1) 87 + : constellationBaseUrl; 88 + 89 + const params = new URLSearchParams({ 90 + subject: subject, 91 + source: source, 92 + limit: limit.toString(), 93 + }); 94 + 95 + const url = `${baseUrl}/xrpc/blue.microcosm.links.getBacklinks?${params}`; 96 + 97 + const response = await fetch(url, { signal }); 98 + 99 + if (!response.ok) { 100 + throw new Error( 101 + `Failed to fetch backlinks: ${response.status} ${response.statusText}`, 102 + ); 103 + } 104 + 105 + const data: BacklinksResponse = await response.json(); 106 + setBacklinks(data.records || []); 107 + setTotal(data.total || 0); 108 + setCursor(data.cursor); 109 + } catch (err) { 110 + if (err instanceof Error && err.name === "AbortError") { 111 + // Ignore abort errors 112 + return; 113 + } 114 + setError( 115 + err instanceof Error ? err : new Error("Unknown error fetching backlinks"), 116 + ); 117 + } finally { 118 + setLoading(false); 119 + } 120 + }, 121 + [subject, source, limit, constellationBaseUrl, enabled], 122 + ); 123 + 124 + const refetch = useCallback(() => { 125 + // Abort any in-flight request 126 + if (abortControllerRef.current) { 127 + abortControllerRef.current.abort(); 128 + } 129 + 130 + const controller = new AbortController(); 131 + abortControllerRef.current = controller; 132 + fetchBacklinks(controller.signal); 133 + }, [fetchBacklinks]); 134 + 135 + useEffect(() => { 136 + if (!enabled) return; 137 + 138 + const controller = new AbortController(); 139 + abortControllerRef.current = controller; 140 + fetchBacklinks(controller.signal); 141 + 142 + return () => { 143 + controller.abort(); 144 + }; 145 + }, [fetchBacklinks, enabled]); 146 + 147 + return { 148 + /** Array of backlink records. */ 149 + backlinks, 150 + /** Whether backlinks are currently being fetched. */ 151 + loading, 152 + /** Error if fetch failed. */ 153 + error, 154 + /** Pagination cursor (not yet implemented for pagination). */ 155 + cursor, 156 + /** Total count of backlinks from the API. */ 157 + total, 158 + /** Total count of backlinks (alias for total). */ 159 + count: total, 160 + /** Function to manually refetch backlinks. */ 161 + refetch, 162 + }; 163 + }
+148 -109
lib/hooks/useBlob.ts
··· 1 - import { useEffect, useRef, useState } from 'react'; 2 - import { useDidResolution } from './useDidResolution'; 3 - import { usePdsEndpoint } from './usePdsEndpoint'; 4 - import { useAtProto } from '../providers/AtProtoProvider'; 1 + import { useEffect, useRef, useState } from "react"; 2 + import { useDidResolution } from "./useDidResolution"; 3 + import { usePdsEndpoint } from "./usePdsEndpoint"; 4 + import { useAtProto } from "../providers/AtProtoProvider"; 5 5 6 6 /** 7 7 * Status returned by {@link useBlob} containing blob URL and metadata flags. 8 8 */ 9 9 export interface UseBlobState { 10 - /** Object URL pointing to the fetched blob, when available. */ 11 - url?: string; 12 - /** Indicates whether a fetch is in progress. */ 13 - loading: boolean; 14 - /** Error encountered while fetching the blob. */ 15 - error?: Error; 10 + /** Object URL pointing to the fetched blob, when available. */ 11 + url?: string; 12 + /** Indicates whether a fetch is in progress. */ 13 + loading: boolean; 14 + /** Error encountered while fetching the blob. */ 15 + error?: Error; 16 16 } 17 17 18 18 /** ··· 22 22 * @param cid - Content identifier for the desired blob. 23 23 * @returns {UseBlobState} Object containing the object URL, loading flag, and any error. 24 24 */ 25 - export function useBlob(handleOrDid: string | undefined, cid: string | undefined): UseBlobState { 26 - const { did, error: didError, loading: didLoading } = useDidResolution(handleOrDid); 27 - const { endpoint, error: endpointError, loading: endpointLoading } = usePdsEndpoint(did); 28 - const { blobCache } = useAtProto(); 29 - const [state, setState] = useState<UseBlobState>({ loading: !!(handleOrDid && cid) }); 30 - const objectUrlRef = useRef<string | undefined>(undefined); 25 + export function useBlob( 26 + handleOrDid: string | undefined, 27 + cid: string | undefined, 28 + ): UseBlobState { 29 + const { 30 + did, 31 + error: didError, 32 + loading: didLoading, 33 + } = useDidResolution(handleOrDid); 34 + const { 35 + endpoint, 36 + error: endpointError, 37 + loading: endpointLoading, 38 + } = usePdsEndpoint(did); 39 + const { blobCache } = useAtProto(); 40 + const [state, setState] = useState<UseBlobState>({ 41 + loading: !!(handleOrDid && cid), 42 + }); 43 + const objectUrlRef = useRef<string | undefined>(undefined); 31 44 32 - useEffect(() => () => { 33 - if (objectUrlRef.current) { 34 - URL.revokeObjectURL(objectUrlRef.current); 35 - objectUrlRef.current = undefined; 36 - } 37 - }, []); 45 + useEffect( 46 + () => () => { 47 + if (objectUrlRef.current) { 48 + URL.revokeObjectURL(objectUrlRef.current); 49 + objectUrlRef.current = undefined; 50 + } 51 + }, 52 + [], 53 + ); 38 54 39 - useEffect(() => { 40 - let cancelled = false; 55 + useEffect(() => { 56 + let cancelled = false; 41 57 42 - const clearObjectUrl = () => { 43 - if (objectUrlRef.current) { 44 - URL.revokeObjectURL(objectUrlRef.current); 45 - objectUrlRef.current = undefined; 46 - } 47 - }; 58 + const clearObjectUrl = () => { 59 + if (objectUrlRef.current) { 60 + URL.revokeObjectURL(objectUrlRef.current); 61 + objectUrlRef.current = undefined; 62 + } 63 + }; 48 64 49 - if (!handleOrDid || !cid) { 50 - clearObjectUrl(); 51 - setState({ loading: false }); 52 - return () => { 53 - cancelled = true; 54 - }; 55 - } 65 + if (!handleOrDid || !cid) { 66 + clearObjectUrl(); 67 + setState({ loading: false }); 68 + return () => { 69 + cancelled = true; 70 + }; 71 + } 56 72 57 - if (didError) { 58 - clearObjectUrl(); 59 - setState({ loading: false, error: didError }); 60 - return () => { 61 - cancelled = true; 62 - }; 63 - } 73 + if (didError) { 74 + clearObjectUrl(); 75 + setState({ loading: false, error: didError }); 76 + return () => { 77 + cancelled = true; 78 + }; 79 + } 64 80 65 - if (endpointError) { 66 - clearObjectUrl(); 67 - setState({ loading: false, error: endpointError }); 68 - return () => { 69 - cancelled = true; 70 - }; 71 - } 81 + if (endpointError) { 82 + clearObjectUrl(); 83 + setState({ loading: false, error: endpointError }); 84 + return () => { 85 + cancelled = true; 86 + }; 87 + } 72 88 73 - if (didLoading || endpointLoading || !did || !endpoint) { 74 - setState(prev => ({ ...prev, loading: true, error: undefined })); 75 - return () => { 76 - cancelled = true; 77 - }; 78 - } 89 + if (didLoading || endpointLoading || !did || !endpoint) { 90 + setState((prev) => ({ ...prev, loading: true, error: undefined })); 91 + return () => { 92 + cancelled = true; 93 + }; 94 + } 79 95 80 - const cachedBlob = blobCache.get(did, cid); 81 - if (cachedBlob) { 82 - const nextUrl = URL.createObjectURL(cachedBlob); 83 - const prevUrl = objectUrlRef.current; 84 - objectUrlRef.current = nextUrl; 85 - if (prevUrl) URL.revokeObjectURL(prevUrl); 86 - setState({ url: nextUrl, loading: false }); 87 - return () => { 88 - cancelled = true; 89 - }; 90 - } 96 + const cachedBlob = blobCache.get(did, cid); 97 + if (cachedBlob) { 98 + const nextUrl = URL.createObjectURL(cachedBlob); 99 + const prevUrl = objectUrlRef.current; 100 + objectUrlRef.current = nextUrl; 101 + if (prevUrl) URL.revokeObjectURL(prevUrl); 102 + setState({ url: nextUrl, loading: false }); 103 + return () => { 104 + cancelled = true; 105 + }; 106 + } 91 107 92 - let controller: AbortController | undefined; 93 - let release: (() => void) | undefined; 108 + let controller: AbortController | undefined; 109 + let release: (() => void) | undefined; 94 110 95 - (async () => { 96 - try { 97 - setState(prev => ({ ...prev, loading: true, error: undefined })); 98 - const ensureResult = blobCache.ensure(did, cid, () => { 99 - controller = new AbortController(); 100 - const promise = (async () => { 101 - const res = await fetch( 102 - `${endpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`, 103 - { signal: controller?.signal } 104 - ); 105 - if (!res.ok) throw new Error(`Blob fetch failed (${res.status})`); 106 - return res.blob(); 107 - })(); 108 - return { promise, abort: () => controller?.abort() }; 109 - }); 110 - release = ensureResult.release; 111 - const blob = await ensureResult.promise; 112 - const nextUrl = URL.createObjectURL(blob); 113 - const prevUrl = objectUrlRef.current; 114 - objectUrlRef.current = nextUrl; 115 - if (prevUrl) URL.revokeObjectURL(prevUrl); 116 - if (!cancelled) setState({ url: nextUrl, loading: false }); 117 - } catch (e) { 118 - const aborted = (controller && controller.signal.aborted) || (e instanceof DOMException && e.name === 'AbortError'); 119 - if (aborted) return; 120 - clearObjectUrl(); 121 - if (!cancelled) setState({ loading: false, error: e as Error }); 122 - } 123 - })(); 111 + (async () => { 112 + try { 113 + setState((prev) => ({ 114 + ...prev, 115 + loading: true, 116 + error: undefined, 117 + })); 118 + const ensureResult = blobCache.ensure(did, cid, () => { 119 + controller = new AbortController(); 120 + const promise = (async () => { 121 + const res = await fetch( 122 + `${endpoint}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`, 123 + { signal: controller?.signal }, 124 + ); 125 + if (!res.ok) 126 + throw new Error( 127 + `Blob fetch failed (${res.status})`, 128 + ); 129 + return res.blob(); 130 + })(); 131 + return { promise, abort: () => controller?.abort() }; 132 + }); 133 + release = ensureResult.release; 134 + const blob = await ensureResult.promise; 135 + const nextUrl = URL.createObjectURL(blob); 136 + const prevUrl = objectUrlRef.current; 137 + objectUrlRef.current = nextUrl; 138 + if (prevUrl) URL.revokeObjectURL(prevUrl); 139 + if (!cancelled) setState({ url: nextUrl, loading: false }); 140 + } catch (e) { 141 + const aborted = 142 + (controller && controller.signal.aborted) || 143 + (e instanceof DOMException && e.name === "AbortError"); 144 + if (aborted) return; 145 + clearObjectUrl(); 146 + if (!cancelled) setState({ loading: false, error: e as Error }); 147 + } 148 + })(); 124 149 125 - return () => { 126 - cancelled = true; 127 - release?.(); 128 - if (controller && controller.signal.aborted && objectUrlRef.current) { 129 - URL.revokeObjectURL(objectUrlRef.current); 130 - objectUrlRef.current = undefined; 131 - } 132 - }; 133 - }, [handleOrDid, cid, did, endpoint, didLoading, endpointLoading, didError, endpointError, blobCache]); 150 + return () => { 151 + cancelled = true; 152 + release?.(); 153 + if ( 154 + controller && 155 + controller.signal.aborted && 156 + objectUrlRef.current 157 + ) { 158 + URL.revokeObjectURL(objectUrlRef.current); 159 + objectUrlRef.current = undefined; 160 + } 161 + }; 162 + }, [ 163 + handleOrDid, 164 + cid, 165 + did, 166 + endpoint, 167 + didLoading, 168 + endpointLoading, 169 + didError, 170 + endpointError, 171 + blobCache, 172 + ]); 134 173 135 - return state; 174 + return state; 136 175 }
+727
lib/hooks/useBlueskyAppview.ts
··· 1 + import { useEffect, useReducer, useRef } from "react"; 2 + import { useDidResolution } from "./useDidResolution"; 3 + import { usePdsEndpoint } from "./usePdsEndpoint"; 4 + import { createAtprotoClient } from "../utils/atproto-client"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 6 + 7 + /** 8 + * Extended blob reference that includes CDN URL from appview responses. 9 + */ 10 + export interface BlobWithCdn { 11 + $type: "blob"; 12 + ref: { $link: string }; 13 + mimeType: string; 14 + size: number; 15 + /** CDN URL from Bluesky appview (e.g., https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg) */ 16 + cdnUrl?: string; 17 + } 18 + 19 + 20 + 21 + /** 22 + * Appview getProfile response structure. 23 + */ 24 + interface AppviewProfileResponse { 25 + did: string; 26 + handle: string; 27 + displayName?: string; 28 + description?: string; 29 + avatar?: string; 30 + banner?: string; 31 + createdAt?: string; 32 + pronouns?: string; 33 + website?: string; 34 + [key: string]: unknown; 35 + } 36 + 37 + /** 38 + * Appview getPostThread response structure. 39 + */ 40 + interface AppviewPostThreadResponse<T = unknown> { 41 + thread?: { 42 + post?: { 43 + record?: T; 44 + embed?: { 45 + $type?: string; 46 + images?: Array<{ 47 + thumb?: string; 48 + fullsize?: string; 49 + alt?: string; 50 + aspectRatio?: { width: number; height: number }; 51 + }>; 52 + media?: { 53 + images?: Array<{ 54 + thumb?: string; 55 + fullsize?: string; 56 + alt?: string; 57 + aspectRatio?: { width: number; height: number }; 58 + }>; 59 + }; 60 + }; 61 + }; 62 + }; 63 + } 64 + 65 + /** 66 + * Options for {@link useBlueskyAppview}. 67 + */ 68 + export interface UseBlueskyAppviewOptions { 69 + /** DID or handle of the actor. */ 70 + did?: string; 71 + /** NSID collection (e.g., "app.bsky.feed.post"). */ 72 + collection?: string; 73 + /** Record key within the collection. */ 74 + rkey?: string; 75 + /** Override for the Bluesky appview service URL. Defaults to public.api.bsky.app. */ 76 + appviewService?: string; 77 + /** If true, skip the appview and go straight to Slingshot/PDS fallback. */ 78 + skipAppview?: boolean; 79 + } 80 + 81 + /** 82 + * Result returned from {@link useBlueskyAppview}. 83 + */ 84 + export interface UseBlueskyAppviewResult<T = unknown> { 85 + /** The fetched record value. */ 86 + record?: T; 87 + /** Indicates whether a fetch is in progress. */ 88 + loading: boolean; 89 + /** Error encountered during fetch. */ 90 + error?: Error; 91 + /** Source from which the record was successfully fetched. */ 92 + source?: "appview" | "slingshot" | "pds"; 93 + } 94 + 95 + /** 96 + * Maps Bluesky collection NSIDs to their corresponding appview API endpoints. 97 + * Only includes endpoints that can fetch individual records (not list endpoints). 98 + */ 99 + const BLUESKY_COLLECTION_TO_ENDPOINT: Record<string, string> = { 100 + "app.bsky.actor.profile": "app.bsky.actor.getProfile", 101 + "app.bsky.feed.post": "app.bsky.feed.getPostThread", 102 + 103 + }; 104 + 105 + /** 106 + * React hook that fetches a Bluesky record with a three-tier fallback strategy: 107 + * 1. Try the Bluesky appview API endpoint (e.g., getProfile, getPostThread) 108 + * 2. Fall back to Slingshot's getRecord 109 + * 3. As a last resort, query the actor's PDS directly 110 + * 111 + * The hook automatically handles DID resolution and determines the appropriate API endpoint 112 + * based on the collection type. The `source` field in the result indicates which tier 113 + * successfully returned the record. 114 + * 115 + * @example 116 + * ```tsx 117 + * // Fetch a Bluesky post with automatic fallback 118 + * import { useBlueskyAppview } from 'atproto-ui'; 119 + * import type { FeedPostRecord } from 'atproto-ui'; 120 + * 121 + * function MyPost({ did, rkey }: { did: string; rkey: string }) { 122 + * const { record, loading, error, source } = useBlueskyAppview<FeedPostRecord>({ 123 + * did, 124 + * collection: 'app.bsky.feed.post', 125 + * rkey, 126 + * }); 127 + * 128 + * if (loading) return <p>Loading post...</p>; 129 + * if (error) return <p>Error: {error.message}</p>; 130 + * if (!record) return <p>No post found</p>; 131 + * 132 + * return ( 133 + * <article> 134 + * <p>{record.text}</p> 135 + * <small>Fetched from: {source}</small> 136 + * </article> 137 + * ); 138 + * } 139 + * ``` 140 + * 141 + * @example 142 + * ```tsx 143 + * // Fetch a Bluesky profile 144 + * import { useBlueskyAppview } from 'atproto-ui'; 145 + * import type { ProfileRecord } from 'atproto-ui'; 146 + * 147 + * function MyProfile({ handle }: { handle: string }) { 148 + * const { record, loading, error } = useBlueskyAppview<ProfileRecord>({ 149 + * did: handle, // Handles are automatically resolved to DIDs 150 + * collection: 'app.bsky.actor.profile', 151 + * rkey: 'self', 152 + * }); 153 + * 154 + * if (loading) return <p>Loading profile...</p>; 155 + * if (!record) return null; 156 + * 157 + * return ( 158 + * <div> 159 + * <h2>{record.displayName}</h2> 160 + * <p>{record.description}</p> 161 + * </div> 162 + * ); 163 + * } 164 + * ``` 165 + * 166 + * @example 167 + * ```tsx 168 + * // Skip the appview and go directly to Slingshot/PDS 169 + * const { record } = useBlueskyAppview({ 170 + * did: 'did:plc:example', 171 + * collection: 'app.bsky.feed.post', 172 + * rkey: '3k2aexample', 173 + * skipAppview: true, // Bypasses Bluesky API, starts with Slingshot 174 + * }); 175 + * ``` 176 + * 177 + * @param options - Configuration object with did, collection, rkey, and optional overrides. 178 + * @returns {UseBlueskyAppviewResult<T>} Object containing the record, loading state, error, and source. 179 + */ 180 + 181 + // Reducer action types for useBlueskyAppview 182 + type BlueskyAppviewAction<T> = 183 + | { type: "SET_LOADING"; loading: boolean } 184 + | { type: "SET_SUCCESS"; record: T; source: "appview" | "slingshot" | "pds" } 185 + | { type: "SET_ERROR"; error: Error } 186 + | { type: "RESET" }; 187 + 188 + // Reducer function for atomic state updates 189 + function blueskyAppviewReducer<T>( 190 + state: UseBlueskyAppviewResult<T>, 191 + action: BlueskyAppviewAction<T> 192 + ): UseBlueskyAppviewResult<T> { 193 + switch (action.type) { 194 + case "SET_LOADING": 195 + return { 196 + ...state, 197 + loading: action.loading, 198 + error: undefined, 199 + }; 200 + case "SET_SUCCESS": 201 + return { 202 + record: action.record, 203 + loading: false, 204 + error: undefined, 205 + source: action.source, 206 + }; 207 + case "SET_ERROR": 208 + // Only update if error message changed (stabilize error reference) 209 + if (state.error?.message === action.error.message) { 210 + return state; 211 + } 212 + return { 213 + ...state, 214 + loading: false, 215 + error: action.error, 216 + source: undefined, 217 + }; 218 + case "RESET": 219 + return { 220 + record: undefined, 221 + loading: false, 222 + error: undefined, 223 + source: undefined, 224 + }; 225 + default: 226 + return state; 227 + } 228 + } 229 + 230 + export function useBlueskyAppview<T = unknown>({ 231 + did: handleOrDid, 232 + collection, 233 + rkey, 234 + appviewService, 235 + skipAppview = false, 236 + }: UseBlueskyAppviewOptions): UseBlueskyAppviewResult<T> { 237 + const { recordCache, blueskyAppviewService, resolver } = useAtProto(); 238 + const effectiveAppviewService = appviewService ?? blueskyAppviewService; 239 + 240 + // Only use this hook for Bluesky collections (app.bsky.*) 241 + const isBlueskyCollection = collection?.startsWith("app.bsky."); 242 + 243 + const { 244 + did, 245 + error: didError, 246 + loading: resolvingDid, 247 + } = useDidResolution(handleOrDid); 248 + const { 249 + endpoint: pdsEndpoint, 250 + error: endpointError, 251 + loading: resolvingEndpoint, 252 + } = usePdsEndpoint(did); 253 + 254 + const [state, dispatch] = useReducer(blueskyAppviewReducer<T>, { 255 + record: undefined, 256 + loading: false, 257 + error: undefined, 258 + source: undefined, 259 + }); 260 + 261 + const releaseRef = useRef<(() => void) | undefined>(undefined); 262 + 263 + useEffect(() => { 264 + let cancelled = false; 265 + 266 + // Early returns for missing inputs or resolution errors 267 + if (!handleOrDid || !collection || !rkey) { 268 + if (!cancelled) dispatch({ type: "RESET" }); 269 + return () => { 270 + cancelled = true; 271 + if (releaseRef.current) { 272 + releaseRef.current(); 273 + releaseRef.current = undefined; 274 + } 275 + }; 276 + } 277 + 278 + // Return early if not a Bluesky collection - this hook should not be used for other lexicons 279 + if (!isBlueskyCollection) { 280 + if (!cancelled) dispatch({ type: "RESET" }); 281 + return () => { 282 + cancelled = true; 283 + if (releaseRef.current) { 284 + releaseRef.current(); 285 + releaseRef.current = undefined; 286 + } 287 + }; 288 + } 289 + 290 + if (didError) { 291 + if (!cancelled) dispatch({ type: "SET_ERROR", error: didError }); 292 + return () => { 293 + cancelled = true; 294 + if (releaseRef.current) { 295 + releaseRef.current(); 296 + releaseRef.current = undefined; 297 + } 298 + }; 299 + } 300 + 301 + if (endpointError) { 302 + if (!cancelled) dispatch({ type: "SET_ERROR", error: endpointError }); 303 + return () => { 304 + cancelled = true; 305 + if (releaseRef.current) { 306 + releaseRef.current(); 307 + releaseRef.current = undefined; 308 + } 309 + }; 310 + } 311 + 312 + if (resolvingDid || resolvingEndpoint || !did || !pdsEndpoint) { 313 + if (!cancelled) dispatch({ type: "SET_LOADING", loading: true }); 314 + return () => { 315 + cancelled = true; 316 + if (releaseRef.current) { 317 + releaseRef.current(); 318 + releaseRef.current = undefined; 319 + } 320 + }; 321 + } 322 + 323 + // Start fetching 324 + dispatch({ type: "SET_LOADING", loading: true }); 325 + 326 + // Use recordCache.ensure for deduplication and caching 327 + const { promise, release } = recordCache.ensure<{ record: T; source: "appview" | "slingshot" | "pds" }>( 328 + did, 329 + collection, 330 + rkey, 331 + () => { 332 + const controller = new AbortController(); 333 + 334 + const fetchPromise = (async (): Promise<{ record: T; source: "appview" | "slingshot" | "pds" }> => { 335 + let lastError: Error | undefined; 336 + 337 + // Tier 1: Try Bluesky appview API 338 + if (!skipAppview && BLUESKY_COLLECTION_TO_ENDPOINT[collection]) { 339 + try { 340 + const result = await fetchFromAppview<T>( 341 + did, 342 + collection, 343 + rkey, 344 + effectiveAppviewService, 345 + ); 346 + if (result) { 347 + return { record: result, source: "appview" }; 348 + } 349 + } catch (err) { 350 + lastError = err as Error; 351 + // Continue to next tier 352 + } 353 + } 354 + 355 + // Tier 2: Try Slingshot getRecord 356 + try { 357 + const slingshotUrl = resolver.getSlingshotUrl(); 358 + const result = await fetchFromSlingshot<T>(did, collection, rkey, slingshotUrl); 359 + if (result) { 360 + return { record: result, source: "slingshot" }; 361 + } 362 + } catch (err) { 363 + lastError = err as Error; 364 + // Continue to next tier 365 + } 366 + 367 + // Tier 3: Try PDS directly 368 + try { 369 + const result = await fetchFromPds<T>( 370 + did, 371 + collection, 372 + rkey, 373 + pdsEndpoint, 374 + ); 375 + if (result) { 376 + return { record: result, source: "pds" }; 377 + } 378 + } catch (err) { 379 + lastError = err as Error; 380 + } 381 + 382 + // All tiers failed - provide helpful error for banned/unreachable Bluesky PDSes 383 + if (pdsEndpoint.includes('.bsky.network')) { 384 + throw new Error( 385 + `Record unavailable. The Bluesky PDS (${pdsEndpoint}) may be unreachable or the account may be banned.` 386 + ); 387 + } 388 + 389 + throw lastError ?? new Error("Failed to fetch record from all sources"); 390 + })(); 391 + 392 + return { 393 + promise: fetchPromise, 394 + abort: () => controller.abort(), 395 + }; 396 + } 397 + ); 398 + 399 + releaseRef.current = release; 400 + 401 + promise 402 + .then(({ record, source }) => { 403 + if (!cancelled) { 404 + dispatch({ 405 + type: "SET_SUCCESS", 406 + record, 407 + source, 408 + }); 409 + } 410 + }) 411 + .catch((err) => { 412 + if (!cancelled) { 413 + dispatch({ 414 + type: "SET_ERROR", 415 + error: err instanceof Error ? err : new Error(String(err)), 416 + }); 417 + } 418 + }); 419 + 420 + return () => { 421 + cancelled = true; 422 + if (releaseRef.current) { 423 + releaseRef.current(); 424 + releaseRef.current = undefined; 425 + } 426 + }; 427 + }, [ 428 + handleOrDid, 429 + did, 430 + collection, 431 + rkey, 432 + pdsEndpoint, 433 + effectiveAppviewService, 434 + skipAppview, 435 + resolvingDid, 436 + resolvingEndpoint, 437 + didError, 438 + endpointError, 439 + recordCache, 440 + resolver, 441 + ]); 442 + 443 + return state; 444 + } 445 + 446 + /** 447 + * Attempts to fetch a record from the Bluesky appview API. 448 + * Different collections map to different endpoints with varying response structures. 449 + */ 450 + async function fetchFromAppview<T>( 451 + did: string, 452 + collection: string, 453 + rkey: string, 454 + appviewService: string, 455 + ): Promise<T | undefined> { 456 + const { rpc } = await createAtprotoClient({ service: appviewService }); 457 + const endpoint = BLUESKY_COLLECTION_TO_ENDPOINT[collection]; 458 + 459 + if (!endpoint) { 460 + throw new Error(`No appview endpoint mapped for collection ${collection}`); 461 + } 462 + 463 + const atUri = `at://${did}/${collection}/${rkey}`; 464 + 465 + // Handle different appview endpoints 466 + if (endpoint === "app.bsky.actor.getProfile") { 467 + const res = await (rpc as unknown as { get: (nsid: string, opts: { params: Record<string, unknown> }) => Promise<{ ok: boolean; data: AppviewProfileResponse }> }).get(endpoint, { 468 + params: { actor: did }, 469 + }); 470 + 471 + if (!res.ok) throw new Error(`Appview ${endpoint} request failed for ${did}`); 472 + 473 + // The appview returns avatar/banner as CDN URLs like: 474 + // https://cdn.bsky.app/img/avatar/plain/{did}/{cid}@jpeg 475 + // We need to extract the CID and convert to ProfileRecord format 476 + const profile = res.data; 477 + const avatarCid = extractCidFromCdnUrl(profile.avatar); 478 + const bannerCid = extractCidFromCdnUrl(profile.banner); 479 + 480 + // Convert hydrated profile to ProfileRecord format 481 + // Store the CDN URL directly so components can use it without re-fetching 482 + const record: Record<string, unknown> = { 483 + displayName: profile.displayName, 484 + description: profile.description, 485 + createdAt: profile.createdAt, 486 + }; 487 + 488 + // Add pronouns and website if they exist 489 + if (profile.pronouns) { 490 + record.pronouns = profile.pronouns; 491 + } 492 + 493 + if (profile.website) { 494 + record.website = profile.website; 495 + } 496 + 497 + if (profile.avatar && avatarCid) { 498 + const avatarBlob: BlobWithCdn = { 499 + $type: "blob", 500 + ref: { $link: avatarCid }, 501 + mimeType: "image/jpeg", 502 + size: 0, 503 + cdnUrl: profile.avatar, 504 + }; 505 + record.avatar = avatarBlob; 506 + } 507 + 508 + if (profile.banner && bannerCid) { 509 + const bannerBlob: BlobWithCdn = { 510 + $type: "blob", 511 + ref: { $link: bannerCid }, 512 + mimeType: "image/jpeg", 513 + size: 0, 514 + cdnUrl: profile.banner, 515 + }; 516 + record.banner = bannerBlob; 517 + } 518 + 519 + return record as T; 520 + } 521 + 522 + if (endpoint === "app.bsky.feed.getPostThread") { 523 + const res = await (rpc as unknown as { get: (nsid: string, opts: { params: Record<string, unknown> }) => Promise<{ ok: boolean; data: AppviewPostThreadResponse<T> }> }).get(endpoint, { 524 + params: { uri: atUri, depth: 0 }, 525 + }); 526 + 527 + if (!res.ok) throw new Error(`Appview ${endpoint} request failed for ${atUri}`); 528 + 529 + const post = res.data.thread?.post; 530 + if (!post?.record) return undefined; 531 + 532 + const record = post.record as Record<string, unknown>; 533 + const appviewEmbed = post.embed; 534 + 535 + // If the appview includes embedded images with CDN URLs, inject them into the record 536 + if (appviewEmbed && record.embed) { 537 + const recordEmbed = record.embed as { $type?: string; images?: Array<Record<string, unknown>>; media?: Record<string, unknown> }; 538 + 539 + // Handle direct image embeds 540 + if (appviewEmbed.$type === "app.bsky.embed.images#view" && appviewEmbed.images) { 541 + if (recordEmbed.images && Array.isArray(recordEmbed.images)) { 542 + recordEmbed.images = recordEmbed.images.map((img: Record<string, unknown>, idx: number) => { 543 + const appviewImg = appviewEmbed.images?.[idx]; 544 + if (appviewImg?.fullsize) { 545 + const cid = extractCidFromCdnUrl(appviewImg.fullsize); 546 + const imageObj = img.image as { ref?: { $link?: string } } | undefined; 547 + return { 548 + ...img, 549 + image: { 550 + ...(img.image as Record<string, unknown> || {}), 551 + cdnUrl: appviewImg.fullsize, 552 + ref: { $link: cid || imageObj?.ref?.$link }, 553 + }, 554 + }; 555 + } 556 + return img; 557 + }); 558 + } 559 + } 560 + 561 + // Handle recordWithMedia embeds 562 + if (appviewEmbed.$type === "app.bsky.embed.recordWithMedia#view" && appviewEmbed.media) { 563 + const mediaImages = appviewEmbed.media.images; 564 + const mediaEmbedImages = (recordEmbed.media as { images?: Array<Record<string, unknown>> } | undefined)?.images; 565 + if (mediaImages && mediaEmbedImages && Array.isArray(mediaEmbedImages)) { 566 + (recordEmbed.media as { images: Array<Record<string, unknown>> }).images = mediaEmbedImages.map((img: Record<string, unknown>, idx: number) => { 567 + const appviewImg = mediaImages[idx]; 568 + if (appviewImg?.fullsize) { 569 + const cid = extractCidFromCdnUrl(appviewImg.fullsize); 570 + const imageObj = img.image as { ref?: { $link?: string } } | undefined; 571 + return { 572 + ...img, 573 + image: { 574 + ...(img.image as Record<string, unknown> || {}), 575 + cdnUrl: appviewImg.fullsize, 576 + ref: { $link: cid || imageObj?.ref?.$link }, 577 + }, 578 + }; 579 + } 580 + return img; 581 + }); 582 + } 583 + } 584 + } 585 + 586 + return record as T; 587 + } 588 + 589 + // For other endpoints, we might not have a clean way to extract the specific record 590 + // Fall through to let the caller try the next tier 591 + throw new Error(`Appview endpoint ${endpoint} not fully implemented`); 592 + } 593 + 594 + /** 595 + * Attempts to fetch a record from Slingshot's getRecord endpoint. 596 + */ 597 + async function fetchFromSlingshot<T>( 598 + did: string, 599 + collection: string, 600 + rkey: string, 601 + slingshotBaseUrl: string, 602 + ): Promise<T | undefined> { 603 + const res = await callGetRecord<T>(slingshotBaseUrl, did, collection, rkey); 604 + if (!res.ok) throw new Error(`Slingshot getRecord failed for ${did}/${collection}/${rkey}`); 605 + return res.data.value; 606 + } 607 + 608 + /** 609 + * Attempts to fetch a record directly from the actor's PDS. 610 + */ 611 + async function fetchFromPds<T>( 612 + did: string, 613 + collection: string, 614 + rkey: string, 615 + pdsEndpoint: string, 616 + ): Promise<T | undefined> { 617 + const res = await callGetRecord<T>(pdsEndpoint, did, collection, rkey); 618 + if (!res.ok) throw new Error(`PDS getRecord failed for ${did}/${collection}/${rkey} at ${pdsEndpoint}`); 619 + return res.data.value; 620 + } 621 + 622 + /** 623 + * Extracts and validates CID from Bluesky CDN URL. 624 + * Format: https://cdn.bsky.app/img/{type}/plain/{did}/{cid}@{format} 625 + * 626 + * @throws Error if URL format is invalid or CID extraction fails 627 + */ 628 + function extractCidFromCdnUrl(url: string | undefined): string | undefined { 629 + if (!url) return undefined; 630 + 631 + try { 632 + // Match pattern: /did:plc:xxxxx/CIDHERE@format or /did:web:xxxxx/CIDHERE@format 633 + const match = url.match(/\/did:[^/]+\/([^@/]+)@/); 634 + const cid = match?.[1]; 635 + 636 + if (!cid) { 637 + console.warn(`Failed to extract CID from CDN URL: ${url}`); 638 + return undefined; 639 + } 640 + 641 + // Basic CID validation - should start with common CID prefixes 642 + if (!cid.startsWith("bafk") && !cid.startsWith("bafyb") && !cid.startsWith("Qm")) { 643 + console.warn(`Extracted string does not appear to be a valid CID: ${cid} from URL: ${url}`); 644 + return undefined; 645 + } 646 + 647 + return cid; 648 + } catch (err) { 649 + console.error(`Error extracting CID from CDN URL: ${url}`, err); 650 + return undefined; 651 + } 652 + } 653 + 654 + /** 655 + * Shared RPC utility for making appview API calls with proper typing. 656 + */ 657 + export async function callAppviewRpc<TResponse>( 658 + service: string, 659 + nsid: string, 660 + params: Record<string, unknown>, 661 + ): Promise<{ ok: boolean; data: TResponse }> { 662 + const { rpc } = await createAtprotoClient({ service }); 663 + return await (rpc as unknown as { 664 + get: (nsid: string, opts: { params: Record<string, unknown> }) => Promise<{ ok: boolean; data: TResponse }>; 665 + }).get(nsid, { params }); 666 + } 667 + 668 + /** 669 + * Shared RPC utility for making getRecord calls (Slingshot or PDS). 670 + */ 671 + export async function callGetRecord<T>( 672 + service: string, 673 + did: string, 674 + collection: string, 675 + rkey: string, 676 + ): Promise<{ ok: boolean; data: { value: T } }> { 677 + const { rpc } = await createAtprotoClient({ service }); 678 + return await (rpc as unknown as { 679 + get: (nsid: string, opts: { params: Record<string, unknown> }) => Promise<{ ok: boolean; data: { value: T } }>; 680 + }).get("com.atproto.repo.getRecord", { 681 + params: { repo: did, collection, rkey }, 682 + }); 683 + } 684 + 685 + /** 686 + * Shared RPC utility for making listRecords calls. 687 + */ 688 + export async function callListRecords<T>( 689 + service: string, 690 + did: string, 691 + collection: string, 692 + limit: number, 693 + cursor?: string, 694 + ): Promise<{ 695 + ok: boolean; 696 + data: { 697 + records: Array<{ uri: string; rkey?: string; value: T }>; 698 + cursor?: string; 699 + }; 700 + }> { 701 + const { rpc } = await createAtprotoClient({ service }); 702 + 703 + const params: Record<string, unknown> = { 704 + repo: did, 705 + collection, 706 + limit, 707 + cursor, 708 + reverse: false, 709 + }; 710 + 711 + return await (rpc as unknown as { 712 + get: ( 713 + nsid: string, 714 + opts: { params: Record<string, unknown> }, 715 + ) => Promise<{ 716 + ok: boolean; 717 + data: { 718 + records: Array<{ uri: string; rkey?: string; value: T }>; 719 + cursor?: string; 720 + }; 721 + }>; 722 + }).get("com.atproto.repo.listRecords", { 723 + params, 724 + }); 725 + } 726 + 727 +
+46 -45
lib/hooks/useBlueskyProfile.ts
··· 1 - import { useEffect, useState } from 'react'; 2 - import { usePdsEndpoint } from './usePdsEndpoint'; 3 - import { createAtprotoClient } from '../utils/atproto-client'; 1 + import { useBlueskyAppview } from "./useBlueskyAppview"; 2 + import type { ProfileRecord } from "../types/bluesky"; 3 + import { extractCidFromBlob } from "../utils/blob"; 4 4 5 5 /** 6 6 * Minimal profile fields returned by the Bluesky actor profile endpoint. 7 7 */ 8 8 export interface BlueskyProfileData { 9 - /** Actor DID. */ 10 - did: string; 11 - /** Actor handle. */ 12 - handle: string; 13 - /** Display name configured by the actor. */ 14 - displayName?: string; 15 - /** Profile description/bio. */ 16 - description?: string; 17 - /** Avatar blob (CID reference). */ 18 - avatar?: string; 19 - /** Banner image blob (CID reference). */ 20 - banner?: string; 21 - /** Creation timestamp for the profile. */ 22 - createdAt?: string; 9 + /** Actor DID. */ 10 + did: string; 11 + /** Actor handle. */ 12 + handle: string; 13 + /** Display name configured by the actor. */ 14 + displayName?: string; 15 + /** Profile description/bio. */ 16 + description?: string; 17 + /** Avatar blob (CID reference). */ 18 + avatar?: string; 19 + /** Banner image blob (CID reference). */ 20 + banner?: string; 21 + /** Creation timestamp for the profile. */ 22 + createdAt?: string; 23 23 } 24 24 25 25 /** 26 26 * Fetches a Bluesky actor profile for a DID and exposes loading/error state. 27 + * 28 + * Uses a three-tier fallback strategy: 29 + * 1. Try Bluesky appview API (app.bsky.actor.getProfile) - CIDs are extracted from CDN URLs 30 + * 2. Fall back to Slingshot getRecord 31 + * 3. Finally query the PDS directly 32 + * 33 + * When using the appview, avatar/banner CDN URLs (e.g., https://cdn.bsky.app/img/avatar/plain/did:plc:xxx/bafkreixxx@jpeg) 34 + * are automatically parsed to extract CIDs and convert them to standard Blob format for compatibility. 27 35 * 28 36 * @param did - Actor DID whose profile should be retrieved. 29 37 * @returns {{ data: BlueskyProfileData | undefined; loading: boolean; error: Error | undefined }} Object exposing the profile payload, loading flag, and any error. 30 38 */ 31 39 export function useBlueskyProfile(did: string | undefined) { 32 - const { endpoint } = usePdsEndpoint(did); 33 - const [data, setData] = useState<BlueskyProfileData | undefined>(); 34 - const [loading, setLoading] = useState<boolean>(!!did); 35 - const [error, setError] = useState<Error | undefined>(); 40 + const { record, loading, error } = useBlueskyAppview<ProfileRecord>({ 41 + did, 42 + collection: "app.bsky.actor.profile", 43 + rkey: "self", 44 + }); 36 45 37 - useEffect(() => { 38 - let cancelled = false; 39 - async function run() { 40 - if (!did || !endpoint) return; 41 - setLoading(true); 42 - try { 43 - const { rpc } = await createAtprotoClient({ service: endpoint }); 44 - const client = rpc as unknown as { 45 - get: (nsid: string, options: { params: { actor: string } }) => Promise<{ ok: boolean; data: unknown }>; 46 - }; 47 - const res = await client.get('app.bsky.actor.getProfile', { params: { actor: did } }); 48 - if (!res.ok) throw new Error('Profile request failed'); 49 - if (!cancelled) setData(res.data as BlueskyProfileData); 50 - } catch (e) { 51 - if (!cancelled) setError(e as Error); 52 - } finally { 53 - if (!cancelled) setLoading(false); 54 - } 55 - } 56 - run(); 57 - return () => { cancelled = true; }; 58 - }, [did, endpoint]); 46 + // Convert ProfileRecord to BlueskyProfileData 47 + // Note: avatar and banner are Blob objects in the record (from all sources) 48 + // The appview response is converted to ProfileRecord format by extracting CIDs from CDN URLs 49 + const data: BlueskyProfileData | undefined = record 50 + ? { 51 + did: did || "", 52 + handle: "", 53 + displayName: record.displayName, 54 + description: record.description, 55 + avatar: extractCidFromBlob(record.avatar), 56 + banner: extractCidFromBlob(record.banner), 57 + createdAt: record.createdAt, 58 + } 59 + : undefined; 59 60 60 - return { data, loading, error }; 61 - } 61 + return { data, loading, error }; 62 + }
-56
lib/hooks/useColorScheme.ts
··· 1 - import { useEffect, useState } from 'react'; 2 - 3 - /** 4 - * Possible user-facing color scheme preferences. 5 - */ 6 - export type ColorSchemePreference = 'light' | 'dark' | 'system'; 7 - 8 - const MEDIA_QUERY = '(prefers-color-scheme: dark)'; 9 - 10 - /** 11 - * Resolves a persisted preference into an explicit light/dark value. 12 - * 13 - * @param pref - Stored preference value (`light`, `dark`, or `system`). 14 - * @returns Explicit light/dark scheme suitable for rendering. 15 - */ 16 - function resolveScheme(pref: ColorSchemePreference): 'light' | 'dark' { 17 - if (pref === 'light' || pref === 'dark') return pref; 18 - if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { 19 - return 'light'; 20 - } 21 - return window.matchMedia(MEDIA_QUERY).matches ? 'dark' : 'light'; 22 - } 23 - 24 - /** 25 - * React hook that returns the effective light/dark scheme, respecting system preferences. 26 - * 27 - * @param preference - User preference; defaults to following the OS setting. 28 - * @returns {'light' | 'dark'} Explicit scheme that should be used for rendering. 29 - */ 30 - export function useColorScheme(preference: ColorSchemePreference = 'system'): 'light' | 'dark' { 31 - const [scheme, setScheme] = useState<'light' | 'dark'>(() => resolveScheme(preference)); 32 - 33 - useEffect(() => { 34 - if (preference === 'light' || preference === 'dark') { 35 - setScheme(preference); 36 - return; 37 - } 38 - if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { 39 - setScheme('light'); 40 - return; 41 - } 42 - const media = window.matchMedia(MEDIA_QUERY); 43 - const update = (event: MediaQueryListEvent | MediaQueryList) => { 44 - setScheme(event.matches ? 'dark' : 'light'); 45 - }; 46 - update(media); 47 - if (typeof media.addEventListener === 'function') { 48 - media.addEventListener('change', update); 49 - return () => media.removeEventListener('change', update); 50 - } 51 - media.addListener(update); 52 - return () => media.removeListener(update); 53 - }, [preference]); 54 - 55 - return scheme; 56 - }
+37 -14
lib/hooks/useDidResolution.ts
··· 1 - import { useEffect, useMemo, useState } from 'react'; 2 - import { useAtProto } from '../providers/AtProtoProvider'; 1 + import { useEffect, useMemo, useState } from "react"; 2 + import { useAtProto } from "../providers/AtProtoProvider"; 3 3 4 4 /** 5 5 * Resolves a handle to its DID, or returns the DID immediately when provided. ··· 30 30 }; 31 31 if (!normalizedInput) { 32 32 reset(); 33 - return () => { cancelled = true; }; 33 + return () => { 34 + cancelled = true; 35 + }; 34 36 } 35 37 36 - const isDid = normalizedInput.startsWith('did:'); 37 - const normalizedHandle = !isDid ? normalizedInput.toLowerCase() : undefined; 38 + const isDid = normalizedInput.startsWith("did:"); 39 + const normalizedHandle = !isDid 40 + ? normalizedInput.toLowerCase() 41 + : undefined; 38 42 const cached = isDid 39 43 ? didCache.getByDid(normalizedInput) 40 44 : didCache.getByHandle(normalizedHandle); 41 45 42 46 const initialDid = cached?.did ?? (isDid ? normalizedInput : undefined); 43 - const initialHandle = cached?.handle ?? (!isDid ? normalizedHandle : undefined); 47 + const initialHandle = 48 + cached?.handle ?? (!isDid ? normalizedHandle : undefined); 44 49 45 50 setError(undefined); 46 51 setDid(initialDid); 47 52 setHandle(initialHandle); 48 53 49 54 const needsHandleResolution = !isDid && !cached?.did; 50 - const needsDocResolution = isDid && (!cached?.doc || cached.handle === undefined); 55 + const needsDocResolution = 56 + isDid && (!cached?.doc || cached.handle === undefined); 51 57 52 58 if (!needsHandleResolution && !needsDocResolution) { 53 59 setLoading(false); 54 - return () => { cancelled = true; }; 60 + return () => { 61 + cancelled = true; 62 + }; 55 63 } 56 64 57 65 setLoading(true); ··· 60 68 try { 61 69 let snapshot = cached; 62 70 if (!isDid && normalizedHandle && needsHandleResolution) { 63 - snapshot = await didCache.ensureHandle(resolver, normalizedHandle); 71 + snapshot = await didCache.ensureHandle( 72 + resolver, 73 + normalizedHandle, 74 + ); 64 75 } 65 76 66 77 if (isDid) { 67 - snapshot = await didCache.ensureDidDoc(resolver, normalizedInput); 78 + snapshot = await didCache.ensureDidDoc( 79 + resolver, 80 + normalizedInput, 81 + ); 68 82 } 69 83 70 84 if (!cancelled) { 71 - const resolvedDid = snapshot?.did ?? (isDid ? normalizedInput : undefined); 72 - const resolvedHandle = snapshot?.handle ?? (!isDid ? normalizedHandle : undefined); 85 + const resolvedDid = 86 + snapshot?.did ?? (isDid ? normalizedInput : undefined); 87 + const resolvedHandle = 88 + snapshot?.handle ?? 89 + (!isDid ? normalizedHandle : undefined); 73 90 setDid(resolvedDid); 74 91 setHandle(resolvedHandle); 75 92 setError(undefined); 76 93 } 77 94 } catch (e) { 78 95 if (!cancelled) { 79 - setError(e as Error); 96 + const newError = e as Error; 97 + // Only update error if message changed (stabilize reference) 98 + setError(prevError => 99 + prevError?.message === newError.message ? prevError : newError 100 + ); 80 101 } 81 102 } finally { 82 103 if (!cancelled) setLoading(false); 83 104 } 84 105 })(); 85 106 86 - return () => { cancelled = true; }; 107 + return () => { 108 + cancelled = true; 109 + }; 87 110 }, [normalizedInput, resolver, didCache]); 88 111 89 112 return { did, handle, error, loading };
+167 -74
lib/hooks/useLatestRecord.ts
··· 1 - import { useEffect, useState } from 'react'; 2 - import { useDidResolution } from './useDidResolution'; 3 - import { usePdsEndpoint } from './usePdsEndpoint'; 4 - import { createAtprotoClient } from '../utils/atproto-client'; 1 + import { useEffect, useState } from "react"; 2 + import { useDidResolution } from "./useDidResolution"; 3 + import { usePdsEndpoint } from "./usePdsEndpoint"; 4 + import { callListRecords } from "./useBlueskyAppview"; 5 5 6 6 /** 7 7 * Shape of the state returned by {@link useLatestRecord}. 8 8 */ 9 9 export interface LatestRecordState<T = unknown> { 10 - /** Latest record value if one exists. */ 11 - record?: T; 12 - /** Record key for the fetched record, when derivable. */ 13 - rkey?: string; 14 - /** Error encountered while fetching. */ 15 - error?: Error; 16 - /** Indicates whether a fetch is in progress. */ 17 - loading: boolean; 18 - /** `true` when the collection has zero records. */ 19 - empty: boolean; 10 + /** Latest record value if one exists. */ 11 + record?: T; 12 + /** Record key for the fetched record, when derivable. */ 13 + rkey?: string; 14 + /** Error encountered while fetching. */ 15 + error?: Error; 16 + /** Indicates whether a fetch is in progress. */ 17 + loading: boolean; 18 + /** `true` when the collection has zero records. */ 19 + empty: boolean; 20 20 } 21 21 22 22 /** 23 - * Fetches the most recent record from a collection using `listRecords(limit=1)`. 23 + * Fetches the most recent record from a collection using `listRecords(limit=3)`. 24 + * 25 + * Note: Slingshot does not support listRecords, so this always queries the actor's PDS directly. 26 + * 27 + * Records with invalid timestamps (before 2023, when ATProto was created) are automatically 28 + * skipped, and additional records are fetched to find a valid one. 24 29 * 25 30 * @param handleOrDid - Handle or DID that owns the collection. 26 31 * @param collection - NSID of the collection to query. 32 + * @param refreshKey - Optional key that when changed, triggers a refetch. Use for auto-refresh scenarios. 27 33 * @returns {LatestRecordState<T>} Object reporting the latest record value, derived rkey, loading status, emptiness, and any error. 28 34 */ 29 - export function useLatestRecord<T = unknown>(handleOrDid: string | undefined, collection: string): LatestRecordState<T> { 30 - const { did, error: didError, loading: resolvingDid } = useDidResolution(handleOrDid); 31 - const { endpoint, error: endpointError, loading: resolvingEndpoint } = usePdsEndpoint(did); 32 - const [state, setState] = useState<LatestRecordState<T>>({ loading: !!handleOrDid, empty: false }); 35 + export function useLatestRecord<T = unknown>( 36 + handleOrDid: string | undefined, 37 + collection: string, 38 + refreshKey?: number, 39 + ): LatestRecordState<T> { 40 + const { 41 + did, 42 + error: didError, 43 + loading: resolvingDid, 44 + } = useDidResolution(handleOrDid); 45 + const { 46 + endpoint, 47 + error: endpointError, 48 + loading: resolvingEndpoint, 49 + } = usePdsEndpoint(did); 50 + const [state, setState] = useState<LatestRecordState<T>>({ 51 + loading: !!handleOrDid, 52 + empty: false, 53 + }); 54 + 55 + useEffect(() => { 56 + let cancelled = false; 33 57 34 - useEffect(() => { 35 - let cancelled = false; 58 + const assign = (next: Partial<LatestRecordState<T>>) => { 59 + if (cancelled) return; 60 + setState((prev) => ({ ...prev, ...next })); 61 + }; 36 62 37 - const assign = (next: Partial<LatestRecordState<T>>) => { 38 - if (cancelled) return; 39 - setState(prev => ({ ...prev, ...next })); 40 - }; 63 + if (!handleOrDid) { 64 + assign({ 65 + loading: false, 66 + record: undefined, 67 + rkey: undefined, 68 + error: undefined, 69 + empty: false, 70 + }); 71 + return () => { 72 + cancelled = true; 73 + }; 74 + } 41 75 42 - if (!handleOrDid) { 43 - assign({ loading: false, record: undefined, rkey: undefined, error: undefined, empty: false }); 44 - return () => { cancelled = true; }; 45 - } 76 + if (didError) { 77 + assign({ loading: false, error: didError, empty: false }); 78 + return () => { 79 + cancelled = true; 80 + }; 81 + } 46 82 47 - if (didError) { 48 - assign({ loading: false, error: didError, empty: false }); 49 - return () => { cancelled = true; }; 50 - } 83 + if (endpointError) { 84 + assign({ loading: false, error: endpointError, empty: false }); 85 + return () => { 86 + cancelled = true; 87 + }; 88 + } 51 89 52 - if (endpointError) { 53 - assign({ loading: false, error: endpointError, empty: false }); 54 - return () => { cancelled = true; }; 55 - } 90 + if (resolvingDid || resolvingEndpoint || !did || !endpoint) { 91 + assign({ loading: true, error: undefined }); 92 + return () => { 93 + cancelled = true; 94 + }; 95 + } 56 96 57 - if (resolvingDid || resolvingEndpoint || !did || !endpoint) { 58 - assign({ loading: true, error: undefined }); 59 - return () => { cancelled = true; }; 60 - } 97 + assign({ loading: true, error: undefined, empty: false }); 61 98 62 - assign({ loading: true, error: undefined, empty: false }); 99 + (async () => { 100 + try { 101 + // Slingshot doesn't support listRecords, so we query PDS directly 102 + const res = await callListRecords<T>( 103 + endpoint, 104 + did, 105 + collection, 106 + 3, // Fetch 3 in case some have invalid timestamps 107 + ); 108 + 109 + if (!res.ok) { 110 + throw new Error("Failed to list records from PDS"); 111 + } 63 112 64 - (async () => { 65 - try { 66 - const { rpc } = await createAtprotoClient({ service: endpoint }); 67 - const res = await (rpc as unknown as { 68 - get: ( 69 - nsid: string, 70 - opts: { params: Record<string, string | number | boolean> } 71 - ) => Promise<{ ok: boolean; data: { records: Array<{ uri: string; rkey?: string; value: T }> } }>; 72 - }).get('com.atproto.repo.listRecords', { 73 - params: { repo: did, collection, limit: 1, reverse: false } 74 - }); 75 - if (!res.ok) throw new Error('Failed to list records'); 76 - const list = res.data.records; 77 - if (list.length === 0) { 78 - assign({ loading: false, empty: true, record: undefined, rkey: undefined }); 79 - return; 80 - } 81 - const first = list[0]; 82 - const derivedRkey = first.rkey ?? extractRkey(first.uri); 83 - assign({ record: first.value, rkey: derivedRkey, loading: false, empty: false }); 84 - } catch (e) { 85 - assign({ error: e as Error, loading: false, empty: false }); 86 - } 87 - })(); 113 + const list = res.data.records; 114 + if (list.length === 0) { 115 + assign({ 116 + loading: false, 117 + empty: true, 118 + record: undefined, 119 + rkey: undefined, 120 + }); 121 + return; 122 + } 123 + 124 + // Find the first valid record (skip records before 2023) 125 + const validRecord = list.find((item) => isValidTimestamp(item.value)); 126 + 127 + if (!validRecord) { 128 + console.warn("No valid records found (all had timestamps before 2023)"); 129 + assign({ 130 + loading: false, 131 + empty: true, 132 + record: undefined, 133 + rkey: undefined, 134 + }); 135 + return; 136 + } 137 + 138 + const derivedRkey = validRecord.rkey ?? extractRkey(validRecord.uri); 139 + assign({ 140 + record: validRecord.value, 141 + rkey: derivedRkey, 142 + loading: false, 143 + empty: false, 144 + }); 145 + } catch (e) { 146 + assign({ error: e as Error, loading: false, empty: false }); 147 + } 148 + })(); 88 149 89 - return () => { 90 - cancelled = true; 91 - }; 92 - }, [handleOrDid, did, endpoint, collection, resolvingDid, resolvingEndpoint, didError, endpointError]); 150 + return () => { 151 + cancelled = true; 152 + }; 153 + }, [ 154 + handleOrDid, 155 + did, 156 + endpoint, 157 + collection, 158 + resolvingDid, 159 + resolvingEndpoint, 160 + didError, 161 + endpointError, 162 + refreshKey, 163 + ]); 93 164 94 - return state; 165 + return state; 95 166 } 96 167 97 168 function extractRkey(uri: string): string | undefined { 98 - if (!uri) return undefined; 99 - const parts = uri.split('/'); 100 - return parts[parts.length - 1]; 169 + if (!uri) return undefined; 170 + const parts = uri.split("/"); 171 + return parts[parts.length - 1]; 172 + } 173 + 174 + /** 175 + * Validates that a record has a reasonable timestamp (not before 2023). 176 + * ATProto was created in 2023, so any timestamp before that is invalid. 177 + */ 178 + function isValidTimestamp(record: unknown): boolean { 179 + if (typeof record !== "object" || record === null) return true; 180 + 181 + const recordObj = record as { createdAt?: string; indexedAt?: string }; 182 + const timestamp = recordObj.createdAt || recordObj.indexedAt; 183 + 184 + if (!timestamp || typeof timestamp !== "string") return true; // No timestamp to validate 185 + 186 + try { 187 + const date = new Date(timestamp); 188 + // ATProto was created in 2023, reject anything before that 189 + return date.getFullYear() >= 2023; 190 + } catch { 191 + // If we can't parse the date, consider it valid to avoid false negatives 192 + return true; 193 + } 101 194 }
+412 -286
lib/hooks/usePaginatedRecords.ts
··· 1 - import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; 2 - import { useDidResolution } from './useDidResolution'; 3 - import { usePdsEndpoint } from './usePdsEndpoint'; 4 - import { createAtprotoClient } from '../utils/atproto-client'; 1 + import { useCallback, useEffect, useMemo, useRef, useState } from "react"; 2 + import { useDidResolution } from "./useDidResolution"; 3 + import { usePdsEndpoint } from "./usePdsEndpoint"; 4 + import { callAppviewRpc, callListRecords } from "./useBlueskyAppview"; 5 + import { useAtProto } from "../providers/AtProtoProvider"; 5 6 6 7 /** 7 8 * Record envelope returned by paginated AT Protocol queries. 8 9 */ 9 10 export interface PaginatedRecord<T> { 10 - /** Fully qualified AT URI for the record. */ 11 - uri: string; 12 - /** Record key extracted from the URI or provided by the API. */ 13 - rkey: string; 14 - /** Raw record value. */ 15 - value: T; 11 + /** Fully qualified AT URI for the record. */ 12 + uri: string; 13 + /** Record key extracted from the URI or provided by the API. */ 14 + rkey: string; 15 + /** Raw record value. */ 16 + value: T; 17 + /** Optional feed metadata (for example, repost context). */ 18 + reason?: AuthorFeedReason; 19 + /** Optional reply context derived from feed metadata. */ 20 + replyParent?: ReplyParentInfo; 16 21 } 17 22 18 23 interface PageData<T> { 19 - records: PaginatedRecord<T>[]; 20 - cursor?: string; 24 + records: PaginatedRecord<T>[]; 25 + cursor?: string; 21 26 } 22 27 23 28 /** 24 29 * Options accepted by {@link usePaginatedRecords}. 25 30 */ 26 31 export interface UsePaginatedRecordsOptions { 27 - /** DID or handle whose repository should be queried. */ 28 - did?: string; 29 - /** NSID collection containing the target records. */ 30 - collection: string; 31 - /** Maximum page size to request; defaults to `5`. */ 32 - limit?: number; 33 - /** Prefer the Bluesky appview author feed endpoint before falling back to the PDS. */ 34 - preferAuthorFeed?: boolean; 35 - /** Optional filter applied when fetching from the appview author feed. */ 36 - authorFeedFilter?: AuthorFeedFilter; 37 - /** Whether to include pinned posts when fetching from the author feed. */ 38 - authorFeedIncludePins?: boolean; 39 - /** Override for the appview service base URL used to query the author feed. */ 40 - authorFeedService?: string; 41 - /** Optional explicit actor identifier for the author feed request. */ 42 - authorFeedActor?: string; 32 + /** DID or handle whose repository should be queried. */ 33 + did?: string; 34 + /** NSID collection containing the target records. */ 35 + collection: string; 36 + /** Maximum page size to request; defaults to `5`. */ 37 + limit?: number; 38 + /** Prefer the Bluesky appview author feed endpoint before falling back to the PDS. */ 39 + preferAuthorFeed?: boolean; 40 + /** Optional filter applied when fetching from the appview author feed. */ 41 + authorFeedFilter?: AuthorFeedFilter; 42 + /** Whether to include pinned posts when fetching from the author feed. */ 43 + authorFeedIncludePins?: boolean; 44 + /** Override for the appview service base URL used to query the author feed. */ 45 + authorFeedService?: string; 46 + /** Optional explicit actor identifier for the author feed request. */ 47 + authorFeedActor?: string; 43 48 } 44 49 45 50 /** 46 51 * Result returned from {@link usePaginatedRecords} describing records and pagination state. 47 52 */ 48 53 export interface UsePaginatedRecordsResult<T> { 49 - /** Records for the active page. */ 50 - records: PaginatedRecord<T>[]; 51 - /** Indicates whether a page load is in progress. */ 52 - loading: boolean; 53 - /** Error produced during the latest fetch, if any. */ 54 - error?: Error; 55 - /** `true` when another page can be fetched forward. */ 56 - hasNext: boolean; 57 - /** `true` when a previous page exists in memory. */ 58 - hasPrev: boolean; 59 - /** Requests the next page (if available). */ 60 - loadNext: () => void; 61 - /** Returns to the previous page when possible. */ 62 - loadPrev: () => void; 63 - /** Index of the currently displayed page. */ 64 - pageIndex: number; 65 - /** Number of pages fetched so far (or inferred total when known). */ 66 - pagesCount: number; 54 + /** Records for the active page. */ 55 + records: PaginatedRecord<T>[]; 56 + /** Indicates whether a page load is in progress. */ 57 + loading: boolean; 58 + /** Error produced during the latest fetch, if any. */ 59 + error?: Error; 60 + /** `true` when another page can be fetched forward. */ 61 + hasNext: boolean; 62 + /** `true` when a previous page exists in memory. */ 63 + hasPrev: boolean; 64 + /** Requests the next page (if available). */ 65 + loadNext: () => void; 66 + /** Returns to the previous page when possible. */ 67 + loadPrev: () => void; 68 + /** Index of the currently displayed page. */ 69 + pageIndex: number; 70 + /** Number of pages fetched so far (or inferred total when known). */ 71 + pagesCount: number; 67 72 } 68 73 69 - const DEFAULT_APPVIEW_SERVICE = 'https://public.api.bsky.app'; 74 + 70 75 71 76 export type AuthorFeedFilter = 72 - | 'posts_with_replies' 73 - | 'posts_no_replies' 74 - | 'posts_with_media' 75 - | 'posts_and_author_threads' 76 - | 'posts_with_video'; 77 + | "posts_with_replies" 78 + | "posts_no_replies" 79 + | "posts_with_media" 80 + | "posts_and_author_threads" 81 + | "posts_with_video"; 82 + 83 + export interface AuthorFeedReason { 84 + $type?: string; 85 + by?: { 86 + handle?: string; 87 + did?: string; 88 + }; 89 + indexedAt?: string; 90 + } 91 + 92 + export interface ReplyParentInfo { 93 + uri?: string; 94 + author?: { 95 + handle?: string; 96 + did?: string; 97 + }; 98 + } 77 99 78 100 /** 79 101 * React hook that fetches a repository collection with cursor-based pagination and prefetching. ··· 84 106 * @returns {UsePaginatedRecordsResult<T>} Object containing the current page, pagination metadata, and navigation callbacks. 85 107 */ 86 108 export function usePaginatedRecords<T>({ 87 - did: handleOrDid, 88 - collection, 89 - limit = 5, 90 - preferAuthorFeed = false, 91 - authorFeedFilter, 92 - authorFeedIncludePins, 93 - authorFeedService, 94 - authorFeedActor 109 + did: handleOrDid, 110 + collection, 111 + limit = 5, 112 + preferAuthorFeed = false, 113 + authorFeedFilter, 114 + authorFeedIncludePins, 115 + authorFeedService, 116 + authorFeedActor, 95 117 }: UsePaginatedRecordsOptions): UsePaginatedRecordsResult<T> { 96 - const { did, handle, error: didError, loading: resolvingDid } = useDidResolution(handleOrDid); 97 - const { endpoint, error: endpointError, loading: resolvingEndpoint } = usePdsEndpoint(did); 98 - const [pages, setPages] = useState<PageData<T>[]>([]); 99 - const [pageIndex, setPageIndex] = useState(0); 100 - const [loading, setLoading] = useState(false); 101 - const [error, setError] = useState<Error | undefined>(undefined); 102 - const inFlight = useRef<Set<string>>(new Set()); 103 - const requestSeq = useRef(0); 104 - const identityRef = useRef<string | undefined>(undefined); 105 - const feedDisabledRef = useRef(false); 118 + const { blueskyAppviewService } = useAtProto(); 119 + const { 120 + did, 121 + handle, 122 + error: didError, 123 + loading: resolvingDid, 124 + } = useDidResolution(handleOrDid); 125 + const { 126 + endpoint, 127 + error: endpointError, 128 + loading: resolvingEndpoint, 129 + } = usePdsEndpoint(did); 130 + const [pages, setPages] = useState<PageData<T>[]>([]); 131 + const [pageIndex, setPageIndex] = useState(0); 132 + const [loading, setLoading] = useState(false); 133 + const [error, setError] = useState<Error | undefined>(undefined); 134 + const inFlight = useRef<Set<string>>(new Set()); 135 + const requestSeq = useRef(0); 136 + const identityRef = useRef<string | undefined>(undefined); 137 + const feedDisabledRef = useRef(false); 106 138 107 - const identity = did && endpoint ? `${did}::${endpoint}` : undefined; 108 - const normalizedInput = useMemo(() => { 109 - if (!handleOrDid) return undefined; 110 - const trimmed = handleOrDid.trim(); 111 - return trimmed || undefined; 112 - }, [handleOrDid]); 139 + const identity = did && endpoint ? `${did}::${endpoint}` : undefined; 140 + const normalizedInput = useMemo(() => { 141 + if (!handleOrDid) return undefined; 142 + const trimmed = handleOrDid.trim(); 143 + return trimmed || undefined; 144 + }, [handleOrDid]); 113 145 114 - const actorIdentifier = useMemo(() => { 115 - const explicit = authorFeedActor?.trim(); 116 - if (explicit) return explicit; 117 - if (handle) return handle; 118 - if (normalizedInput) return normalizedInput; 119 - if (did) return did; 120 - return undefined; 121 - }, [authorFeedActor, handle, normalizedInput, did]); 146 + const actorIdentifier = useMemo(() => { 147 + const explicit = authorFeedActor?.trim(); 148 + if (explicit) return explicit; 149 + if (handle) return handle; 150 + if (normalizedInput) return normalizedInput; 151 + if (did) return did; 152 + return undefined; 153 + }, [authorFeedActor, handle, normalizedInput, did]); 122 154 123 - const resetState = useCallback(() => { 124 - setPages([]); 125 - setPageIndex(0); 126 - setError(undefined); 127 - inFlight.current.clear(); 128 - requestSeq.current += 1; 129 - feedDisabledRef.current = false; 130 - }, []); 155 + const resetState = useCallback(() => { 156 + setPages([]); 157 + setPageIndex(0); 158 + setError(undefined); 159 + inFlight.current.clear(); 160 + requestSeq.current += 1; 161 + feedDisabledRef.current = false; 162 + }, []); 131 163 132 - const fetchPage = useCallback(async (identityKey: string, cursor: string | undefined, targetIndex: number, mode: 'active' | 'prefetch') => { 133 - if (!did || !endpoint) return; 134 - const currentIdentity = `${did}::${endpoint}`; 135 - if (identityKey !== currentIdentity) return; 136 - const token = requestSeq.current; 137 - const key = `${identityKey}:${targetIndex}:${cursor ?? 'start'}`; 138 - if (inFlight.current.has(key)) return; 139 - inFlight.current.add(key); 140 - if (mode === 'active') { 141 - setLoading(true); 142 - setError(undefined); 143 - } 144 - try { 145 - let nextCursor: string | undefined; 146 - let mapped: PaginatedRecord<T>[] | undefined; 164 + const fetchPage = useCallback( 165 + async ( 166 + identityKey: string, 167 + cursor: string | undefined, 168 + targetIndex: number, 169 + mode: "active" | "prefetch", 170 + ) => { 171 + if (!did || !endpoint) return; 172 + const currentIdentity = `${did}::${endpoint}`; 173 + if (identityKey !== currentIdentity) return; 174 + const token = requestSeq.current; 175 + const key = `${identityKey}:${targetIndex}:${cursor ?? "start"}`; 176 + if (inFlight.current.has(key)) return; 177 + inFlight.current.add(key); 178 + if (mode === "active") { 179 + setLoading(true); 180 + setError(undefined); 181 + } 182 + try { 183 + let nextCursor: string | undefined; 184 + let mapped: PaginatedRecord<T>[] | undefined; 147 185 148 - const shouldUseAuthorFeed = preferAuthorFeed && collection === 'app.bsky.feed.post' && !feedDisabledRef.current && !!actorIdentifier; 149 - if (shouldUseAuthorFeed) { 150 - try { 151 - const { rpc } = await createAtprotoClient({ service: authorFeedService ?? DEFAULT_APPVIEW_SERVICE }); 152 - const res = await (rpc as unknown as { 153 - get: ( 154 - nsid: string, 155 - opts: { params: Record<string, string | number | boolean | undefined> } 156 - ) => Promise<{ ok: boolean; data: { feed?: Array<{ post?: { uri?: string; record?: T } }>; cursor?: string } }>; 157 - }).get('app.bsky.feed.getAuthorFeed', { 158 - params: { 159 - actor: actorIdentifier, 160 - limit, 161 - cursor, 162 - filter: authorFeedFilter, 163 - includePins: authorFeedIncludePins 164 - } 165 - }); 166 - if (!res.ok) throw new Error('Failed to fetch author feed'); 167 - const { feed, cursor: feedCursor } = res.data; 168 - mapped = (feed ?? []).reduce<PaginatedRecord<T>[]>((acc, item) => { 169 - const post = item?.post; 170 - if (!post || typeof post.uri !== 'string' || !post.record) return acc; 171 - acc.push({ 172 - uri: post.uri, 173 - rkey: extractRkey(post.uri), 174 - value: post.record as T 175 - }); 176 - return acc; 177 - }, []); 178 - nextCursor = feedCursor; 179 - } catch (err) { 180 - feedDisabledRef.current = true; 181 - if (process.env.NODE_ENV !== 'production') { 182 - console.warn('[usePaginatedRecords] Author feed unavailable, falling back to PDS', err); 183 - } 184 - } 185 - } 186 + const shouldUseAuthorFeed = 187 + preferAuthorFeed && 188 + collection === "app.bsky.feed.post" && 189 + !feedDisabledRef.current && 190 + !!actorIdentifier; 191 + if (shouldUseAuthorFeed) { 192 + try { 193 + interface AuthorFeedResponse { 194 + feed?: Array<{ 195 + post?: { 196 + uri?: string; 197 + record?: T; 198 + reply?: { 199 + parent?: { 200 + uri?: string; 201 + author?: { 202 + handle?: string; 203 + did?: string; 204 + }; 205 + }; 206 + }; 207 + }; 208 + reason?: AuthorFeedReason; 209 + }>; 210 + cursor?: string; 211 + } 212 + 213 + const res = await callAppviewRpc<AuthorFeedResponse>( 214 + authorFeedService ?? blueskyAppviewService, 215 + "app.bsky.feed.getAuthorFeed", 216 + { 217 + actor: actorIdentifier, 218 + limit, 219 + cursor, 220 + filter: authorFeedFilter, 221 + includePins: authorFeedIncludePins, 222 + }, 223 + ); 224 + if (!res.ok) 225 + throw new Error("Failed to fetch author feed"); 226 + const { feed, cursor: feedCursor } = res.data; 227 + mapped = (feed ?? []).reduce<PaginatedRecord<T>[]>( 228 + (acc, item) => { 229 + const post = item?.post; 230 + if ( 231 + !post || 232 + typeof post.uri !== "string" || 233 + !post.record 234 + ) 235 + return acc; 236 + // Skip records with invalid timestamps (before 2023) 237 + if (!isValidTimestamp(post.record)) { 238 + console.warn("Skipping record with invalid timestamp:", post.uri); 239 + return acc; 240 + } 241 + acc.push({ 242 + uri: post.uri, 243 + rkey: extractRkey(post.uri), 244 + value: post.record as T, 245 + reason: item?.reason, 246 + replyParent: post.reply?.parent, 247 + }); 248 + return acc; 249 + }, 250 + [], 251 + ); 252 + nextCursor = feedCursor; 253 + } catch (err) { 254 + console.log(err); 255 + feedDisabledRef.current = true; 256 + } 257 + } 186 258 187 - if (!mapped) { 188 - const { rpc } = await createAtprotoClient({ service: endpoint }); 189 - const res = await (rpc as unknown as { 190 - get: ( 191 - nsid: string, 192 - opts: { params: Record<string, string | number | boolean | undefined> } 193 - ) => Promise<{ ok: boolean; data: { records: Array<{ uri: string; rkey?: string; value: T }>; cursor?: string } }>; 194 - }).get('com.atproto.repo.listRecords', { 195 - params: { 196 - repo: did, 197 - collection, 198 - limit, 199 - cursor, 200 - reverse: false 201 - } 202 - }); 203 - if (!res.ok) throw new Error('Failed to list records'); 204 - const { records, cursor: repoCursor } = res.data; 205 - mapped = records.map((item) => ({ 206 - uri: item.uri, 207 - rkey: item.rkey ?? extractRkey(item.uri), 208 - value: item.value 209 - })); 210 - nextCursor = repoCursor; 211 - } 259 + if (!mapped) { 260 + // Slingshot doesn't support listRecords, query PDS directly 261 + const res = await callListRecords<T>( 262 + endpoint, 263 + did, 264 + collection, 265 + limit, 266 + cursor, 267 + ); 268 + 269 + if (!res.ok) throw new Error("Failed to list records from PDS"); 270 + const { records, cursor: repoCursor } = res.data; 271 + mapped = records 272 + .filter((item) => { 273 + if (!isValidTimestamp(item.value)) { 274 + console.warn("Skipping record with invalid timestamp:", item.uri); 275 + return false; 276 + } 277 + return true; 278 + }) 279 + .map((item) => ({ 280 + uri: item.uri, 281 + rkey: item.rkey ?? extractRkey(item.uri), 282 + value: item.value, 283 + })); 284 + nextCursor = repoCursor; 285 + } 212 286 213 - if (token !== requestSeq.current || identityKey !== identityRef.current) { 214 - return nextCursor; 215 - } 216 - if (mode === 'active') setPageIndex(targetIndex); 217 - setPages(prev => { 218 - const next = [...prev]; 219 - next[targetIndex] = { records: mapped!, cursor: nextCursor }; 220 - return next; 221 - }); 222 - return nextCursor; 223 - } catch (e) { 224 - if (mode === 'active' && token === requestSeq.current && identityKey === identityRef.current) { 225 - setError(e as Error); 226 - } 227 - } finally { 228 - if (mode === 'active' && token === requestSeq.current && identityKey === identityRef.current) { 229 - setLoading(false); 230 - } 231 - inFlight.current.delete(key); 232 - } 233 - return undefined; 234 - }, [ 235 - did, 236 - endpoint, 237 - collection, 238 - limit, 239 - preferAuthorFeed, 240 - actorIdentifier, 241 - authorFeedService, 242 - authorFeedFilter, 243 - authorFeedIncludePins 244 - ]); 287 + if ( 288 + token !== requestSeq.current || 289 + identityKey !== identityRef.current 290 + ) { 291 + return nextCursor; 292 + } 293 + if (mode === "active") setPageIndex(targetIndex); 294 + setPages((prev) => { 295 + const next = [...prev]; 296 + next[targetIndex] = { 297 + records: mapped!, 298 + cursor: nextCursor, 299 + }; 300 + return next; 301 + }); 302 + return nextCursor; 303 + } catch (e) { 304 + if ( 305 + mode === "active" && 306 + token === requestSeq.current && 307 + identityKey === identityRef.current 308 + ) { 309 + setError(e as Error); 310 + } 311 + } finally { 312 + if ( 313 + mode === "active" && 314 + token === requestSeq.current && 315 + identityKey === identityRef.current 316 + ) { 317 + setLoading(false); 318 + } 319 + inFlight.current.delete(key); 320 + } 321 + return undefined; 322 + }, 323 + [ 324 + did, 325 + endpoint, 326 + collection, 327 + limit, 328 + preferAuthorFeed, 329 + actorIdentifier, 330 + authorFeedService, 331 + authorFeedFilter, 332 + authorFeedIncludePins, 333 + ], 334 + ); 245 335 246 - useEffect(() => { 247 - if (!handleOrDid) { 248 - identityRef.current = undefined; 249 - resetState(); 250 - setLoading(false); 251 - setError(undefined); 252 - return; 253 - } 336 + useEffect(() => { 337 + if (!handleOrDid) { 338 + identityRef.current = undefined; 339 + resetState(); 340 + setLoading(false); 341 + setError(undefined); 342 + return; 343 + } 254 344 255 - if (didError) { 256 - identityRef.current = undefined; 257 - resetState(); 258 - setLoading(false); 259 - setError(didError); 260 - return; 261 - } 345 + if (didError) { 346 + identityRef.current = undefined; 347 + resetState(); 348 + setLoading(false); 349 + setError(didError); 350 + return; 351 + } 262 352 263 - if (endpointError) { 264 - identityRef.current = undefined; 265 - resetState(); 266 - setLoading(false); 267 - setError(endpointError); 268 - return; 269 - } 353 + if (endpointError) { 354 + identityRef.current = undefined; 355 + resetState(); 356 + setLoading(false); 357 + setError(endpointError); 358 + return; 359 + } 270 360 271 - if (resolvingDid || resolvingEndpoint || !identity) { 272 - if (identityRef.current !== identity) { 273 - identityRef.current = identity; 274 - resetState(); 275 - } 276 - setLoading(!!handleOrDid); 277 - setError(undefined); 278 - return; 279 - } 361 + if (resolvingDid || resolvingEndpoint || !identity) { 362 + if (identityRef.current !== identity) { 363 + identityRef.current = identity; 364 + resetState(); 365 + } 366 + setLoading(!!handleOrDid); 367 + setError(undefined); 368 + return; 369 + } 280 370 281 - if (identityRef.current !== identity) { 282 - identityRef.current = identity; 283 - resetState(); 284 - } 371 + if (identityRef.current !== identity) { 372 + identityRef.current = identity; 373 + resetState(); 374 + } 285 375 286 - fetchPage(identity, undefined, 0, 'active').catch(() => { 287 - /* error handled in state */ 288 - }); 289 - }, [handleOrDid, identity, fetchPage, resetState, resolvingDid, resolvingEndpoint, didError, endpointError]); 376 + fetchPage(identity, undefined, 0, "active").catch(() => { 377 + /* error handled in state */ 378 + }); 379 + }, [ 380 + handleOrDid, 381 + identity, 382 + fetchPage, 383 + resetState, 384 + resolvingDid, 385 + resolvingEndpoint, 386 + didError, 387 + endpointError, 388 + ]); 290 389 291 - const currentPage = pages[pageIndex]; 292 - const hasNext = !!currentPage?.cursor || !!pages[pageIndex + 1]; 293 - const hasPrev = pageIndex > 0; 390 + const currentPage = pages[pageIndex]; 391 + const hasNext = !!currentPage?.cursor || !!pages[pageIndex + 1]; 392 + const hasPrev = pageIndex > 0; 294 393 295 - const loadNext = useCallback(() => { 296 - const identityKey = identityRef.current; 297 - if (!identityKey) return; 298 - const page = pages[pageIndex]; 299 - if (!page?.cursor && !pages[pageIndex + 1]) return; 300 - if (pages[pageIndex + 1]) { 301 - setPageIndex(pageIndex + 1); 302 - return; 303 - } 304 - fetchPage(identityKey, page.cursor, pageIndex + 1, 'active').catch(() => { 305 - /* handled via error state */ 306 - }); 307 - }, [fetchPage, pageIndex, pages]); 394 + const loadNext = useCallback(() => { 395 + const identityKey = identityRef.current; 396 + if (!identityKey) return; 397 + const page = pages[pageIndex]; 398 + if (!page?.cursor && !pages[pageIndex + 1]) return; 399 + if (pages[pageIndex + 1]) { 400 + setPageIndex(pageIndex + 1); 401 + return; 402 + } 403 + fetchPage(identityKey, page.cursor, pageIndex + 1, "active").catch( 404 + () => { 405 + /* handled via error state */ 406 + }, 407 + ); 408 + }, [fetchPage, pageIndex, pages]); 308 409 309 - const loadPrev = useCallback(() => { 310 - if (pageIndex === 0) return; 311 - setPageIndex(pageIndex - 1); 312 - }, [pageIndex]); 410 + const loadPrev = useCallback(() => { 411 + if (pageIndex === 0) return; 412 + setPageIndex(pageIndex - 1); 413 + }, [pageIndex]); 313 414 314 - const records = useMemo(() => currentPage?.records ?? [], [currentPage]); 415 + const records = useMemo(() => currentPage?.records ?? [], [currentPage]); 315 416 316 - const effectiveError = error ?? (endpointError as Error | undefined) ?? (didError as Error | undefined); 417 + const effectiveError = 418 + error ?? 419 + (endpointError as Error | undefined) ?? 420 + (didError as Error | undefined); 317 421 318 - useEffect(() => { 319 - const cursor = pages[pageIndex]?.cursor; 320 - if (!cursor) return; 321 - if (pages[pageIndex + 1]) return; 322 - const identityKey = identityRef.current; 323 - if (!identityKey) return; 324 - fetchPage(identityKey, cursor, pageIndex + 1, 'prefetch').catch(() => { 325 - /* ignore prefetch errors */ 326 - }); 327 - }, [fetchPage, pageIndex, pages]); 422 + useEffect(() => { 423 + const cursor = pages[pageIndex]?.cursor; 424 + if (!cursor) return; 425 + if (pages[pageIndex + 1]) return; 426 + const identityKey = identityRef.current; 427 + if (!identityKey) return; 428 + fetchPage(identityKey, cursor, pageIndex + 1, "prefetch").catch(() => { 429 + /* ignore prefetch errors */ 430 + }); 431 + }, [fetchPage, pageIndex, pages]); 328 432 329 - return { 330 - records, 331 - loading, 332 - error: effectiveError, 333 - hasNext, 334 - hasPrev, 335 - loadNext, 336 - loadPrev, 337 - pageIndex, 338 - pagesCount: pages.length || (currentPage ? pageIndex + 1 : 0) 339 - }; 433 + return { 434 + records, 435 + loading, 436 + error: effectiveError, 437 + hasNext, 438 + hasPrev, 439 + loadNext, 440 + loadPrev, 441 + pageIndex, 442 + pagesCount: pages.length || (currentPage ? pageIndex + 1 : 0), 443 + }; 340 444 } 341 445 342 446 function extractRkey(uri: string): string { 343 - const parts = uri.split('/'); 344 - return parts[parts.length - 1]; 447 + const parts = uri.split("/"); 448 + return parts[parts.length - 1]; 449 + } 450 + 451 + /** 452 + * Validates that a record has a reasonable timestamp (not before 2023). 453 + * ATProto was created in 2023, so any timestamp before that is invalid. 454 + */ 455 + function isValidTimestamp(record: unknown): boolean { 456 + if (typeof record !== "object" || record === null) return true; 457 + 458 + const recordObj = record as { createdAt?: string; indexedAt?: string }; 459 + const timestamp = recordObj.createdAt || recordObj.indexedAt; 460 + 461 + if (!timestamp || typeof timestamp !== "string") return true; // No timestamp to validate 462 + 463 + try { 464 + const date = new Date(timestamp); 465 + // ATProto was created in 2023, reject anything before that 466 + return date.getFullYear() >= 2023; 467 + } catch { 468 + // If we can't parse the date, consider it valid to avoid false negatives 469 + return true; 470 + } 345 471 }
+20 -9
lib/hooks/usePdsEndpoint.ts
··· 1 - import { useEffect, useState } from 'react'; 2 - import { useAtProto } from '../providers/AtProtoProvider'; 1 + import { useEffect, useState } from "react"; 2 + import { useAtProto } from "../providers/AtProtoProvider"; 3 3 4 4 /** 5 5 * Resolves the PDS service endpoint for a given DID and tracks loading state. ··· 19 19 setEndpoint(undefined); 20 20 setError(undefined); 21 21 setLoading(false); 22 - return () => { cancelled = true; }; 22 + return () => { 23 + cancelled = true; 24 + }; 23 25 } 24 26 25 27 const cached = didCache.getByDid(did); ··· 27 29 setEndpoint(cached.pdsEndpoint); 28 30 setError(undefined); 29 31 setLoading(false); 30 - return () => { cancelled = true; }; 32 + return () => { 33 + cancelled = true; 34 + }; 31 35 } 32 36 33 37 setEndpoint(undefined); 34 38 setLoading(true); 35 39 setError(undefined); 36 - didCache.ensurePdsEndpoint(resolver, did) 37 - .then(snapshot => { 40 + didCache 41 + .ensurePdsEndpoint(resolver, did) 42 + .then((snapshot) => { 38 43 if (cancelled) return; 39 44 setEndpoint(snapshot.pdsEndpoint); 40 45 }) 41 - .catch(e => { 46 + .catch((e) => { 42 47 if (cancelled) return; 43 - setError(e as Error); 48 + const newError = e as Error; 49 + // Only update error if message changed (stabilize reference) 50 + setError(prevError => 51 + prevError?.message === newError.message ? prevError : newError 52 + ); 44 53 }) 45 54 .finally(() => { 46 55 if (!cancelled) setLoading(false); 47 56 }); 48 - return () => { cancelled = true; }; 57 + return () => { 58 + cancelled = true; 59 + }; 49 60 }, [did, resolver, didCache]); 50 61 51 62 return { endpoint, error, loading };
+104
lib/hooks/useRepoLanguages.ts
··· 1 + import { useState, useEffect } from "react"; 2 + import type { RepoLanguagesResponse } from "../types/tangled"; 3 + 4 + export interface UseRepoLanguagesOptions { 5 + /** The knot server URL (e.g., "knot.gaze.systems") */ 6 + knot?: string; 7 + /** DID of the repository owner */ 8 + did?: string; 9 + /** Repository name */ 10 + repoName?: string; 11 + /** Branch to query (defaults to trying "main", then "master") */ 12 + branch?: string; 13 + /** Whether to enable the query */ 14 + enabled?: boolean; 15 + } 16 + 17 + export interface UseRepoLanguagesResult { 18 + /** Language data from the knot server */ 19 + data?: RepoLanguagesResponse; 20 + /** Loading state */ 21 + loading: boolean; 22 + /** Error state */ 23 + error?: Error; 24 + } 25 + 26 + /** 27 + * Hook to fetch repository language information from a Tangled knot server. 28 + * If no branch supplied, tries "main" first, then falls back to "master". 29 + */ 30 + export function useRepoLanguages({ 31 + knot, 32 + did, 33 + repoName, 34 + branch, 35 + enabled = true, 36 + }: UseRepoLanguagesOptions): UseRepoLanguagesResult { 37 + const [data, setData] = useState<RepoLanguagesResponse | undefined>(); 38 + const [loading, setLoading] = useState(false); 39 + const [error, setError] = useState<Error | undefined>(); 40 + 41 + useEffect(() => { 42 + if (!enabled || !knot || !did || !repoName) { 43 + return; 44 + } 45 + 46 + let cancelled = false; 47 + 48 + const fetchLanguages = async (ref: string): Promise<boolean> => { 49 + try { 50 + const url = `https://${knot}/xrpc/sh.tangled.repo.languages?repo=${encodeURIComponent(`${did}/${repoName}`)}&ref=${encodeURIComponent(ref)}`; 51 + const response = await fetch(url); 52 + 53 + if (!response.ok) { 54 + return false; 55 + } 56 + 57 + const result = await response.json(); 58 + if (!cancelled) { 59 + setData(result); 60 + setError(undefined); 61 + } 62 + return true; 63 + } catch (err) { 64 + return false; 65 + } 66 + }; 67 + 68 + const fetchWithFallback = async () => { 69 + setLoading(true); 70 + setError(undefined); 71 + 72 + if (branch) { 73 + const success = await fetchLanguages(branch); 74 + if (!cancelled) { 75 + if (!success) { 76 + setError(new Error(`Failed to fetch languages for branch: ${branch}`)); 77 + } 78 + setLoading(false); 79 + } 80 + } else { 81 + // Try "main" first, then "master" 82 + let success = await fetchLanguages("main"); 83 + if (!success && !cancelled) { 84 + success = await fetchLanguages("master"); 85 + } 86 + 87 + if (!cancelled) { 88 + if (!success) { 89 + setError(new Error("Failed to fetch languages for main or master branch")); 90 + } 91 + setLoading(false); 92 + } 93 + } 94 + }; 95 + 96 + fetchWithFallback(); 97 + 98 + return () => { 99 + cancelled = true; 100 + }; 101 + }, [knot, did, repoName, branch, enabled]); 102 + 103 + return { data, loading, error }; 104 + }
+43 -27
lib/index.ts
··· 1 1 // Master exporter for the AT React component library. 2 2 3 + import "./styles.css"; 4 + 3 5 // Providers & core primitives 4 - export * from './providers/AtProtoProvider'; 5 - export * from './core/AtProtoRecord'; 6 + export * from "./providers/AtProtoProvider"; 7 + export * from "./core/AtProtoRecord"; 6 8 7 9 // Components 8 - export * from './components/BlueskyIcon'; 9 - export * from './components/BlueskyPost'; 10 - export * from './components/BlueskyPostList'; 11 - export * from './components/BlueskyProfile'; 12 - export * from './components/BlueskyQuotePost'; 13 - export * from './components/ColorSchemeToggle'; 14 - export * from './components/LeafletDocument'; 15 - export * from './components/TangledString'; 10 + export * from "./components/BlueskyIcon"; 11 + export * from "./components/BlueskyPost"; 12 + export * from "./components/BlueskyPostList"; 13 + export * from "./components/BlueskyProfile"; 14 + export * from "./components/BlueskyQuotePost"; 15 + export * from "./components/GrainGallery"; 16 + export * from "./components/LeafletDocument"; 17 + export * from "./components/TangledRepo"; 18 + export * from "./components/TangledString"; 19 + export * from "./components/CurrentlyPlaying"; 20 + export * from "./components/LastPlayed"; 21 + export * from "./components/SongHistoryList"; 16 22 17 23 // Hooks 18 - export * from './hooks/useAtProtoRecord'; 19 - export * from './hooks/useBlob'; 20 - export * from './hooks/useBlueskyProfile'; 21 - export * from './hooks/useColorScheme'; 22 - export * from './hooks/useDidResolution'; 23 - export * from './hooks/useLatestRecord'; 24 - export * from './hooks/usePaginatedRecords'; 25 - export * from './hooks/usePdsEndpoint'; 24 + export * from "./hooks/useAtProtoRecord"; 25 + export * from "./hooks/useBacklinks"; 26 + export * from "./hooks/useBlob"; 27 + export * from "./hooks/useBlueskyAppview"; 28 + export * from "./hooks/useBlueskyProfile"; 29 + export * from "./hooks/useDidResolution"; 30 + export * from "./hooks/useLatestRecord"; 31 + export * from "./hooks/usePaginatedRecords"; 32 + export * from "./hooks/usePdsEndpoint"; 33 + export * from "./hooks/useRepoLanguages"; 26 34 27 35 // Renderers 28 - export * from './renderers/BlueskyPostRenderer'; 29 - export * from './renderers/BlueskyProfileRenderer'; 30 - export * from './renderers/LeafletDocumentRenderer'; 31 - export * from './renderers/TangledStringRenderer'; 36 + export * from "./renderers/BlueskyPostRenderer"; 37 + export * from "./renderers/BlueskyProfileRenderer"; 38 + export * from "./renderers/GrainGalleryRenderer"; 39 + export * from "./renderers/LeafletDocumentRenderer"; 40 + export * from "./renderers/TangledRepoRenderer"; 41 + export * from "./renderers/TangledStringRenderer"; 42 + export * from "./renderers/CurrentlyPlayingRenderer"; 32 43 33 44 // Types 34 - export * from './types/bluesky'; 35 - export * from './types/leaflet'; 45 + export * from "./types/bluesky"; 46 + export * from "./types/grain"; 47 + export * from "./types/leaflet"; 48 + export * from "./types/tangled"; 49 + export * from "./types/teal"; 50 + export * from "./types/theme"; 36 51 37 52 // Utilities 38 - export * from './utils/at-uri'; 39 - export * from './utils/atproto-client'; 40 - export * from './utils/profile'; 53 + export * from "./utils/at-uri"; 54 + export * from "./utils/atproto-client"; 55 + export * from "./utils/blob"; 56 + export * from "./utils/profile";
+213 -17
lib/providers/AtProtoProvider.tsx
··· 1 1 /* eslint-disable react-refresh/only-export-components */ 2 - import React, { createContext, useContext, useMemo, useRef } from 'react'; 3 - import { ServiceResolver, normalizeBaseUrl } from '../utils/atproto-client'; 4 - import { BlobCache, DidCache } from '../utils/cache'; 2 + import React, { 3 + createContext, 4 + useContext, 5 + useMemo, 6 + useRef, 7 + } from "react"; 8 + import { ServiceResolver, normalizeBaseUrl, DEFAULT_CONFIG } from "../utils/atproto-client"; 9 + import { BlobCache, DidCache, RecordCache } from "../utils/cache"; 5 10 11 + /** 12 + * Props for the AT Protocol context provider. 13 + */ 6 14 export interface AtProtoProviderProps { 15 + /** Child components that will have access to the AT Protocol context. */ 7 16 children: React.ReactNode; 17 + /** Optional custom PLC directory URL. Defaults to https://plc.directory */ 8 18 plcDirectory?: string; 19 + /** Optional custom identity service URL. Defaults to https://public.api.bsky.app */ 20 + identityService?: string; 21 + /** Optional custom Slingshot service URL. Defaults to https://slingshot.microcosm.blue */ 22 + slingshotBaseUrl?: string; 23 + /** Optional custom Bluesky appview service URL. Defaults to https://public.api.bsky.app */ 24 + blueskyAppviewService?: string; 25 + /** Optional custom Bluesky app base URL for links. Defaults to https://bsky.app */ 26 + blueskyAppBaseUrl?: string; 27 + /** Optional custom Tangled base URL for links. Defaults to https://tangled.org */ 28 + tangledBaseUrl?: string; 29 + /** Optional custom Constellation API URL for backlinks. Defaults to https://constellation.microcosm.blue */ 30 + constellationBaseUrl?: string; 9 31 } 10 32 33 + /** 34 + * Internal context value shared across all AT Protocol hooks. 35 + */ 11 36 interface AtProtoContextValue { 37 + /** Service resolver for DID resolution and PDS endpoint discovery. */ 12 38 resolver: ServiceResolver; 39 + /** Normalized PLC directory base URL. */ 13 40 plcDirectory: string; 41 + /** Normalized Bluesky appview service URL. */ 42 + blueskyAppviewService: string; 43 + /** Normalized Bluesky app base URL for links. */ 44 + blueskyAppBaseUrl: string; 45 + /** Normalized Tangled base URL for links. */ 46 + tangledBaseUrl: string; 47 + /** Normalized Constellation API base URL for backlinks. */ 48 + constellationBaseUrl: string; 49 + /** Cache for DID documents and handle mappings. */ 14 50 didCache: DidCache; 51 + /** Cache for fetched blob data. */ 15 52 blobCache: BlobCache; 53 + /** Cache for fetched AT Protocol records. */ 54 + recordCache: RecordCache; 16 55 } 17 56 18 - const AtProtoContext = createContext<AtProtoContextValue | undefined>(undefined); 57 + const AtProtoContext = createContext<AtProtoContextValue | undefined>( 58 + undefined, 59 + ); 19 60 20 - export function AtProtoProvider({ children, plcDirectory }: AtProtoProviderProps) { 21 - const normalizedPlc = useMemo(() => normalizeBaseUrl(plcDirectory && plcDirectory.trim() ? plcDirectory : 'https://plc.directory'), [plcDirectory]); 22 - const resolver = useMemo(() => new ServiceResolver({ plcDirectory: normalizedPlc }), [normalizedPlc]); 23 - const cachesRef = useRef<{ didCache: DidCache; blobCache: BlobCache } | null>(null); 61 + /** 62 + * Context provider that supplies AT Protocol infrastructure to all child components. 63 + * 64 + * This provider initializes and shares: 65 + * - Service resolver for DID and PDS endpoint resolution 66 + * - DID cache for identity resolution 67 + * - Blob cache for efficient media handling 68 + * 69 + * All AT Protocol components (`BlueskyPost`, `LeafletDocument`, etc.) must be wrapped 70 + * in this provider to function correctly. 71 + * 72 + * @example 73 + * ```tsx 74 + * import { AtProtoProvider, BlueskyPost } from 'atproto-ui'; 75 + * 76 + * function App() { 77 + * return ( 78 + * <AtProtoProvider> 79 + * <BlueskyPost did="did:plc:example" rkey="3k2aexample" /> 80 + * </AtProtoProvider> 81 + * ); 82 + * } 83 + * ``` 84 + * 85 + * @example 86 + * ```tsx 87 + * // Using a custom PLC directory 88 + * <AtProtoProvider plcDirectory="https://custom-plc.example.com"> 89 + * <YourComponents /> 90 + * </AtProtoProvider> 91 + * ``` 92 + * 93 + * @param children - Child components to render within the provider. 94 + * @param plcDirectory - Optional PLC directory override (defaults to https://plc.directory). 95 + * @returns Provider component that enables AT Protocol functionality. 96 + */ 97 + export function AtProtoProvider({ 98 + children, 99 + plcDirectory, 100 + identityService, 101 + slingshotBaseUrl, 102 + blueskyAppviewService, 103 + blueskyAppBaseUrl, 104 + tangledBaseUrl, 105 + constellationBaseUrl, 106 + }: AtProtoProviderProps) { 107 + const normalizedPlc = useMemo( 108 + () => 109 + normalizeBaseUrl( 110 + plcDirectory && plcDirectory.trim() 111 + ? plcDirectory 112 + : DEFAULT_CONFIG.plcDirectory, 113 + ), 114 + [plcDirectory], 115 + ); 116 + const normalizedIdentity = useMemo( 117 + () => 118 + normalizeBaseUrl( 119 + identityService && identityService.trim() 120 + ? identityService 121 + : DEFAULT_CONFIG.identityService, 122 + ), 123 + [identityService], 124 + ); 125 + const normalizedSlingshot = useMemo( 126 + () => 127 + normalizeBaseUrl( 128 + slingshotBaseUrl && slingshotBaseUrl.trim() 129 + ? slingshotBaseUrl 130 + : DEFAULT_CONFIG.slingshotBaseUrl, 131 + ), 132 + [slingshotBaseUrl], 133 + ); 134 + const normalizedAppview = useMemo( 135 + () => 136 + normalizeBaseUrl( 137 + blueskyAppviewService && blueskyAppviewService.trim() 138 + ? blueskyAppviewService 139 + : DEFAULT_CONFIG.blueskyAppviewService, 140 + ), 141 + [blueskyAppviewService], 142 + ); 143 + const normalizedBlueskyApp = useMemo( 144 + () => 145 + normalizeBaseUrl( 146 + blueskyAppBaseUrl && blueskyAppBaseUrl.trim() 147 + ? blueskyAppBaseUrl 148 + : DEFAULT_CONFIG.blueskyAppBaseUrl, 149 + ), 150 + [blueskyAppBaseUrl], 151 + ); 152 + const normalizedTangled = useMemo( 153 + () => 154 + normalizeBaseUrl( 155 + tangledBaseUrl && tangledBaseUrl.trim() 156 + ? tangledBaseUrl 157 + : DEFAULT_CONFIG.tangledBaseUrl, 158 + ), 159 + [tangledBaseUrl], 160 + ); 161 + const normalizedConstellation = useMemo( 162 + () => 163 + normalizeBaseUrl( 164 + constellationBaseUrl && constellationBaseUrl.trim() 165 + ? constellationBaseUrl 166 + : DEFAULT_CONFIG.constellationBaseUrl, 167 + ), 168 + [constellationBaseUrl], 169 + ); 170 + const resolver = useMemo( 171 + () => new ServiceResolver({ 172 + plcDirectory: normalizedPlc, 173 + identityService: normalizedIdentity, 174 + slingshotBaseUrl: normalizedSlingshot, 175 + }), 176 + [normalizedPlc, normalizedIdentity, normalizedSlingshot], 177 + ); 178 + const cachesRef = useRef<{ 179 + didCache: DidCache; 180 + blobCache: BlobCache; 181 + recordCache: RecordCache; 182 + } | null>(null); 24 183 if (!cachesRef.current) { 25 - cachesRef.current = { didCache: new DidCache(), blobCache: new BlobCache() }; 184 + cachesRef.current = { 185 + didCache: new DidCache(), 186 + blobCache: new BlobCache(), 187 + recordCache: new RecordCache(), 188 + }; 26 189 } 27 - const value = useMemo<AtProtoContextValue>(() => ({ 28 - resolver, 29 - plcDirectory: normalizedPlc, 30 - didCache: cachesRef.current!.didCache, 31 - blobCache: cachesRef.current!.blobCache, 32 - }), [resolver, normalizedPlc]); 33 - return <AtProtoContext.Provider value={value}>{children}</AtProtoContext.Provider>; 190 + 191 + const value = useMemo<AtProtoContextValue>( 192 + () => ({ 193 + resolver, 194 + plcDirectory: normalizedPlc, 195 + blueskyAppviewService: normalizedAppview, 196 + blueskyAppBaseUrl: normalizedBlueskyApp, 197 + tangledBaseUrl: normalizedTangled, 198 + constellationBaseUrl: normalizedConstellation, 199 + didCache: cachesRef.current!.didCache, 200 + blobCache: cachesRef.current!.blobCache, 201 + recordCache: cachesRef.current!.recordCache, 202 + }), 203 + [resolver, normalizedPlc, normalizedAppview, normalizedBlueskyApp, normalizedTangled, normalizedConstellation], 204 + ); 205 + 206 + return ( 207 + <AtProtoContext.Provider value={value}> 208 + {children} 209 + </AtProtoContext.Provider> 210 + ); 34 211 } 35 212 213 + /** 214 + * Hook that accesses the AT Protocol context provided by `AtProtoProvider`. 215 + * 216 + * This hook exposes the service resolver, DID cache, blob cache, and record cache 217 + * for building custom AT Protocol functionality. 218 + * 219 + * @throws {Error} When called outside of an `AtProtoProvider`. 220 + * @returns {AtProtoContextValue} Object containing resolver, caches, and PLC directory URL. 221 + * 222 + * @example 223 + * ```tsx 224 + * import { useAtProto } from 'atproto-ui'; 225 + * 226 + * function MyCustomComponent() { 227 + * const { resolver, didCache, blobCache, recordCache } = useAtProto(); 228 + * // Use the resolver and caches for custom AT Protocol operations 229 + * } 230 + * ``` 231 + */ 36 232 export function useAtProto() { 37 233 const ctx = useContext(AtProtoContext); 38 - if (!ctx) throw new Error('useAtProto must be used within AtProtoProvider'); 234 + if (!ctx) throw new Error("useAtProto must be used within AtProtoProvider"); 39 235 return ctx; 40 236 }
+624 -435
lib/renderers/BlueskyPostRenderer.tsx
··· 1 - import React from 'react'; 2 - import type { FeedPostRecord } from '../types/bluesky'; 3 - import { useColorScheme, type ColorSchemePreference } from '../hooks/useColorScheme'; 4 - import { parseAtUri, toBlueskyPostUrl, formatDidForLabel, type ParsedAtUri } from '../utils/at-uri'; 5 - import { useDidResolution } from '../hooks/useDidResolution'; 6 - import { useBlob } from '../hooks/useBlob'; 7 - import { BlueskyIcon } from '../components/BlueskyIcon'; 1 + import React from "react"; 2 + import type { FeedPostRecord } from "../types/bluesky"; 3 + import { 4 + parseAtUri, 5 + toBlueskyPostUrl, 6 + formatDidForLabel, 7 + type ParsedAtUri, 8 + } from "../utils/at-uri"; 9 + import { useDidResolution } from "../hooks/useDidResolution"; 10 + import { useBlob } from "../hooks/useBlob"; 11 + import { BlueskyIcon } from "../components/BlueskyIcon"; 12 + import { isBlobWithCdn, extractCidFromBlob } from "../utils/blob"; 13 + import { RichText } from "../components/RichText"; 8 14 9 15 export interface BlueskyPostRendererProps { 10 - record: FeedPostRecord; 11 - loading: boolean; 12 - error?: Error; 13 - // Optionally pass in actor display info if pre-fetched 14 - authorHandle?: string; 15 - authorDisplayName?: string; 16 - avatarUrl?: string; 17 - colorScheme?: ColorSchemePreference; 18 - authorDid?: string; 19 - embed?: React.ReactNode; 20 - iconPlacement?: 'cardBottomRight' | 'timestamp' | 'linkInline'; 21 - showIcon?: boolean; 22 - atUri?: string; 16 + record: FeedPostRecord; 17 + loading: boolean; 18 + error?: Error; 19 + authorHandle?: string; 20 + authorDisplayName?: string; 21 + avatarUrl?: string; 22 + authorDid?: string; 23 + embed?: React.ReactNode; 24 + iconPlacement?: "cardBottomRight" | "timestamp" | "linkInline"; 25 + showIcon?: boolean; 26 + atUri?: string; 27 + isInThread?: boolean; 28 + threadDepth?: number; 29 + isQuotePost?: boolean; 30 + showThreadBorder?: boolean; 23 31 } 24 32 25 - export const BlueskyPostRenderer: React.FC<BlueskyPostRendererProps> = ({ record, loading, error, authorDisplayName, authorHandle, avatarUrl, colorScheme = 'system', authorDid, embed, iconPlacement = 'timestamp', showIcon = true, atUri }) => { 26 - const scheme = useColorScheme(colorScheme); 27 - const replyParentUri = record.reply?.parent?.uri; 28 - const replyTarget = replyParentUri ? parseAtUri(replyParentUri) : undefined; 29 - const { handle: parentHandle, loading: parentHandleLoading } = useDidResolution(replyTarget?.did); 33 + export const BlueskyPostRenderer: React.FC<BlueskyPostRendererProps> = ({ 34 + record, 35 + loading, 36 + error, 37 + authorDisplayName, 38 + authorHandle, 39 + avatarUrl, 40 + authorDid, 41 + embed, 42 + iconPlacement = "timestamp", 43 + showIcon = true, 44 + atUri, 45 + isInThread = false, 46 + threadDepth = 0, 47 + isQuotePost = false, 48 + showThreadBorder = false 49 + }) => { 50 + void threadDepth; 51 + 52 + const replyParentUri = record.reply?.parent?.uri; 53 + const replyTarget = replyParentUri ? parseAtUri(replyParentUri) : undefined; 54 + const { handle: parentHandle, loading: parentHandleLoading } = 55 + useDidResolution(replyTarget?.did); 30 56 31 - if (error) return <div style={{ padding: 8, color: 'crimson' }}>Failed to load post.</div>; 32 - if (loading && !record) return <div style={{ padding: 8 }}>Loadingโ€ฆ</div>; 57 + if (error) { 58 + return ( 59 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 60 + Failed to load post. 61 + </div> 62 + ); 63 + } 64 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 33 65 34 - const palette = scheme === 'dark' ? themeStyles.dark : themeStyles.light; 66 + const text = record.text; 67 + const createdDate = new Date(record.createdAt); 68 + const created = createdDate.toLocaleString(undefined, { 69 + dateStyle: "medium", 70 + timeStyle: "short", 71 + }); 72 + const primaryName = authorDisplayName || authorHandle || "โ€ฆ"; 73 + const replyHref = replyTarget ? toBlueskyPostUrl(replyTarget) : undefined; 74 + const replyLabel = replyTarget 75 + ? formatReplyLabel(replyTarget, parentHandle, parentHandleLoading) 76 + : undefined; 35 77 36 - const text = record.text; 37 - const createdDate = new Date(record.createdAt); 38 - const created = createdDate.toLocaleString(undefined, { 39 - dateStyle: 'medium', 40 - timeStyle: 'short' 41 - }); 42 - const primaryName = authorDisplayName || authorHandle || 'โ€ฆ'; 43 - const replyHref = replyTarget ? toBlueskyPostUrl(replyTarget) : undefined; 44 - const replyLabel = replyTarget ? formatReplyLabel(replyTarget, parentHandle, parentHandleLoading) : undefined; 78 + const makeIcon = () => (showIcon ? <BlueskyIcon size={16} /> : null); 79 + const resolvedEmbed = embed ?? createAutoEmbed(record, authorDid); 80 + const parsedSelf = atUri ? parseAtUri(atUri) : undefined; 81 + const postUrl = parsedSelf ? toBlueskyPostUrl(parsedSelf) : undefined; 82 + const cardPadding = 83 + typeof baseStyles.card.padding === "number" 84 + ? baseStyles.card.padding 85 + : 12; 45 86 46 - const makeIcon = () => (showIcon ? <BlueskyIcon size={16} /> : null); 47 - const resolvedEmbed = embed ?? createAutoEmbed(record, authorDid, scheme); 48 - const parsedSelf = atUri ? parseAtUri(atUri) : undefined; 49 - const postUrl = parsedSelf ? toBlueskyPostUrl(parsedSelf) : undefined; 50 - const cardPadding = typeof baseStyles.card.padding === 'number' ? baseStyles.card.padding : 12; 51 - const cardStyle: React.CSSProperties = { 52 - ...baseStyles.card, 53 - ...palette.card, 54 - ...(iconPlacement === 'cardBottomRight' && showIcon ? { paddingBottom: cardPadding + 16 } : {}) 55 - }; 87 + const cardStyle: React.CSSProperties = { 88 + ...baseStyles.card, 89 + border: (isInThread && !isQuotePost && !showThreadBorder) ? "none" : `1px solid var(--atproto-color-border)`, 90 + background: `var(--atproto-color-bg)`, 91 + color: `var(--atproto-color-text)`, 92 + borderRadius: (isInThread && !isQuotePost && !showThreadBorder) ? "0" : "12px", 93 + ...(iconPlacement === "cardBottomRight" && showIcon && !isInThread 94 + ? { paddingBottom: cardPadding + 16 } 95 + : {}), 96 + }; 56 97 57 - return ( 58 - <article style={cardStyle} aria-busy={loading}> 59 - <header style={baseStyles.header}> 60 - {avatarUrl ? ( 61 - <img src={avatarUrl} alt="avatar" style={baseStyles.avatarImg} /> 62 - ) : ( 63 - <div style={{ ...baseStyles.avatarPlaceholder, ...palette.avatarPlaceholder }} aria-hidden /> 64 - )} 65 - <div style={{ display: 'flex', flexDirection: 'column' }}> 66 - <strong style={{ fontSize: 14 }}>{primaryName}</strong> 67 - {authorDisplayName && authorHandle && <span style={{ ...baseStyles.handle, ...palette.handle }}>@{authorHandle}</span>} 68 - </div> 69 - {iconPlacement === 'timestamp' && showIcon && ( 70 - <div style={baseStyles.headerIcon}>{makeIcon()}</div> 71 - )} 72 - </header> 73 - {replyHref && replyLabel && ( 74 - <div style={{ ...baseStyles.replyLine, ...palette.replyLine }}> 75 - Replying to{' '} 76 - <a href={replyHref} target="_blank" rel="noopener noreferrer" style={{ ...baseStyles.replyLink, ...palette.replyLink }}> 77 - {replyLabel} 78 - </a> 79 - </div> 80 - )} 81 - <div style={baseStyles.body}> 82 - <p style={{ ...baseStyles.text, ...palette.text }}>{text}</p> 83 - {record.facets && record.facets.length > 0 && ( 84 - <div style={baseStyles.facets}> 85 - {record.facets.map((_, idx) => ( 86 - <span key={idx} style={{ ...baseStyles.facetTag, ...palette.facetTag }}>facet</span> 87 - ))} 88 - </div> 89 - )} 90 - <div style={baseStyles.timestampRow}> 91 - <time style={{ ...baseStyles.time, ...palette.time }} dateTime={record.createdAt}>{created}</time> 92 - {postUrl && ( 93 - <span style={baseStyles.linkWithIcon}> 94 - <a href={postUrl} target="_blank" rel="noopener noreferrer" style={{ ...baseStyles.postLink, ...palette.postLink }}> 95 - View on Bluesky 96 - </a> 97 - {iconPlacement === 'linkInline' && showIcon && ( 98 - <span style={baseStyles.inlineIcon} aria-hidden> 99 - {makeIcon()} 100 - </span> 101 - )} 102 - </span> 103 - )} 104 - </div> 105 - {resolvedEmbed && ( 106 - <div style={{ ...baseStyles.embedContainer, ...palette.embedContainer }}> 107 - {resolvedEmbed} 108 - </div> 109 - )} 110 - </div> 111 - {iconPlacement === 'cardBottomRight' && showIcon && ( 112 - <div style={baseStyles.iconCorner} aria-hidden> 113 - {makeIcon()} 114 - </div> 115 - )} 116 - </article> 117 - ); 98 + return ( 99 + <article style={cardStyle} aria-busy={loading}> 100 + {isInThread ? ( 101 + <ThreadLayout 102 + avatarUrl={avatarUrl} 103 + primaryName={primaryName} 104 + authorDisplayName={authorDisplayName} 105 + authorHandle={authorHandle} 106 + iconPlacement={iconPlacement} 107 + showIcon={showIcon} 108 + makeIcon={makeIcon} 109 + replyHref={replyHref} 110 + replyLabel={replyLabel} 111 + text={text} 112 + record={record} 113 + created={created} 114 + postUrl={postUrl} 115 + resolvedEmbed={resolvedEmbed} 116 + /> 117 + ) : ( 118 + <DefaultLayout 119 + avatarUrl={avatarUrl} 120 + primaryName={primaryName} 121 + authorDisplayName={authorDisplayName} 122 + authorHandle={authorHandle} 123 + iconPlacement={iconPlacement} 124 + showIcon={showIcon} 125 + makeIcon={makeIcon} 126 + replyHref={replyHref} 127 + replyLabel={replyLabel} 128 + text={text} 129 + record={record} 130 + created={created} 131 + postUrl={postUrl} 132 + resolvedEmbed={resolvedEmbed} 133 + /> 134 + )} 135 + </article> 136 + ); 118 137 }; 119 138 139 + interface LayoutProps { 140 + avatarUrl?: string; 141 + primaryName: string; 142 + authorDisplayName?: string; 143 + authorHandle?: string; 144 + iconPlacement: "cardBottomRight" | "timestamp" | "linkInline"; 145 + showIcon: boolean; 146 + makeIcon: () => React.ReactNode; 147 + replyHref?: string; 148 + replyLabel?: string; 149 + text: string; 150 + record: FeedPostRecord; 151 + created: string; 152 + postUrl?: string; 153 + resolvedEmbed: React.ReactNode; 154 + } 155 + 156 + const AuthorInfo: React.FC<{ 157 + primaryName: string; 158 + authorDisplayName?: string; 159 + authorHandle?: string; 160 + inline?: boolean; 161 + }> = ({ primaryName, authorDisplayName, authorHandle, inline = false }) => ( 162 + <div 163 + style={{ 164 + display: "flex", 165 + flexDirection: inline ? "row" : "column", 166 + alignItems: inline ? "center" : "flex-start", 167 + gap: inline ? 8 : 0, 168 + }} 169 + > 170 + <strong style={{ fontSize: 14 }}>{authorDisplayName || primaryName}</strong> 171 + {authorHandle && ( 172 + <span 173 + style={{ 174 + ...baseStyles.handle, 175 + color: `var(--atproto-color-text-secondary)`, 176 + }} 177 + > 178 + @{authorHandle} 179 + </span> 180 + )} 181 + </div> 182 + ); 183 + 184 + const Avatar: React.FC<{ avatarUrl?: string; name?: string }> = ({ avatarUrl, name }) => 185 + avatarUrl ? ( 186 + <img src={avatarUrl} alt={`${name || 'User'}'s profile picture`} style={baseStyles.avatarImg} /> 187 + ) : ( 188 + <div style={baseStyles.avatarPlaceholder} aria-hidden="true" /> 189 + ); 190 + 191 + const ReplyInfo: React.FC<{ 192 + replyHref?: string; 193 + replyLabel?: string; 194 + marginBottom?: number; 195 + }> = ({ replyHref, replyLabel, marginBottom = 0 }) => 196 + replyHref && replyLabel ? ( 197 + <div 198 + style={{ 199 + ...baseStyles.replyLine, 200 + color: `var(--atproto-color-text-secondary)`, 201 + marginBottom, 202 + }} 203 + > 204 + Replying to{" "} 205 + <a 206 + href={replyHref} 207 + target="_blank" 208 + rel="noopener noreferrer" 209 + style={{ 210 + ...baseStyles.replyLink, 211 + color: `var(--atproto-color-link)`, 212 + }} 213 + > 214 + {replyLabel} 215 + </a> 216 + </div> 217 + ) : null; 218 + 219 + const PostContent: React.FC<{ 220 + text: string; 221 + record: FeedPostRecord; 222 + created: string; 223 + postUrl?: string; 224 + iconPlacement: "cardBottomRight" | "timestamp" | "linkInline"; 225 + showIcon: boolean; 226 + makeIcon: () => React.ReactNode; 227 + resolvedEmbed: React.ReactNode; 228 + }> = ({ 229 + text, 230 + record, 231 + created, 232 + postUrl, 233 + iconPlacement, 234 + showIcon, 235 + makeIcon, 236 + resolvedEmbed, 237 + }) => ( 238 + <div style={baseStyles.body}> 239 + <p style={{ ...baseStyles.text, color: `var(--atproto-color-text)` }}> 240 + <RichText text={text} facets={record.facets} /> 241 + </p> 242 + {resolvedEmbed && ( 243 + <div style={baseStyles.embedContainer}>{resolvedEmbed}</div> 244 + )} 245 + <div style={baseStyles.timestampRow}> 246 + <time 247 + style={{ 248 + ...baseStyles.time, 249 + color: `var(--atproto-color-text-muted)`, 250 + }} 251 + dateTime={record.createdAt} 252 + > 253 + {created} 254 + </time> 255 + {postUrl && ( 256 + <span style={baseStyles.linkWithIcon}> 257 + <a 258 + href={postUrl} 259 + target="_blank" 260 + rel="noopener noreferrer" 261 + style={{ 262 + ...baseStyles.postLink, 263 + color: `var(--atproto-color-link)`, 264 + }} 265 + > 266 + View on Bluesky 267 + </a> 268 + {iconPlacement === "linkInline" && showIcon && ( 269 + <span style={baseStyles.inlineIcon} aria-hidden> 270 + {makeIcon()} 271 + </span> 272 + )} 273 + </span> 274 + )} 275 + </div> 276 + </div> 277 + ); 278 + 279 + const ThreadLayout: React.FC<LayoutProps> = (props) => ( 280 + <div style={{ display: "flex", gap: 8, alignItems: "flex-start" }}> 281 + <Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} /> 282 + <div style={{ flex: 1, minWidth: 0 }}> 283 + <div 284 + style={{ 285 + display: "flex", 286 + alignItems: "center", 287 + gap: 8, 288 + marginBottom: 4, 289 + }} 290 + > 291 + <AuthorInfo 292 + primaryName={props.primaryName} 293 + authorDisplayName={props.authorDisplayName} 294 + authorHandle={props.authorHandle} 295 + inline 296 + /> 297 + {props.iconPlacement === "timestamp" && props.showIcon && ( 298 + <div style={{ marginLeft: "auto" }}>{props.makeIcon()}</div> 299 + )} 300 + </div> 301 + <ReplyInfo 302 + replyHref={props.replyHref} 303 + replyLabel={props.replyLabel} 304 + marginBottom={4} 305 + /> 306 + <PostContent {...props} /> 307 + {props.iconPlacement === "cardBottomRight" && props.showIcon && ( 308 + <div 309 + style={{ 310 + position: "relative", 311 + right: 0, 312 + bottom: 0, 313 + justifyContent: "flex-start", 314 + marginTop: 8, 315 + display: "flex", 316 + }} 317 + aria-hidden 318 + > 319 + {props.makeIcon()} 320 + </div> 321 + )} 322 + </div> 323 + </div> 324 + ); 325 + 326 + const DefaultLayout: React.FC<LayoutProps> = (props) => ( 327 + <> 328 + <header style={baseStyles.header}> 329 + <Avatar avatarUrl={props.avatarUrl} name={props.authorDisplayName || props.authorHandle} /> 330 + <AuthorInfo 331 + primaryName={props.primaryName} 332 + authorDisplayName={props.authorDisplayName} 333 + authorHandle={props.authorHandle} 334 + /> 335 + {props.iconPlacement === "timestamp" && props.showIcon && ( 336 + <div style={baseStyles.headerIcon}>{props.makeIcon()}</div> 337 + )} 338 + </header> 339 + <ReplyInfo replyHref={props.replyHref} replyLabel={props.replyLabel} /> 340 + <PostContent {...props} /> 341 + {props.iconPlacement === "cardBottomRight" && props.showIcon && ( 342 + <div style={baseStyles.iconCorner} aria-hidden> 343 + {props.makeIcon()} 344 + </div> 345 + )} 346 + </> 347 + ); 348 + 120 349 const baseStyles: Record<string, React.CSSProperties> = { 121 - card: { 122 - borderRadius: 12, 123 - padding: 12, 124 - fontFamily: 'system-ui, sans-serif', 125 - display: 'flex', 126 - flexDirection: 'column', 127 - gap: 8, 128 - maxWidth: 600, 129 - transition: 'background-color 180ms ease, border-color 180ms ease, color 180ms ease', 130 - position: 'relative' 131 - }, 132 - header: { 133 - display: 'flex', 134 - alignItems: 'center', 135 - gap: 8 136 - }, 137 - headerIcon: { 138 - marginLeft: 'auto', 139 - display: 'flex', 140 - alignItems: 'center' 141 - }, 142 - avatarPlaceholder: { 143 - width: 40, 144 - height: 40, 145 - borderRadius: '50%' 146 - }, 147 - avatarImg: { 148 - width: 40, 149 - height: 40, 150 - borderRadius: '50%', 151 - objectFit: 'cover' 152 - }, 153 - handle: { 154 - fontSize: 12 155 - }, 156 - time: { 157 - fontSize: 11 158 - }, 159 - timestampIcon: { 160 - display: 'flex', 161 - alignItems: 'center', 162 - justifyContent: 'center' 163 - }, 164 - body: { 165 - fontSize: 14, 166 - lineHeight: 1.4 167 - }, 168 - text: { 169 - margin: 0, 170 - whiteSpace: 'pre-wrap', 171 - overflowWrap: 'anywhere' 172 - }, 173 - facets: { 174 - marginTop: 8, 175 - display: 'flex', 176 - gap: 4 177 - }, 178 - embedContainer: { 179 - marginTop: 12, 180 - padding: 8, 181 - borderRadius: 12, 182 - display: 'flex', 183 - flexDirection: 'column', 184 - gap: 8 185 - }, 186 - timestampRow: { 187 - display: 'flex', 188 - justifyContent: 'flex-end', 189 - alignItems: 'center', 190 - gap: 12, 191 - marginTop: 12, 192 - flexWrap: 'wrap' 193 - }, 194 - linkWithIcon: { 195 - display: 'inline-flex', 196 - alignItems: 'center', 197 - gap: 6 198 - }, 199 - postLink: { 200 - fontSize: 11, 201 - textDecoration: 'none', 202 - fontWeight: 600 203 - }, 204 - inlineIcon: { 205 - display: 'inline-flex', 206 - alignItems: 'center' 207 - }, 208 - facetTag: { 209 - padding: '2px 6px', 210 - borderRadius: 4, 211 - fontSize: 11 212 - }, 213 - replyLine: { 214 - fontSize: 12 215 - }, 216 - replyLink: { 217 - textDecoration: 'none', 218 - fontWeight: 500 219 - }, 220 - iconCorner: { 221 - position: 'absolute', 222 - right: 12, 223 - bottom: 12, 224 - display: 'flex', 225 - alignItems: 'center', 226 - justifyContent: 'flex-end' 227 - } 350 + card: { 351 + borderRadius: 12, 352 + padding: 12, 353 + fontFamily: "system-ui, sans-serif", 354 + display: "flex", 355 + flexDirection: "column", 356 + gap: 8, 357 + maxWidth: 600, 358 + transition: 359 + "background-color 180ms ease, border-color 180ms ease, color 180ms ease", 360 + position: "relative", 361 + }, 362 + header: { 363 + display: "flex", 364 + alignItems: "center", 365 + gap: 8, 366 + }, 367 + headerIcon: { 368 + marginLeft: "auto", 369 + display: "flex", 370 + alignItems: "center", 371 + }, 372 + avatarPlaceholder: { 373 + width: 40, 374 + height: 40, 375 + borderRadius: "50%", 376 + }, 377 + avatarImg: { 378 + width: 40, 379 + height: 40, 380 + borderRadius: "50%", 381 + objectFit: "cover", 382 + }, 383 + handle: { 384 + fontSize: 12, 385 + }, 386 + time: { 387 + fontSize: 11, 388 + }, 389 + body: { 390 + fontSize: 14, 391 + lineHeight: 1.4, 392 + }, 393 + text: { 394 + margin: 0, 395 + whiteSpace: "pre-wrap", 396 + overflowWrap: "anywhere", 397 + }, 398 + embedContainer: { 399 + marginTop: 12, 400 + padding: 8, 401 + borderRadius: 12, 402 + border: `1px solid var(--atproto-color-border)`, 403 + background: `var(--atproto-color-bg-elevated)`, 404 + display: "flex", 405 + flexDirection: "column", 406 + gap: 8, 407 + }, 408 + timestampRow: { 409 + display: "flex", 410 + justifyContent: "flex-end", 411 + alignItems: "center", 412 + gap: 12, 413 + marginTop: 12, 414 + flexWrap: "wrap", 415 + }, 416 + linkWithIcon: { 417 + display: "inline-flex", 418 + alignItems: "center", 419 + gap: 6, 420 + }, 421 + postLink: { 422 + fontSize: 11, 423 + textDecoration: "none", 424 + fontWeight: 600, 425 + }, 426 + inlineIcon: { 427 + display: "inline-flex", 428 + alignItems: "center", 429 + }, 430 + replyLine: { 431 + fontSize: 12, 432 + }, 433 + replyLink: { 434 + textDecoration: "none", 435 + fontWeight: 500, 436 + }, 437 + iconCorner: { 438 + position: "absolute", 439 + right: 12, 440 + bottom: 12, 441 + display: "flex", 442 + alignItems: "center", 443 + justifyContent: "flex-end", 444 + }, 228 445 }; 229 446 230 - const themeStyles = { 231 - light: { 232 - card: { 233 - border: '1px solid #e2e8f0', 234 - background: '#ffffff', 235 - color: '#0f172a' 236 - }, 237 - avatarPlaceholder: { 238 - background: '#cbd5e1' 239 - }, 240 - handle: { 241 - color: '#64748b' 242 - }, 243 - time: { 244 - color: '#94a3b8' 245 - }, 246 - text: { 247 - color: '#0f172a' 248 - }, 249 - facetTag: { 250 - background: '#f1f5f9', 251 - color: '#475569' 252 - }, 253 - replyLine: { 254 - color: '#475569' 255 - }, 256 - replyLink: { 257 - color: '#2563eb' 258 - }, 259 - embedContainer: { 260 - border: '1px solid #e2e8f0', 261 - borderRadius: 12, 262 - background: '#f8fafc' 263 - }, 264 - postLink: { 265 - color: '#2563eb' 266 - } 267 - }, 268 - dark: { 269 - card: { 270 - border: '1px solid #1e293b', 271 - background: '#0f172a', 272 - color: '#e2e8f0' 273 - }, 274 - avatarPlaceholder: { 275 - background: '#1e293b' 276 - }, 277 - handle: { 278 - color: '#cbd5f5' 279 - }, 280 - time: { 281 - color: '#94a3ff' 282 - }, 283 - text: { 284 - color: '#e2e8f0' 285 - }, 286 - facetTag: { 287 - background: '#1e293b', 288 - color: '#e0f2fe' 289 - }, 290 - replyLine: { 291 - color: '#cbd5f5' 292 - }, 293 - replyLink: { 294 - color: '#38bdf8' 295 - }, 296 - embedContainer: { 297 - border: '1px solid #1e293b', 298 - borderRadius: 12, 299 - background: '#0b1120' 300 - }, 301 - postLink: { 302 - color: '#38bdf8' 303 - } 304 - } 305 - } satisfies Record<'light' | 'dark', Record<string, React.CSSProperties>>; 306 - 307 - function formatReplyLabel(target: ParsedAtUri, resolvedHandle?: string, loading?: boolean): string { 308 - if (resolvedHandle) return `@${resolvedHandle}`; 309 - if (loading) return 'โ€ฆ'; 310 - return `@${formatDidForLabel(target.did)}`; 447 + function formatReplyLabel( 448 + target: ParsedAtUri, 449 + resolvedHandle?: string, 450 + loading?: boolean, 451 + ): string { 452 + if (resolvedHandle) return `@${resolvedHandle}`; 453 + if (loading) return "โ€ฆ"; 454 + return `@${formatDidForLabel(target.did)}`; 311 455 } 312 456 313 - function createAutoEmbed(record: FeedPostRecord, authorDid: string | undefined, scheme: 'light' | 'dark'): React.ReactNode { 314 - const embed = record.embed as { $type?: string } | undefined; 315 - if (!embed) return null; 316 - if (embed.$type === 'app.bsky.embed.images') { 317 - return <ImagesEmbed embed={embed as ImagesEmbedType} did={authorDid} scheme={scheme} />; 318 - } 319 - if (embed.$type === 'app.bsky.embed.recordWithMedia') { 320 - const media = (embed as RecordWithMediaEmbed).media; 321 - if (media?.$type === 'app.bsky.embed.images') { 322 - return <ImagesEmbed embed={media as ImagesEmbedType} did={authorDid} scheme={scheme} />; 323 - } 324 - } 325 - return null; 457 + function createAutoEmbed( 458 + record: FeedPostRecord, 459 + authorDid: string | undefined, 460 + ): React.ReactNode { 461 + const embed = record.embed as { $type?: string } | undefined; 462 + if (!embed) return null; 463 + if (embed.$type === "app.bsky.embed.images") { 464 + return <ImagesEmbed embed={embed as ImagesEmbedType} did={authorDid} />; 465 + } 466 + if (embed.$type === "app.bsky.embed.recordWithMedia") { 467 + const media = (embed as RecordWithMediaEmbed).media; 468 + if (media?.$type === "app.bsky.embed.images") { 469 + return ( 470 + <ImagesEmbed embed={media as ImagesEmbedType} did={authorDid} /> 471 + ); 472 + } 473 + } 474 + return null; 326 475 } 327 476 328 477 type ImagesEmbedType = { 329 - $type: 'app.bsky.embed.images'; 330 - images: Array<{ 331 - alt?: string; 332 - mime?: string; 333 - size?: number; 334 - image?: { 335 - $type?: string; 336 - ref?: { $link?: string }; 337 - cid?: string; 338 - }; 339 - aspectRatio?: { 340 - width: number; 341 - height: number; 342 - }; 343 - }>; 478 + $type: "app.bsky.embed.images"; 479 + images: Array<{ 480 + alt?: string; 481 + mime?: string; 482 + size?: number; 483 + image?: { 484 + $type?: string; 485 + ref?: { $link?: string }; 486 + cid?: string; 487 + }; 488 + aspectRatio?: { 489 + width: number; 490 + height: number; 491 + }; 492 + }>; 344 493 }; 345 494 346 495 type RecordWithMediaEmbed = { 347 - $type: 'app.bsky.embed.recordWithMedia'; 348 - record?: unknown; 349 - media?: { $type?: string }; 496 + $type: "app.bsky.embed.recordWithMedia"; 497 + record?: unknown; 498 + media?: { $type?: string }; 350 499 }; 351 500 352 501 interface ImagesEmbedProps { 353 - embed: ImagesEmbedType; 354 - did?: string; 355 - scheme: 'light' | 'dark'; 502 + embed: ImagesEmbedType; 503 + did?: string; 356 504 } 357 505 358 - const ImagesEmbed: React.FC<ImagesEmbedProps> = ({ embed, did, scheme }) => { 359 - if (!embed.images || embed.images.length === 0) return null; 360 - const palette = scheme === 'dark' ? imagesPalette.dark : imagesPalette.light; 361 - const columns = embed.images.length > 1 ? 'repeat(auto-fit, minmax(160px, 1fr))' : '1fr'; 362 - return ( 363 - <div style={{ ...imagesBase.container, ...palette.container, gridTemplateColumns: columns }}> 364 - {embed.images.map((image, idx) => ( 365 - <PostImage key={idx} image={image} did={did} scheme={scheme} /> 366 - ))} 367 - </div> 368 - ); 506 + const ImagesEmbed: React.FC<ImagesEmbedProps> = ({ embed, did }) => { 507 + if (!embed.images || embed.images.length === 0) return null; 508 + 509 + const columns = 510 + embed.images.length > 1 511 + ? "repeat(auto-fit, minmax(160px, 1fr))" 512 + : "1fr"; 513 + return ( 514 + <div 515 + style={{ 516 + ...imagesBase.container, 517 + background: `var(--atproto-color-bg-elevated)`, 518 + gridTemplateColumns: columns, 519 + }} 520 + > 521 + {embed.images.map((img, idx) => ( 522 + <PostImage key={idx} image={img} did={did} /> 523 + ))} 524 + </div> 525 + ); 369 526 }; 370 527 371 528 interface PostImageProps { 372 - image: ImagesEmbedType['images'][number]; 373 - did?: string; 374 - scheme: 'light' | 'dark'; 529 + image: ImagesEmbedType["images"][number]; 530 + did?: string; 375 531 } 376 532 377 - const PostImage: React.FC<PostImageProps> = ({ image, did, scheme }) => { 378 - const cid = image.image?.ref?.$link ?? image.image?.cid; 379 - const { url, loading, error } = useBlob(did, cid); 380 - const alt = image.alt?.trim() || 'Bluesky attachment'; 381 - const palette = scheme === 'dark' ? imagesPalette.dark : imagesPalette.light; 382 - const aspect = image.aspectRatio && image.aspectRatio.height > 0 383 - ? `${image.aspectRatio.width} / ${image.aspectRatio.height}` 384 - : undefined; 533 + const PostImage: React.FC<PostImageProps> = ({ image, did }) => { 534 + const [showAltText, setShowAltText] = React.useState(false); 535 + const imageBlob = image.image; 536 + const cdnUrl = isBlobWithCdn(imageBlob) ? imageBlob.cdnUrl : undefined; 537 + const cid = cdnUrl ? undefined : extractCidFromBlob(imageBlob); 538 + const { url: urlFromBlob, loading, error } = useBlob(did, cid); 539 + const url = cdnUrl || urlFromBlob; 540 + const alt = image.alt?.trim() || "Bluesky attachment"; 541 + const hasAlt = image.alt && image.alt.trim().length > 0; 385 542 386 - return ( 387 - <figure style={{ ...imagesBase.item, ...palette.item }}> 388 - <div style={{ ...imagesBase.media, ...palette.media, aspectRatio: aspect }}> 389 - {url ? ( 390 - <img src={url} alt={alt} style={imagesBase.img} /> 391 - ) : ( 392 - <div style={{ ...imagesBase.placeholder, ...palette.placeholder }}> 393 - {loading ? 'Loading imageโ€ฆ' : error ? 'Image failed to load' : 'Image unavailable'} 394 - </div> 395 - )} 396 - </div> 397 - {image.alt && image.alt.trim().length > 0 && ( 398 - <figcaption style={{ ...imagesBase.caption, ...palette.caption }}>{image.alt}</figcaption> 399 - )} 400 - </figure> 401 - ); 543 + const aspect = 544 + image.aspectRatio && image.aspectRatio.height > 0 545 + ? `${image.aspectRatio.width} / ${image.aspectRatio.height}` 546 + : undefined; 547 + 548 + return ( 549 + <figure 550 + style={{ 551 + ...imagesBase.item, 552 + background: `var(--atproto-color-bg-elevated)`, 553 + }} 554 + > 555 + <div 556 + style={{ 557 + ...imagesBase.media, 558 + background: `var(--atproto-color-image-bg)`, 559 + aspectRatio: aspect, 560 + }} 561 + > 562 + {url ? ( 563 + <img src={url} alt={alt} style={imagesBase.img} /> 564 + ) : ( 565 + <div 566 + role={error ? "alert" : "status"} 567 + style={{ 568 + ...imagesBase.placeholder, 569 + color: `var(--atproto-color-text-muted)`, 570 + }} 571 + > 572 + {loading 573 + ? "Loading imageโ€ฆ" 574 + : error 575 + ? "Image failed to load" 576 + : "Image unavailable"} 577 + </div> 578 + )} 579 + {hasAlt && ( 580 + <button 581 + onClick={() => setShowAltText(!showAltText)} 582 + style={{ 583 + ...imagesBase.altBadge, 584 + background: showAltText 585 + ? `var(--atproto-color-text)` 586 + : `var(--atproto-color-bg-secondary)`, 587 + color: showAltText 588 + ? `var(--atproto-color-bg)` 589 + : `var(--atproto-color-text)`, 590 + }} 591 + title="Toggle alt text" 592 + aria-label="Toggle alt text" 593 + > 594 + ALT 595 + </button> 596 + )} 597 + </div> 598 + {hasAlt && showAltText && ( 599 + <figcaption 600 + style={{ 601 + ...imagesBase.caption, 602 + color: `var(--atproto-color-text-secondary)`, 603 + }} 604 + > 605 + {image.alt} 606 + </figcaption> 607 + )} 608 + </figure> 609 + ); 402 610 }; 403 611 404 612 const imagesBase = { 405 - container: { 406 - display: 'grid', 407 - gap: 8, 408 - width: '100%' 409 - } satisfies React.CSSProperties, 410 - item: { 411 - margin: 0, 412 - display: 'flex', 413 - flexDirection: 'column', 414 - gap: 4 415 - } satisfies React.CSSProperties, 416 - media: { 417 - position: 'relative', 418 - width: '100%', 419 - borderRadius: 12, 420 - overflow: 'hidden' 421 - } satisfies React.CSSProperties, 422 - img: { 423 - width: '100%', 424 - height: '100%', 425 - objectFit: 'cover' 426 - } satisfies React.CSSProperties, 427 - placeholder: { 428 - display: 'flex', 429 - alignItems: 'center', 430 - justifyContent: 'center', 431 - width: '100%', 432 - height: '100%' 433 - } satisfies React.CSSProperties, 434 - caption: { 435 - fontSize: 12, 436 - lineHeight: 1.3 437 - } satisfies React.CSSProperties 613 + container: { 614 + display: "grid", 615 + gap: 8, 616 + width: "100%", 617 + } satisfies React.CSSProperties, 618 + item: { 619 + margin: 0, 620 + display: "flex", 621 + flexDirection: "column", 622 + gap: 4, 623 + } satisfies React.CSSProperties, 624 + media: { 625 + position: "relative", 626 + width: "100%", 627 + borderRadius: 12, 628 + overflow: "hidden", 629 + } satisfies React.CSSProperties, 630 + img: { 631 + width: "100%", 632 + height: "100%", 633 + objectFit: "cover", 634 + } satisfies React.CSSProperties, 635 + placeholder: { 636 + display: "flex", 637 + alignItems: "center", 638 + justifyContent: "center", 639 + width: "100%", 640 + height: "100%", 641 + } satisfies React.CSSProperties, 642 + caption: { 643 + fontSize: 12, 644 + lineHeight: 1.3, 645 + } satisfies React.CSSProperties, 646 + altBadge: { 647 + position: "absolute", 648 + bottom: 8, 649 + right: 8, 650 + padding: "4px 8px", 651 + fontSize: 10, 652 + fontWeight: 600, 653 + letterSpacing: "0.5px", 654 + border: "none", 655 + borderRadius: 4, 656 + cursor: "pointer", 657 + transition: "background 150ms ease, color 150ms ease", 658 + fontFamily: "system-ui, sans-serif", 659 + } satisfies React.CSSProperties, 438 660 }; 439 661 440 - const imagesPalette = { 441 - light: { 442 - container: { 443 - padding: 0 444 - } satisfies React.CSSProperties, 445 - item: {}, 446 - media: { 447 - background: '#e2e8f0' 448 - } satisfies React.CSSProperties, 449 - placeholder: { 450 - color: '#475569' 451 - } satisfies React.CSSProperties, 452 - caption: { 453 - color: '#475569' 454 - } satisfies React.CSSProperties 455 - }, 456 - dark: { 457 - container: { 458 - padding: 0 459 - } satisfies React.CSSProperties, 460 - item: {}, 461 - media: { 462 - background: '#1e293b' 463 - } satisfies React.CSSProperties, 464 - placeholder: { 465 - color: '#cbd5f5' 466 - } satisfies React.CSSProperties, 467 - caption: { 468 - color: '#94a3b8' 469 - } satisfies React.CSSProperties 470 - } 471 - } as const; 472 - 473 - export default BlueskyPostRenderer; 662 + export default BlueskyPostRenderer;
+176 -178
lib/renderers/BlueskyProfileRenderer.tsx
··· 1 - import React from 'react'; 2 - import type { ProfileRecord } from '../types/bluesky'; 3 - import { useColorScheme, type ColorSchemePreference } from '../hooks/useColorScheme'; 4 - import { BlueskyIcon } from '../components/BlueskyIcon'; 1 + import React from "react"; 2 + import type { ProfileRecord } from "../types/bluesky"; 3 + import { BlueskyIcon } from "../components/BlueskyIcon"; 4 + import { useAtProto } from "../providers/AtProtoProvider"; 5 5 6 6 export interface BlueskyProfileRendererProps { 7 - record: ProfileRecord; 8 - loading: boolean; 9 - error?: Error; 10 - did: string; 11 - handle?: string; 12 - avatarUrl?: string; 13 - colorScheme?: ColorSchemePreference; 7 + record: ProfileRecord; 8 + loading: boolean; 9 + error?: Error; 10 + did: string; 11 + handle?: string; 12 + avatarUrl?: string; 14 13 } 15 14 16 - export const BlueskyProfileRenderer: React.FC<BlueskyProfileRendererProps> = ({ record, loading, error, did, handle, avatarUrl, colorScheme = 'system' }) => { 17 - const scheme = useColorScheme(colorScheme); 15 + export const BlueskyProfileRenderer: React.FC<BlueskyProfileRendererProps> = ({ 16 + record, 17 + loading, 18 + error, 19 + did, 20 + handle, 21 + avatarUrl, 22 + }) => { 23 + const { blueskyAppBaseUrl } = useAtProto(); 18 24 19 - if (error) return <div style={{ padding: 8, color: 'crimson' }}>Failed to load profile.</div>; 20 - if (loading && !record) return <div style={{ padding: 8 }}>Loadingโ€ฆ</div>; 25 + if (error) 26 + return ( 27 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 28 + Failed to load profile. 29 + </div> 30 + ); 31 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 21 32 22 - const palette = scheme === 'dark' ? theme.dark : theme.light; 23 - const profileUrl = `https://bsky.app/profile/${encodeURIComponent(did)}`; 24 - const rawWebsite = record.website?.trim(); 25 - const websiteHref = rawWebsite ? (rawWebsite.match(/^https?:\/\//i) ? rawWebsite : `https://${rawWebsite}`) : undefined; 26 - const websiteLabel = rawWebsite ? rawWebsite.replace(/^https?:\/\//i, '') : undefined; 33 + const profileUrl = `${blueskyAppBaseUrl}/profile/${did}`; 34 + const rawWebsite = record.website?.trim(); 35 + const websiteHref = rawWebsite 36 + ? rawWebsite.match(/^https?:\/\//i) 37 + ? rawWebsite 38 + : `https://${rawWebsite}` 39 + : undefined; 40 + const websiteLabel = rawWebsite 41 + ? rawWebsite.replace(/^https?:\/\//i, "") 42 + : undefined; 27 43 28 - return ( 29 - <div style={{ ...base.card, ...palette.card }}> 30 - <div style={base.header}> 31 - {avatarUrl ? <img src={avatarUrl} alt="avatar" style={base.avatarImg} /> : <div style={{ ...base.avatar, ...palette.avatar }} aria-label="avatar" />} 32 - <div style={{ flex: 1 }}> 33 - <div style={{ ...base.display, ...palette.display }}>{record.displayName ?? handle ?? did}</div> 34 - <div style={{ ...base.handleLine, ...palette.handleLine }}>@{handle ?? did}</div> 35 - {record.pronouns && <div style={{ ...base.pronouns, ...palette.pronouns }}>{record.pronouns}</div>} 36 - </div> 37 - </div> 38 - {record.description && <p style={{ ...base.desc, ...palette.desc }}>{record.description}</p>} 39 - {record.createdAt && <div style={{ ...base.meta, ...palette.meta }}>Joined {new Date(record.createdAt).toLocaleDateString()}</div>} 40 - <div style={base.links}> 41 - {websiteHref && websiteLabel && ( 42 - <a href={websiteHref} target="_blank" rel="noopener noreferrer" style={{ ...base.link, ...palette.link }}> 43 - {websiteLabel} 44 - </a> 45 - )} 46 - <a href={profileUrl} target="_blank" rel="noopener noreferrer" style={{ ...base.link, ...palette.link }}> 47 - View on Bluesky 48 - </a> 49 - </div> 50 - <div style={base.iconCorner} aria-hidden> 51 - <BlueskyIcon size={18} /> 52 - </div> 53 - </div> 54 - ); 44 + return ( 45 + <div style={{ ...base.card, background: `var(--atproto-color-bg)`, borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 46 + <div style={base.header}> 47 + {avatarUrl ? ( 48 + <img src={avatarUrl} alt={`${record.displayName || handle || did}'s profile picture`} style={base.avatarImg} /> 49 + ) : ( 50 + <div 51 + style={{ ...base.avatar, background: `var(--atproto-color-bg-elevated)` }} 52 + aria-hidden="true" 53 + /> 54 + )} 55 + <div style={{ flex: 1 }}> 56 + <div style={{ ...base.display, color: `var(--atproto-color-text)` }}> 57 + {record.displayName ?? handle ?? did} 58 + </div> 59 + <div style={{ ...base.handleLine, color: `var(--atproto-color-text-secondary)` }}> 60 + @{handle ?? did} 61 + </div> 62 + {record.pronouns && ( 63 + <div style={{ ...base.pronouns, background: `var(--atproto-color-bg-elevated)`, color: `var(--atproto-color-text-secondary)` }}> 64 + {record.pronouns} 65 + </div> 66 + )} 67 + </div> 68 + </div> 69 + {record.description && ( 70 + <p style={{ ...base.desc, color: `var(--atproto-color-text)` }}> 71 + {record.description} 72 + </p> 73 + )} 74 + <div style={base.bottomRow}> 75 + <div style={base.bottomLeft}> 76 + {record.createdAt && ( 77 + <div style={{ ...base.meta, color: `var(--atproto-color-text-secondary)` }}> 78 + Joined {new Date(record.createdAt).toLocaleDateString()} 79 + </div> 80 + )} 81 + {websiteHref && websiteLabel && ( 82 + <a 83 + href={websiteHref} 84 + target="_blank" 85 + rel="noopener noreferrer" 86 + style={{ ...base.link, color: `var(--atproto-color-link)` }} 87 + > 88 + {websiteLabel} 89 + </a> 90 + )} 91 + <a 92 + href={profileUrl} 93 + target="_blank" 94 + rel="noopener noreferrer" 95 + style={{ ...base.link, color: `var(--atproto-color-link)` }} 96 + > 97 + View on Bluesky 98 + </a> 99 + </div> 100 + <div aria-hidden> 101 + <BlueskyIcon size={18} /> 102 + </div> 103 + </div> 104 + </div> 105 + ); 55 106 }; 56 107 57 108 const base: Record<string, React.CSSProperties> = { 58 - card: { 59 - borderRadius: 12, 60 - padding: 16, 61 - fontFamily: 'system-ui, sans-serif', 62 - maxWidth: 480, 63 - transition: 'background-color 180ms ease, border-color 180ms ease, color 180ms ease', 64 - position: 'relative' 65 - }, 66 - header: { 67 - display: 'flex', 68 - gap: 12, 69 - marginBottom: 8 70 - }, 71 - avatar: { 72 - width: 64, 73 - height: 64, 74 - borderRadius: '50%' 75 - }, 76 - avatarImg: { 77 - width: 64, 78 - height: 64, 79 - borderRadius: '50%', 80 - objectFit: 'cover' 81 - }, 82 - display: { 83 - fontSize: 20, 84 - fontWeight: 600 85 - }, 86 - handleLine: { 87 - fontSize: 13 88 - }, 89 - desc: { 90 - whiteSpace: 'pre-wrap', 91 - fontSize: 14, 92 - lineHeight: 1.4 93 - }, 94 - meta: { 95 - marginTop: 12, 96 - fontSize: 12 97 - }, 98 - pronouns: { 99 - display: 'inline-flex', 100 - alignItems: 'center', 101 - gap: 4, 102 - fontSize: 12, 103 - fontWeight: 500, 104 - borderRadius: 999, 105 - padding: '2px 8px', 106 - marginTop: 6 107 - }, 108 - links: { 109 - display: 'flex', 110 - flexDirection: 'column', 111 - gap: 8, 112 - marginTop: 12 113 - }, 114 - link: { 115 - display: 'inline-flex', 116 - alignItems: 'center', 117 - gap: 4, 118 - fontSize: 12, 119 - fontWeight: 600, 120 - textDecoration: 'none' 121 - }, 122 - iconCorner: { 123 - position: 'absolute', 124 - right: 12, 125 - bottom: 12 126 - } 109 + card: { 110 + display: "flex", 111 + flexDirection: "column", 112 + height: "100%", 113 + borderRadius: 12, 114 + padding: 16, 115 + fontFamily: "system-ui, sans-serif", 116 + maxWidth: 480, 117 + transition: 118 + "background-color 180ms ease, border-color 180ms ease, color 180ms ease", 119 + position: "relative", 120 + }, 121 + header: { 122 + display: "flex", 123 + gap: 12, 124 + marginBottom: 8, 125 + }, 126 + avatar: { 127 + width: 64, 128 + height: 64, 129 + borderRadius: "50%", 130 + }, 131 + avatarImg: { 132 + width: 64, 133 + height: 64, 134 + borderRadius: "50%", 135 + objectFit: "cover", 136 + }, 137 + display: { 138 + fontSize: 20, 139 + fontWeight: 600, 140 + }, 141 + handleLine: { 142 + fontSize: 13, 143 + }, 144 + desc: { 145 + whiteSpace: "pre-wrap", 146 + fontSize: 14, 147 + lineHeight: 1.4, 148 + }, 149 + meta: { 150 + marginTop: 0, 151 + fontSize: 12, 152 + }, 153 + pronouns: { 154 + display: "inline-flex", 155 + alignItems: "center", 156 + gap: 4, 157 + fontSize: 12, 158 + fontWeight: 500, 159 + borderRadius: 999, 160 + padding: "2px 8px", 161 + marginTop: 6, 162 + }, 163 + link: { 164 + display: "inline-flex", 165 + alignItems: "center", 166 + gap: 4, 167 + fontSize: 12, 168 + fontWeight: 600, 169 + textDecoration: "none", 170 + }, 171 + bottomRow: { 172 + display: "flex", 173 + alignItems: "flex-end", 174 + justifyContent: "space-between", 175 + marginTop: "auto", 176 + paddingTop: 12, 177 + }, 178 + bottomLeft: { 179 + display: "flex", 180 + flexDirection: "column", 181 + gap: 8, 182 + }, 183 + iconCorner: { 184 + // Removed absolute positioning, now in flex layout 185 + }, 127 186 }; 128 187 129 - const theme = { 130 - light: { 131 - card: { 132 - border: '1px solid #e2e8f0', 133 - background: '#ffffff', 134 - color: '#0f172a' 135 - }, 136 - avatar: { 137 - background: '#cbd5e1' 138 - }, 139 - display: { 140 - color: '#0f172a' 141 - }, 142 - handleLine: { 143 - color: '#64748b' 144 - }, 145 - desc: { 146 - color: '#0f172a' 147 - }, 148 - meta: { 149 - color: '#94a3b8' 150 - }, 151 - pronouns: { 152 - background: '#e2e8f0', 153 - color: '#1e293b' 154 - }, 155 - link: { 156 - color: '#2563eb' 157 - } 158 - }, 159 - dark: { 160 - card: { 161 - border: '1px solid #1e293b', 162 - background: '#0b1120', 163 - color: '#e2e8f0' 164 - }, 165 - avatar: { 166 - background: '#1e293b' 167 - }, 168 - display: { 169 - color: '#e2e8f0' 170 - }, 171 - handleLine: { 172 - color: '#cbd5f5' 173 - }, 174 - desc: { 175 - color: '#e2e8f0' 176 - }, 177 - meta: { 178 - color: '#a5b4fc' 179 - }, 180 - pronouns: { 181 - background: '#1e293b', 182 - color: '#e2e8f0' 183 - }, 184 - link: { 185 - color: '#38bdf8' 186 - } 187 - } 188 - } satisfies Record<'light' | 'dark', Record<string, React.CSSProperties>>; 189 - 190 - export default BlueskyProfileRenderer; 188 + export default BlueskyProfileRenderer;
+749
lib/renderers/CurrentlyPlayingRenderer.tsx
··· 1 + import React, { useState, useEffect, useRef } from "react"; 2 + import type { TealActorStatusRecord } from "../types/teal"; 3 + 4 + export interface CurrentlyPlayingRendererProps { 5 + record: TealActorStatusRecord; 6 + error?: Error; 7 + loading: boolean; 8 + did: string; 9 + rkey: string; 10 + colorScheme?: "light" | "dark" | "system"; 11 + /** Label to display (e.g., "CURRENTLY PLAYING", "LAST PLAYED"). Defaults to "CURRENTLY PLAYING". */ 12 + label?: string; 13 + /** Handle to display in not listening state */ 14 + handle?: string; 15 + } 16 + 17 + interface SonglinkPlatform { 18 + url: string; 19 + entityUniqueId: string; 20 + nativeAppUriMobile?: string; 21 + nativeAppUriDesktop?: string; 22 + } 23 + 24 + interface SonglinkResponse { 25 + linksByPlatform: { 26 + [platform: string]: SonglinkPlatform; 27 + }; 28 + entitiesByUniqueId: { 29 + [id: string]: { 30 + thumbnailUrl?: string; 31 + title?: string; 32 + artistName?: string; 33 + }; 34 + }; 35 + } 36 + 37 + export const CurrentlyPlayingRenderer: React.FC<CurrentlyPlayingRendererProps> = ({ 38 + record, 39 + error, 40 + loading, 41 + label = "CURRENTLY PLAYING", 42 + handle, 43 + }) => { 44 + const [albumArt, setAlbumArt] = useState<string | undefined>(undefined); 45 + const [artworkLoading, setArtworkLoading] = useState(true); 46 + const [songlinkData, setSonglinkData] = useState<SonglinkResponse | undefined>(undefined); 47 + const [showPlatformModal, setShowPlatformModal] = useState(false); 48 + const previousTrackIdentityRef = useRef<string>(""); 49 + 50 + // Auto-refresh interval removed - handled by AtProtoRecord 51 + 52 + useEffect(() => { 53 + if (!record) return; 54 + 55 + const { item } = record; 56 + const artistName = item.artists[0]?.artistName; 57 + const trackName = item.trackName; 58 + 59 + if (!artistName || !trackName) { 60 + setArtworkLoading(false); 61 + return; 62 + } 63 + 64 + // Create a unique identity for this track 65 + const trackIdentity = `${trackName}::${artistName}`; 66 + 67 + // Check if the track has actually changed 68 + const trackHasChanged = trackIdentity !== previousTrackIdentityRef.current; 69 + 70 + // Update tracked identity 71 + previousTrackIdentityRef.current = trackIdentity; 72 + 73 + // Only reset loading state and clear data when track actually changes 74 + // This prevents the loading flicker when auto-refreshing the same track 75 + if (trackHasChanged) { 76 + console.log(`[teal.fm] ๐ŸŽต Track changed: "${trackName}" by ${artistName}`); 77 + setArtworkLoading(true); 78 + setAlbumArt(undefined); 79 + setSonglinkData(undefined); 80 + } else { 81 + console.log(`[teal.fm] ๐Ÿ”„ Auto-refresh: same track still playing ("${trackName}" by ${artistName})`); 82 + } 83 + 84 + let cancelled = false; 85 + 86 + const fetchMusicData = async () => { 87 + try { 88 + // Step 1: Check if we have an ISRC - Songlink supports this directly 89 + if (item.isrc) { 90 + console.log(`[teal.fm] Attempting ISRC lookup for ${trackName} by ${artistName}`, { isrc: item.isrc }); 91 + const response = await fetch( 92 + `https://api.song.link/v1-alpha.1/links?platform=isrc&type=song&id=${encodeURIComponent(item.isrc)}&songIfSingle=true` 93 + ); 94 + if (cancelled) return; 95 + if (response.ok) { 96 + const data = await response.json(); 97 + setSonglinkData(data); 98 + 99 + // Extract album art from Songlink data 100 + const entityId = data.entityUniqueId; 101 + const entity = data.entitiesByUniqueId?.[entityId]; 102 + 103 + // Debug: Log the entity structure to see what fields are available 104 + console.log(`[teal.fm] ISRC entity data:`, { entityId, entity }); 105 + 106 + if (entity?.thumbnailUrl) { 107 + console.log(`[teal.fm] โœ“ Found album art via ISRC lookup`); 108 + setAlbumArt(entity.thumbnailUrl); 109 + } else { 110 + console.warn(`[teal.fm] ISRC lookup succeeded but no thumbnail found`, { 111 + entityId, 112 + entityKeys: entity ? Object.keys(entity) : 'no entity', 113 + entity 114 + }); 115 + } 116 + setArtworkLoading(false); 117 + return; 118 + } else { 119 + console.warn(`[teal.fm] ISRC lookup failed with status ${response.status}`); 120 + } 121 + } 122 + 123 + // Step 2: Search iTunes Search API to find the track (single request for both artwork and links) 124 + console.log(`[teal.fm] Attempting iTunes search for: "${trackName}" by "${artistName}"`); 125 + const iTunesSearchUrl = `https://itunes.apple.com/search?term=${encodeURIComponent( 126 + `${trackName} ${artistName}` 127 + )}&media=music&entity=song&limit=1`; 128 + 129 + const iTunesResponse = await fetch(iTunesSearchUrl); 130 + 131 + if (cancelled) return; 132 + 133 + if (iTunesResponse.ok) { 134 + const iTunesData = await iTunesResponse.json(); 135 + 136 + if (iTunesData.results && iTunesData.results.length > 0) { 137 + const match = iTunesData.results[0]; 138 + const iTunesId = match.trackId; 139 + 140 + // Set album artwork immediately (600x600 for high quality) 141 + const artworkUrl = match.artworkUrl100?.replace('100x100', '600x600') || match.artworkUrl100; 142 + if (artworkUrl) { 143 + console.log(`[teal.fm] โœ“ Found album art via iTunes search`, { url: artworkUrl }); 144 + setAlbumArt(artworkUrl); 145 + } else { 146 + console.warn(`[teal.fm] iTunes match found but no artwork URL`); 147 + } 148 + setArtworkLoading(false); 149 + 150 + // Step 3: Use iTunes ID with Songlink to get all platform links 151 + console.log(`[teal.fm] Fetching platform links via Songlink (iTunes ID: ${iTunesId})`); 152 + const songlinkResponse = await fetch( 153 + `https://api.song.link/v1-alpha.1/links?platform=itunes&type=song&id=${iTunesId}&songIfSingle=true` 154 + ); 155 + 156 + if (cancelled) return; 157 + 158 + if (songlinkResponse.ok) { 159 + const songlinkData = await songlinkResponse.json(); 160 + console.log(`[teal.fm] โœ“ Got platform links from Songlink`); 161 + setSonglinkData(songlinkData); 162 + return; 163 + } else { 164 + console.warn(`[teal.fm] Songlink request failed with status ${songlinkResponse.status}`); 165 + } 166 + } else { 167 + console.warn(`[teal.fm] No iTunes results found for "${trackName}" by "${artistName}"`); 168 + setArtworkLoading(false); 169 + } 170 + } else { 171 + console.warn(`[teal.fm] iTunes search failed with status ${iTunesResponse.status}`); 172 + } 173 + 174 + // Step 4: Fallback - if originUrl is from a supported platform, try it directly 175 + if (item.originUrl && ( 176 + item.originUrl.includes('spotify.com') || 177 + item.originUrl.includes('apple.com') || 178 + item.originUrl.includes('youtube.com') || 179 + item.originUrl.includes('tidal.com') 180 + )) { 181 + console.log(`[teal.fm] Attempting Songlink lookup via originUrl`, { url: item.originUrl }); 182 + const songlinkResponse = await fetch( 183 + `https://api.song.link/v1-alpha.1/links?url=${encodeURIComponent(item.originUrl)}&songIfSingle=true` 184 + ); 185 + 186 + if (cancelled) return; 187 + 188 + if (songlinkResponse.ok) { 189 + const data = await songlinkResponse.json(); 190 + console.log(`[teal.fm] โœ“ Got data from Songlink via originUrl`); 191 + setSonglinkData(data); 192 + 193 + // Try to get artwork from Songlink if we don't have it yet 194 + if (!albumArt) { 195 + const entityId = data.entityUniqueId; 196 + const entity = data.entitiesByUniqueId?.[entityId]; 197 + 198 + // Debug: Log the entity structure to see what fields are available 199 + console.log(`[teal.fm] Songlink originUrl entity data:`, { entityId, entity }); 200 + 201 + if (entity?.thumbnailUrl) { 202 + console.log(`[teal.fm] โœ“ Found album art via Songlink originUrl lookup`); 203 + setAlbumArt(entity.thumbnailUrl); 204 + } else { 205 + console.warn(`[teal.fm] Songlink lookup succeeded but no thumbnail found`, { 206 + entityId, 207 + entityKeys: entity ? Object.keys(entity) : 'no entity', 208 + entity 209 + }); 210 + } 211 + } 212 + } else { 213 + console.warn(`[teal.fm] Songlink originUrl lookup failed with status ${songlinkResponse.status}`); 214 + } 215 + } 216 + 217 + if (!albumArt) { 218 + console.warn(`[teal.fm] โœ— All album art fetch methods failed for "${trackName}" by "${artistName}"`); 219 + } 220 + 221 + setArtworkLoading(false); 222 + } catch (err) { 223 + console.error(`[teal.fm] โœ— Error fetching music data for "${trackName}" by "${artistName}":`, err); 224 + setArtworkLoading(false); 225 + } 226 + }; 227 + 228 + fetchMusicData(); 229 + 230 + return () => { 231 + cancelled = true; 232 + }; 233 + }, [record]); // Runs on record change 234 + 235 + if (error) 236 + return ( 237 + <div role="alert" style={{ padding: 8, color: "var(--atproto-color-error)" }}> 238 + Failed to load status. 239 + </div> 240 + ); 241 + if (loading && !record) 242 + return ( 243 + <div role="status" aria-live="polite" style={{ padding: 8, color: "var(--atproto-color-text-secondary)" }}> 244 + Loadingโ€ฆ 245 + </div> 246 + ); 247 + 248 + const { item } = record; 249 + 250 + // Check if user is not listening to anything 251 + const isNotListening = !item.trackName || item.artists.length === 0; 252 + 253 + // Show "not listening" state 254 + if (isNotListening) { 255 + const displayHandle = handle || "User"; 256 + return ( 257 + <div style={styles.notListeningContainer}> 258 + <div style={styles.notListeningIcon}> 259 + <svg 260 + width="80" 261 + height="80" 262 + viewBox="0 0 24 24" 263 + fill="none" 264 + stroke="currentColor" 265 + strokeWidth="1.5" 266 + strokeLinecap="round" 267 + strokeLinejoin="round" 268 + > 269 + <path d="M9 18V5l12-2v13" /> 270 + <circle cx="6" cy="18" r="3" /> 271 + <circle cx="18" cy="16" r="3" /> 272 + </svg> 273 + </div> 274 + <div style={styles.notListeningTitle}> 275 + {displayHandle} isn't listening to anything 276 + </div> 277 + <div style={styles.notListeningSubtitle}>Check back soon</div> 278 + </div> 279 + ); 280 + } 281 + 282 + const artistNames = item.artists.map((a) => a.artistName).join(", "); 283 + 284 + const platformConfig: Record<string, { name: string; svg: string; color: string }> = { 285 + spotify: { 286 + name: "Spotify", 287 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="#1ed760" d="M248 8C111.1 8 0 119.1 0 256s111.1 248 248 248 248-111.1 248-248S384.9 8 248 8Z"/><path d="M406.6 231.1c-5.2 0-8.4-1.3-12.9-3.9-71.2-42.5-198.5-52.7-280.9-29.7-3.6 1-8.1 2.6-12.9 2.6-13.2 0-23.3-10.3-23.3-23.6 0-13.6 8.4-21.3 17.4-23.9 35.2-10.3 74.6-15.2 117.5-15.2 73 0 149.5 15.2 205.4 47.8 7.8 4.5 12.9 10.7 12.9 22.6 0 13.6-11 23.3-23.2 23.3zm-31 76.2c-5.2 0-8.7-2.3-12.3-4.2-62.5-37-155.7-51.9-238.6-29.4-4.8 1.3-7.4 2.6-11.9 2.6-10.7 0-19.4-8.7-19.4-19.4s5.2-17.8 15.5-20.7c27.8-7.8 56.2-13.6 97.8-13.6 64.9 0 127.6 16.1 177 45.5 8.1 4.8 11.3 11 11.3 19.7-.1 10.8-8.5 19.5-19.4 19.5zm-26.9 65.6c-4.2 0-6.8-1.3-10.7-3.6-62.4-37.6-135-39.2-206.7-24.5-3.9 1-9 2.6-11.9 2.6-9.7 0-15.8-7.7-15.8-15.8 0-10.3 6.1-15.2 13.6-16.8 81.9-18.1 165.6-16.5 237 26.2 6.1 3.9 9.7 7.4 9.7 16.5s-7.1 15.4-15.2 15.4z"/></svg>', 288 + color: "#1DB954" 289 + }, 290 + appleMusic: { 291 + name: "Apple Music", 292 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 361 361"><defs><linearGradient id="apple-grad" x1="180" y1="358.6" x2="180" y2="7.76" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#FA233B"/><stop offset="1" style="stop-color:#FB5C74"/></linearGradient></defs><path fill="url(#apple-grad)" d="M360 112.61V247.39c0 4.3 0 8.6-.02 12.9-.02 3.62-.06 7.24-.16 10.86-.21 7.89-.68 15.84-2.08 23.64-1.42 7.92-3.75 15.29-7.41 22.49-3.6 7.07-8.3 13.53-13.91 19.14-5.61 5.61-12.08 10.31-19.15 13.91-7.19 3.66-14.56 5.98-22.47 7.41-7.8 1.4-15.76 1.87-23.65 2.08-3.62.1-7.24.14-10.86.16-4.3.03-8.6.02-12.9.02H112.61c-4.3 0-8.6 0-12.9-.02-3.62-.02-7.24-.06-10.86-.16-7.89-.21-15.85-.68-23.65-2.08-7.92-1.42-15.28-3.75-22.47-7.41-7.07-3.6-13.54-8.3-19.15-13.91-5.61-5.61-10.31-12.07-13.91-19.14-3.66-7.2-5.99-14.57-7.41-22.49-1.4-7.8-1.87-15.76-2.08-23.64-.1-3.62-.14-7.24-.16-10.86C0 255.99 0 251.69 0 247.39V112.61c0-4.3 0-8.6.02-12.9.02-3.62.06-7.24.16-10.86.21-7.89.68-15.84 2.08-23.64 1.42-7.92 3.75-15.29 7.41-22.49 3.6-7.07 8.3-13.53 13.91-19.14 5.61-5.61 12.08-10.31 19.15-13.91 7.19-3.66 14.56-5.98 22.47-7.41 7.8-1.4 15.76-1.87 23.65-2.08 3.62-.1 7.24-.14 10.86-.16C104.01 0 108.31 0 112.61 0h134.77c4.3 0 8.6 0 12.9.02 3.62.02 7.24.06 10.86.16 7.89.21 15.85.68 23.65 2.08 7.92 1.42 15.28 3.75 22.47 7.41 7.07 3.6 13.54 8.3 19.15 13.91 5.61 5.61 10.31 12.07 13.91 19.14 3.66 7.2 5.99 14.57 7.41 22.49 1.4 7.8 1.87 15.76 2.08 23.64.1 3.62.14 7.24.16 10.86.03 4.3.02 8.6.02 12.9z"/><path fill="#FFF" d="M254.5 55c-.87.08-8.6 1.45-9.53 1.64l-107 21.59-.04.01c-2.79.59-4.98 1.58-6.67 3-2.04 1.71-3.17 4.13-3.6 6.95-.09.6-.24 1.82-.24 3.62v133.92c0 3.13-.25 6.17-2.37 8.76-2.12 2.59-4.74 3.37-7.81 3.99-2.33.47-4.66.94-6.99 1.41-8.84 1.78-14.59 2.99-19.8 5.01-4.98 1.93-8.71 4.39-11.68 7.51-5.89 6.17-8.28 14.54-7.46 22.38.7 6.69 3.71 13.09 8.88 17.82 3.49 3.2 7.85 5.63 12.99 6.66 5.33 1.07 11.01.7 19.31-.98 4.42-.89 8.56-2.28 12.5-4.61 3.9-2.3 7.24-5.37 9.85-9.11 2.62-3.75 4.31-7.92 5.24-12.35.96-4.57 1.19-8.7 1.19-13.26V128.82c0-6.22 1.76-7.86 6.78-9.08l93.09-18.75c5.79-1.11 8.52.54 8.52 6.61v79.29c0 3.14-.03 6.32-2.17 8.92-2.12 2.59-4.74 3.37-7.81 3.99-2.33.47-4.66.94-6.99 1.41-8.84 1.78-14.59 2.99-19.8 5.01-4.98 1.93-8.71 4.39-11.68 7.51-5.89 6.17-8.49 14.54-7.67 22.38.7 6.69 3.92 13.09 9.09 17.82 3.49 3.2 7.85 5.56 12.99 6.6 5.33 1.07 11.01.69 19.31-.98 4.42-.89 8.56-2.22 12.5-4.55 3.9-2.3 7.24-5.37 9.85-9.11 2.62-3.75 4.31-7.92 5.24-12.35.96-4.57 1-8.7 1-13.26V64.46c0-6.16-3.25-9.96-9.04-9.46z"/></svg>', 293 + color: "#FA243C" 294 + }, 295 + youtube: { 296 + name: "YouTube", 297 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><g transform="scale(.75)"><path fill="red" d="M199.917 105.63s-84.292 0-105.448 5.497c-11.328 3.165-20.655 12.493-23.82 23.987-5.498 21.156-5.498 64.969-5.498 64.969s0 43.979 5.497 64.802c3.165 11.494 12.326 20.655 23.82 23.82 21.323 5.664 105.448 5.664 105.448 5.664s84.459 0 105.615-5.497c11.494-3.165 20.655-12.16 23.654-23.82 5.664-20.99 5.664-64.803 5.664-64.803s.166-43.98-5.664-65.135c-2.999-11.494-12.16-20.655-23.654-23.654-21.156-5.83-105.615-5.83-105.615-5.83zm-26.82 53.974 70.133 40.479-70.133 40.312v-80.79z"/><path fill="#fff" d="m173.097 159.604 70.133 40.479-70.133 40.312v-80.79z"/></g></svg>', 298 + color: "#FF0000" 299 + }, 300 + youtubeMusic: { 301 + name: "YouTube Music", 302 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 176 176"><circle fill="#FF0000" cx="88" cy="88" r="88"/><path fill="#FFF" d="M88 46c23.1 0 42 18.8 42 42s-18.8 42-42 42-42-18.8-42-42 18.8-42 42-42m0-4c-25.4 0-46 20.6-46 46s20.6 46 46 46 46-20.6 46-46-20.6-46-46-46z"/><path fill="#FFF" d="m72 111 39-24-39-22z"/></svg>', 303 + color: "#FF0000" 304 + }, 305 + tidal: { 306 + name: "Tidal", 307 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 0c141.385 0 256 114.615 256 256S397.385 512 256 512 0 397.385 0 256 114.615 0 256 0zm50.384 219.459-50.372 50.383 50.379 50.391-50.382 50.393-50.395-50.393 50.393-50.389-50.393-50.39 50.395-50.372 50.38 50.369 50.389-50.375 50.382 50.382-50.382 50.392-50.394-50.391zm-100.767-.001-50.392 50.392-50.385-50.392 50.385-50.382 50.392 50.382z"/></svg>', 308 + color: "#000000" 309 + }, 310 + bandcamp: { 311 + name: "Bandcamp", 312 + svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#1DA0C3" d="M0 156v200h172l84-200z"/></svg>', 313 + color: "#1DA0C3" 314 + }, 315 + }; 316 + 317 + const availablePlatforms = songlinkData 318 + ? Object.keys(platformConfig).filter((platform) => 319 + songlinkData.linksByPlatform[platform] 320 + ) 321 + : []; 322 + 323 + return ( 324 + <> 325 + <div style={styles.container}> 326 + {/* Album Artwork */} 327 + <div style={styles.artworkContainer}> 328 + {artworkLoading ? ( 329 + <div style={styles.artworkPlaceholder}> 330 + <div style={styles.loadingSpinner} /> 331 + </div> 332 + ) : albumArt ? ( 333 + <img 334 + src={albumArt} 335 + alt={`${item.releaseName || "Album"} cover`} 336 + style={styles.artwork} 337 + onError={(e) => { 338 + console.error("Failed to load album art:", { 339 + url: albumArt, 340 + track: item.trackName, 341 + artist: item.artists[0]?.artistName, 342 + error: "Image load error" 343 + }); 344 + e.currentTarget.style.display = "none"; 345 + }} 346 + /> 347 + ) : ( 348 + <div style={styles.artworkPlaceholder}> 349 + <svg 350 + width="64" 351 + height="64" 352 + viewBox="0 0 24 24" 353 + fill="none" 354 + stroke="currentColor" 355 + strokeWidth="1.5" 356 + > 357 + <circle cx="12" cy="12" r="10" /> 358 + <circle cx="12" cy="12" r="3" /> 359 + <path d="M12 2v3M12 19v3M2 12h3M19 12h3" /> 360 + </svg> 361 + </div> 362 + )} 363 + </div> 364 + 365 + {/* Content */} 366 + <div style={styles.content}> 367 + <div style={styles.label}>{label}</div> 368 + <h2 style={styles.trackName}>{item.trackName}</h2> 369 + <div style={styles.artistName}>{artistNames}</div> 370 + {item.releaseName && ( 371 + <div style={styles.releaseName}>from {item.releaseName}</div> 372 + )} 373 + 374 + {/* Listen Button */} 375 + {availablePlatforms.length > 0 ? ( 376 + <button 377 + onClick={() => setShowPlatformModal(true)} 378 + style={styles.listenButton} 379 + data-teal-listen-button="true" 380 + > 381 + <span>Listen with your Streaming Client</span> 382 + <svg 383 + width="16" 384 + height="16" 385 + viewBox="0 0 24 24" 386 + fill="none" 387 + stroke="currentColor" 388 + strokeWidth="2" 389 + strokeLinecap="round" 390 + strokeLinejoin="round" 391 + > 392 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 393 + <polyline points="15 3 21 3 21 9" /> 394 + <line x1="10" y1="14" x2="21" y2="3" /> 395 + </svg> 396 + </button> 397 + ) : item.originUrl ? ( 398 + <a 399 + href={item.originUrl} 400 + target="_blank" 401 + rel="noopener noreferrer" 402 + style={styles.listenButton} 403 + data-teal-listen-button="true" 404 + > 405 + <span>Listen on Last.fm</span> 406 + <svg 407 + width="16" 408 + height="16" 409 + viewBox="0 0 24 24" 410 + fill="none" 411 + stroke="currentColor" 412 + strokeWidth="2" 413 + strokeLinecap="round" 414 + strokeLinejoin="round" 415 + > 416 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 417 + <polyline points="15 3 21 3 21 9" /> 418 + <line x1="10" y1="14" x2="21" y2="3" /> 419 + </svg> 420 + </a> 421 + ) : null} 422 + </div> 423 + </div> 424 + 425 + {/* Platform Selection Modal */} 426 + {showPlatformModal && songlinkData && ( 427 + <div style={styles.modalOverlay} onClick={() => setShowPlatformModal(false)}> 428 + <div 429 + role="dialog" 430 + aria-modal="true" 431 + aria-labelledby="platform-modal-title" 432 + style={styles.modalContent} 433 + onClick={(e) => e.stopPropagation()} 434 + > 435 + <div style={styles.modalHeader}> 436 + <h3 id="platform-modal-title" style={styles.modalTitle}>Choose your streaming service</h3> 437 + <button 438 + style={styles.closeButton} 439 + onClick={() => setShowPlatformModal(false)} 440 + data-teal-close="true" 441 + > 442 + ร— 443 + </button> 444 + </div> 445 + <div style={styles.platformList}> 446 + {availablePlatforms.map((platform) => { 447 + const config = platformConfig[platform]; 448 + const link = songlinkData.linksByPlatform[platform]; 449 + return ( 450 + <a 451 + key={platform} 452 + href={link.url} 453 + target="_blank" 454 + rel="noopener noreferrer" 455 + style={{ 456 + ...styles.platformItem, 457 + borderLeft: `4px solid ${config.color}`, 458 + }} 459 + onClick={() => setShowPlatformModal(false)} 460 + data-teal-platform="true" 461 + > 462 + <span 463 + style={styles.platformIcon} 464 + dangerouslySetInnerHTML={{ __html: config.svg }} 465 + /> 466 + <span style={styles.platformName}>{config.name}</span> 467 + <svg 468 + width="20" 469 + height="20" 470 + viewBox="0 0 24 24" 471 + fill="none" 472 + stroke="currentColor" 473 + strokeWidth="2" 474 + style={styles.platformArrow} 475 + > 476 + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> 477 + <polyline points="15 3 21 3 21 9" /> 478 + <line x1="10" y1="14" x2="21" y2="3" /> 479 + </svg> 480 + </a> 481 + ); 482 + })} 483 + </div> 484 + </div> 485 + </div> 486 + )} 487 + </> 488 + ); 489 + }; 490 + 491 + const styles: Record<string, React.CSSProperties> = { 492 + container: { 493 + fontFamily: "system-ui, -apple-system, sans-serif", 494 + display: "flex", 495 + flexDirection: "column", 496 + background: "var(--atproto-color-bg)", 497 + borderRadius: 16, 498 + overflow: "hidden", 499 + maxWidth: 420, 500 + color: "var(--atproto-color-text)", 501 + boxShadow: "0 8px 24px rgba(0, 0, 0, 0.4)", 502 + border: "1px solid var(--atproto-color-border)", 503 + }, 504 + artworkContainer: { 505 + width: "100%", 506 + aspectRatio: "1 / 1", 507 + position: "relative", 508 + overflow: "hidden", 509 + }, 510 + artwork: { 511 + width: "100%", 512 + height: "100%", 513 + objectFit: "cover", 514 + display: "block", 515 + }, 516 + artworkPlaceholder: { 517 + width: "100%", 518 + height: "100%", 519 + display: "flex", 520 + alignItems: "center", 521 + justifyContent: "center", 522 + background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", 523 + color: "rgba(255, 255, 255, 0.5)", 524 + }, 525 + loadingSpinner: { 526 + width: 40, 527 + height: 40, 528 + border: "3px solid var(--atproto-color-border)", 529 + borderTop: "3px solid var(--atproto-color-primary)", 530 + borderRadius: "50%", 531 + animation: "spin 1s linear infinite", 532 + }, 533 + content: { 534 + padding: "24px", 535 + display: "flex", 536 + flexDirection: "column", 537 + gap: "8px", 538 + }, 539 + label: { 540 + fontSize: 11, 541 + fontWeight: 600, 542 + letterSpacing: "0.1em", 543 + textTransform: "uppercase", 544 + color: "var(--atproto-color-text-secondary)", 545 + marginBottom: "4px", 546 + }, 547 + trackName: { 548 + fontSize: 28, 549 + fontWeight: 700, 550 + margin: 0, 551 + lineHeight: 1.2, 552 + color: "var(--atproto-color-text)", 553 + }, 554 + artistName: { 555 + fontSize: 16, 556 + color: "var(--atproto-color-text-secondary)", 557 + marginTop: "4px", 558 + }, 559 + releaseName: { 560 + fontSize: 14, 561 + color: "var(--atproto-color-text-secondary)", 562 + marginTop: "2px", 563 + }, 564 + listenButton: { 565 + display: "inline-flex", 566 + alignItems: "center", 567 + gap: "8px", 568 + marginTop: "16px", 569 + padding: "12px 20px", 570 + background: "var(--atproto-color-bg-elevated)", 571 + border: "1px solid var(--atproto-color-border)", 572 + borderRadius: 24, 573 + color: "var(--atproto-color-text)", 574 + fontSize: 14, 575 + fontWeight: 600, 576 + textDecoration: "none", 577 + cursor: "pointer", 578 + transition: "all 0.2s ease", 579 + alignSelf: "flex-start", 580 + }, 581 + modalOverlay: { 582 + position: "fixed", 583 + top: 0, 584 + left: 0, 585 + right: 0, 586 + bottom: 0, 587 + backgroundColor: "rgba(0, 0, 0, 0.85)", 588 + display: "flex", 589 + alignItems: "center", 590 + justifyContent: "center", 591 + zIndex: 9999, 592 + backdropFilter: "blur(4px)", 593 + }, 594 + modalContent: { 595 + background: "var(--atproto-color-bg)", 596 + borderRadius: 16, 597 + padding: 0, 598 + maxWidth: 450, 599 + width: "90%", 600 + maxHeight: "80vh", 601 + overflow: "auto", 602 + boxShadow: "0 20px 60px rgba(0, 0, 0, 0.8)", 603 + border: "1px solid var(--atproto-color-border)", 604 + }, 605 + modalHeader: { 606 + display: "flex", 607 + justifyContent: "space-between", 608 + alignItems: "center", 609 + padding: "24px 24px 16px 24px", 610 + borderBottom: "1px solid var(--atproto-color-border)", 611 + }, 612 + modalTitle: { 613 + margin: 0, 614 + fontSize: 20, 615 + fontWeight: 700, 616 + color: "var(--atproto-color-text)", 617 + }, 618 + closeButton: { 619 + background: "transparent", 620 + border: "none", 621 + color: "var(--atproto-color-text-secondary)", 622 + fontSize: 32, 623 + cursor: "pointer", 624 + padding: 0, 625 + width: 32, 626 + height: 32, 627 + display: "flex", 628 + alignItems: "center", 629 + justifyContent: "center", 630 + borderRadius: "50%", 631 + transition: "all 0.2s ease", 632 + lineHeight: 1, 633 + }, 634 + platformList: { 635 + padding: "16px", 636 + display: "flex", 637 + flexDirection: "column", 638 + gap: "8px", 639 + }, 640 + platformItem: { 641 + display: "flex", 642 + alignItems: "center", 643 + gap: "16px", 644 + padding: "16px", 645 + background: "var(--atproto-color-bg-hover)", 646 + borderRadius: 12, 647 + textDecoration: "none", 648 + color: "var(--atproto-color-text)", 649 + transition: "all 0.2s ease", 650 + cursor: "pointer", 651 + border: "1px solid var(--atproto-color-border)", 652 + }, 653 + platformIcon: { 654 + fontSize: 24, 655 + width: 32, 656 + height: 32, 657 + display: "flex", 658 + alignItems: "center", 659 + justifyContent: "center", 660 + }, 661 + platformName: { 662 + flex: 1, 663 + fontSize: 16, 664 + fontWeight: 600, 665 + }, 666 + platformArrow: { 667 + opacity: 0.5, 668 + transition: "opacity 0.2s ease", 669 + }, 670 + notListeningContainer: { 671 + fontFamily: "system-ui, -apple-system, sans-serif", 672 + display: "flex", 673 + flexDirection: "column", 674 + alignItems: "center", 675 + justifyContent: "center", 676 + background: "var(--atproto-color-bg)", 677 + borderRadius: 16, 678 + padding: "80px 40px", 679 + maxWidth: 420, 680 + color: "var(--atproto-color-text-secondary)", 681 + border: "1px solid var(--atproto-color-border)", 682 + textAlign: "center", 683 + }, 684 + notListeningIcon: { 685 + width: 120, 686 + height: 120, 687 + borderRadius: "50%", 688 + background: "var(--atproto-color-bg-elevated)", 689 + display: "flex", 690 + alignItems: "center", 691 + justifyContent: "center", 692 + marginBottom: 24, 693 + color: "var(--atproto-color-text-muted)", 694 + }, 695 + notListeningTitle: { 696 + fontSize: 18, 697 + fontWeight: 600, 698 + color: "var(--atproto-color-text)", 699 + marginBottom: 8, 700 + }, 701 + notListeningSubtitle: { 702 + fontSize: 14, 703 + color: "var(--atproto-color-text-secondary)", 704 + }, 705 + }; 706 + 707 + // Add keyframes and hover styles 708 + if (typeof document !== "undefined") { 709 + const styleId = "teal-status-styles"; 710 + if (!document.getElementById(styleId)) { 711 + const styleElement = document.createElement("style"); 712 + styleElement.id = styleId; 713 + styleElement.textContent = ` 714 + @keyframes spin { 715 + 0% { transform: rotate(0deg); } 716 + 100% { transform: rotate(360deg); } 717 + } 718 + 719 + button[data-teal-listen-button]:hover:not(:disabled), 720 + a[data-teal-listen-button]:hover { 721 + background: var(--atproto-color-bg-pressed) !important; 722 + border-color: var(--atproto-color-border-hover) !important; 723 + transform: translateY(-2px); 724 + } 725 + 726 + button[data-teal-listen-button]:disabled { 727 + opacity: 0.5; 728 + cursor: not-allowed; 729 + } 730 + 731 + button[data-teal-close]:hover { 732 + background: var(--atproto-color-bg-hover) !important; 733 + color: var(--atproto-color-text) !important; 734 + } 735 + 736 + a[data-teal-platform]:hover { 737 + background: var(--atproto-color-bg-pressed) !important; 738 + transform: translateX(4px); 739 + } 740 + 741 + a[data-teal-platform]:hover svg { 742 + opacity: 1 !important; 743 + } 744 + `; 745 + document.head.appendChild(styleElement); 746 + } 747 + } 748 + 749 + export default CurrentlyPlayingRenderer;
+971
lib/renderers/GrainGalleryRenderer.tsx
··· 1 + import React from "react"; 2 + import type { GrainGalleryRecord, GrainPhotoRecord } from "../types/grain"; 3 + import { useBlob } from "../hooks/useBlob"; 4 + import { isBlobWithCdn, extractCidFromBlob } from "../utils/blob"; 5 + 6 + export interface GrainGalleryPhoto { 7 + record: GrainPhotoRecord; 8 + did: string; 9 + rkey: string; 10 + position?: number; 11 + } 12 + 13 + export interface GrainGalleryRendererProps { 14 + gallery: GrainGalleryRecord; 15 + photos: GrainGalleryPhoto[]; 16 + loading: boolean; 17 + error?: Error; 18 + authorHandle?: string; 19 + authorDisplayName?: string; 20 + avatarUrl?: string; 21 + } 22 + 23 + export const GrainGalleryRenderer: React.FC<GrainGalleryRendererProps> = ({ 24 + gallery, 25 + photos, 26 + loading, 27 + error, 28 + authorDisplayName, 29 + authorHandle, 30 + avatarUrl, 31 + }) => { 32 + const [currentPage, setCurrentPage] = React.useState(0); 33 + const [lightboxOpen, setLightboxOpen] = React.useState(false); 34 + const [lightboxPhotoIndex, setLightboxPhotoIndex] = React.useState(0); 35 + 36 + const createdDate = new Date(gallery.createdAt); 37 + const created = createdDate.toLocaleString(undefined, { 38 + dateStyle: "medium", 39 + timeStyle: "short", 40 + }); 41 + 42 + const primaryName = authorDisplayName || authorHandle || "โ€ฆ"; 43 + 44 + // Memoize sorted photos to prevent re-sorting on every render 45 + const sortedPhotos = React.useMemo( 46 + () => [...photos].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)), 47 + [photos] 48 + ); 49 + 50 + // Open lightbox 51 + const openLightbox = React.useCallback((photoIndex: number) => { 52 + setLightboxPhotoIndex(photoIndex); 53 + setLightboxOpen(true); 54 + }, []); 55 + 56 + // Close lightbox 57 + const closeLightbox = React.useCallback(() => { 58 + setLightboxOpen(false); 59 + }, []); 60 + 61 + // Navigate lightbox 62 + const goToNextPhoto = React.useCallback(() => { 63 + setLightboxPhotoIndex((prev) => (prev + 1) % sortedPhotos.length); 64 + }, [sortedPhotos.length]); 65 + 66 + const goToPrevPhoto = React.useCallback(() => { 67 + setLightboxPhotoIndex((prev) => (prev - 1 + sortedPhotos.length) % sortedPhotos.length); 68 + }, [sortedPhotos.length]); 69 + 70 + // Keyboard navigation 71 + React.useEffect(() => { 72 + if (!lightboxOpen) return; 73 + 74 + const handleKeyDown = (e: KeyboardEvent) => { 75 + if (e.key === "Escape") closeLightbox(); 76 + if (e.key === "ArrowLeft") goToPrevPhoto(); 77 + if (e.key === "ArrowRight") goToNextPhoto(); 78 + }; 79 + 80 + window.addEventListener("keydown", handleKeyDown); 81 + return () => window.removeEventListener("keydown", handleKeyDown); 82 + }, [lightboxOpen, closeLightbox, goToPrevPhoto, goToNextPhoto]); 83 + 84 + const isSinglePhoto = sortedPhotos.length === 1; 85 + 86 + // Preload all photos to avoid loading states when paginating 87 + usePreloadAllPhotos(sortedPhotos); 88 + 89 + // Reset to first page when photos change 90 + React.useEffect(() => { 91 + setCurrentPage(0); 92 + }, [sortedPhotos.length]); 93 + 94 + // Memoize pagination calculations with intelligent photo count per page 95 + const paginationData = React.useMemo(() => { 96 + const pages = calculatePages(sortedPhotos); 97 + const totalPages = pages.length; 98 + const visiblePhotos = pages[currentPage] || []; 99 + const hasMultiplePages = totalPages > 1; 100 + const layoutPhotos = calculateLayout(visiblePhotos); 101 + 102 + return { 103 + pages, 104 + totalPages, 105 + visiblePhotos, 106 + hasMultiplePages, 107 + layoutPhotos, 108 + }; 109 + }, [sortedPhotos, currentPage]); 110 + 111 + const { totalPages, hasMultiplePages, layoutPhotos } = paginationData; 112 + 113 + // Memoize navigation handlers to prevent re-creation 114 + const goToNextPage = React.useCallback(() => { 115 + setCurrentPage((prev) => (prev + 1) % totalPages); 116 + }, [totalPages]); 117 + 118 + const goToPrevPage = React.useCallback(() => { 119 + setCurrentPage((prev) => (prev - 1 + totalPages) % totalPages); 120 + }, [totalPages]); 121 + 122 + if (error) { 123 + return ( 124 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 125 + Failed to load gallery. 126 + </div> 127 + ); 128 + } 129 + 130 + if (loading && photos.length === 0) { 131 + return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loading galleryโ€ฆ</div>; 132 + } 133 + 134 + return ( 135 + <> 136 + {/* Hidden preload elements for all photos */} 137 + <div style={{ display: "none" }} aria-hidden> 138 + {sortedPhotos.map((photo) => ( 139 + <PreloadPhoto key={`${photo.did}-${photo.rkey}-preload`} photo={photo} /> 140 + ))} 141 + </div> 142 + 143 + {/* Lightbox */} 144 + {lightboxOpen && ( 145 + <Lightbox 146 + photo={sortedPhotos[lightboxPhotoIndex]} 147 + photoIndex={lightboxPhotoIndex} 148 + totalPhotos={sortedPhotos.length} 149 + onClose={closeLightbox} 150 + onNext={goToNextPhoto} 151 + onPrev={goToPrevPhoto} 152 + /> 153 + )} 154 + 155 + <article style={styles.card}> 156 + <header style={styles.header}> 157 + {avatarUrl ? ( 158 + <img src={avatarUrl} alt={`${authorDisplayName || authorHandle || 'User'}'s profile picture`} style={styles.avatarImg} /> 159 + ) : ( 160 + <div style={styles.avatarPlaceholder} aria-hidden /> 161 + )} 162 + <div style={styles.authorInfo}> 163 + <strong style={styles.displayName}>{primaryName}</strong> 164 + {authorHandle && ( 165 + <span 166 + style={{ 167 + ...styles.handle, 168 + color: `var(--atproto-color-text-secondary)`, 169 + }} 170 + > 171 + @{authorHandle} 172 + </span> 173 + )} 174 + </div> 175 + </header> 176 + 177 + <div style={styles.galleryInfo}> 178 + <h2 179 + style={{ 180 + ...styles.title, 181 + color: `var(--atproto-color-text)`, 182 + }} 183 + > 184 + {gallery.title} 185 + </h2> 186 + {gallery.description && ( 187 + <p 188 + style={{ 189 + ...styles.description, 190 + color: `var(--atproto-color-text-secondary)`, 191 + }} 192 + > 193 + {gallery.description} 194 + </p> 195 + )} 196 + </div> 197 + 198 + {isSinglePhoto ? ( 199 + <div style={styles.singlePhotoContainer}> 200 + <GalleryPhotoItem 201 + key={`${sortedPhotos[0].did}-${sortedPhotos[0].rkey}`} 202 + photo={sortedPhotos[0]} 203 + isSingle={true} 204 + onClick={() => openLightbox(0)} 205 + /> 206 + </div> 207 + ) : ( 208 + <div style={styles.carouselContainer}> 209 + {hasMultiplePages && currentPage > 0 && ( 210 + <button 211 + onClick={goToPrevPage} 212 + onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")} 213 + onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")} 214 + style={{ 215 + ...styles.navButton, 216 + ...styles.navButtonLeft, 217 + color: "white", 218 + background: "rgba(0, 0, 0, 0.5)", 219 + boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", 220 + }} 221 + aria-label="Previous photos" 222 + > 223 + โ€น 224 + </button> 225 + )} 226 + <div style={styles.photosGrid}> 227 + {layoutPhotos.map((item) => { 228 + const photoIndex = sortedPhotos.findIndex(p => p.did === item.did && p.rkey === item.rkey); 229 + return ( 230 + <GalleryPhotoItem 231 + key={`${item.did}-${item.rkey}`} 232 + photo={item} 233 + isSingle={false} 234 + span={item.span} 235 + onClick={() => openLightbox(photoIndex)} 236 + /> 237 + ); 238 + })} 239 + </div> 240 + {hasMultiplePages && currentPage < totalPages - 1 && ( 241 + <button 242 + onClick={goToNextPage} 243 + onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")} 244 + onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")} 245 + style={{ 246 + ...styles.navButton, 247 + ...styles.navButtonRight, 248 + color: "white", 249 + background: "rgba(0, 0, 0, 0.5)", 250 + boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", 251 + }} 252 + aria-label="Next photos" 253 + > 254 + โ€บ 255 + </button> 256 + )} 257 + </div> 258 + )} 259 + 260 + <footer style={styles.footer}> 261 + <time 262 + style={{ 263 + ...styles.time, 264 + color: `var(--atproto-color-text-muted)`, 265 + }} 266 + dateTime={gallery.createdAt} 267 + > 268 + {created} 269 + </time> 270 + {hasMultiplePages && !isSinglePhoto && ( 271 + <div style={styles.paginationDots}> 272 + {Array.from({ length: totalPages }, (_, i) => ( 273 + <button 274 + key={i} 275 + onClick={() => setCurrentPage(i)} 276 + style={{ 277 + ...styles.paginationDot, 278 + background: i === currentPage 279 + ? `var(--atproto-color-text)` 280 + : `var(--atproto-color-border)`, 281 + }} 282 + aria-label={`Go to page ${i + 1}`} 283 + aria-current={i === currentPage ? "page" : undefined} 284 + /> 285 + ))} 286 + </div> 287 + )} 288 + </footer> 289 + </article> 290 + </> 291 + ); 292 + }; 293 + 294 + // Component to preload a single photo's blob 295 + const PreloadPhoto: React.FC<{ photo: GrainGalleryPhoto }> = ({ photo }) => { 296 + const photoBlob = photo.record.photo; 297 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 298 + const cid = cdnUrl ? undefined : extractCidFromBlob(photoBlob); 299 + 300 + // Trigger blob loading via the hook 301 + useBlob(photo.did, cid); 302 + 303 + // Preload CDN images via Image element 304 + React.useEffect(() => { 305 + if (cdnUrl) { 306 + const img = new Image(); 307 + img.src = cdnUrl; 308 + } 309 + }, [cdnUrl]); 310 + 311 + return null; 312 + }; 313 + 314 + // Hook to preload all photos (CDN-based) 315 + const usePreloadAllPhotos = (photos: GrainGalleryPhoto[]) => { 316 + React.useEffect(() => { 317 + // Preload CDN images 318 + photos.forEach((photo) => { 319 + const photoBlob = photo.record.photo; 320 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 321 + 322 + if (cdnUrl) { 323 + const img = new Image(); 324 + img.src = cdnUrl; 325 + } 326 + }); 327 + }, [photos]); 328 + }; 329 + 330 + // Calculate pages with intelligent photo count (1, 2, or 3) 331 + // Only includes multiple photos when they fit well together 332 + const calculatePages = (photos: GrainGalleryPhoto[]): GrainGalleryPhoto[][] => { 333 + if (photos.length === 0) return []; 334 + if (photos.length === 1) return [[photos[0]]]; 335 + 336 + const pages: GrainGalleryPhoto[][] = []; 337 + let i = 0; 338 + 339 + while (i < photos.length) { 340 + const remaining = photos.length - i; 341 + 342 + // Only one photo left - use it 343 + if (remaining === 1) { 344 + pages.push([photos[i]]); 345 + break; 346 + } 347 + 348 + // Check if next 3 photos can fit well together 349 + if (remaining >= 3) { 350 + const nextThree = photos.slice(i, i + 3); 351 + if (canFitThreePhotos(nextThree)) { 352 + pages.push(nextThree); 353 + i += 3; 354 + continue; 355 + } 356 + } 357 + 358 + // Check if next 2 photos can fit well together 359 + if (remaining >= 2) { 360 + const nextTwo = photos.slice(i, i + 2); 361 + if (canFitTwoPhotos(nextTwo)) { 362 + pages.push(nextTwo); 363 + i += 2; 364 + continue; 365 + } 366 + } 367 + 368 + // Photos don't fit well together, use 1 per page 369 + pages.push([photos[i]]); 370 + i += 1; 371 + } 372 + 373 + return pages; 374 + }; 375 + 376 + // Helper functions for aspect ratio classification 377 + const isPortrait = (ratio: number) => ratio < 0.8; 378 + const isLandscape = (ratio: number) => ratio > 1.2; 379 + const isSquarish = (ratio: number) => ratio >= 0.8 && ratio <= 1.2; 380 + 381 + // Determine if 2 photos can fit well together side by side 382 + const canFitTwoPhotos = (photos: GrainGalleryPhoto[]): boolean => { 383 + if (photos.length !== 2) return false; 384 + 385 + const ratios = photos.map((p) => { 386 + const ar = p.record.aspectRatio; 387 + return ar ? ar.width / ar.height : 1; 388 + }); 389 + 390 + const [r1, r2] = ratios; 391 + 392 + // Two portraits side by side don't work well (too narrow) 393 + if (isPortrait(r1) && isPortrait(r2)) return false; 394 + 395 + // Portrait + landscape/square creates awkward layout 396 + if (isPortrait(r1) && !isPortrait(r2)) return false; 397 + if (!isPortrait(r1) && isPortrait(r2)) return false; 398 + 399 + // Two landscape or two squarish photos work well 400 + if ((isLandscape(r1) || isSquarish(r1)) && (isLandscape(r2) || isSquarish(r2))) { 401 + return true; 402 + } 403 + 404 + // Default to not fitting 405 + return false; 406 + }; 407 + 408 + // Determine if 3 photos can fit well together in a layout 409 + const canFitThreePhotos = (photos: GrainGalleryPhoto[]): boolean => { 410 + if (photos.length !== 3) return false; 411 + 412 + const ratios = photos.map((p) => { 413 + const ar = p.record.aspectRatio; 414 + return ar ? ar.width / ar.height : 1; 415 + }); 416 + 417 + const [r1, r2, r3] = ratios; 418 + 419 + // Good pattern: one portrait, two landscape/square 420 + if (isPortrait(r1) && !isPortrait(r2) && !isPortrait(r3)) return true; 421 + if (isPortrait(r3) && !isPortrait(r1) && !isPortrait(r2)) return true; 422 + 423 + // Good pattern: all similar aspect ratios (all landscape or all squarish) 424 + const allLandscape = ratios.every(isLandscape); 425 + const allSquarish = ratios.every(isSquarish); 426 + if (allLandscape || allSquarish) return true; 427 + 428 + // Three portraits in a row can work 429 + const allPortrait = ratios.every(isPortrait); 430 + if (allPortrait) return true; 431 + 432 + // Otherwise don't fit 3 together 433 + return false; 434 + }; 435 + 436 + // Layout calculator for intelligent photo grid arrangement 437 + const calculateLayout = (photos: GrainGalleryPhoto[]) => { 438 + if (photos.length === 0) return []; 439 + if (photos.length === 1) { 440 + return [{ ...photos[0], span: { row: 2, col: 2 } }]; 441 + } 442 + 443 + const photosWithRatios = photos.map((photo) => { 444 + const ratio = photo.record.aspectRatio 445 + ? photo.record.aspectRatio.width / photo.record.aspectRatio.height 446 + : 1; 447 + return { 448 + ...photo, 449 + ratio, 450 + isPortrait: isPortrait(ratio), 451 + isLandscape: isLandscape(ratio) 452 + }; 453 + }); 454 + 455 + // For 2 photos: side by side 456 + if (photos.length === 2) { 457 + return photosWithRatios.map((p) => ({ ...p, span: { row: 2, col: 1 } })); 458 + } 459 + 460 + // For 3 photos: try to create a balanced layout 461 + if (photos.length === 3) { 462 + const [p1, p2, p3] = photosWithRatios; 463 + 464 + // Pattern 1: One tall on left, two stacked on right 465 + if (p1.isPortrait && !p2.isPortrait && !p3.isPortrait) { 466 + return [ 467 + { ...p1, span: { row: 2, col: 1 } }, 468 + { ...p2, span: { row: 1, col: 1 } }, 469 + { ...p3, span: { row: 1, col: 1 } }, 470 + ]; 471 + } 472 + 473 + // Pattern 2: Two stacked on left, one tall on right 474 + if (!p1.isPortrait && !p2.isPortrait && p3.isPortrait) { 475 + return [ 476 + { ...p1, span: { row: 1, col: 1 } }, 477 + { ...p2, span: { row: 1, col: 1 } }, 478 + { ...p3, span: { row: 2, col: 1 } }, 479 + ]; 480 + } 481 + 482 + // Pattern 3: All in a row 483 + const allPortrait = photosWithRatios.every((p) => p.isPortrait); 484 + if (allPortrait) { 485 + // All portraits: display in a row with smaller cells 486 + return photosWithRatios.map((p) => ({ ...p, span: { row: 1, col: 1 } })); 487 + } 488 + 489 + // Default: All three in a row 490 + return photosWithRatios.map((p) => ({ ...p, span: { row: 1, col: 1 } })); 491 + } 492 + 493 + return photosWithRatios.map((p) => ({ ...p, span: { row: 1, col: 1 } })); 494 + }; 495 + 496 + // Lightbox component for fullscreen image viewing 497 + const Lightbox: React.FC<{ 498 + photo: GrainGalleryPhoto; 499 + photoIndex: number; 500 + totalPhotos: number; 501 + onClose: () => void; 502 + onNext: () => void; 503 + onPrev: () => void; 504 + }> = ({ photo, photoIndex, totalPhotos, onClose, onNext, onPrev }) => { 505 + const photoBlob = photo.record.photo; 506 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 507 + const cid = cdnUrl ? undefined : extractCidFromBlob(photoBlob); 508 + const { url: urlFromBlob, loading: photoLoading, error: photoError } = useBlob(photo.did, cid); 509 + const url = cdnUrl || urlFromBlob; 510 + const alt = photo.record.alt?.trim() || "grain.social photo"; 511 + 512 + return ( 513 + <div 514 + role="dialog" 515 + aria-modal="true" 516 + aria-label={`Photo ${photoIndex + 1} of ${totalPhotos}`} 517 + style={{ 518 + position: "fixed", 519 + top: 0, 520 + left: 0, 521 + right: 0, 522 + bottom: 0, 523 + background: "rgba(0, 0, 0, 0.95)", 524 + zIndex: 9999, 525 + display: "flex", 526 + alignItems: "center", 527 + justifyContent: "center", 528 + padding: 20, 529 + }} 530 + onClick={onClose} 531 + > 532 + {/* Close button */} 533 + <button 534 + onClick={onClose} 535 + style={{ 536 + position: "absolute", 537 + top: 20, 538 + right: 20, 539 + width: 40, 540 + height: 40, 541 + border: "none", 542 + borderRadius: "50%", 543 + background: "rgba(255, 255, 255, 0.1)", 544 + color: "white", 545 + fontSize: 24, 546 + cursor: "pointer", 547 + display: "flex", 548 + alignItems: "center", 549 + justifyContent: "center", 550 + transition: "background 200ms ease", 551 + }} 552 + onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)")} 553 + onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)")} 554 + aria-label="Close lightbox" 555 + > 556 + ร— 557 + </button> 558 + 559 + {/* Previous button */} 560 + {totalPhotos > 1 && ( 561 + <button 562 + onClick={(e) => { 563 + e.stopPropagation(); 564 + onPrev(); 565 + }} 566 + style={{ 567 + position: "absolute", 568 + left: 20, 569 + top: "50%", 570 + transform: "translateY(-50%)", 571 + width: 50, 572 + height: 50, 573 + border: "none", 574 + borderRadius: "50%", 575 + background: "rgba(255, 255, 255, 0.1)", 576 + color: "white", 577 + fontSize: 24, 578 + cursor: "pointer", 579 + display: "flex", 580 + alignItems: "center", 581 + justifyContent: "center", 582 + transition: "background 200ms ease", 583 + }} 584 + onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)")} 585 + onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)")} 586 + aria-label={`Previous photo (${photoIndex} of ${totalPhotos})`} 587 + > 588 + โ€น 589 + </button> 590 + )} 591 + 592 + {/* Next button */} 593 + {totalPhotos > 1 && ( 594 + <button 595 + onClick={(e) => { 596 + e.stopPropagation(); 597 + onNext(); 598 + }} 599 + style={{ 600 + position: "absolute", 601 + right: 20, 602 + top: "50%", 603 + transform: "translateY(-50%)", 604 + width: 50, 605 + height: 50, 606 + border: "none", 607 + borderRadius: "50%", 608 + background: "rgba(255, 255, 255, 0.1)", 609 + color: "white", 610 + fontSize: 24, 611 + cursor: "pointer", 612 + display: "flex", 613 + alignItems: "center", 614 + justifyContent: "center", 615 + transition: "background 200ms ease", 616 + }} 617 + onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)")} 618 + onMouseLeave={(e) => (e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)")} 619 + aria-label={`Next photo (${photoIndex + 2} of ${totalPhotos})`} 620 + > 621 + โ€บ 622 + </button> 623 + )} 624 + 625 + {/* Image */} 626 + <div 627 + style={{ 628 + maxWidth: "90vw", 629 + maxHeight: "90vh", 630 + display: "flex", 631 + alignItems: "center", 632 + justifyContent: "center", 633 + }} 634 + onClick={(e) => e.stopPropagation()} 635 + > 636 + {url ? ( 637 + <img 638 + src={url} 639 + alt={alt} 640 + style={{ 641 + maxWidth: "100%", 642 + maxHeight: "100%", 643 + objectFit: "contain", 644 + borderRadius: 8, 645 + }} 646 + /> 647 + ) : ( 648 + <div 649 + style={{ 650 + color: "white", 651 + fontSize: 16, 652 + textAlign: "center", 653 + }} 654 + > 655 + {photoLoading ? "Loadingโ€ฆ" : photoError ? "Failed to load" : "Unavailable"} 656 + </div> 657 + )} 658 + </div> 659 + 660 + {/* Photo counter */} 661 + {totalPhotos > 1 && ( 662 + <div 663 + style={{ 664 + position: "absolute", 665 + bottom: 20, 666 + left: "50%", 667 + transform: "translateX(-50%)", 668 + color: "white", 669 + fontSize: 14, 670 + background: "rgba(0, 0, 0, 0.5)", 671 + padding: "8px 16px", 672 + borderRadius: 20, 673 + }} 674 + > 675 + {photoIndex + 1} / {totalPhotos} 676 + </div> 677 + )} 678 + </div> 679 + ); 680 + }; 681 + 682 + const GalleryPhotoItem: React.FC<{ 683 + photo: GrainGalleryPhoto; 684 + isSingle: boolean; 685 + span?: { row: number; col: number }; 686 + onClick?: () => void; 687 + }> = ({ photo, isSingle, span, onClick }) => { 688 + const [showAltText, setShowAltText] = React.useState(false); 689 + const photoBlob = photo.record.photo; 690 + const cdnUrl = isBlobWithCdn(photoBlob) ? photoBlob.cdnUrl : undefined; 691 + const cid = cdnUrl ? undefined : extractCidFromBlob(photoBlob); 692 + const { url: urlFromBlob, loading: photoLoading, error: photoError } = useBlob(photo.did, cid); 693 + const url = cdnUrl || urlFromBlob; 694 + const alt = photo.record.alt?.trim() || "grain.social photo"; 695 + const hasAlt = photo.record.alt && photo.record.alt.trim().length > 0; 696 + 697 + const aspect = 698 + photo.record.aspectRatio && photo.record.aspectRatio.height > 0 699 + ? `${photo.record.aspectRatio.width} / ${photo.record.aspectRatio.height}` 700 + : undefined; 701 + 702 + const gridItemStyle = span 703 + ? { 704 + gridRow: `span ${span.row}`, 705 + gridColumn: `span ${span.col}`, 706 + } 707 + : {}; 708 + 709 + return ( 710 + <figure style={{ ...(isSingle ? styles.singlePhotoItem : styles.photoItem), ...gridItemStyle }}> 711 + <button 712 + onClick={onClick} 713 + aria-label={hasAlt ? `View photo: ${alt}` : "View photo"} 714 + style={{ 715 + ...(isSingle ? styles.singlePhotoMedia : styles.photoContainer), 716 + background: `var(--atproto-color-image-bg)`, 717 + // Only apply aspect ratio for single photos; grid photos fill their cells 718 + ...(isSingle && aspect ? { aspectRatio: aspect } : {}), 719 + cursor: onClick ? "pointer" : "default", 720 + border: "none", 721 + padding: 0, 722 + display: "block", 723 + width: "100%", 724 + }} 725 + > 726 + {url ? ( 727 + <img src={url} alt={alt} style={isSingle ? styles.photo : styles.photoGrid} /> 728 + ) : ( 729 + <div 730 + style={{ 731 + ...styles.placeholder, 732 + color: `var(--atproto-color-text-muted)`, 733 + }} 734 + > 735 + {photoLoading 736 + ? "Loadingโ€ฆ" 737 + : photoError 738 + ? "Failed to load" 739 + : "Unavailable"} 740 + </div> 741 + )} 742 + {hasAlt && ( 743 + <button 744 + onClick={(e) => { 745 + e.stopPropagation(); 746 + setShowAltText(!showAltText); 747 + }} 748 + style={{ 749 + ...styles.altBadge, 750 + background: showAltText 751 + ? `var(--atproto-color-text)` 752 + : `var(--atproto-color-bg-secondary)`, 753 + color: showAltText 754 + ? `var(--atproto-color-bg)` 755 + : `var(--atproto-color-text)`, 756 + }} 757 + title="Toggle alt text" 758 + aria-label="Toggle alt text" 759 + aria-pressed={showAltText} 760 + > 761 + ALT 762 + </button> 763 + )} 764 + </button> 765 + {hasAlt && showAltText && ( 766 + <figcaption 767 + style={{ 768 + ...styles.caption, 769 + color: `var(--atproto-color-text-secondary)`, 770 + }} 771 + > 772 + {photo.record.alt} 773 + </figcaption> 774 + )} 775 + </figure> 776 + ); 777 + }; 778 + 779 + const styles: Record<string, React.CSSProperties> = { 780 + card: { 781 + borderRadius: 12, 782 + border: `1px solid var(--atproto-color-border)`, 783 + background: `var(--atproto-color-bg)`, 784 + color: `var(--atproto-color-text)`, 785 + fontFamily: "system-ui, sans-serif", 786 + display: "flex", 787 + flexDirection: "column", 788 + maxWidth: 600, 789 + transition: 790 + "background-color 180ms ease, border-color 180ms ease, color 180ms ease", 791 + overflow: "hidden", 792 + }, 793 + header: { 794 + display: "flex", 795 + alignItems: "center", 796 + gap: 12, 797 + padding: 12, 798 + paddingBottom: 0, 799 + }, 800 + avatarPlaceholder: { 801 + width: 32, 802 + height: 32, 803 + borderRadius: "50%", 804 + background: `var(--atproto-color-border)`, 805 + }, 806 + avatarImg: { 807 + width: 32, 808 + height: 32, 809 + borderRadius: "50%", 810 + objectFit: "cover", 811 + }, 812 + authorInfo: { 813 + display: "flex", 814 + flexDirection: "column", 815 + gap: 2, 816 + }, 817 + displayName: { 818 + fontSize: 14, 819 + fontWeight: 600, 820 + }, 821 + handle: { 822 + fontSize: 12, 823 + }, 824 + galleryInfo: { 825 + padding: 12, 826 + paddingBottom: 8, 827 + }, 828 + title: { 829 + margin: 0, 830 + fontSize: 18, 831 + fontWeight: 600, 832 + marginBottom: 4, 833 + }, 834 + description: { 835 + margin: 0, 836 + fontSize: 14, 837 + lineHeight: 1.4, 838 + whiteSpace: "pre-wrap", 839 + }, 840 + singlePhotoContainer: { 841 + padding: 0, 842 + }, 843 + carouselContainer: { 844 + position: "relative", 845 + padding: 4, 846 + }, 847 + photosGrid: { 848 + display: "grid", 849 + gridTemplateColumns: "repeat(2, 1fr)", 850 + gridTemplateRows: "repeat(2, 1fr)", 851 + gap: 4, 852 + minHeight: 400, 853 + }, 854 + navButton: { 855 + position: "absolute", 856 + top: "50%", 857 + transform: "translateY(-50%)", 858 + width: 28, 859 + height: 28, 860 + border: "none", 861 + borderRadius: "50%", 862 + fontSize: 18, 863 + fontWeight: "600", 864 + cursor: "pointer", 865 + display: "flex", 866 + alignItems: "center", 867 + justifyContent: "center", 868 + zIndex: 10, 869 + transition: "opacity 150ms ease", 870 + userSelect: "none", 871 + opacity: 0.7, 872 + }, 873 + navButtonLeft: { 874 + left: 8, 875 + }, 876 + navButtonRight: { 877 + right: 8, 878 + }, 879 + photoItem: { 880 + margin: 0, 881 + display: "flex", 882 + flexDirection: "column", 883 + gap: 4, 884 + }, 885 + singlePhotoItem: { 886 + margin: 0, 887 + display: "flex", 888 + flexDirection: "column", 889 + gap: 8, 890 + }, 891 + photoContainer: { 892 + position: "relative", 893 + width: "100%", 894 + height: "100%", 895 + overflow: "hidden", 896 + borderRadius: 4, 897 + }, 898 + singlePhotoMedia: { 899 + position: "relative", 900 + width: "100%", 901 + overflow: "hidden", 902 + borderRadius: 0, 903 + }, 904 + photo: { 905 + width: "100%", 906 + height: "100%", 907 + objectFit: "cover", 908 + display: "block", 909 + }, 910 + photoGrid: { 911 + width: "100%", 912 + height: "100%", 913 + objectFit: "cover", 914 + display: "block", 915 + }, 916 + placeholder: { 917 + display: "flex", 918 + alignItems: "center", 919 + justifyContent: "center", 920 + width: "100%", 921 + height: "100%", 922 + minHeight: 100, 923 + fontSize: 12, 924 + }, 925 + caption: { 926 + fontSize: 12, 927 + lineHeight: 1.3, 928 + padding: "0 12px 8px", 929 + }, 930 + altBadge: { 931 + position: "absolute", 932 + bottom: 8, 933 + right: 8, 934 + padding: "4px 8px", 935 + fontSize: 10, 936 + fontWeight: 600, 937 + letterSpacing: "0.5px", 938 + border: "none", 939 + borderRadius: 4, 940 + cursor: "pointer", 941 + transition: "background 150ms ease, color 150ms ease", 942 + fontFamily: "system-ui, sans-serif", 943 + }, 944 + footer: { 945 + padding: 12, 946 + paddingTop: 8, 947 + display: "flex", 948 + justifyContent: "space-between", 949 + alignItems: "center", 950 + }, 951 + time: { 952 + fontSize: 11, 953 + }, 954 + paginationDots: { 955 + display: "flex", 956 + gap: 6, 957 + alignItems: "center", 958 + }, 959 + paginationDot: { 960 + width: 6, 961 + height: 6, 962 + borderRadius: "50%", 963 + border: "none", 964 + padding: 0, 965 + cursor: "pointer", 966 + transition: "background 200ms ease, transform 150ms ease", 967 + flexShrink: 0, 968 + }, 969 + }; 970 + 971 + export default GrainGalleryRenderer;
+1076 -868
lib/renderers/LeafletDocumentRenderer.tsx
··· 1 - import React, { useMemo, useRef } from 'react'; 2 - import { useColorScheme, type ColorSchemePreference } from '../hooks/useColorScheme'; 3 - import { useDidResolution } from '../hooks/useDidResolution'; 4 - import { useBlob } from '../hooks/useBlob'; 5 - import { parseAtUri, formatDidForLabel, toBlueskyPostUrl, leafletRkeyUrl, normalizeLeafletBasePath } from '../utils/at-uri'; 6 - import { BlueskyPost } from '../components/BlueskyPost'; 1 + import React, { useMemo, useRef } from "react"; 2 + import { useDidResolution } from "../hooks/useDidResolution"; 3 + import { useBlob } from "../hooks/useBlob"; 4 + import { useAtProto } from "../providers/AtProtoProvider"; 5 + import { 6 + parseAtUri, 7 + formatDidForLabel, 8 + toBlueskyPostUrl, 9 + leafletRkeyUrl, 10 + normalizeLeafletBasePath, 11 + } from "../utils/at-uri"; 12 + import { BlueskyPost } from "../components/BlueskyPost"; 7 13 import type { 8 - LeafletDocumentRecord, 9 - LeafletLinearDocumentPage, 10 - LeafletLinearDocumentBlock, 11 - LeafletBlock, 12 - LeafletTextBlock, 13 - LeafletHeaderBlock, 14 - LeafletBlockquoteBlock, 15 - LeafletImageBlock, 16 - LeafletUnorderedListBlock, 17 - LeafletListItem, 18 - LeafletWebsiteBlock, 19 - LeafletIFrameBlock, 20 - LeafletMathBlock, 21 - LeafletCodeBlock, 22 - LeafletBskyPostBlock, 23 - LeafletAlignmentValue, 24 - LeafletRichTextFacet, 25 - LeafletRichTextFeature, 26 - LeafletPublicationRecord 27 - } from '../types/leaflet'; 14 + LeafletDocumentRecord, 15 + LeafletLinearDocumentPage, 16 + LeafletLinearDocumentBlock, 17 + LeafletBlock, 18 + LeafletTextBlock, 19 + LeafletHeaderBlock, 20 + LeafletBlockquoteBlock, 21 + LeafletImageBlock, 22 + LeafletUnorderedListBlock, 23 + LeafletListItem, 24 + LeafletWebsiteBlock, 25 + LeafletIFrameBlock, 26 + LeafletMathBlock, 27 + LeafletCodeBlock, 28 + LeafletBskyPostBlock, 29 + LeafletAlignmentValue, 30 + LeafletRichTextFacet, 31 + LeafletRichTextFeature, 32 + LeafletPublicationRecord, 33 + } from "../types/leaflet"; 28 34 29 35 export interface LeafletDocumentRendererProps { 30 - record: LeafletDocumentRecord; 31 - loading: boolean; 32 - error?: Error; 33 - colorScheme?: ColorSchemePreference; 34 - did: string; 35 - rkey: string; 36 - canonicalUrl?: string; 37 - publicationBaseUrl?: string; 38 - publicationRecord?: LeafletPublicationRecord; 36 + record: LeafletDocumentRecord; 37 + loading: boolean; 38 + error?: Error; 39 + did: string; 40 + rkey: string; 41 + canonicalUrl?: string; 42 + publicationBaseUrl?: string; 43 + publicationRecord?: LeafletPublicationRecord; 39 44 } 40 45 41 - export const LeafletDocumentRenderer: React.FC<LeafletDocumentRendererProps> = ({ record, loading, error, colorScheme = 'system', did, rkey, canonicalUrl, publicationBaseUrl, publicationRecord }) => { 42 - const scheme = useColorScheme(colorScheme); 43 - const palette = scheme === 'dark' ? theme.dark : theme.light; 44 - const authorDid = record.author?.startsWith('did:') ? record.author : undefined; 45 - const publicationUri = useMemo(() => parseAtUri(record.publication), [record.publication]); 46 - const postUrl = useMemo(() => { 47 - const postRefUri = record.postRef?.uri; 48 - if (!postRefUri) return undefined; 49 - const parsed = parseAtUri(postRefUri); 50 - return parsed ? toBlueskyPostUrl(parsed) : undefined; 51 - }, [record.postRef?.uri]); 52 - const { handle: publicationHandle } = useDidResolution(publicationUri?.did); 53 - const fallbackAuthorLabel = useAuthorLabel(record.author, authorDid); 54 - const resolvedPublicationLabel = publicationRecord?.name?.trim() 55 - ?? (publicationHandle ? `@${publicationHandle}` : publicationUri ? formatDidForLabel(publicationUri.did) : undefined); 56 - const authorLabel = resolvedPublicationLabel ?? fallbackAuthorLabel; 57 - const authorHref = publicationUri ? `https://bsky.app/profile/${publicationUri.did}` : undefined; 46 + export const LeafletDocumentRenderer: React.FC< 47 + LeafletDocumentRendererProps 48 + > = ({ 49 + record, 50 + loading, 51 + error, 52 + did, 53 + rkey, 54 + canonicalUrl, 55 + publicationBaseUrl, 56 + publicationRecord, 57 + }) => { 58 + const { blueskyAppBaseUrl } = useAtProto(); 59 + const authorDid = record.author?.startsWith("did:") 60 + ? record.author 61 + : undefined; 62 + const publicationUri = useMemo( 63 + () => parseAtUri(record.publication), 64 + [record.publication], 65 + ); 66 + const postUrl = useMemo(() => { 67 + const postRefUri = record.postRef?.uri; 68 + if (!postRefUri) return undefined; 69 + const parsed = parseAtUri(postRefUri); 70 + return parsed ? toBlueskyPostUrl(parsed) : undefined; 71 + }, [record.postRef?.uri]); 72 + const { handle: publicationHandle } = useDidResolution(publicationUri?.did); 73 + const fallbackAuthorLabel = useAuthorLabel(record.author, authorDid); 74 + const resolvedPublicationLabel = 75 + publicationRecord?.name?.trim() ?? 76 + (publicationHandle 77 + ? `@${publicationHandle}` 78 + : publicationUri 79 + ? formatDidForLabel(publicationUri.did) 80 + : undefined); 81 + const authorLabel = resolvedPublicationLabel ?? fallbackAuthorLabel; 82 + const authorHref = publicationUri 83 + ? `${blueskyAppBaseUrl}/profile/${publicationUri.did}` 84 + : undefined; 58 85 59 - if (error) return <div style={{ padding: 12, color: 'crimson' }}>Failed to load leaflet.</div>; 60 - if (loading && !record) return <div style={{ padding: 12 }}>Loading leafletโ€ฆ</div>; 61 - if (!record) return <div style={{ padding: 12, color: 'crimson' }}>Leaflet record missing.</div>; 86 + if (error) 87 + return ( 88 + <div style={{ padding: 12, color: "crimson" }}> 89 + Failed to load leaflet. 90 + </div> 91 + ); 92 + if (loading && !record) 93 + return <div style={{ padding: 12 }}>Loading leafletโ€ฆ</div>; 94 + if (!record) 95 + return ( 96 + <div style={{ padding: 12, color: "crimson" }}> 97 + Leaflet record missing. 98 + </div> 99 + ); 62 100 63 - const publishedAt = record.publishedAt ? new Date(record.publishedAt) : undefined; 64 - const publishedLabel = publishedAt ? publishedAt.toLocaleString(undefined, { dateStyle: 'long', timeStyle: 'short' }) : undefined; 65 - const fallbackLeafletUrl = `https://bsky.app/leaflet/${encodeURIComponent(did)}/${encodeURIComponent(rkey)}`; 66 - const publicationRoot = publicationBaseUrl ?? (publicationRecord?.base_path ?? undefined); 67 - const resolvedPublicationRoot = publicationRoot ? normalizeLeafletBasePath(publicationRoot) : undefined; 68 - const publicationLeafletUrl = leafletRkeyUrl(publicationRoot, rkey); 69 - const viewUrl = canonicalUrl ?? publicationLeafletUrl ?? postUrl ?? (publicationUri ? `https://bsky.app/profile/${publicationUri.did}` : undefined) ?? fallbackLeafletUrl; 101 + const publishedAt = record.publishedAt 102 + ? new Date(record.publishedAt) 103 + : undefined; 104 + const publishedLabel = publishedAt 105 + ? publishedAt.toLocaleString(undefined, { 106 + dateStyle: "long", 107 + timeStyle: "short", 108 + }) 109 + : undefined; 110 + const fallbackLeafletUrl = `${blueskyAppBaseUrl}/leaflet/${encodeURIComponent(did)}/${encodeURIComponent(rkey)}`; 111 + const publicationRoot = 112 + publicationBaseUrl ?? publicationRecord?.base_path ?? undefined; 113 + const resolvedPublicationRoot = publicationRoot 114 + ? normalizeLeafletBasePath(publicationRoot) 115 + : undefined; 116 + const publicationLeafletUrl = leafletRkeyUrl(publicationRoot, rkey); 117 + const viewUrl = 118 + canonicalUrl ?? 119 + publicationLeafletUrl ?? 120 + postUrl ?? 121 + (publicationUri 122 + ? `${blueskyAppBaseUrl}/profile/${publicationUri.did}` 123 + : undefined) ?? 124 + fallbackLeafletUrl; 70 125 71 - const metaItems: React.ReactNode[] = []; 72 - if (authorLabel) { 73 - const authorNode = authorHref 74 - ? ( 75 - <a href={authorHref} target="_blank" rel="noopener noreferrer" style={palette.metaLink}> 76 - {authorLabel} 77 - </a> 78 - ) 79 - : authorLabel; 80 - metaItems.push(<span>By {authorNode}</span>); 81 - } 82 - if (publishedLabel) metaItems.push(<time dateTime={record.publishedAt}>{publishedLabel}</time>); 83 - if (resolvedPublicationRoot) { 84 - metaItems.push( 85 - <a href={resolvedPublicationRoot} target="_blank" rel="noopener noreferrer" style={palette.metaLink}> 86 - {resolvedPublicationRoot.replace(/^https?:\/\//, '')} 87 - </a> 88 - ); 89 - } 90 - if (viewUrl) { 91 - metaItems.push( 92 - <a href={viewUrl} target="_blank" rel="noopener noreferrer" style={palette.metaLink}> 93 - View source 94 - </a> 95 - ); 96 - } 126 + const metaItems: React.ReactNode[] = []; 127 + if (authorLabel) { 128 + const authorNode = authorHref ? ( 129 + <a 130 + href={authorHref} 131 + target="_blank" 132 + rel="noopener noreferrer" 133 + style={{ color: `var(--atproto-color-link)`, textDecoration: "none" }} 134 + > 135 + {authorLabel} 136 + </a> 137 + ) : ( 138 + authorLabel 139 + ); 140 + metaItems.push(<span>By {authorNode}</span>); 141 + } 142 + if (publishedLabel) 143 + metaItems.push( 144 + <time dateTime={record.publishedAt}>{publishedLabel}</time>, 145 + ); 146 + if (resolvedPublicationRoot) { 147 + metaItems.push( 148 + <a 149 + href={resolvedPublicationRoot} 150 + target="_blank" 151 + rel="noopener noreferrer" 152 + style={{ color: `var(--atproto-color-link)`, textDecoration: "none" }} 153 + > 154 + {resolvedPublicationRoot.replace(/^https?:\/\//, "")} 155 + </a>, 156 + ); 157 + } 158 + if (viewUrl) { 159 + metaItems.push( 160 + <a 161 + href={viewUrl} 162 + target="_blank" 163 + rel="noopener noreferrer" 164 + style={{ color: `var(--atproto-color-link)`, textDecoration: "none" }} 165 + > 166 + View source 167 + </a>, 168 + ); 169 + } 97 170 98 - return ( 99 - <article style={{ ...base.container, ...palette.container }}> 100 - <header style={{ ...base.header, ...palette.header }}> 101 - <div style={base.headerContent}> 102 - <h1 style={{ ...base.title, ...palette.title }}>{record.title}</h1> 103 - {record.description && ( 104 - <p style={{ ...base.subtitle, ...palette.subtitle }}>{record.description}</p> 105 - )} 106 - </div> 107 - <div style={{ ...base.meta, ...palette.meta }}> 108 - {metaItems.map((item, idx) => ( 109 - <React.Fragment key={`meta-${idx}`}> 110 - {idx > 0 && <span style={palette.metaSeparator}>โ€ข</span>} 111 - {item} 112 - </React.Fragment> 113 - ))} 114 - </div> 115 - </header> 116 - <div style={base.body}> 117 - {record.pages?.map((page, pageIndex) => ( 118 - <LeafletPageRenderer 119 - key={`page-${pageIndex}`} 120 - page={page} 121 - documentDid={did} 122 - colorScheme={scheme} 123 - /> 124 - ))} 125 - </div> 126 - </article> 127 - ); 171 + return ( 172 + <article style={{ ...base.container, background: `var(--atproto-color-bg)`, borderWidth: "1px", borderStyle: "solid", borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 173 + <header style={{ ...base.header }}> 174 + <div style={base.headerContent}> 175 + <h1 style={{ ...base.title, color: `var(--atproto-color-text)` }}> 176 + {record.title} 177 + </h1> 178 + {record.description && ( 179 + <p style={{ ...base.subtitle, color: `var(--atproto-color-text-secondary)` }}> 180 + {record.description} 181 + </p> 182 + )} 183 + </div> 184 + <div style={{ ...base.meta, color: `var(--atproto-color-text-secondary)` }}> 185 + {metaItems.map((item, idx) => ( 186 + <React.Fragment key={`meta-${idx}`}> 187 + {idx > 0 && ( 188 + <span style={{ margin: "0 4px" }}>โ€ข</span> 189 + )} 190 + {item} 191 + </React.Fragment> 192 + ))} 193 + </div> 194 + </header> 195 + <div style={base.body}> 196 + {record.pages?.map((page, pageIndex) => ( 197 + <LeafletPageRenderer 198 + key={`page-${pageIndex}`} 199 + page={page} 200 + documentDid={did} 201 + /> 202 + ))} 203 + </div> 204 + </article> 205 + ); 128 206 }; 129 207 130 - const LeafletPageRenderer: React.FC<{ page: LeafletLinearDocumentPage; documentDid: string; colorScheme: 'light' | 'dark' }> = ({ page, documentDid, colorScheme }) => { 131 - if (!page.blocks?.length) return null; 132 - return ( 133 - <div style={base.page}> 134 - {page.blocks.map((blockWrapper, idx) => ( 135 - <LeafletBlockRenderer 136 - key={`block-${idx}`} 137 - wrapper={blockWrapper} 138 - documentDid={documentDid} 139 - colorScheme={colorScheme} 140 - isFirst={idx === 0} 141 - /> 142 - ))} 143 - </div> 144 - ); 208 + const LeafletPageRenderer: React.FC<{ 209 + page: LeafletLinearDocumentPage; 210 + documentDid: string; 211 + }> = ({ page, documentDid }) => { 212 + if (!page.blocks?.length) return null; 213 + return ( 214 + <div style={base.page}> 215 + {page.blocks.map((blockWrapper, idx) => ( 216 + <LeafletBlockRenderer 217 + key={`block-${idx}`} 218 + wrapper={blockWrapper} 219 + documentDid={documentDid} 220 + isFirst={idx === 0} 221 + /> 222 + ))} 223 + </div> 224 + ); 145 225 }; 146 226 147 227 interface LeafletBlockRendererProps { 148 - wrapper: LeafletLinearDocumentBlock; 149 - documentDid: string; 150 - colorScheme: 'light' | 'dark'; 151 - isFirst?: boolean; 228 + wrapper: LeafletLinearDocumentBlock; 229 + documentDid: string; 230 + isFirst?: boolean; 152 231 } 153 232 154 - const LeafletBlockRenderer: React.FC<LeafletBlockRendererProps> = ({ wrapper, documentDid, colorScheme, isFirst }) => { 155 - const block = wrapper.block; 156 - if (!block || !('$type' in block) || !block.$type) { 157 - return null; 158 - } 159 - const alignment = alignmentValue(wrapper.alignment); 233 + const LeafletBlockRenderer: React.FC<LeafletBlockRendererProps> = ({ 234 + wrapper, 235 + documentDid, 236 + isFirst, 237 + }) => { 238 + const block = wrapper.block; 239 + if (!block || !("$type" in block) || !block.$type) { 240 + return null; 241 + } 242 + const alignment = alignmentValue(wrapper.alignment); 160 243 161 - switch (block.$type) { 162 - case 'pub.leaflet.blocks.header': 163 - return <LeafletHeaderBlockView block={block} alignment={alignment} colorScheme={colorScheme} isFirst={isFirst} />; 164 - case 'pub.leaflet.blocks.blockquote': 165 - return <LeafletBlockquoteBlockView block={block} alignment={alignment} colorScheme={colorScheme} isFirst={isFirst} />; 166 - case 'pub.leaflet.blocks.image': 167 - return <LeafletImageBlockView block={block} alignment={alignment} documentDid={documentDid} colorScheme={colorScheme} />; 168 - case 'pub.leaflet.blocks.unorderedList': 169 - return <LeafletListBlockView block={block} alignment={alignment} documentDid={documentDid} colorScheme={colorScheme} />; 170 - case 'pub.leaflet.blocks.website': 171 - return <LeafletWebsiteBlockView block={block} alignment={alignment} documentDid={documentDid} colorScheme={colorScheme} />; 172 - case 'pub.leaflet.blocks.iframe': 173 - return <LeafletIframeBlockView block={block} alignment={alignment} />; 174 - case 'pub.leaflet.blocks.math': 175 - return <LeafletMathBlockView block={block} alignment={alignment} colorScheme={colorScheme} />; 176 - case 'pub.leaflet.blocks.code': 177 - return <LeafletCodeBlockView block={block} alignment={alignment} colorScheme={colorScheme} />; 178 - case 'pub.leaflet.blocks.horizontalRule': 179 - return <LeafletHorizontalRuleBlockView alignment={alignment} colorScheme={colorScheme} />; 180 - case 'pub.leaflet.blocks.bskyPost': 181 - return <LeafletBskyPostBlockView block={block} colorScheme={colorScheme} />; 182 - case 'pub.leaflet.blocks.text': 183 - default: 184 - return <LeafletTextBlockView block={block as LeafletTextBlock} alignment={alignment} colorScheme={colorScheme} isFirst={isFirst} />; 185 - } 244 + switch (block.$type) { 245 + case "pub.leaflet.blocks.header": 246 + return ( 247 + <LeafletHeaderBlockView 248 + block={block} 249 + alignment={alignment} 250 + isFirst={isFirst} 251 + /> 252 + ); 253 + case "pub.leaflet.blocks.blockquote": 254 + return ( 255 + <LeafletBlockquoteBlockView 256 + block={block} 257 + alignment={alignment} 258 + isFirst={isFirst} 259 + /> 260 + ); 261 + case "pub.leaflet.blocks.image": 262 + return ( 263 + <LeafletImageBlockView 264 + block={block} 265 + alignment={alignment} 266 + documentDid={documentDid} 267 + /> 268 + ); 269 + case "pub.leaflet.blocks.unorderedList": 270 + return ( 271 + <LeafletListBlockView 272 + block={block} 273 + alignment={alignment} 274 + documentDid={documentDid} 275 + /> 276 + ); 277 + case "pub.leaflet.blocks.website": 278 + return ( 279 + <LeafletWebsiteBlockView 280 + block={block} 281 + alignment={alignment} 282 + documentDid={documentDid} 283 + /> 284 + ); 285 + case "pub.leaflet.blocks.iframe": 286 + return ( 287 + <LeafletIframeBlockView block={block} alignment={alignment} /> 288 + ); 289 + case "pub.leaflet.blocks.math": 290 + return ( 291 + <LeafletMathBlockView 292 + block={block} 293 + alignment={alignment} 294 + /> 295 + ); 296 + case "pub.leaflet.blocks.code": 297 + return ( 298 + <LeafletCodeBlockView 299 + block={block} 300 + alignment={alignment} 301 + /> 302 + ); 303 + case "pub.leaflet.blocks.horizontalRule": 304 + return ( 305 + <LeafletHorizontalRuleBlockView 306 + alignment={alignment} 307 + /> 308 + ); 309 + case "pub.leaflet.blocks.bskyPost": 310 + return ( 311 + <LeafletBskyPostBlockView 312 + block={block} 313 + /> 314 + ); 315 + case "pub.leaflet.blocks.text": 316 + default: 317 + return ( 318 + <LeafletTextBlockView 319 + block={block as LeafletTextBlock} 320 + alignment={alignment} 321 + isFirst={isFirst} 322 + /> 323 + ); 324 + } 186 325 }; 187 326 188 - const LeafletTextBlockView: React.FC<{ block: LeafletTextBlock; alignment?: React.CSSProperties['textAlign']; colorScheme: 'light' | 'dark'; isFirst?: boolean }> = ({ block, alignment, colorScheme, isFirst }) => { 189 - const segments = useMemo(() => createFacetedSegments(block.plaintext, block.facets), [block.plaintext, block.facets]); 190 - const textContent = block.plaintext ?? ''; 191 - if (!textContent.trim() && segments.length === 0) { 192 - return null; 193 - } 194 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 195 - const style: React.CSSProperties = { 196 - ...base.paragraph, 197 - ...palette.paragraph, 198 - ...(alignment ? { textAlign: alignment } : undefined), 199 - ...(isFirst ? { marginTop: 0 } : undefined) 200 - }; 201 - return ( 202 - <p style={style}> 203 - {segments.map((segment, idx) => ( 204 - <React.Fragment key={`text-${idx}`}> 205 - {renderSegment(segment, colorScheme)} 206 - </React.Fragment> 207 - ))} 208 - </p> 209 - ); 327 + const LeafletTextBlockView: React.FC<{ 328 + block: LeafletTextBlock; 329 + alignment?: React.CSSProperties["textAlign"]; 330 + isFirst?: boolean; 331 + }> = ({ block, alignment, isFirst }) => { 332 + const segments = useMemo( 333 + () => createFacetedSegments(block.plaintext, block.facets), 334 + [block.plaintext, block.facets], 335 + ); 336 + const textContent = block.plaintext ?? ""; 337 + if (!textContent.trim() && segments.length === 0) { 338 + return null; 339 + } 340 + const style: React.CSSProperties = { 341 + ...base.paragraph, 342 + color: `var(--atproto-color-text)`, 343 + ...(alignment ? { textAlign: alignment } : undefined), 344 + ...(isFirst ? { marginTop: 0 } : undefined), 345 + }; 346 + return ( 347 + <p style={style}> 348 + {segments.map((segment, idx) => ( 349 + <React.Fragment key={`text-${idx}`}> 350 + {renderSegment(segment)} 351 + </React.Fragment> 352 + ))} 353 + </p> 354 + ); 210 355 }; 211 356 212 - const LeafletHeaderBlockView: React.FC<{ block: LeafletHeaderBlock; alignment?: React.CSSProperties['textAlign']; colorScheme: 'light' | 'dark'; isFirst?: boolean }> = ({ block, alignment, colorScheme, isFirst }) => { 213 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 214 - const level = block.level && block.level >= 1 && block.level <= 6 ? block.level : 2; 215 - const segments = useMemo(() => createFacetedSegments(block.plaintext, block.facets), [block.plaintext, block.facets]); 216 - const normalizedLevel = Math.min(Math.max(level, 1), 6) as 1 | 2 | 3 | 4 | 5 | 6; 217 - const headingTag = (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const)[normalizedLevel - 1]; 218 - const headingStyles = palette.heading[normalizedLevel]; 219 - const style: React.CSSProperties = { 220 - ...base.heading, 221 - ...headingStyles, 222 - ...(alignment ? { textAlign: alignment } : undefined), 223 - ...(isFirst ? { marginTop: 0 } : undefined) 224 - }; 357 + const LeafletHeaderBlockView: React.FC<{ 358 + block: LeafletHeaderBlock; 359 + alignment?: React.CSSProperties["textAlign"]; 360 + isFirst?: boolean; 361 + }> = ({ block, alignment, isFirst }) => { 362 + const level = 363 + block.level && block.level >= 1 && block.level <= 6 ? block.level : 2; 364 + const segments = useMemo( 365 + () => createFacetedSegments(block.plaintext, block.facets), 366 + [block.plaintext, block.facets], 367 + ); 368 + const normalizedLevel = Math.min(Math.max(level, 1), 6) as 369 + | 1 370 + | 2 371 + | 3 372 + | 4 373 + | 5 374 + | 6; 375 + const headingTag = (["h1", "h2", "h3", "h4", "h5", "h6"] as const)[ 376 + normalizedLevel - 1 377 + ]; 378 + const style: React.CSSProperties = { 379 + ...base.heading, 380 + color: `var(--atproto-color-text)`, 381 + fontSize: normalizedLevel === 1 ? 30 : normalizedLevel === 2 ? 28 : normalizedLevel === 3 ? 24 : normalizedLevel === 4 ? 20 : normalizedLevel === 5 ? 18 : 16, 382 + ...(alignment ? { textAlign: alignment } : undefined), 383 + ...(isFirst ? { marginTop: 0 } : undefined), 384 + }; 225 385 226 - return React.createElement( 227 - headingTag, 228 - { style }, 229 - segments.map((segment, idx) => ( 230 - <React.Fragment key={`header-${idx}`}> 231 - {renderSegment(segment, colorScheme)} 232 - </React.Fragment> 233 - )) 234 - ); 386 + return React.createElement( 387 + headingTag, 388 + { style }, 389 + segments.map((segment, idx) => ( 390 + <React.Fragment key={`header-${idx}`}> 391 + {renderSegment(segment)} 392 + </React.Fragment> 393 + )), 394 + ); 235 395 }; 236 396 237 - const LeafletBlockquoteBlockView: React.FC<{ block: LeafletBlockquoteBlock; alignment?: React.CSSProperties['textAlign']; colorScheme: 'light' | 'dark'; isFirst?: boolean }> = ({ block, alignment, colorScheme, isFirst }) => { 238 - const segments = useMemo(() => createFacetedSegments(block.plaintext, block.facets), [block.plaintext, block.facets]); 239 - const textContent = block.plaintext ?? ''; 240 - if (!textContent.trim() && segments.length === 0) { 241 - return null; 242 - } 243 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 244 - return ( 245 - <blockquote style={{ ...base.blockquote, ...palette.blockquote, ...(alignment ? { textAlign: alignment } : undefined), ...(isFirst ? { marginTop: 0 } : undefined) }}> 246 - {segments.map((segment, idx) => ( 247 - <React.Fragment key={`quote-${idx}`}> 248 - {renderSegment(segment, colorScheme)} 249 - </React.Fragment> 250 - ))} 251 - </blockquote> 252 - ); 397 + const LeafletBlockquoteBlockView: React.FC<{ 398 + block: LeafletBlockquoteBlock; 399 + alignment?: React.CSSProperties["textAlign"]; 400 + isFirst?: boolean; 401 + }> = ({ block, alignment, isFirst }) => { 402 + const segments = useMemo( 403 + () => createFacetedSegments(block.plaintext, block.facets), 404 + [block.plaintext, block.facets], 405 + ); 406 + const textContent = block.plaintext ?? ""; 407 + if (!textContent.trim() && segments.length === 0) { 408 + return null; 409 + } 410 + return ( 411 + <blockquote 412 + style={{ 413 + ...base.blockquote, 414 + background: `var(--atproto-color-bg-elevated)`, 415 + borderLeftWidth: "4px", 416 + borderLeftStyle: "solid", 417 + borderColor: `var(--atproto-color-border)`, 418 + color: `var(--atproto-color-text)`, 419 + ...(alignment ? { textAlign: alignment } : undefined), 420 + ...(isFirst ? { marginTop: 0 } : undefined), 421 + }} 422 + > 423 + {segments.map((segment, idx) => ( 424 + <React.Fragment key={`quote-${idx}`}> 425 + {renderSegment(segment)} 426 + </React.Fragment> 427 + ))} 428 + </blockquote> 429 + ); 253 430 }; 254 431 255 - const LeafletImageBlockView: React.FC<{ block: LeafletImageBlock; alignment?: React.CSSProperties['textAlign']; documentDid: string; colorScheme: 'light' | 'dark' }> = ({ block, alignment, documentDid, colorScheme }) => { 256 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 257 - const cid = block.image?.ref?.$link ?? block.image?.cid; 258 - const { url, loading, error } = useBlob(documentDid, cid); 259 - const aspectRatio = block.aspectRatio?.height && block.aspectRatio?.width 260 - ? `${block.aspectRatio.width} / ${block.aspectRatio.height}` 261 - : undefined; 432 + const LeafletImageBlockView: React.FC<{ 433 + block: LeafletImageBlock; 434 + alignment?: React.CSSProperties["textAlign"]; 435 + documentDid: string; 436 + }> = ({ block, alignment, documentDid }) => { 437 + const cid = block.image?.ref?.$link ?? block.image?.cid; 438 + const { url, loading, error } = useBlob(documentDid, cid); 439 + const aspectRatio = 440 + block.aspectRatio?.height && block.aspectRatio?.width 441 + ? `${block.aspectRatio.width} / ${block.aspectRatio.height}` 442 + : undefined; 262 443 263 - return ( 264 - <figure style={{ ...base.figure, ...palette.figure, ...(alignment ? { textAlign: alignment } : undefined) }}> 265 - <div style={{ ...base.imageWrapper, ...palette.imageWrapper, ...(aspectRatio ? { aspectRatio } : {}) }}> 266 - {url && !error ? ( 267 - <img src={url} alt={block.alt ?? ''} style={{ ...base.image, ...palette.image }} /> 268 - ) : ( 269 - <div style={{ ...base.imagePlaceholder, ...palette.imagePlaceholder }}> 270 - {loading ? 'Loading imageโ€ฆ' : error ? 'Image unavailable' : 'No image'} 271 - </div> 272 - )} 273 - </div> 274 - {block.alt && block.alt.trim().length > 0 && ( 275 - <figcaption style={{ ...base.caption, ...palette.caption }}>{block.alt}</figcaption> 276 - )} 277 - </figure> 278 - ); 444 + return ( 445 + <figure 446 + style={{ 447 + ...base.figure, 448 + ...(alignment ? { textAlign: alignment } : undefined), 449 + }} 450 + > 451 + <div 452 + style={{ 453 + ...base.imageWrapper, 454 + background: `var(--atproto-color-bg-elevated)`, 455 + ...(aspectRatio ? { aspectRatio } : {}), 456 + }} 457 + > 458 + {url && !error ? ( 459 + <img 460 + src={url} 461 + alt={block.alt ?? ""} 462 + style={{ ...base.image }} 463 + /> 464 + ) : ( 465 + <div 466 + style={{ 467 + ...base.imagePlaceholder, 468 + color: `var(--atproto-color-text-secondary)`, 469 + }} 470 + > 471 + {loading 472 + ? "Loading imageโ€ฆ" 473 + : error 474 + ? "Image unavailable" 475 + : "No image"} 476 + </div> 477 + )} 478 + </div> 479 + {block.alt && block.alt.trim().length > 0 && ( 480 + <figcaption style={{ ...base.caption, color: `var(--atproto-color-text-secondary)` }}> 481 + {block.alt} 482 + </figcaption> 483 + )} 484 + </figure> 485 + ); 279 486 }; 280 487 281 - const LeafletListBlockView: React.FC<{ block: LeafletUnorderedListBlock; alignment?: React.CSSProperties['textAlign']; documentDid: string; colorScheme: 'light' | 'dark' }> = ({ block, alignment, documentDid, colorScheme }) => { 282 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 283 - return ( 284 - <ul style={{ ...base.list, ...palette.list, ...(alignment ? { textAlign: alignment } : undefined) }}> 285 - {block.children?.map((child, idx) => ( 286 - <LeafletListItemRenderer 287 - key={`list-item-${idx}`} 288 - item={child} 289 - documentDid={documentDid} 290 - colorScheme={colorScheme} 291 - alignment={alignment} 292 - /> 293 - ))} 294 - </ul> 295 - ); 488 + const LeafletListBlockView: React.FC<{ 489 + block: LeafletUnorderedListBlock; 490 + alignment?: React.CSSProperties["textAlign"]; 491 + documentDid: string; 492 + }> = ({ block, alignment, documentDid }) => { 493 + return ( 494 + <ul 495 + style={{ 496 + ...base.list, 497 + color: `var(--atproto-color-text)`, 498 + ...(alignment ? { textAlign: alignment } : undefined), 499 + }} 500 + > 501 + {block.children?.map((child, idx) => ( 502 + <LeafletListItemRenderer 503 + key={`list-item-${idx}`} 504 + item={child} 505 + documentDid={documentDid} 506 + alignment={alignment} 507 + /> 508 + ))} 509 + </ul> 510 + ); 296 511 }; 297 512 298 - const LeafletListItemRenderer: React.FC<{ item: LeafletListItem; documentDid: string; colorScheme: 'light' | 'dark'; alignment?: React.CSSProperties['textAlign'] }> = ({ item, documentDid, colorScheme, alignment }) => { 299 - return ( 300 - <li style={{ ...base.listItem, ...(alignment ? { textAlign: alignment } : undefined) }}> 301 - <div> 302 - <LeafletInlineBlock block={item.content} colorScheme={colorScheme} documentDid={documentDid} alignment={alignment} /> 303 - </div> 304 - {item.children && item.children.length > 0 && ( 305 - <ul style={{ ...base.nestedList, ...(alignment ? { textAlign: alignment } : undefined) }}> 306 - {item.children.map((child, idx) => ( 307 - <LeafletListItemRenderer key={`nested-${idx}`} item={child} documentDid={documentDid} colorScheme={colorScheme} alignment={alignment} /> 308 - ))} 309 - </ul> 310 - )} 311 - </li> 312 - ); 513 + const LeafletListItemRenderer: React.FC<{ 514 + item: LeafletListItem; 515 + documentDid: string; 516 + alignment?: React.CSSProperties["textAlign"]; 517 + }> = ({ item, documentDid, alignment }) => { 518 + return ( 519 + <li 520 + style={{ 521 + ...base.listItem, 522 + ...(alignment ? { textAlign: alignment } : undefined), 523 + }} 524 + > 525 + <div> 526 + <LeafletInlineBlock 527 + block={item.content} 528 + documentDid={documentDid} 529 + alignment={alignment} 530 + /> 531 + </div> 532 + {item.children && item.children.length > 0 && ( 533 + <ul 534 + style={{ 535 + ...base.nestedList, 536 + ...(alignment ? { textAlign: alignment } : undefined), 537 + }} 538 + > 539 + {item.children.map((child, idx) => ( 540 + <LeafletListItemRenderer 541 + key={`nested-${idx}`} 542 + item={child} 543 + documentDid={documentDid} 544 + alignment={alignment} 545 + /> 546 + ))} 547 + </ul> 548 + )} 549 + </li> 550 + ); 313 551 }; 314 552 315 - const LeafletInlineBlock: React.FC<{ block: LeafletBlock; colorScheme: 'light' | 'dark'; documentDid: string; alignment?: React.CSSProperties['textAlign'] }> = ({ block, colorScheme, documentDid, alignment }) => { 316 - switch (block.$type) { 317 - case 'pub.leaflet.blocks.header': 318 - return <LeafletHeaderBlockView block={block as LeafletHeaderBlock} colorScheme={colorScheme} alignment={alignment} />; 319 - case 'pub.leaflet.blocks.blockquote': 320 - return <LeafletBlockquoteBlockView block={block as LeafletBlockquoteBlock} colorScheme={colorScheme} alignment={alignment} />; 321 - case 'pub.leaflet.blocks.image': 322 - return <LeafletImageBlockView block={block as LeafletImageBlock} documentDid={documentDid} colorScheme={colorScheme} alignment={alignment} />; 323 - default: 324 - return <LeafletTextBlockView block={block as LeafletTextBlock} colorScheme={colorScheme} alignment={alignment} />; 325 - } 553 + const LeafletInlineBlock: React.FC<{ 554 + block: LeafletBlock; 555 + documentDid: string; 556 + alignment?: React.CSSProperties["textAlign"]; 557 + }> = ({ block, documentDid, alignment }) => { 558 + switch (block.$type) { 559 + case "pub.leaflet.blocks.header": 560 + return ( 561 + <LeafletHeaderBlockView 562 + block={block as LeafletHeaderBlock} 563 + alignment={alignment} 564 + /> 565 + ); 566 + case "pub.leaflet.blocks.blockquote": 567 + return ( 568 + <LeafletBlockquoteBlockView 569 + block={block as LeafletBlockquoteBlock} 570 + alignment={alignment} 571 + /> 572 + ); 573 + case "pub.leaflet.blocks.image": 574 + return ( 575 + <LeafletImageBlockView 576 + block={block as LeafletImageBlock} 577 + documentDid={documentDid} 578 + alignment={alignment} 579 + /> 580 + ); 581 + default: 582 + return ( 583 + <LeafletTextBlockView 584 + block={block as LeafletTextBlock} 585 + alignment={alignment} 586 + /> 587 + ); 588 + } 326 589 }; 327 590 328 - const LeafletWebsiteBlockView: React.FC<{ block: LeafletWebsiteBlock; alignment?: React.CSSProperties['textAlign']; documentDid: string; colorScheme: 'light' | 'dark' }> = ({ block, alignment, documentDid, colorScheme }) => { 329 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 330 - const previewCid = block.previewImage?.ref?.$link ?? block.previewImage?.cid; 331 - const { url, loading, error } = useBlob(documentDid, previewCid); 591 + const LeafletWebsiteBlockView: React.FC<{ 592 + block: LeafletWebsiteBlock; 593 + alignment?: React.CSSProperties["textAlign"]; 594 + documentDid: string; 595 + }> = ({ block, alignment, documentDid }) => { 596 + const previewCid = 597 + block.previewImage?.ref?.$link ?? block.previewImage?.cid; 598 + const { url, loading, error } = useBlob(documentDid, previewCid); 332 599 333 - return ( 334 - <a href={block.src} target="_blank" rel="noopener noreferrer" style={{ ...base.linkCard, ...palette.linkCard, ...(alignment ? { textAlign: alignment } : undefined) }}> 335 - {url && !error ? ( 336 - <img src={url} alt={block.title ?? 'Website preview'} style={{ ...base.linkPreview, ...palette.linkPreview }} /> 337 - ) : ( 338 - <div style={{ ...base.linkPreviewPlaceholder, ...palette.linkPreviewPlaceholder }}> 339 - {loading ? 'Loading previewโ€ฆ' : 'Open link'} 340 - </div> 341 - )} 342 - <div style={base.linkContent}> 343 - {block.title && <strong style={palette.linkTitle}>{block.title}</strong>} 344 - {block.description && <p style={palette.linkDescription}>{block.description}</p>} 345 - <span style={palette.linkUrl}>{block.src}</span> 346 - </div> 347 - </a> 348 - ); 600 + return ( 601 + <a 602 + href={block.src} 603 + target="_blank" 604 + rel="noopener noreferrer" 605 + style={{ 606 + ...base.linkCard, 607 + borderWidth: "1px", 608 + borderStyle: "solid", 609 + borderColor: `var(--atproto-color-border)`, 610 + background: `var(--atproto-color-bg-elevated)`, 611 + color: `var(--atproto-color-text)`, 612 + ...(alignment ? { textAlign: alignment } : undefined), 613 + }} 614 + > 615 + {url && !error ? ( 616 + <img 617 + src={url} 618 + alt={block.title ?? "Website preview"} 619 + style={{ ...base.linkPreview }} 620 + /> 621 + ) : ( 622 + <div 623 + style={{ 624 + ...base.linkPreviewPlaceholder, 625 + background: `var(--atproto-color-bg-elevated)`, 626 + color: `var(--atproto-color-text-secondary)`, 627 + }} 628 + > 629 + {loading ? "Loading previewโ€ฆ" : "Open link"} 630 + </div> 631 + )} 632 + <div style={base.linkContent}> 633 + {block.title && ( 634 + <strong style={{ fontSize: 16, color: `var(--atproto-color-text)` }}>{block.title}</strong> 635 + )} 636 + {block.description && ( 637 + <p style={{ margin: 0, fontSize: 14, color: `var(--atproto-color-text-secondary)`, lineHeight: 1.5 }}>{block.description}</p> 638 + )} 639 + <span style={{ fontSize: 13, color: `var(--atproto-color-link)`, wordBreak: "break-all" }}>{block.src}</span> 640 + </div> 641 + </a> 642 + ); 349 643 }; 350 644 351 - const LeafletIframeBlockView: React.FC<{ block: LeafletIFrameBlock; alignment?: React.CSSProperties['textAlign'] }> = ({ block, alignment }) => { 352 - return ( 353 - <div style={{ ...(alignment ? { textAlign: alignment } : undefined) }}> 354 - <iframe 355 - src={block.url} 356 - title={block.url} 357 - style={{ ...base.iframe, ...(block.height ? { height: Math.min(Math.max(block.height, 120), 800) } : {}) }} 358 - loading="lazy" 359 - allowFullScreen 360 - /> 361 - </div> 362 - ); 645 + const LeafletIframeBlockView: React.FC<{ 646 + block: LeafletIFrameBlock; 647 + alignment?: React.CSSProperties["textAlign"]; 648 + }> = ({ block, alignment }) => { 649 + return ( 650 + <div style={{ ...(alignment ? { textAlign: alignment } : undefined) }}> 651 + <iframe 652 + src={block.url} 653 + title={block.url} 654 + style={{ 655 + ...base.iframe, 656 + ...(block.height 657 + ? { height: Math.min(Math.max(block.height, 120), 800) } 658 + : {}), 659 + }} 660 + loading="lazy" 661 + allowFullScreen 662 + /> 663 + </div> 664 + ); 363 665 }; 364 666 365 - const LeafletMathBlockView: React.FC<{ block: LeafletMathBlock; alignment?: React.CSSProperties['textAlign']; colorScheme: 'light' | 'dark' }> = ({ block, alignment, colorScheme }) => { 366 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 367 - return ( 368 - <pre style={{ ...base.math, ...palette.math, ...(alignment ? { textAlign: alignment } : undefined) }}>{block.tex}</pre> 369 - ); 667 + const LeafletMathBlockView: React.FC<{ 668 + block: LeafletMathBlock; 669 + alignment?: React.CSSProperties["textAlign"]; 670 + }> = ({ block, alignment }) => { 671 + return ( 672 + <pre 673 + style={{ 674 + ...base.math, 675 + background: `var(--atproto-color-bg-elevated)`, 676 + color: `var(--atproto-color-text)`, 677 + border: `1px solid var(--atproto-color-border)`, 678 + ...(alignment ? { textAlign: alignment } : undefined), 679 + }} 680 + > 681 + {block.tex} 682 + </pre> 683 + ); 370 684 }; 371 685 372 - const LeafletCodeBlockView: React.FC<{ block: LeafletCodeBlock; alignment?: React.CSSProperties['textAlign']; colorScheme: 'light' | 'dark' }> = ({ block, alignment, colorScheme }) => { 373 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 374 - const codeRef = useRef<HTMLElement | null>(null); 375 - const langClass = block.language ? `language-${block.language.toLowerCase()}` : undefined; 376 - return ( 377 - <pre style={{ ...base.code, ...palette.code, ...(alignment ? { textAlign: alignment } : undefined) }}> 378 - <code ref={codeRef} className={langClass}>{block.plaintext}</code> 379 - </pre> 380 - ); 686 + const LeafletCodeBlockView: React.FC<{ 687 + block: LeafletCodeBlock; 688 + alignment?: React.CSSProperties["textAlign"]; 689 + }> = ({ block, alignment }) => { 690 + const codeRef = useRef<HTMLElement | null>(null); 691 + const langClass = block.language 692 + ? `language-${block.language.toLowerCase()}` 693 + : undefined; 694 + return ( 695 + <pre 696 + style={{ 697 + ...base.code, 698 + background: `var(--atproto-color-bg)`, 699 + color: `var(--atproto-color-text)`, 700 + ...(alignment ? { textAlign: alignment } : undefined), 701 + }} 702 + > 703 + <code ref={codeRef} className={langClass}> 704 + {block.plaintext} 705 + </code> 706 + </pre> 707 + ); 381 708 }; 382 709 383 - const LeafletHorizontalRuleBlockView: React.FC<{ alignment?: React.CSSProperties['textAlign']; colorScheme: 'light' | 'dark' }> = ({ alignment, colorScheme }) => { 384 - const palette = colorScheme === 'dark' ? theme.dark : theme.light; 385 - return <hr style={{ ...base.hr, ...palette.hr, marginLeft: alignment ? 'auto' : undefined, marginRight: alignment ? 'auto' : undefined }} />; 710 + const LeafletHorizontalRuleBlockView: React.FC<{ 711 + alignment?: React.CSSProperties["textAlign"]; 712 + }> = ({ alignment }) => { 713 + return ( 714 + <hr 715 + style={{ 716 + ...base.hr, 717 + borderTopWidth: "1px", 718 + borderTopStyle: "solid", 719 + borderColor: `var(--atproto-color-border)`, 720 + marginLeft: alignment ? "auto" : undefined, 721 + marginRight: alignment ? "auto" : undefined, 722 + }} 723 + /> 724 + ); 386 725 }; 387 726 388 - const LeafletBskyPostBlockView: React.FC<{ block: LeafletBskyPostBlock; colorScheme: 'light' | 'dark' }> = ({ block, colorScheme }) => { 389 - const parsed = parseAtUri(block.postRef?.uri); 390 - if (!parsed) { 391 - return <div style={base.embedFallback}>Referenced post unavailable.</div>; 392 - } 393 - return <BlueskyPost did={parsed.did} rkey={parsed.rkey} colorScheme={colorScheme} iconPlacement="linkInline" />; 727 + const LeafletBskyPostBlockView: React.FC<{ 728 + block: LeafletBskyPostBlock; 729 + }> = ({ block }) => { 730 + const parsed = parseAtUri(block.postRef?.uri); 731 + if (!parsed) { 732 + return ( 733 + <div style={base.embedFallback}>Referenced post unavailable.</div> 734 + ); 735 + } 736 + return ( 737 + <BlueskyPost 738 + did={parsed.did} 739 + rkey={parsed.rkey} 740 + iconPlacement="linkInline" 741 + /> 742 + ); 394 743 }; 395 744 396 - function alignmentValue(value?: LeafletAlignmentValue): React.CSSProperties['textAlign'] | undefined { 397 - if (!value) return undefined; 398 - let normalized = value.startsWith('#') ? value.slice(1) : value; 399 - if (normalized.includes('#')) { 400 - normalized = normalized.split('#').pop() ?? normalized; 401 - } 402 - if (normalized.startsWith('lex:')) { 403 - normalized = normalized.split(':').pop() ?? normalized; 404 - } 405 - switch (normalized) { 406 - case 'textAlignLeft': 407 - return 'left'; 408 - case 'textAlignCenter': 409 - return 'center'; 410 - case 'textAlignRight': 411 - return 'right'; 412 - case 'textAlignJustify': 413 - return 'justify'; 414 - default: 415 - return undefined; 416 - } 745 + function alignmentValue( 746 + value?: LeafletAlignmentValue, 747 + ): React.CSSProperties["textAlign"] | undefined { 748 + if (!value) return undefined; 749 + let normalized = value.startsWith("#") ? value.slice(1) : value; 750 + if (normalized.includes("#")) { 751 + normalized = normalized.split("#").pop() ?? normalized; 752 + } 753 + if (normalized.startsWith("lex:")) { 754 + normalized = normalized.split(":").pop() ?? normalized; 755 + } 756 + switch (normalized) { 757 + case "textAlignLeft": 758 + return "left"; 759 + case "textAlignCenter": 760 + return "center"; 761 + case "textAlignRight": 762 + return "right"; 763 + case "textAlignJustify": 764 + return "justify"; 765 + default: 766 + return undefined; 767 + } 417 768 } 418 769 419 - function useAuthorLabel(author: string | undefined, authorDid: string | undefined): string | undefined { 420 - const { handle } = useDidResolution(authorDid); 421 - if (!author) return undefined; 422 - if (handle) return `@${handle}`; 423 - if (authorDid) return formatDidForLabel(authorDid); 424 - return author; 770 + function useAuthorLabel( 771 + author: string | undefined, 772 + authorDid: string | undefined, 773 + ): string | undefined { 774 + const { handle } = useDidResolution(authorDid); 775 + if (!author) return undefined; 776 + if (handle) return `@${handle}`; 777 + if (authorDid) return formatDidForLabel(authorDid); 778 + return author; 425 779 } 426 780 427 781 interface Segment { 428 - text: string; 429 - features: LeafletRichTextFeature[]; 782 + text: string; 783 + features: LeafletRichTextFeature[]; 430 784 } 431 785 432 - function createFacetedSegments(plaintext: string, facets?: LeafletRichTextFacet[]): Segment[] { 433 - if (!facets?.length) { 434 - return [{ text: plaintext, features: [] }]; 435 - } 436 - const prefix = buildBytePrefix(plaintext); 437 - const startEvents = new Map<number, LeafletRichTextFeature[]>(); 438 - const endEvents = new Map<number, LeafletRichTextFeature[]>(); 439 - const boundaries = new Set<number>([0, prefix.length - 1]); 440 - for (const facet of facets) { 441 - const { byteStart, byteEnd } = facet.index ?? {}; 442 - if (typeof byteStart !== 'number' || typeof byteEnd !== 'number' || byteStart >= byteEnd) continue; 443 - const start = byteOffsetToCharIndex(prefix, byteStart); 444 - const end = byteOffsetToCharIndex(prefix, byteEnd); 445 - if (start >= end) continue; 446 - boundaries.add(start); 447 - boundaries.add(end); 448 - if (facet.features?.length) { 449 - startEvents.set(start, [...(startEvents.get(start) ?? []), ...facet.features]); 450 - endEvents.set(end, [...(endEvents.get(end) ?? []), ...facet.features]); 451 - } 452 - } 453 - const sortedBounds = [...boundaries].sort((a, b) => a - b); 454 - const segments: Segment[] = []; 455 - let active: LeafletRichTextFeature[] = []; 456 - for (let i = 0; i < sortedBounds.length - 1; i++) { 457 - const boundary = sortedBounds[i]; 458 - const next = sortedBounds[i + 1]; 459 - const endFeatures = endEvents.get(boundary); 460 - if (endFeatures?.length) { 461 - active = active.filter((feature) => !endFeatures.includes(feature)); 462 - } 463 - const startFeatures = startEvents.get(boundary); 464 - if (startFeatures?.length) { 465 - active = [...active, ...startFeatures]; 466 - } 467 - if (boundary === next) continue; 468 - const text = sliceByCharRange(plaintext, boundary, next); 469 - segments.push({ text, features: active.slice() }); 470 - } 471 - return segments; 786 + function createFacetedSegments( 787 + plaintext: string, 788 + facets?: LeafletRichTextFacet[], 789 + ): Segment[] { 790 + if (!facets?.length) { 791 + return [{ text: plaintext, features: [] }]; 792 + } 793 + const prefix = buildBytePrefix(plaintext); 794 + const startEvents = new Map<number, LeafletRichTextFeature[]>(); 795 + const endEvents = new Map<number, LeafletRichTextFeature[]>(); 796 + const boundaries = new Set<number>([0, prefix.length - 1]); 797 + for (const facet of facets) { 798 + const { byteStart, byteEnd } = facet.index ?? {}; 799 + if ( 800 + typeof byteStart !== "number" || 801 + typeof byteEnd !== "number" || 802 + byteStart >= byteEnd 803 + ) 804 + continue; 805 + const start = byteOffsetToCharIndex(prefix, byteStart); 806 + const end = byteOffsetToCharIndex(prefix, byteEnd); 807 + if (start >= end) continue; 808 + boundaries.add(start); 809 + boundaries.add(end); 810 + if (facet.features?.length) { 811 + startEvents.set(start, [ 812 + ...(startEvents.get(start) ?? []), 813 + ...facet.features, 814 + ]); 815 + endEvents.set(end, [ 816 + ...(endEvents.get(end) ?? []), 817 + ...facet.features, 818 + ]); 819 + } 820 + } 821 + const sortedBounds = Array.from(boundaries).sort((a, b) => a - b); 822 + const segments: Segment[] = []; 823 + let active: LeafletRichTextFeature[] = []; 824 + for (let i = 0; i < sortedBounds.length - 1; i++) { 825 + const boundary = sortedBounds[i]; 826 + const next = sortedBounds[i + 1]; 827 + const endFeatures = endEvents.get(boundary); 828 + if (endFeatures?.length) { 829 + active = active.filter((feature) => !endFeatures.includes(feature)); 830 + } 831 + const startFeatures = startEvents.get(boundary); 832 + if (startFeatures?.length) { 833 + active = [...active, ...startFeatures]; 834 + } 835 + if (boundary === next) continue; 836 + const text = sliceByCharRange(plaintext, boundary, next); 837 + segments.push({ text, features: active.slice() }); 838 + } 839 + return segments; 472 840 } 473 841 474 842 function buildBytePrefix(text: string): number[] { 475 - const encoder = new TextEncoder(); 476 - const prefix: number[] = [0]; 477 - let byteCount = 0; 478 - for (let i = 0; i < text.length;) { 479 - const codePoint = text.codePointAt(i)!; 480 - const char = String.fromCodePoint(codePoint); 481 - const encoded = encoder.encode(char); 482 - byteCount += encoded.length; 483 - prefix.push(byteCount); 484 - i += codePoint > 0xffff ? 2 : 1; 485 - } 486 - return prefix; 843 + const encoder = new TextEncoder(); 844 + const prefix: number[] = [0]; 845 + let byteCount = 0; 846 + for (let i = 0; i < text.length; ) { 847 + const codePoint = text.codePointAt(i)!; 848 + const char = String.fromCodePoint(codePoint); 849 + const encoded = encoder.encode(char); 850 + byteCount += encoded.length; 851 + prefix.push(byteCount); 852 + i += codePoint > 0xffff ? 2 : 1; 853 + } 854 + return prefix; 487 855 } 488 856 489 857 function byteOffsetToCharIndex(prefix: number[], byteOffset: number): number { 490 - for (let i = 0; i < prefix.length; i++) { 491 - if (prefix[i] === byteOffset) return i; 492 - if (prefix[i] > byteOffset) return Math.max(0, i - 1); 493 - } 494 - return prefix.length - 1; 858 + for (let i = 0; i < prefix.length; i++) { 859 + if (prefix[i] === byteOffset) return i; 860 + if (prefix[i] > byteOffset) return Math.max(0, i - 1); 861 + } 862 + return prefix.length - 1; 495 863 } 496 864 497 865 function sliceByCharRange(text: string, start: number, end: number): string { 498 - if (start <= 0 && end >= text.length) return text; 499 - let result = ''; 500 - let charIndex = 0; 501 - for (let i = 0; i < text.length && charIndex < end;) { 502 - const codePoint = text.codePointAt(i)!; 503 - const char = String.fromCodePoint(codePoint); 504 - if (charIndex >= start && charIndex < end) result += char; 505 - i += codePoint > 0xffff ? 2 : 1; 506 - charIndex++; 507 - } 508 - return result; 866 + if (start <= 0 && end >= text.length) return text; 867 + let result = ""; 868 + let charIndex = 0; 869 + for (let i = 0; i < text.length && charIndex < end; ) { 870 + const codePoint = text.codePointAt(i)!; 871 + const char = String.fromCodePoint(codePoint); 872 + if (charIndex >= start && charIndex < end) result += char; 873 + i += codePoint > 0xffff ? 2 : 1; 874 + charIndex++; 875 + } 876 + return result; 509 877 } 510 878 511 - function renderSegment(segment: Segment, colorScheme: 'light' | 'dark'): React.ReactNode { 512 - const parts = segment.text.split('\n'); 513 - return parts.flatMap((part, idx) => { 514 - const key = `${segment.text}-${idx}-${part.length}`; 515 - const wrapped = applyFeatures(part.length ? part : '\u00a0', segment.features, key, colorScheme); 516 - if (idx === parts.length - 1) return wrapped; 517 - return [wrapped, <br key={`${key}-br`} />]; 518 - }); 879 + function renderSegment( 880 + segment: Segment, 881 + ): React.ReactNode { 882 + const parts = segment.text.split("\n"); 883 + return parts.flatMap((part, idx) => { 884 + const key = `${segment.text}-${idx}-${part.length}`; 885 + const wrapped = applyFeatures( 886 + part.length ? part : "\u00a0", 887 + segment.features, 888 + key, 889 + ); 890 + if (idx === parts.length - 1) return wrapped; 891 + return [wrapped, <br key={`${key}-br`} />]; 892 + }); 519 893 } 520 894 521 - function applyFeatures(content: React.ReactNode, features: LeafletRichTextFeature[], key: string, colorScheme: 'light' | 'dark'): React.ReactNode { 522 - if (!features?.length) return <React.Fragment key={key}>{content}</React.Fragment>; 523 - return ( 524 - <React.Fragment key={key}> 525 - {features.reduce<React.ReactNode>((child, feature, idx) => wrapFeature(child, feature, `${key}-feature-${idx}`, colorScheme), content)} 526 - </React.Fragment> 527 - ); 895 + function applyFeatures( 896 + content: React.ReactNode, 897 + features: LeafletRichTextFeature[], 898 + key: string, 899 + ): React.ReactNode { 900 + if (!features?.length) 901 + return <React.Fragment key={key}>{content}</React.Fragment>; 902 + return ( 903 + <React.Fragment key={key}> 904 + {features.reduce<React.ReactNode>( 905 + (child, feature, idx) => 906 + wrapFeature( 907 + child, 908 + feature, 909 + `${key}-feature-${idx}`, 910 + ), 911 + content, 912 + )} 913 + </React.Fragment> 914 + ); 528 915 } 529 916 530 - function wrapFeature(child: React.ReactNode, feature: LeafletRichTextFeature, key: string, colorScheme: 'light' | 'dark'): React.ReactNode { 531 - switch (feature.$type) { 532 - case 'pub.leaflet.richtext.facet#link': 533 - return <a key={key} href={feature.uri} target="_blank" rel="noopener noreferrer" style={linkStyles[colorScheme]}>{child}</a>; 534 - case 'pub.leaflet.richtext.facet#code': 535 - return <code key={key} style={inlineCodeStyles[colorScheme]}>{child}</code>; 536 - case 'pub.leaflet.richtext.facet#highlight': 537 - return <mark key={key} style={highlightStyles[colorScheme]}>{child}</mark>; 538 - case 'pub.leaflet.richtext.facet#underline': 539 - return <span key={key} style={{ textDecoration: 'underline' }}>{child}</span>; 540 - case 'pub.leaflet.richtext.facet#strikethrough': 541 - return <span key={key} style={{ textDecoration: 'line-through' }}>{child}</span>; 542 - case 'pub.leaflet.richtext.facet#bold': 543 - return <strong key={key}>{child}</strong>; 544 - case 'pub.leaflet.richtext.facet#italic': 545 - return <em key={key}>{child}</em>; 546 - case 'pub.leaflet.richtext.facet#id': 547 - return <span key={key} id={feature.id}>{child}</span>; 548 - default: 549 - return <span key={key}>{child}</span>; 550 - } 917 + function wrapFeature( 918 + child: React.ReactNode, 919 + feature: LeafletRichTextFeature, 920 + key: string, 921 + ): React.ReactNode { 922 + switch (feature.$type) { 923 + case "pub.leaflet.richtext.facet#link": 924 + return ( 925 + <a 926 + key={key} 927 + href={feature.uri} 928 + target="_blank" 929 + rel="noopener noreferrer" 930 + style={{ color: `var(--atproto-color-link)`, textDecoration: "underline" }} 931 + > 932 + {child} 933 + </a> 934 + ); 935 + case "pub.leaflet.richtext.facet#code": 936 + return ( 937 + <code key={key} style={{ 938 + fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace', 939 + background: `var(--atproto-color-bg-elevated)`, 940 + padding: "0 4px", 941 + borderRadius: 4, 942 + }}> 943 + {child} 944 + </code> 945 + ); 946 + case "pub.leaflet.richtext.facet#highlight": 947 + return ( 948 + <mark key={key} style={{ background: `var(--atproto-color-highlight)` }}> 949 + {child} 950 + </mark> 951 + ); 952 + case "pub.leaflet.richtext.facet#underline": 953 + return ( 954 + <span key={key} style={{ textDecoration: "underline" }}> 955 + {child} 956 + </span> 957 + ); 958 + case "pub.leaflet.richtext.facet#strikethrough": 959 + return ( 960 + <span key={key} style={{ textDecoration: "line-through" }}> 961 + {child} 962 + </span> 963 + ); 964 + case "pub.leaflet.richtext.facet#bold": 965 + return <strong key={key}>{child}</strong>; 966 + case "pub.leaflet.richtext.facet#italic": 967 + return <em key={key}>{child}</em>; 968 + case "pub.leaflet.richtext.facet#id": 969 + return ( 970 + <span key={key} id={feature.id}> 971 + {child} 972 + </span> 973 + ); 974 + default: 975 + return <span key={key}>{child}</span>; 976 + } 551 977 } 552 978 553 979 const base: Record<string, React.CSSProperties> = { 554 - container: { 555 - display: 'flex', 556 - flexDirection: 'column', 557 - gap: 24, 558 - padding: '24px 28px', 559 - borderRadius: 20, 560 - border: '1px solid transparent', 561 - maxWidth: 720, 562 - width: '100%', 563 - fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' 564 - }, 565 - header: { 566 - display: 'flex', 567 - flexDirection: 'column', 568 - gap: 16 569 - }, 570 - headerContent: { 571 - display: 'flex', 572 - flexDirection: 'column', 573 - gap: 8 574 - }, 575 - title: { 576 - fontSize: 32, 577 - margin: 0, 578 - lineHeight: 1.15 579 - }, 580 - subtitle: { 581 - margin: 0, 582 - fontSize: 16, 583 - lineHeight: 1.5 584 - }, 585 - meta: { 586 - display: 'flex', 587 - flexWrap: 'wrap', 588 - gap: 8, 589 - alignItems: 'center', 590 - fontSize: 14 591 - }, 592 - body: { 593 - display: 'flex', 594 - flexDirection: 'column', 595 - gap: 18 596 - }, 597 - page: { 598 - display: 'flex', 599 - flexDirection: 'column', 600 - gap: 18 601 - }, 602 - paragraph: { 603 - margin: '1em 0 0', 604 - lineHeight: 1.65, 605 - fontSize: 16 606 - }, 607 - heading: { 608 - margin: '0.5em 0 0', 609 - fontWeight: 700 610 - }, 611 - blockquote: { 612 - margin: '1em 0 0', 613 - padding: '0.6em 1em', 614 - borderLeft: '4px solid' 615 - }, 616 - figure: { 617 - margin: '1.2em 0 0', 618 - display: 'flex', 619 - flexDirection: 'column', 620 - gap: 12 621 - }, 622 - imageWrapper: { 623 - borderRadius: 16, 624 - overflow: 'hidden', 625 - width: '100%', 626 - position: 'relative', 627 - background: '#e2e8f0' 628 - }, 629 - image: { 630 - width: '100%', 631 - height: '100%', 632 - objectFit: 'cover', 633 - display: 'block' 634 - }, 635 - imagePlaceholder: { 636 - width: '100%', 637 - padding: '24px 16px', 638 - textAlign: 'center' 639 - }, 640 - caption: { 641 - fontSize: 13, 642 - lineHeight: 1.4 643 - }, 644 - list: { 645 - paddingLeft: 28, 646 - margin: '1em 0 0', 647 - listStyleType: 'disc', 648 - listStylePosition: 'outside' 649 - }, 650 - nestedList: { 651 - paddingLeft: 20, 652 - marginTop: 8, 653 - listStyleType: 'circle', 654 - listStylePosition: 'outside' 655 - }, 656 - listItem: { 657 - marginTop: 8, 658 - display: 'list-item' 659 - }, 660 - linkCard: { 661 - borderRadius: 16, 662 - border: '1px solid', 663 - display: 'flex', 664 - flexDirection: 'column', 665 - overflow: 'hidden', 666 - textDecoration: 'none' 667 - }, 668 - linkPreview: { 669 - width: '100%', 670 - height: 180, 671 - objectFit: 'cover' 672 - }, 673 - linkPreviewPlaceholder: { 674 - width: '100%', 675 - height: 180, 676 - display: 'flex', 677 - alignItems: 'center', 678 - justifyContent: 'center', 679 - fontSize: 14 680 - }, 681 - linkContent: { 682 - display: 'flex', 683 - flexDirection: 'column', 684 - gap: 6, 685 - padding: '16px 18px' 686 - }, 687 - iframe: { 688 - width: '100%', 689 - height: 360, 690 - border: '1px solid #cbd5f5', 691 - borderRadius: 16 692 - }, 693 - math: { 694 - margin: '1em 0 0', 695 - padding: '14px 16px', 696 - borderRadius: 12, 697 - fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace', 698 - overflowX: 'auto' 699 - }, 700 - code: { 701 - margin: '1em 0 0', 702 - padding: '14px 16px', 703 - borderRadius: 12, 704 - overflowX: 'auto', 705 - fontSize: 14 706 - }, 707 - hr: { 708 - border: 0, 709 - borderTop: '1px solid', 710 - margin: '24px 0 0' 711 - }, 712 - embedFallback: { 713 - padding: '12px 16px', 714 - borderRadius: 12, 715 - border: '1px solid #e2e8f0', 716 - fontSize: 14 717 - } 980 + container: { 981 + display: "flex", 982 + flexDirection: "column", 983 + gap: 24, 984 + padding: "24px 28px", 985 + borderRadius: 20, 986 + borderWidth: "1px", 987 + borderStyle: "solid", 988 + borderColor: "transparent", 989 + maxWidth: 720, 990 + width: "100%", 991 + fontFamily: 992 + 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', 993 + }, 994 + header: { 995 + display: "flex", 996 + flexDirection: "column", 997 + gap: 16, 998 + }, 999 + headerContent: { 1000 + display: "flex", 1001 + flexDirection: "column", 1002 + gap: 8, 1003 + }, 1004 + title: { 1005 + fontSize: 32, 1006 + margin: 0, 1007 + lineHeight: 1.15, 1008 + }, 1009 + subtitle: { 1010 + margin: 0, 1011 + fontSize: 16, 1012 + lineHeight: 1.5, 1013 + }, 1014 + meta: { 1015 + display: "flex", 1016 + flexWrap: "wrap", 1017 + gap: 8, 1018 + alignItems: "center", 1019 + fontSize: 14, 1020 + }, 1021 + body: { 1022 + display: "flex", 1023 + flexDirection: "column", 1024 + gap: 18, 1025 + }, 1026 + page: { 1027 + display: "flex", 1028 + flexDirection: "column", 1029 + gap: 18, 1030 + }, 1031 + paragraph: { 1032 + margin: "1em 0 0", 1033 + lineHeight: 1.65, 1034 + fontSize: 16, 1035 + }, 1036 + heading: { 1037 + margin: "0.5em 0 0", 1038 + fontWeight: 700, 1039 + }, 1040 + blockquote: { 1041 + margin: "1em 0 0", 1042 + padding: "0.6em 1em", 1043 + borderLeftWidth: "4px", 1044 + borderLeftStyle: "solid", 1045 + }, 1046 + figure: { 1047 + margin: "1.2em 0 0", 1048 + display: "flex", 1049 + flexDirection: "column", 1050 + gap: 12, 1051 + }, 1052 + imageWrapper: { 1053 + borderRadius: 16, 1054 + overflow: "hidden", 1055 + width: "100%", 1056 + position: "relative", 1057 + background: "#e2e8f0", 1058 + }, 1059 + image: { 1060 + width: "100%", 1061 + height: "100%", 1062 + objectFit: "cover", 1063 + display: "block", 1064 + }, 1065 + imagePlaceholder: { 1066 + width: "100%", 1067 + padding: "24px 16px", 1068 + textAlign: "center", 1069 + }, 1070 + caption: { 1071 + fontSize: 13, 1072 + lineHeight: 1.4, 1073 + }, 1074 + list: { 1075 + paddingLeft: 28, 1076 + margin: "1em 0 0", 1077 + listStyleType: "disc", 1078 + listStylePosition: "outside", 1079 + }, 1080 + nestedList: { 1081 + paddingLeft: 20, 1082 + marginTop: 8, 1083 + listStyleType: "circle", 1084 + listStylePosition: "outside", 1085 + }, 1086 + listItem: { 1087 + marginTop: 8, 1088 + display: "list-item", 1089 + }, 1090 + linkCard: { 1091 + borderRadius: 16, 1092 + borderWidth: "1px", 1093 + borderStyle: "solid", 1094 + display: "flex", 1095 + flexDirection: "column", 1096 + overflow: "hidden", 1097 + textDecoration: "none", 1098 + }, 1099 + linkPreview: { 1100 + width: "100%", 1101 + height: 180, 1102 + objectFit: "cover", 1103 + }, 1104 + linkPreviewPlaceholder: { 1105 + width: "100%", 1106 + height: 180, 1107 + display: "flex", 1108 + alignItems: "center", 1109 + justifyContent: "center", 1110 + fontSize: 14, 1111 + }, 1112 + linkContent: { 1113 + display: "flex", 1114 + flexDirection: "column", 1115 + gap: 6, 1116 + padding: "16px 18px", 1117 + }, 1118 + iframe: { 1119 + width: "100%", 1120 + height: 360, 1121 + border: "1px solid #cbd5f5", 1122 + borderRadius: 16, 1123 + }, 1124 + math: { 1125 + margin: "1em 0 0", 1126 + padding: "14px 16px", 1127 + borderRadius: 12, 1128 + fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace', 1129 + overflowX: "auto", 1130 + }, 1131 + code: { 1132 + margin: "1em 0 0", 1133 + padding: "14px 16px", 1134 + borderRadius: 12, 1135 + overflowX: "auto", 1136 + fontSize: 14, 1137 + }, 1138 + hr: { 1139 + border: 0, 1140 + borderTopWidth: "1px", 1141 + borderTopStyle: "solid", 1142 + margin: "24px 0 0", 1143 + }, 1144 + embedFallback: { 1145 + padding: "12px 16px", 1146 + borderRadius: 12, 1147 + border: "1px solid #e2e8f0", 1148 + fontSize: 14, 1149 + }, 718 1150 }; 719 - 720 - const theme = { 721 - light: { 722 - container: { 723 - background: '#ffffff', 724 - borderColor: '#e2e8f0', 725 - color: '#0f172a', 726 - boxShadow: '0 4px 18px rgba(15, 23, 42, 0.06)' 727 - }, 728 - header: {}, 729 - title: { 730 - color: '#0f172a' 731 - }, 732 - subtitle: { 733 - color: '#475569' 734 - }, 735 - meta: { 736 - color: '#64748b' 737 - }, 738 - metaLink: { 739 - color: '#2563eb', 740 - textDecoration: 'none' 741 - } satisfies React.CSSProperties, 742 - metaSeparator: { 743 - margin: '0 4px' 744 - } satisfies React.CSSProperties, 745 - paragraph: { 746 - color: '#1f2937' 747 - }, 748 - heading: { 749 - 1: { color: '#0f172a', fontSize: 30 }, 750 - 2: { color: '#0f172a', fontSize: 28 }, 751 - 3: { color: '#0f172a', fontSize: 24 }, 752 - 4: { color: '#0f172a', fontSize: 20 }, 753 - 5: { color: '#0f172a', fontSize: 18 }, 754 - 6: { color: '#0f172a', fontSize: 16 } 755 - } satisfies Record<number, React.CSSProperties>, 756 - blockquote: { 757 - background: '#f8fafc', 758 - borderColor: '#cbd5f5', 759 - color: '#1f2937' 760 - }, 761 - figure: {}, 762 - imageWrapper: { 763 - background: '#e2e8f0' 764 - }, 765 - image: {}, 766 - imagePlaceholder: { 767 - color: '#475569' 768 - }, 769 - caption: { 770 - color: '#475569' 771 - }, 772 - list: { 773 - color: '#1f2937' 774 - }, 775 - linkCard: { 776 - borderColor: '#e2e8f0', 777 - background: '#f8fafc', 778 - color: '#0f172a' 779 - }, 780 - linkPreview: {}, 781 - linkPreviewPlaceholder: { 782 - background: '#e2e8f0', 783 - color: '#475569' 784 - }, 785 - linkTitle: { 786 - fontSize: 16, 787 - color: '#0f172a' 788 - } satisfies React.CSSProperties, 789 - linkDescription: { 790 - margin: 0, 791 - fontSize: 14, 792 - color: '#475569', 793 - lineHeight: 1.5 794 - } satisfies React.CSSProperties, 795 - linkUrl: { 796 - fontSize: 13, 797 - color: '#2563eb', 798 - wordBreak: 'break-all' 799 - } satisfies React.CSSProperties, 800 - math: { 801 - background: '#f1f5f9', 802 - color: '#1f2937', 803 - border: '1px solid #e2e8f0' 804 - }, 805 - code: { 806 - background: '#0f172a', 807 - color: '#e2e8f0' 808 - }, 809 - hr: { 810 - borderColor: '#e2e8f0' 811 - } 812 - }, 813 - dark: { 814 - container: { 815 - background: 'rgba(15, 23, 42, 0.6)', 816 - borderColor: 'rgba(148, 163, 184, 0.3)', 817 - color: '#e2e8f0', 818 - backdropFilter: 'blur(8px)', 819 - boxShadow: '0 10px 40px rgba(2, 6, 23, 0.45)' 820 - }, 821 - header: {}, 822 - title: { 823 - color: '#f8fafc' 824 - }, 825 - subtitle: { 826 - color: '#cbd5f5' 827 - }, 828 - meta: { 829 - color: '#94a3b8' 830 - }, 831 - metaLink: { 832 - color: '#38bdf8', 833 - textDecoration: 'none' 834 - } satisfies React.CSSProperties, 835 - metaSeparator: { 836 - margin: '0 4px' 837 - } satisfies React.CSSProperties, 838 - paragraph: { 839 - color: '#e2e8f0' 840 - }, 841 - heading: { 842 - 1: { color: '#f8fafc', fontSize: 30 }, 843 - 2: { color: '#f8fafc', fontSize: 28 }, 844 - 3: { color: '#f8fafc', fontSize: 24 }, 845 - 4: { color: '#e2e8f0', fontSize: 20 }, 846 - 5: { color: '#e2e8f0', fontSize: 18 }, 847 - 6: { color: '#e2e8f0', fontSize: 16 } 848 - } satisfies Record<number, React.CSSProperties>, 849 - blockquote: { 850 - background: 'rgba(30, 41, 59, 0.6)', 851 - borderColor: '#38bdf8', 852 - color: '#e2e8f0' 853 - }, 854 - figure: {}, 855 - imageWrapper: { 856 - background: '#1e293b' 857 - }, 858 - image: {}, 859 - imagePlaceholder: { 860 - color: '#94a3b8' 861 - }, 862 - caption: { 863 - color: '#94a3b8' 864 - }, 865 - list: { 866 - color: '#f1f5f9' 867 - }, 868 - linkCard: { 869 - borderColor: 'rgba(148, 163, 184, 0.3)', 870 - background: 'rgba(15, 23, 42, 0.8)', 871 - color: '#e2e8f0' 872 - }, 873 - linkPreview: {}, 874 - linkPreviewPlaceholder: { 875 - background: '#1e293b', 876 - color: '#94a3b8' 877 - }, 878 - linkTitle: { 879 - fontSize: 16, 880 - color: '#e0f2fe' 881 - } satisfies React.CSSProperties, 882 - linkDescription: { 883 - margin: 0, 884 - fontSize: 14, 885 - color: '#cbd5f5', 886 - lineHeight: 1.5 887 - } satisfies React.CSSProperties, 888 - linkUrl: { 889 - fontSize: 13, 890 - color: '#38bdf8', 891 - wordBreak: 'break-all' 892 - } satisfies React.CSSProperties, 893 - math: { 894 - background: 'rgba(15, 23, 42, 0.8)', 895 - color: '#e2e8f0', 896 - border: '1px solid rgba(148, 163, 184, 0.35)' 897 - }, 898 - code: { 899 - background: '#020617', 900 - color: '#e2e8f0' 901 - }, 902 - hr: { 903 - borderColor: 'rgba(148, 163, 184, 0.3)' 904 - } 905 - } 906 - } as const; 907 - 908 - const linkStyles = { 909 - light: { 910 - color: '#2563eb', 911 - textDecoration: 'underline' 912 - } satisfies React.CSSProperties, 913 - dark: { 914 - color: '#38bdf8', 915 - textDecoration: 'underline' 916 - } satisfies React.CSSProperties 917 - } as const; 918 - 919 - const inlineCodeStyles = { 920 - light: { 921 - fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace', 922 - background: '#f1f5f9', 923 - padding: '0 4px', 924 - borderRadius: 4 925 - } satisfies React.CSSProperties, 926 - dark: { 927 - fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace', 928 - background: '#1e293b', 929 - padding: '0 4px', 930 - borderRadius: 4 931 - } satisfies React.CSSProperties 932 - } as const; 933 - 934 - const highlightStyles = { 935 - light: { 936 - background: '#fef08a' 937 - } satisfies React.CSSProperties, 938 - dark: { 939 - background: '#facc15', 940 - color: '#0f172a' 941 - } satisfies React.CSSProperties 942 - } as const; 943 1151 944 1152 export default LeafletDocumentRenderer;
+330
lib/renderers/TangledRepoRenderer.tsx
··· 1 + import React from "react"; 2 + import type { TangledRepoRecord } from "../types/tangled"; 3 + import { useAtProto } from "../providers/AtProtoProvider"; 4 + import { useBacklinks } from "../hooks/useBacklinks"; 5 + import { useRepoLanguages } from "../hooks/useRepoLanguages"; 6 + 7 + export interface TangledRepoRendererProps { 8 + record: TangledRepoRecord; 9 + error?: Error; 10 + loading: boolean; 11 + did: string; 12 + rkey: string; 13 + canonicalUrl?: string; 14 + showStarCount?: boolean; 15 + branch?: string; 16 + languages?: string[]; 17 + } 18 + 19 + export const TangledRepoRenderer: React.FC<TangledRepoRendererProps> = ({ 20 + record, 21 + error, 22 + loading, 23 + did, 24 + rkey, 25 + canonicalUrl, 26 + showStarCount = true, 27 + branch, 28 + languages, 29 + }) => { 30 + const { tangledBaseUrl, constellationBaseUrl } = useAtProto(); 31 + 32 + // Construct the AT-URI for this repo record 33 + const atUri = `at://${did}/sh.tangled.repo/${rkey}`; 34 + 35 + // Fetch star backlinks 36 + const { 37 + count: starCount, 38 + loading: starsLoading, 39 + error: starsError, 40 + } = useBacklinks({ 41 + subject: atUri, 42 + source: "sh.tangled.feed.star:subject", 43 + limit: 100, 44 + constellationBaseUrl, 45 + enabled: showStarCount, 46 + }); 47 + 48 + // Extract knot server from record.knot (e.g., "knot.gaze.systems") 49 + const knotUrl = record?.knot 50 + ? record.knot.startsWith("http://") || record.knot.startsWith("https://") 51 + ? new URL(record.knot).hostname 52 + : record.knot 53 + : undefined; 54 + 55 + // Fetch language data from knot server only if languages not provided 56 + const { 57 + data: languagesData, 58 + loading: _languagesLoading, 59 + error: _languagesError, 60 + } = useRepoLanguages({ 61 + knot: knotUrl, 62 + did, 63 + repoName: record?.name, 64 + branch, 65 + enabled: !languages && !!knotUrl && !!record?.name, 66 + }); 67 + 68 + // Convert provided language names to the format expected by the renderer 69 + const providedLanguagesData = languages 70 + ? { 71 + languages: languages.map((name) => ({ 72 + name, 73 + percentage: 0, 74 + size: 0, 75 + })), 76 + ref: branch || "main", 77 + totalFiles: 0, 78 + totalSize: 0, 79 + } 80 + : undefined; 81 + 82 + // Use provided languages or fetched languages 83 + const finalLanguagesData = providedLanguagesData ?? languagesData; 84 + 85 + if (error) 86 + return ( 87 + <div role="alert" style={{ padding: 8, color: "crimson" }}> 88 + Failed to load repository. 89 + </div> 90 + ); 91 + if (loading && !record) return <div role="status" aria-live="polite" style={{ padding: 8 }}>Loadingโ€ฆ</div>; 92 + 93 + // Construct the canonical URL: tangled.org/[did]/[repo-name] 94 + const viewUrl = 95 + canonicalUrl ?? 96 + `${tangledBaseUrl}/${did}/${encodeURIComponent(record.name)}`; 97 + 98 + const tangledIcon = ( 99 + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 25 25" style={{ display: "block" }}> 100 + <path fill="currentColor" d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z"/> 101 + </svg> 102 + ); 103 + 104 + return ( 105 + <div 106 + style={{ 107 + ...base.container, 108 + background: `var(--atproto-color-bg)`, 109 + borderWidth: "1px", 110 + borderStyle: "solid", 111 + borderColor: `var(--atproto-color-border)`, 112 + color: `var(--atproto-color-text)`, 113 + }} 114 + > 115 + {/* Header with title and icons */} 116 + <div 117 + style={{ 118 + ...base.header, 119 + background: `var(--atproto-color-bg)`, 120 + }} 121 + > 122 + <div style={base.headerTop}> 123 + <strong 124 + style={{ 125 + ...base.repoName, 126 + color: `var(--atproto-color-text)`, 127 + }} 128 + > 129 + {record.name} 130 + </strong> 131 + <div style={base.headerRight}> 132 + <a 133 + href={viewUrl} 134 + target="_blank" 135 + rel="noopener noreferrer" 136 + style={{ 137 + ...base.iconLink, 138 + color: `var(--atproto-color-text)`, 139 + }} 140 + title="View on Tangled" 141 + > 142 + {tangledIcon} 143 + </a> 144 + {record.source && ( 145 + <a 146 + href={record.source} 147 + target="_blank" 148 + rel="noopener noreferrer" 149 + style={{ 150 + ...base.iconLink, 151 + color: `var(--atproto-color-text)`, 152 + }} 153 + title="View source repository" 154 + > 155 + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="currentColor" style={{ display: "block" }}> 156 + <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/> 157 + </svg> 158 + </a> 159 + )} 160 + </div> 161 + </div> 162 + </div> 163 + 164 + {/* Description */} 165 + {record.description && ( 166 + <div 167 + style={{ 168 + ...base.description, 169 + background: `var(--atproto-color-bg)`, 170 + color: `var(--atproto-color-text-secondary)`, 171 + }} 172 + > 173 + {record.description} 174 + </div> 175 + )} 176 + 177 + {/* Languages and Stars */} 178 + <div 179 + style={{ 180 + ...base.languageSection, 181 + background: `var(--atproto-color-bg)`, 182 + }} 183 + > 184 + {/* Languages */} 185 + {finalLanguagesData && finalLanguagesData.languages.length > 0 && (() => { 186 + const topLanguages = finalLanguagesData.languages 187 + .filter((lang) => lang.name && (lang.percentage > 0 || finalLanguagesData.languages.every(l => l.percentage === 0))) 188 + .sort((a, b) => b.percentage - a.percentage) 189 + .slice(0, 2); 190 + return topLanguages.length > 0 ? ( 191 + <div style={base.languageTags}> 192 + {topLanguages.map((lang) => ( 193 + <span key={lang.name} style={base.languageTag}> 194 + {lang.name} 195 + </span> 196 + ))} 197 + </div> 198 + ) : null; 199 + })()} 200 + 201 + {/* Right side: Stars and View on Tangled link */} 202 + <div style={base.rightSection}> 203 + {/* Stars */} 204 + {showStarCount && ( 205 + <div 206 + style={{ 207 + ...base.starCountContainer, 208 + color: `var(--atproto-color-text-secondary)`, 209 + }} 210 + > 211 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style={{ display: "block" }}> 212 + <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/> 213 + </svg> 214 + {starsLoading ? ( 215 + <span style={base.starCount}>...</span> 216 + ) : starsError ? ( 217 + <span style={base.starCount}>โ€”</span> 218 + ) : ( 219 + <span style={base.starCount}>{starCount}</span> 220 + )} 221 + </div> 222 + )} 223 + 224 + {/* View on Tangled link */} 225 + <a 226 + href={viewUrl} 227 + target="_blank" 228 + rel="noopener noreferrer" 229 + style={{ 230 + ...base.viewLink, 231 + color: `var(--atproto-color-link)`, 232 + }} 233 + > 234 + View on Tangled 235 + </a> 236 + </div> 237 + </div> 238 + </div> 239 + ); 240 + }; 241 + 242 + const base: Record<string, React.CSSProperties> = { 243 + container: { 244 + fontFamily: "system-ui, sans-serif", 245 + borderRadius: 6, 246 + overflow: "hidden", 247 + transition: 248 + "background-color 180ms ease, border-color 180ms ease, color 180ms ease, box-shadow 180ms ease", 249 + width: "100%", 250 + }, 251 + header: { 252 + padding: "16px", 253 + display: "flex", 254 + flexDirection: "column", 255 + }, 256 + headerTop: { 257 + display: "flex", 258 + justifyContent: "space-between", 259 + alignItems: "flex-start", 260 + gap: 12, 261 + }, 262 + headerRight: { 263 + display: "flex", 264 + alignItems: "center", 265 + gap: 8, 266 + }, 267 + repoName: { 268 + fontFamily: 269 + 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', 270 + fontSize: 18, 271 + fontWeight: 600, 272 + wordBreak: "break-word", 273 + margin: 0, 274 + }, 275 + iconLink: { 276 + display: "flex", 277 + alignItems: "center", 278 + textDecoration: "none", 279 + opacity: 0.7, 280 + transition: "opacity 150ms ease", 281 + }, 282 + description: { 283 + padding: "0 16px 16px 16px", 284 + fontSize: 14, 285 + lineHeight: 1.5, 286 + }, 287 + languageSection: { 288 + padding: "0 16px 16px 16px", 289 + display: "flex", 290 + justifyContent: "space-between", 291 + alignItems: "center", 292 + gap: 12, 293 + flexWrap: "wrap", 294 + }, 295 + languageTags: { 296 + display: "flex", 297 + gap: 8, 298 + flexWrap: "wrap", 299 + }, 300 + languageTag: { 301 + fontSize: 12, 302 + fontWeight: 500, 303 + padding: "4px 10px", 304 + background: `var(--atproto-color-bg)`, 305 + borderRadius: 12, 306 + border: "1px solid var(--atproto-color-border)", 307 + }, 308 + rightSection: { 309 + display: "flex", 310 + alignItems: "center", 311 + gap: 12, 312 + }, 313 + starCountContainer: { 314 + display: "flex", 315 + alignItems: "center", 316 + gap: 4, 317 + fontSize: 13, 318 + }, 319 + starCount: { 320 + fontSize: 13, 321 + fontWeight: 500, 322 + }, 323 + viewLink: { 324 + fontSize: 13, 325 + fontWeight: 500, 326 + textDecoration: "none", 327 + }, 328 + }; 329 + 330 + export default TangledRepoRenderer;
+78 -113
lib/renderers/TangledStringRenderer.tsx
··· 1 - import React from 'react'; 2 - import type { ShTangledString } from '@atcute/tangled'; 3 - import { useColorScheme, type ColorSchemePreference } from '../hooks/useColorScheme'; 4 - 5 - export type TangledStringRecord = ShTangledString.Main; 1 + import React from "react"; 2 + import { useAtProto } from "../providers/AtProtoProvider"; 3 + import type { TangledStringRecord } from "../types/tangled"; 6 4 7 5 export interface TangledStringRendererProps { 8 6 record: TangledStringRecord; 9 7 error?: Error; 10 8 loading: boolean; 11 - colorScheme?: ColorSchemePreference; 12 9 did: string; 13 10 rkey: string; 14 11 canonicalUrl?: string; 15 12 } 16 13 17 - export const TangledStringRenderer: React.FC<TangledStringRendererProps> = ({ record, error, loading, colorScheme = 'system', did, rkey, canonicalUrl }) => { 18 - const scheme = useColorScheme(colorScheme); 14 + export const TangledStringRenderer: React.FC<TangledStringRendererProps> = ({ 15 + record, 16 + error, 17 + loading, 18 + did, 19 + rkey, 20 + canonicalUrl, 21 + }) => { 22 + const { tangledBaseUrl } = useAtProto(); 19 23 20 - if (error) return <div style={{ padding: 8, color: 'crimson' }}>Failed to load snippet.</div>; 24 + if (error) 25 + return ( 26 + <div style={{ padding: 8, color: "crimson" }}> 27 + Failed to load snippet. 28 + </div> 29 + ); 21 30 if (loading && !record) return <div style={{ padding: 8 }}>Loadingโ€ฆ</div>; 22 31 23 - const palette = scheme === 'dark' ? theme.dark : theme.light; 24 - const viewUrl = canonicalUrl ?? `https://tangled.org/strings/${did}/${encodeURIComponent(rkey)}`; 25 - const timestamp = new Date(record.createdAt).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }); 32 + const viewUrl = 33 + canonicalUrl ?? 34 + `${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`; 35 + const timestamp = new Date(record.createdAt).toLocaleString(undefined, { 36 + dateStyle: "medium", 37 + timeStyle: "short", 38 + }); 26 39 return ( 27 - <div style={{ ...base.container, ...palette.container }}> 28 - <div style={{ ...base.header, ...palette.header }}> 29 - <strong style={{ ...base.filename, ...palette.filename }}>{record.filename}</strong> 30 - <div style={{ ...base.headerRight, ...palette.headerRight }}> 31 - <time style={{ ...base.timestamp, ...palette.timestamp }} dateTime={record.createdAt}>{timestamp}</time> 32 - <a href={viewUrl} target="_blank" rel="noopener noreferrer" style={{ ...base.headerLink, ...palette.headerLink }}> 40 + <div style={{ ...base.container, background: `var(--atproto-color-bg-elevated)`, borderWidth: "1px", borderStyle: "solid", borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 41 + <div style={{ ...base.header, background: `var(--atproto-color-bg-elevated)`, borderBottomWidth: "1px", borderBottomStyle: "solid", borderBottomColor: `var(--atproto-color-border)` }}> 42 + <strong style={{ ...base.filename, color: `var(--atproto-color-text)` }}> 43 + {record.filename} 44 + </strong> 45 + <div style={{ ...base.headerRight }}> 46 + <time 47 + style={{ ...base.timestamp, color: `var(--atproto-color-text-secondary)` }} 48 + dateTime={record.createdAt} 49 + > 50 + {timestamp} 51 + </time> 52 + <a 53 + href={viewUrl} 54 + target="_blank" 55 + rel="noopener noreferrer" 56 + style={{ ...base.headerLink, color: `var(--atproto-color-link)` }} 57 + > 33 58 View on Tangled 34 59 </a> 35 60 </div> 36 61 </div> 37 62 {record.description && ( 38 - <div style={{ ...base.description, ...palette.description }}>{record.description}</div> 63 + <div style={{ ...base.description, background: `var(--atproto-color-bg)`, borderTopWidth: "1px", borderTopStyle: "solid", borderTopColor: `var(--atproto-color-border)`, borderBottomWidth: "1px", borderBottomStyle: "solid", borderBottomColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 64 + {record.description} 65 + </div> 39 66 )} 40 - <pre style={{ ...base.codeBlock, ...palette.codeBlock }}> 67 + <pre style={{ ...base.codeBlock, background: `var(--atproto-color-bg)`, color: `var(--atproto-color-text)`, borderTopWidth: "1px", borderTopStyle: "solid", borderTopColor: `var(--atproto-color-border)` }}> 41 68 <code>{record.contents}</code> 42 69 </pre> 43 70 </div> ··· 46 73 47 74 const base: Record<string, React.CSSProperties> = { 48 75 container: { 49 - fontFamily: 'system-ui, sans-serif', 76 + fontFamily: "system-ui, sans-serif", 50 77 borderRadius: 6, 51 - overflow: 'hidden', 52 - transition: 'background-color 180ms ease, border-color 180ms ease, color 180ms ease, box-shadow 180ms ease', 53 - width: '100%' 78 + overflow: "hidden", 79 + transition: 80 + "background-color 180ms ease, border-color 180ms ease, color 180ms ease, box-shadow 180ms ease", 81 + width: "100%", 54 82 }, 55 83 header: { 56 - padding: '10px 16px', 57 - display: 'flex', 58 - justifyContent: 'space-between', 59 - alignItems: 'center', 60 - gap: 12 84 + padding: "10px 16px", 85 + display: "flex", 86 + justifyContent: "space-between", 87 + alignItems: "center", 88 + gap: 12, 61 89 }, 62 90 headerRight: { 63 - display: 'flex', 64 - alignItems: 'center', 91 + display: "flex", 92 + alignItems: "center", 65 93 gap: 12, 66 - flexWrap: 'wrap', 67 - justifyContent: 'flex-end' 94 + flexWrap: "wrap", 95 + justifyContent: "flex-end", 68 96 }, 69 97 filename: { 70 - fontFamily: 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', 98 + fontFamily: 99 + 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', 71 100 fontSize: 13, 72 - wordBreak: 'break-all' 101 + wordBreak: "break-all", 73 102 }, 74 103 timestamp: { 75 - fontSize: 12 104 + fontSize: 12, 76 105 }, 77 106 headerLink: { 78 107 fontSize: 12, 79 108 fontWeight: 600, 80 - textDecoration: 'none' 109 + textDecoration: "none", 81 110 }, 82 111 description: { 83 - padding: '10px 16px', 112 + padding: "10px 16px", 84 113 fontSize: 13, 85 - borderTop: '1px solid transparent' 114 + borderTopWidth: "1px", 115 + borderTopStyle: "solid", 116 + borderTopColor: "transparent", 86 117 }, 87 118 codeBlock: { 88 119 margin: 0, 89 - padding: '16px', 120 + padding: "16px", 90 121 fontSize: 13, 91 - overflowX: 'auto', 92 - borderTop: '1px solid transparent', 93 - fontFamily: 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace' 94 - } 122 + overflowX: "auto", 123 + borderTopWidth: "1px", 124 + borderTopStyle: "solid", 125 + borderTopColor: "transparent", 126 + fontFamily: 127 + 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', 128 + }, 95 129 }; 96 - 97 - const theme = { 98 - light: { 99 - container: { 100 - border: '1px solid #d0d7de', 101 - background: '#f6f8fa', 102 - color: '#1f2328', 103 - boxShadow: '0 1px 2px rgba(31,35,40,0.05)' 104 - }, 105 - header: { 106 - background: '#f6f8fa', 107 - borderBottom: '1px solid #d0d7de' 108 - }, 109 - headerRight: {}, 110 - filename: { 111 - color: '#1f2328' 112 - }, 113 - timestamp: { 114 - color: '#57606a' 115 - }, 116 - headerLink: { 117 - color: '#2563eb' 118 - }, 119 - description: { 120 - background: '#ffffff', 121 - borderBottom: '1px solid #d0d7de', 122 - borderTopColor: '#d0d7de', 123 - color: '#1f2328' 124 - }, 125 - codeBlock: { 126 - background: '#ffffff', 127 - color: '#1f2328', 128 - borderTopColor: '#d0d7de' 129 - } 130 - }, 131 - dark: { 132 - container: { 133 - border: '1px solid #30363d', 134 - background: '#0d1117', 135 - color: '#c9d1d9', 136 - boxShadow: '0 0 0 1px rgba(1,4,9,0.3) inset' 137 - }, 138 - header: { 139 - background: '#161b22', 140 - borderBottom: '1px solid #30363d' 141 - }, 142 - headerRight: {}, 143 - filename: { 144 - color: '#c9d1d9' 145 - }, 146 - timestamp: { 147 - color: '#8b949e' 148 - }, 149 - headerLink: { 150 - color: '#58a6ff' 151 - }, 152 - description: { 153 - background: '#161b22', 154 - borderBottom: '1px solid #30363d', 155 - borderTopColor: '#30363d', 156 - color: '#c9d1d9' 157 - }, 158 - codeBlock: { 159 - background: '#0d1117', 160 - color: '#c9d1d9', 161 - borderTopColor: '#30363d' 162 - } 163 - } 164 - } satisfies Record<'light' | 'dark', Record<string, React.CSSProperties>>; 165 130 166 131 export default TangledStringRenderer;
-120
lib/styles/highlight.css
··· 1 - :root { 2 - --hljs-foreground: #1f2328; 3 - --hljs-background: transparent; 4 - --hljs-comment: #6e7781; 5 - --hljs-keyword: #cf222e; 6 - --hljs-attr: #0550ae; 7 - --hljs-string: #0a3069; 8 - --hljs-title: #24292f; 9 - --hljs-number: #953800; 10 - --hljs-addition: #116329; 11 - --hljs-deletion: #cf222e; 12 - } 13 - 14 - :root[data-color-scheme='light'], 15 - [data-color-scheme='light'] { 16 - --hljs-foreground: #1f2328; 17 - --hljs-background: rgba(15, 23, 42, 0.03); 18 - --hljs-comment: #6e7781; 19 - --hljs-keyword: #0f59d1; 20 - --hljs-attr: #1d4ed8; 21 - --hljs-string: #0f766e; 22 - --hljs-title: #1f2937; 23 - --hljs-number: #b45309; 24 - --hljs-addition: #15803d; 25 - --hljs-deletion: #dc2626; 26 - } 27 - 28 - :root[data-color-scheme='dark'], 29 - [data-color-scheme='dark'] { 30 - --hljs-foreground: #c9d1d9; 31 - --hljs-background: rgba(8, 16, 32, 0.55); 32 - --hljs-comment: #8b949e; 33 - --hljs-keyword: #ff7b72; 34 - --hljs-attr: #79c0ff; 35 - --hljs-string: #a5d6ff; 36 - --hljs-title: #d2a8ff; 37 - --hljs-number: #ffa657; 38 - --hljs-addition: #1a7f37; 39 - --hljs-deletion: #ff7b72; 40 - } 41 - 42 - :root:not([data-color-scheme]), 43 - [data-color-scheme]:not([data-color-scheme='light']):not([data-color-scheme='dark']) { 44 - --hljs-foreground: #1f2328; 45 - --hljs-background: transparent; 46 - --hljs-comment: #6e7781; 47 - --hljs-keyword: #cf222e; 48 - --hljs-attr: #0550ae; 49 - --hljs-string: #0a3069; 50 - --hljs-title: #24292f; 51 - --hljs-number: #953800; 52 - --hljs-addition: #116329; 53 - --hljs-deletion: #cf222e; 54 - } 55 - 56 - .hljs { 57 - display: block; 58 - overflow-x: auto; 59 - padding: 0; 60 - background: var(--hljs-background); 61 - color: var(--hljs-foreground); 62 - } 63 - 64 - .hljs-comment, 65 - .hljs-quote { 66 - color: var(--hljs-comment); 67 - font-style: italic; 68 - } 69 - 70 - .hljs-keyword, 71 - .hljs-selector-tag, 72 - .hljs-literal { 73 - color: var(--hljs-keyword); 74 - } 75 - 76 - .hljs-attr, 77 - .hljs-attribute, 78 - .hljs-symbol, 79 - .hljs-bullet, 80 - .hljs-built_in, 81 - .hljs-link, 82 - .hljs-meta, 83 - .hljs-selector-attr, 84 - .hljs-selector-pseudo { 85 - color: var(--hljs-attr); 86 - } 87 - 88 - .hljs-number, 89 - .hljs-variable, 90 - .hljs-template-variable, 91 - .hljs-title, 92 - .hljs-name, 93 - .hljs-tag { 94 - color: var(--hljs-number); 95 - } 96 - 97 - .hljs-string, 98 - .hljs-doctag, 99 - .hljs-regexp, 100 - .hljs-addition { 101 - color: var(--hljs-string); 102 - } 103 - 104 - .hljs-type, 105 - .hljs-class .hljs-title { 106 - color: var(--hljs-title); 107 - font-weight: 600; 108 - } 109 - 110 - .hljs-deletion { 111 - color: var(--hljs-deletion); 112 - } 113 - 114 - .hljs-emphasis { 115 - font-style: italic; 116 - } 117 - 118 - .hljs-strong { 119 - font-weight: 600; 120 - }
+97
lib/styles.css
··· 1 + /** 2 + * Global CSS variables for AtProto UI components 3 + * 4 + * These variables can be customized in your application by setting them 5 + * at the :root level or within specific components. 6 + */ 7 + 8 + :root { 9 + /* Light theme colors (default) */ 10 + --atproto-color-bg: #f5f7f9; 11 + --atproto-color-bg-elevated: #f8f9fb; 12 + --atproto-color-bg-secondary: #edf1f5; 13 + --atproto-color-text: #0f172a; 14 + --atproto-color-text-secondary: #475569; 15 + --atproto-color-text-muted: #64748b; 16 + --atproto-color-border: #d6dce3; 17 + --atproto-color-border-subtle: #c1cad4; 18 + --atproto-color-border-hover: #94a3b8; 19 + --atproto-color-link: #2563eb; 20 + --atproto-color-link-hover: #1d4ed8; 21 + --atproto-color-error: #dc2626; 22 + --atproto-color-primary: #2563eb; 23 + --atproto-color-button-bg: #edf1f5; 24 + --atproto-color-button-hover: #e3e9ef; 25 + --atproto-color-button-text: #0f172a; 26 + --atproto-color-bg-hover: #f0f3f6; 27 + --atproto-color-bg-pressed: #e3e9ef; 28 + --atproto-color-code-bg: #edf1f5; 29 + --atproto-color-code-border: #d6dce3; 30 + --atproto-color-blockquote-border: #c1cad4; 31 + --atproto-color-blockquote-bg: #f0f3f6; 32 + --atproto-color-hr: #d6dce3; 33 + --atproto-color-image-bg: #edf1f5; 34 + --atproto-color-highlight: #fef08a; 35 + } 36 + 37 + /* Dark theme - can be applied via [data-theme="dark"] or .dark class */ 38 + [data-theme="dark"], 39 + .dark { 40 + --atproto-color-bg: #141b22; 41 + --atproto-color-bg-elevated: #1a222a; 42 + --atproto-color-bg-secondary: #0f161c; 43 + --atproto-color-text: #fafafa; 44 + --atproto-color-text-secondary: #a1a1aa; 45 + --atproto-color-text-muted: #71717a; 46 + --atproto-color-border: #1f2933; 47 + --atproto-color-border-subtle: #2d3748; 48 + --atproto-color-border-hover: #4a5568; 49 + --atproto-color-link: #60a5fa; 50 + --atproto-color-link-hover: #93c5fd; 51 + --atproto-color-error: #ef4444; 52 + --atproto-color-primary: #3b82f6; 53 + --atproto-color-button-bg: #1a222a; 54 + --atproto-color-button-hover: #243039; 55 + --atproto-color-button-text: #fafafa; 56 + --atproto-color-bg-hover: #1a222a; 57 + --atproto-color-bg-pressed: #243039; 58 + --atproto-color-code-bg: #0f161c; 59 + --atproto-color-code-border: #1f2933; 60 + --atproto-color-blockquote-border: #2d3748; 61 + --atproto-color-blockquote-bg: #1a222a; 62 + --atproto-color-hr: #243039; 63 + --atproto-color-image-bg: #1a222a; 64 + --atproto-color-highlight: #854d0e; 65 + } 66 + 67 + /* Support for system preference based theming */ 68 + @media (prefers-color-scheme: dark) { 69 + :root:not([data-theme]), 70 + :root[data-theme="system"] { 71 + --atproto-color-bg: #141b22; 72 + --atproto-color-bg-elevated: #1a222a; 73 + --atproto-color-bg-secondary: #0f161c; 74 + --atproto-color-text: #fafafa; 75 + --atproto-color-text-secondary: #a1a1aa; 76 + --atproto-color-text-muted: #71717a; 77 + --atproto-color-border: #1f2933; 78 + --atproto-color-border-subtle: #2d3748; 79 + --atproto-color-border-hover: #4a5568; 80 + --atproto-color-link: #60a5fa; 81 + --atproto-color-link-hover: #93c5fd; 82 + --atproto-color-error: #ef4444; 83 + --atproto-color-primary: #3b82f6; 84 + --atproto-color-button-bg: #1a222a; 85 + --atproto-color-button-hover: #243039; 86 + --atproto-color-button-text: #fafafa; 87 + --atproto-color-bg-hover: #1a222a; 88 + --atproto-color-bg-pressed: #243039; 89 + --atproto-color-code-bg: #0f161c; 90 + --atproto-color-code-border: #1f2933; 91 + --atproto-color-blockquote-border: #2d3748; 92 + --atproto-color-blockquote-bg: #1a222a; 93 + --atproto-color-hr: #243039; 94 + --atproto-color-image-bg: #1a222a; 95 + --atproto-color-highlight: #854d0e; 96 + } 97 + }
+1 -1
lib/types/bluesky.ts
··· 1 1 // Re-export precise lexicon types from @atcute/bluesky instead of redefining. 2 - import type { AppBskyFeedPost, AppBskyActorProfile } from '@atcute/bluesky'; 2 + import type { AppBskyFeedPost, AppBskyActorProfile } from "@atcute/bluesky"; 3 3 4 4 // The atcute lexicon modules expose Main interface for record input shapes. 5 5 export type FeedPostRecord = AppBskyFeedPost.Main;
+95
lib/types/grain.ts
··· 1 + /** 2 + * Type definitions for grain.social records 3 + * Uses standard atcute blob types for compatibility 4 + */ 5 + import type { Blob } from "@atcute/lexicons/interfaces"; 6 + import type { BlobWithCdn } from "../hooks/useBlueskyAppview"; 7 + 8 + /** 9 + * grain.social gallery record 10 + * A container for a collection of photos 11 + */ 12 + export interface GrainGalleryRecord { 13 + /** 14 + * Record type identifier 15 + */ 16 + $type: "social.grain.gallery"; 17 + /** 18 + * Gallery title 19 + */ 20 + title: string; 21 + /** 22 + * Gallery description 23 + */ 24 + description?: string; 25 + /** 26 + * Self-label values (content warnings) 27 + */ 28 + labels?: { 29 + $type: "com.atproto.label.defs#selfLabels"; 30 + values: Array<{ val: string }>; 31 + }; 32 + /** 33 + * Timestamp when the gallery was created 34 + */ 35 + createdAt: string; 36 + } 37 + 38 + /** 39 + * grain.social gallery item record 40 + * Links a photo to a gallery 41 + */ 42 + export interface GrainGalleryItemRecord { 43 + /** 44 + * Record type identifier 45 + */ 46 + $type: "social.grain.gallery.item"; 47 + /** 48 + * AT URI of the photo (social.grain.photo) 49 + */ 50 + item: string; 51 + /** 52 + * AT URI of the gallery this item belongs to 53 + */ 54 + gallery: string; 55 + /** 56 + * Position/order within the gallery 57 + */ 58 + position?: number; 59 + /** 60 + * Timestamp when the item was added to the gallery 61 + */ 62 + createdAt: string; 63 + } 64 + 65 + /** 66 + * grain.social photo record 67 + * Compatible with records from @atcute clients 68 + */ 69 + export interface GrainPhotoRecord { 70 + /** 71 + * Record type identifier 72 + */ 73 + $type: "social.grain.photo"; 74 + /** 75 + * Alt text description of the image (required for accessibility) 76 + */ 77 + alt: string; 78 + /** 79 + * Photo blob reference - uses standard AT Proto blob format 80 + * Supports any image/* mime type 81 + * May include cdnUrl when fetched from appview 82 + */ 83 + photo: Blob<`image/${string}`> | BlobWithCdn; 84 + /** 85 + * Timestamp when the photo was created 86 + */ 87 + createdAt?: string; 88 + /** 89 + * Aspect ratio of the photo 90 + */ 91 + aspectRatio?: { 92 + width: number; 93 + height: number; 94 + }; 95 + }
+133 -127
lib/types/leaflet.ts
··· 1 1 export interface StrongRef { 2 - uri: string; 3 - cid: string; 2 + uri: string; 3 + cid: string; 4 4 } 5 5 6 6 export interface LeafletDocumentRecord { 7 - $type?: "pub.leaflet.document"; 8 - title: string; 9 - postRef?: StrongRef; 10 - description?: string; 11 - publishedAt?: string; 12 - publication: string; 13 - author: string; 14 - pages: LeafletDocumentPage[]; 7 + $type?: "pub.leaflet.document"; 8 + title: string; 9 + postRef?: StrongRef; 10 + description?: string; 11 + publishedAt?: string; 12 + publication: string; 13 + author: string; 14 + pages: LeafletDocumentPage[]; 15 15 } 16 16 17 17 export type LeafletDocumentPage = LeafletLinearDocumentPage; 18 18 19 19 export interface LeafletLinearDocumentPage { 20 - $type?: "pub.leaflet.pages.linearDocument"; 21 - blocks?: LeafletLinearDocumentBlock[]; 20 + $type?: "pub.leaflet.pages.linearDocument"; 21 + blocks?: LeafletLinearDocumentBlock[]; 22 22 } 23 23 24 24 export type LeafletAlignmentValue = 25 - | "#textAlignLeft" 26 - | "#textAlignCenter" 27 - | "#textAlignRight" 28 - | "#textAlignJustify" 29 - | "textAlignLeft" 30 - | "textAlignCenter" 31 - | "textAlignRight" 32 - | "textAlignJustify"; 25 + | "#textAlignLeft" 26 + | "#textAlignCenter" 27 + | "#textAlignRight" 28 + | "#textAlignJustify" 29 + | "textAlignLeft" 30 + | "textAlignCenter" 31 + | "textAlignRight" 32 + | "textAlignJustify"; 33 33 34 34 export interface LeafletLinearDocumentBlock { 35 - block: LeafletBlock; 36 - alignment?: LeafletAlignmentValue; 35 + block: LeafletBlock; 36 + alignment?: LeafletAlignmentValue; 37 37 } 38 38 39 39 export type LeafletBlock = 40 - | LeafletTextBlock 41 - | LeafletHeaderBlock 42 - | LeafletBlockquoteBlock 43 - | LeafletImageBlock 44 - | LeafletUnorderedListBlock 45 - | LeafletWebsiteBlock 46 - | LeafletIFrameBlock 47 - | LeafletMathBlock 48 - | LeafletCodeBlock 49 - | LeafletHorizontalRuleBlock 50 - | LeafletBskyPostBlock; 40 + | LeafletTextBlock 41 + | LeafletHeaderBlock 42 + | LeafletBlockquoteBlock 43 + | LeafletImageBlock 44 + | LeafletUnorderedListBlock 45 + | LeafletWebsiteBlock 46 + | LeafletIFrameBlock 47 + | LeafletMathBlock 48 + | LeafletCodeBlock 49 + | LeafletHorizontalRuleBlock 50 + | LeafletBskyPostBlock; 51 51 52 52 export interface LeafletBaseTextBlock { 53 - plaintext: string; 54 - facets?: LeafletRichTextFacet[]; 53 + plaintext: string; 54 + facets?: LeafletRichTextFacet[]; 55 55 } 56 56 57 57 export interface LeafletTextBlock extends LeafletBaseTextBlock { 58 - $type?: "pub.leaflet.blocks.text"; 58 + $type?: "pub.leaflet.blocks.text"; 59 59 } 60 60 61 61 export interface LeafletHeaderBlock extends LeafletBaseTextBlock { 62 - $type?: "pub.leaflet.blocks.header"; 63 - level?: number; 62 + $type?: "pub.leaflet.blocks.header"; 63 + level?: number; 64 64 } 65 65 66 66 export interface LeafletBlockquoteBlock extends LeafletBaseTextBlock { 67 - $type?: "pub.leaflet.blocks.blockquote"; 67 + $type?: "pub.leaflet.blocks.blockquote"; 68 68 } 69 69 70 70 export interface LeafletImageBlock { 71 - $type?: "pub.leaflet.blocks.image"; 72 - image: LeafletBlobRef; 73 - alt?: string; 74 - aspectRatio: { 75 - width: number; 76 - height: number; 77 - }; 71 + $type?: "pub.leaflet.blocks.image"; 72 + image: LeafletBlobRef; 73 + alt?: string; 74 + aspectRatio: { 75 + width: number; 76 + height: number; 77 + }; 78 78 } 79 79 80 80 export interface LeafletUnorderedListBlock { 81 - $type?: "pub.leaflet.blocks.unorderedList"; 82 - children: LeafletListItem[]; 81 + $type?: "pub.leaflet.blocks.unorderedList"; 82 + children: LeafletListItem[]; 83 83 } 84 84 85 85 export interface LeafletListItem { 86 - content: LeafletListContent; 87 - children?: LeafletListItem[]; 86 + content: LeafletListContent; 87 + children?: LeafletListItem[]; 88 88 } 89 89 90 - export type LeafletListContent = LeafletTextBlock | LeafletHeaderBlock | LeafletImageBlock; 90 + export type LeafletListContent = 91 + | LeafletTextBlock 92 + | LeafletHeaderBlock 93 + | LeafletImageBlock; 91 94 92 95 export interface LeafletWebsiteBlock { 93 - $type?: "pub.leaflet.blocks.website"; 94 - src: string; 95 - title?: string; 96 - description?: string; 97 - previewImage?: LeafletBlobRef; 96 + $type?: "pub.leaflet.blocks.website"; 97 + src: string; 98 + title?: string; 99 + description?: string; 100 + previewImage?: LeafletBlobRef; 98 101 } 99 102 100 103 export interface LeafletIFrameBlock { 101 - $type?: "pub.leaflet.blocks.iframe"; 102 - url: string; 103 - height?: number; 104 + $type?: "pub.leaflet.blocks.iframe"; 105 + url: string; 106 + height?: number; 104 107 } 105 108 106 109 export interface LeafletMathBlock { 107 - $type?: "pub.leaflet.blocks.math"; 108 - tex: string; 110 + $type?: "pub.leaflet.blocks.math"; 111 + tex: string; 109 112 } 110 113 111 114 export interface LeafletCodeBlock { 112 - $type?: "pub.leaflet.blocks.code"; 113 - plaintext: string; 114 - language?: string; 115 - syntaxHighlightingTheme?: string; 115 + $type?: "pub.leaflet.blocks.code"; 116 + plaintext: string; 117 + language?: string; 118 + syntaxHighlightingTheme?: string; 116 119 } 117 120 118 121 export interface LeafletHorizontalRuleBlock { 119 - $type?: "pub.leaflet.blocks.horizontalRule"; 122 + $type?: "pub.leaflet.blocks.horizontalRule"; 120 123 } 121 124 122 125 export interface LeafletBskyPostBlock { 123 - $type?: "pub.leaflet.blocks.bskyPost"; 124 - postRef: StrongRef; 126 + $type?: "pub.leaflet.blocks.bskyPost"; 127 + postRef: StrongRef; 125 128 } 126 129 127 130 export interface LeafletRichTextFacet { 128 - index: LeafletByteSlice; 129 - features: LeafletRichTextFeature[]; 131 + index: LeafletByteSlice; 132 + features: LeafletRichTextFeature[]; 130 133 } 131 134 132 135 export interface LeafletByteSlice { 133 - byteStart: number; 134 - byteEnd: number; 136 + byteStart: number; 137 + byteEnd: number; 135 138 } 136 139 137 140 export type LeafletRichTextFeature = 138 - | LeafletRichTextLinkFeature 139 - | LeafletRichTextCodeFeature 140 - | LeafletRichTextHighlightFeature 141 - | LeafletRichTextUnderlineFeature 142 - | LeafletRichTextStrikethroughFeature 143 - | LeafletRichTextIdFeature 144 - | LeafletRichTextBoldFeature 145 - | LeafletRichTextItalicFeature; 141 + | LeafletRichTextLinkFeature 142 + | LeafletRichTextCodeFeature 143 + | LeafletRichTextHighlightFeature 144 + | LeafletRichTextUnderlineFeature 145 + | LeafletRichTextStrikethroughFeature 146 + | LeafletRichTextIdFeature 147 + | LeafletRichTextBoldFeature 148 + | LeafletRichTextItalicFeature; 146 149 147 150 export interface LeafletRichTextLinkFeature { 148 - $type: "pub.leaflet.richtext.facet#link"; 149 - uri: string; 151 + $type: "pub.leaflet.richtext.facet#link"; 152 + uri: string; 150 153 } 151 154 152 155 export interface LeafletRichTextCodeFeature { 153 - $type: "pub.leaflet.richtext.facet#code"; 156 + $type: "pub.leaflet.richtext.facet#code"; 154 157 } 155 158 156 159 export interface LeafletRichTextHighlightFeature { 157 - $type: "pub.leaflet.richtext.facet#highlight"; 160 + $type: "pub.leaflet.richtext.facet#highlight"; 158 161 } 159 162 160 163 export interface LeafletRichTextUnderlineFeature { 161 - $type: "pub.leaflet.richtext.facet#underline"; 164 + $type: "pub.leaflet.richtext.facet#underline"; 162 165 } 163 166 164 167 export interface LeafletRichTextStrikethroughFeature { 165 - $type: "pub.leaflet.richtext.facet#strikethrough"; 168 + $type: "pub.leaflet.richtext.facet#strikethrough"; 166 169 } 167 170 168 171 export interface LeafletRichTextIdFeature { 169 - $type: "pub.leaflet.richtext.facet#id"; 170 - id?: string; 172 + $type: "pub.leaflet.richtext.facet#id"; 173 + id?: string; 171 174 } 172 175 173 176 export interface LeafletRichTextBoldFeature { 174 - $type: "pub.leaflet.richtext.facet#bold"; 177 + $type: "pub.leaflet.richtext.facet#bold"; 175 178 } 176 179 177 180 export interface LeafletRichTextItalicFeature { 178 - $type: "pub.leaflet.richtext.facet#italic"; 181 + $type: "pub.leaflet.richtext.facet#italic"; 179 182 } 180 183 181 184 export interface LeafletBlobRef { 182 - $type?: string; 183 - ref?: { 184 - $link?: string; 185 - }; 186 - cid?: string; 187 - mimeType?: string; 188 - size?: number; 185 + $type?: string; 186 + ref?: { 187 + $link?: string; 188 + }; 189 + cid?: string; 190 + mimeType?: string; 191 + size?: number; 189 192 } 190 193 191 194 export interface LeafletPublicationRecord { 192 - $type?: "pub.leaflet.publication"; 193 - name: string; 194 - base_path?: string; 195 - description?: string; 196 - icon?: LeafletBlobRef; 197 - theme?: LeafletTheme; 198 - preferences?: LeafletPublicationPreferences; 195 + $type?: "pub.leaflet.publication"; 196 + name: string; 197 + base_path?: string; 198 + description?: string; 199 + icon?: LeafletBlobRef; 200 + theme?: LeafletTheme; 201 + preferences?: LeafletPublicationPreferences; 199 202 } 200 203 201 204 export interface LeafletPublicationPreferences { 202 - showInDiscover?: boolean; 203 - showComments?: boolean; 205 + showInDiscover?: boolean; 206 + showComments?: boolean; 204 207 } 205 208 206 209 export interface LeafletTheme { 207 - backgroundColor?: LeafletThemeColor; 208 - backgroundImage?: LeafletThemeBackgroundImage; 209 - primary?: LeafletThemeColor; 210 - pageBackground?: LeafletThemeColor; 211 - showPageBackground?: boolean; 212 - accentBackground?: LeafletThemeColor; 213 - accentText?: LeafletThemeColor; 210 + backgroundColor?: LeafletThemeColor; 211 + backgroundImage?: LeafletThemeBackgroundImage; 212 + primary?: LeafletThemeColor; 213 + pageBackground?: LeafletThemeColor; 214 + showPageBackground?: boolean; 215 + accentBackground?: LeafletThemeColor; 216 + accentText?: LeafletThemeColor; 214 217 } 215 218 216 219 export type LeafletThemeColor = LeafletThemeColorRgb | LeafletThemeColorRgba; 217 220 218 221 export interface LeafletThemeColorRgb { 219 - $type?: "pub.leaflet.theme.color#rgb"; 220 - r: number; 221 - g: number; 222 - b: number; 222 + $type?: "pub.leaflet.theme.color#rgb"; 223 + r: number; 224 + g: number; 225 + b: number; 223 226 } 224 227 225 228 export interface LeafletThemeColorRgba { 226 - $type?: "pub.leaflet.theme.color#rgba"; 227 - r: number; 228 - g: number; 229 - b: number; 230 - a: number; 229 + $type?: "pub.leaflet.theme.color#rgba"; 230 + r: number; 231 + g: number; 232 + b: number; 233 + a: number; 231 234 } 232 235 233 236 export interface LeafletThemeBackgroundImage { 234 - $type?: "pub.leaflet.theme.backgroundImage"; 235 - image: LeafletBlobRef; 236 - width?: number; 237 - repeat?: boolean; 237 + $type?: "pub.leaflet.theme.backgroundImage"; 238 + image: LeafletBlobRef; 239 + width?: number; 240 + repeat?: boolean; 238 241 } 239 242 240 - export type LeafletInlineRenderable = LeafletTextBlock | LeafletHeaderBlock | LeafletBlockquoteBlock; 243 + export type LeafletInlineRenderable = 244 + | LeafletTextBlock 245 + | LeafletHeaderBlock 246 + | LeafletBlockquoteBlock;
+22
lib/types/tangled.ts
··· 1 + import type { ShTangledRepo, ShTangledString } from "@atcute/tangled"; 2 + 3 + export type TangledRepoRecord = ShTangledRepo.Main; 4 + export type TangledStringRecord = ShTangledString.Main; 5 + 6 + /** Language information from sh.tangled.repo.languages endpoint */ 7 + export interface RepoLanguage { 8 + name: string; 9 + percentage: number; 10 + size: number; 11 + } 12 + 13 + /** 14 + * Response from sh.tangled.repo.languages endpoint from tangled knot 15 + */ 16 + export interface RepoLanguagesResponse { 17 + languages: RepoLanguage[]; 18 + /** Branch name */ 19 + ref: string; 20 + totalFiles: number; 21 + totalSize: number; 22 + }
+40
lib/types/teal.ts
··· 1 + /** 2 + * teal.fm record types for music listening history 3 + * Specification: fm.teal.alpha.actor.status and fm.teal.alpha.feed.play 4 + */ 5 + 6 + export interface TealArtist { 7 + artistName: string; 8 + artistMbId?: string; 9 + } 10 + 11 + export interface TealPlayItem { 12 + artists: TealArtist[]; 13 + originUrl?: string; 14 + trackName: string; 15 + playedTime: string; 16 + releaseName?: string; 17 + recordingMbId?: string; 18 + releaseMbId?: string; 19 + submissionClientAgent?: string; 20 + musicServiceBaseDomain?: string; 21 + isrc?: string; 22 + duration?: number; 23 + } 24 + 25 + /** 26 + * fm.teal.alpha.actor.status - The last played song 27 + */ 28 + export interface TealActorStatusRecord { 29 + $type: "fm.teal.alpha.actor.status"; 30 + item: TealPlayItem; 31 + time: string; 32 + expiry?: string; 33 + } 34 + 35 + /** 36 + * fm.teal.alpha.feed.play - A single play record 37 + */ 38 + export interface TealFeedPlayRecord extends TealPlayItem { 39 + $type: "fm.teal.alpha.feed.play"; 40 + }
+23
lib/types/theme.ts
··· 1 + export type AtProtoStyles = React.CSSProperties & { 2 + '--atproto-color-bg'?: string; 3 + '--atproto-color-bg-elevated'?: string; 4 + '--atproto-color-bg-secondary'?: string; 5 + '--atproto-color-text'?: string; 6 + '--atproto-color-text-secondary'?: string; 7 + '--atproto-color-text-muted'?: string; 8 + '--atproto-color-border'?: string; 9 + '--atproto-color-border-subtle'?: string; 10 + '--atproto-color-link'?: string; 11 + '--atproto-color-link-hover'?: string; 12 + '--atproto-color-error'?: string; 13 + '--atproto-color-button-bg'?: string; 14 + '--atproto-color-button-hover'?: string; 15 + '--atproto-color-button-text'?: string; 16 + '--atproto-color-code-bg'?: string; 17 + '--atproto-color-code-border'?: string; 18 + '--atproto-color-blockquote-border'?: string; 19 + '--atproto-color-blockquote-bg'?: string; 20 + '--atproto-color-hr'?: string; 21 + '--atproto-color-image-bg'?: string; 22 + '--atproto-color-highlight'?: string; 23 + };
+34 -27
lib/utils/at-uri.ts
··· 1 1 export interface ParsedAtUri { 2 - did: string; 3 - collection: string; 4 - rkey: string; 2 + did: string; 3 + collection: string; 4 + rkey: string; 5 5 } 6 6 7 7 export function parseAtUri(uri?: string): ParsedAtUri | undefined { 8 - if (!uri || !uri.startsWith('at://')) return undefined; 9 - const withoutScheme = uri.slice('at://'.length); 10 - const [did, collection, rkey] = withoutScheme.split('/'); 11 - if (!did || !collection || !rkey) return undefined; 12 - return { did, collection, rkey }; 8 + if (!uri || !uri.startsWith("at://")) return undefined; 9 + const withoutScheme = uri.slice("at://".length); 10 + const [did, collection, rkey] = withoutScheme.split("/"); 11 + if (!did || !collection || !rkey) return undefined; 12 + return { did, collection, rkey }; 13 13 } 14 14 15 15 export function toBlueskyPostUrl(target: ParsedAtUri): string | undefined { 16 - if (target.collection !== 'app.bsky.feed.post') return undefined; 17 - return `https://bsky.app/profile/${target.did}/post/${target.rkey}`; 16 + if (target.collection !== "app.bsky.feed.post") return undefined; 17 + return `https://bsky.app/profile/${target.did}/post/${target.rkey}`; 18 18 } 19 19 20 20 export function formatDidForLabel(did: string): string { 21 - return did.replace(/^did:(plc:)?/, ''); 21 + return did.replace(/^did:(plc:)?/, ""); 22 22 } 23 23 24 24 const ABSOLUTE_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:/; 25 25 26 - export function normalizeLeafletBasePath(basePath?: string): string | undefined { 27 - if (!basePath) return undefined; 28 - const trimmed = basePath.trim(); 29 - if (!trimmed) return undefined; 30 - const withScheme = ABSOLUTE_URL_PATTERN.test(trimmed) ? trimmed : `https://${trimmed}`; 31 - try { 32 - const url = new URL(withScheme); 33 - url.hash = ''; 34 - return url.href.replace(/\/?$/, ''); 35 - } catch { 36 - return undefined; 37 - } 26 + export function normalizeLeafletBasePath( 27 + basePath?: string, 28 + ): string | undefined { 29 + if (!basePath) return undefined; 30 + const trimmed = basePath.trim(); 31 + if (!trimmed) return undefined; 32 + const withScheme = ABSOLUTE_URL_PATTERN.test(trimmed) 33 + ? trimmed 34 + : `https://${trimmed}`; 35 + try { 36 + const url = new URL(withScheme); 37 + url.hash = ""; 38 + return url.href.replace(/\/?$/, ""); 39 + } catch { 40 + return undefined; 41 + } 38 42 } 39 43 40 - export function leafletRkeyUrl(basePath: string | undefined, rkey: string): string | undefined { 41 - const normalized = normalizeLeafletBasePath(basePath); 42 - if (!normalized) return undefined; 43 - return `${normalized}/${encodeURIComponent(rkey)}`; 44 + export function leafletRkeyUrl( 45 + basePath: string | undefined, 46 + rkey: string, 47 + ): string | undefined { 48 + const normalized = normalizeLeafletBasePath(basePath); 49 + if (!normalized) return undefined; 50 + return `${normalized}/${encodeURIComponent(rkey)}`; 44 51 }
+218 -145
lib/utils/atproto-client.ts
··· 1 - import { Client, simpleFetchHandler, type FetchHandler } from '@atcute/client'; 2 - import { CompositeDidDocumentResolver, PlcDidDocumentResolver, WebDidDocumentResolver, XrpcHandleResolver } from '@atcute/identity-resolver'; 3 - import type { DidDocument } from '@atcute/identity'; 4 - import type { Did, Handle } from '@atcute/lexicons/syntax'; 5 - import type {} from '@atcute/tangled'; 6 - import type {} from '@atcute/atproto'; 1 + import { Client, simpleFetchHandler, type FetchHandler } from "@atcute/client"; 2 + import { 3 + CompositeDidDocumentResolver, 4 + PlcDidDocumentResolver, 5 + WebDidDocumentResolver, 6 + XrpcHandleResolver, 7 + } from "@atcute/identity-resolver"; 8 + import type { DidDocument } from "@atcute/identity"; 9 + import type { Did, Handle } from "@atcute/lexicons/syntax"; 10 + import type {} from "@atcute/tangled"; 11 + import type {} from "@atcute/atproto"; 7 12 8 13 export interface ServiceResolverOptions { 9 - plcDirectory?: string; 10 - identityService?: string; 11 - fetch?: typeof fetch; 14 + plcDirectory?: string; 15 + identityService?: string; 16 + slingshotBaseUrl?: string; 17 + fetch?: typeof fetch; 12 18 } 13 19 14 - const DEFAULT_PLC = 'https://plc.directory'; 15 - const DEFAULT_IDENTITY_SERVICE = 'https://public.api.bsky.app'; 20 + const DEFAULT_PLC = "https://plc.directory"; 21 + const DEFAULT_IDENTITY_SERVICE = "https://public.api.bsky.app"; 22 + const DEFAULT_SLINGSHOT = "https://slingshot.microcosm.blue"; 23 + const DEFAULT_APPVIEW = "https://public.api.bsky.app"; 24 + const DEFAULT_BLUESKY_APP = "https://bsky.app"; 25 + const DEFAULT_TANGLED = "https://tangled.org"; 26 + const DEFAULT_CONSTELLATION = "https://constellation.microcosm.blue"; 27 + 16 28 const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/; 17 - const SUPPORTED_DID_METHODS = ['plc', 'web'] as const; 29 + const SUPPORTED_DID_METHODS = ["plc", "web"] as const; 18 30 type SupportedDidMethod = (typeof SUPPORTED_DID_METHODS)[number]; 19 31 type SupportedDid = Did<SupportedDidMethod>; 20 32 21 - export const SLINGSHOT_BASE_URL = 'https://slingshot.microcosm.blue'; 33 + /** 34 + * Default configuration values for AT Protocol services. 35 + * These can be overridden via AtProtoProvider props. 36 + */ 37 + export const DEFAULT_CONFIG = { 38 + plcDirectory: DEFAULT_PLC, 39 + identityService: DEFAULT_IDENTITY_SERVICE, 40 + slingshotBaseUrl: DEFAULT_SLINGSHOT, 41 + blueskyAppviewService: DEFAULT_APPVIEW, 42 + blueskyAppBaseUrl: DEFAULT_BLUESKY_APP, 43 + tangledBaseUrl: DEFAULT_TANGLED, 44 + constellationBaseUrl: DEFAULT_CONSTELLATION, 45 + } as const; 46 + 47 + export const SLINGSHOT_BASE_URL = DEFAULT_SLINGSHOT; 22 48 23 49 export const normalizeBaseUrl = (input: string): string => { 24 - const trimmed = input.trim(); 25 - if (!trimmed) throw new Error('Service URL cannot be empty'); 26 - const withScheme = ABSOLUTE_URL_RE.test(trimmed) ? trimmed : `https://${trimmed.replace(/^\/+/, '')}`; 27 - const url = new URL(withScheme); 28 - const pathname = url.pathname.replace(/\/+$/, ''); 29 - return pathname ? `${url.origin}${pathname}` : url.origin; 50 + const trimmed = input.trim(); 51 + if (!trimmed) throw new Error("Service URL cannot be empty"); 52 + const withScheme = ABSOLUTE_URL_RE.test(trimmed) 53 + ? trimmed 54 + : `https://${trimmed.replace(/^\/+/, "")}`; 55 + const url = new URL(withScheme); 56 + const pathname = url.pathname.replace(/\/+$/, ""); 57 + return pathname ? `${url.origin}${pathname}` : url.origin; 30 58 }; 31 59 32 60 export class ServiceResolver { 33 - private plc: string; 34 - private didResolver: CompositeDidDocumentResolver<SupportedDidMethod>; 35 - private handleResolver: XrpcHandleResolver; 36 - private fetchImpl: typeof fetch; 37 - constructor(opts: ServiceResolverOptions = {}) { 38 - const plcSource = opts.plcDirectory && opts.plcDirectory.trim() ? opts.plcDirectory : DEFAULT_PLC; 39 - const identitySource = opts.identityService && opts.identityService.trim() ? opts.identityService : DEFAULT_IDENTITY_SERVICE; 40 - this.plc = normalizeBaseUrl(plcSource); 41 - const identityBase = normalizeBaseUrl(identitySource); 42 - this.fetchImpl = bindFetch(opts.fetch); 43 - const plcResolver = new PlcDidDocumentResolver({ apiUrl: this.plc, fetch: this.fetchImpl }); 44 - const webResolver = new WebDidDocumentResolver({ fetch: this.fetchImpl }); 45 - this.didResolver = new CompositeDidDocumentResolver({ methods: { plc: plcResolver, web: webResolver } }); 46 - this.handleResolver = new XrpcHandleResolver({ serviceUrl: identityBase, fetch: this.fetchImpl }); 47 - } 61 + private plc: string; 62 + private slingshot: string; 63 + private didResolver: CompositeDidDocumentResolver<SupportedDidMethod>; 64 + private handleResolver: XrpcHandleResolver; 65 + private fetchImpl: typeof fetch; 66 + constructor(opts: ServiceResolverOptions = {}) { 67 + const plcSource = 68 + opts.plcDirectory && opts.plcDirectory.trim() 69 + ? opts.plcDirectory 70 + : DEFAULT_PLC; 71 + const identitySource = 72 + opts.identityService && opts.identityService.trim() 73 + ? opts.identityService 74 + : DEFAULT_IDENTITY_SERVICE; 75 + const slingshotSource = 76 + opts.slingshotBaseUrl && opts.slingshotBaseUrl.trim() 77 + ? opts.slingshotBaseUrl 78 + : DEFAULT_SLINGSHOT; 79 + this.plc = normalizeBaseUrl(plcSource); 80 + const identityBase = normalizeBaseUrl(identitySource); 81 + this.slingshot = normalizeBaseUrl(slingshotSource); 82 + this.fetchImpl = bindFetch(opts.fetch); 83 + const plcResolver = new PlcDidDocumentResolver({ 84 + apiUrl: this.plc, 85 + fetch: this.fetchImpl, 86 + }); 87 + const webResolver = new WebDidDocumentResolver({ 88 + fetch: this.fetchImpl, 89 + }); 90 + this.didResolver = new CompositeDidDocumentResolver({ 91 + methods: { plc: plcResolver, web: webResolver }, 92 + }); 93 + this.handleResolver = new XrpcHandleResolver({ 94 + serviceUrl: identityBase, 95 + fetch: this.fetchImpl, 96 + }); 97 + } 48 98 49 - async resolveDidDoc(did: string): Promise<DidDocument> { 50 - const trimmed = did.trim(); 51 - if (!trimmed.startsWith('did:')) throw new Error(`Invalid DID ${did}`); 52 - const methodEnd = trimmed.indexOf(':', 4); 53 - const method = (methodEnd === -1 ? trimmed.slice(4) : trimmed.slice(4, methodEnd)) as string; 54 - if (!SUPPORTED_DID_METHODS.includes(method as SupportedDidMethod)) { 55 - throw new Error(`Unsupported DID method ${method ?? '<unknown>'}`); 56 - } 57 - return this.didResolver.resolve(trimmed as SupportedDid); 58 - } 99 + async resolveDidDoc(did: string): Promise<DidDocument> { 100 + const trimmed = did.trim(); 101 + if (!trimmed.startsWith("did:")) throw new Error(`Invalid DID ${did}`); 102 + const methodEnd = trimmed.indexOf(":", 4); 103 + const method = ( 104 + methodEnd === -1 ? trimmed.slice(4) : trimmed.slice(4, methodEnd) 105 + ) as string; 106 + if (!SUPPORTED_DID_METHODS.includes(method as SupportedDidMethod)) { 107 + throw new Error(`Unsupported DID method ${method ?? "<unknown>"}`); 108 + } 109 + return this.didResolver.resolve(trimmed as SupportedDid); 110 + } 111 + 112 + async pdsEndpointForDid(did: string): Promise<string> { 113 + const doc = await this.resolveDidDoc(did); 114 + const svc = doc.service?.find( 115 + (s) => s.type === "AtprotoPersonalDataServer", 116 + ); 117 + if ( 118 + !svc || 119 + !svc.serviceEndpoint || 120 + typeof svc.serviceEndpoint !== "string" 121 + ) { 122 + throw new Error(`No PDS endpoint in DID doc for ${did}`); 123 + } 124 + return svc.serviceEndpoint.replace(/\/$/, ""); 125 + } 59 126 60 - async pdsEndpointForDid(did: string): Promise<string> { 61 - const doc = await this.resolveDidDoc(did); 62 - const svc = doc.service?.find(s => s.type === 'AtprotoPersonalDataServer'); 63 - if (!svc || !svc.serviceEndpoint || typeof svc.serviceEndpoint !== 'string') { 64 - throw new Error(`No PDS endpoint in DID doc for ${did}`); 65 - } 66 - return svc.serviceEndpoint.replace(/\/$/, ''); 67 - } 127 + getSlingshotUrl(): string { 128 + return this.slingshot; 129 + } 68 130 69 - async resolveHandle(handle: string): Promise<string> { 70 - const normalized = handle.trim().toLowerCase(); 71 - if (!normalized) throw new Error('Handle cannot be empty'); 72 - let slingshotError: Error | undefined; 73 - try { 74 - const url = new URL('/xrpc/com.atproto.identity.resolveHandle', SLINGSHOT_BASE_URL); 75 - url.searchParams.set('handle', normalized); 76 - const response = await this.fetchImpl(url); 77 - if (response.ok) { 78 - const payload = await response.json() as { did?: string } | null; 79 - if (payload?.did) { 80 - console.info('[slingshot] resolveHandle cache hit', { handle: normalized }); 81 - return payload.did; 82 - } 83 - slingshotError = new Error('Slingshot resolveHandle response missing DID'); 84 - console.warn('[slingshot] resolveHandle payload missing DID; falling back', { handle: normalized }); 85 - } else { 86 - slingshotError = new Error(`Slingshot resolveHandle failed with status ${response.status}`); 87 - const body = response.body; 88 - if (body) { 89 - body.cancel().catch(() => {}); 90 - } 91 - console.info('[slingshot] resolveHandle cache miss', { handle: normalized, status: response.status }); 92 - } 93 - } catch (err) { 94 - if (err instanceof DOMException && err.name === 'AbortError') throw err; 95 - slingshotError = err instanceof Error ? err : new Error(String(err)); 96 - console.warn('[slingshot] resolveHandle error; falling back to identity service', { handle: normalized, error: slingshotError }); 97 - } 131 + async resolveHandle(handle: string): Promise<string> { 132 + const normalized = handle.trim().toLowerCase(); 133 + if (!normalized) throw new Error("Handle cannot be empty"); 134 + let slingshotError: Error | undefined; 135 + try { 136 + const url = new URL( 137 + "/xrpc/com.atproto.identity.resolveHandle", 138 + this.slingshot, 139 + ); 140 + url.searchParams.set("handle", normalized); 141 + const response = await this.fetchImpl(url); 142 + if (response.ok) { 143 + const payload = (await response.json()) as { 144 + did?: string; 145 + } | null; 146 + if (payload?.did) { 147 + return payload.did; 148 + } 149 + slingshotError = new Error( 150 + "Slingshot resolveHandle response missing DID", 151 + ); 152 + } else { 153 + slingshotError = new Error( 154 + `Slingshot resolveHandle failed with status ${response.status}`, 155 + ); 156 + const body = response.body; 157 + if (body) { 158 + body.cancel().catch(() => {}); 159 + } 160 + } 161 + } catch (err) { 162 + if (err instanceof DOMException && err.name === "AbortError") 163 + throw err; 164 + slingshotError = 165 + err instanceof Error ? err : new Error(String(err)); 166 + } 98 167 99 - try { 100 - const did = await this.handleResolver.resolve(normalized as Handle); 101 - if (slingshotError) { 102 - console.info('[slingshot] resolveHandle fallback succeeded', { handle: normalized }); 103 - } 104 - return did; 105 - } catch (err) { 106 - if (slingshotError && err instanceof Error) { 107 - const prior = err.message; 108 - err.message = `${prior}; Slingshot resolveHandle failed: ${slingshotError.message}`; 109 - if (slingshotError) { 110 - console.warn('[slingshot] resolveHandle fallback failed', { handle: normalized, error: slingshotError }); 111 - } 112 - } 113 - throw err; 114 - } 115 - } 168 + try { 169 + const did = await this.handleResolver.resolve(normalized as Handle); 170 + return did; 171 + } catch (err) { 172 + if (slingshotError && err instanceof Error) { 173 + const prior = err.message; 174 + err.message = `${prior}; Slingshot resolveHandle failed: ${slingshotError.message}`; 175 + } 176 + throw err; 177 + } 178 + } 116 179 } 117 180 118 181 export interface CreateClientOptions extends ServiceResolverOptions { 119 - did?: string; // optional to create a DID-scoped client 120 - service?: string; // override service base url 182 + did?: string; // optional to create a DID-scoped client 183 + service?: string; // override service base url 121 184 } 122 185 123 186 export async function createAtprotoClient(opts: CreateClientOptions = {}) { 124 - const fetchImpl = bindFetch(opts.fetch); 125 - let service = opts.service; 126 - const resolver = new ServiceResolver({ ...opts, fetch: fetchImpl }); 127 - if (!service && opts.did) { 128 - service = await resolver.pdsEndpointForDid(opts.did); 129 - } 130 - if (!service) throw new Error('service or did required'); 131 - const normalizedService = normalizeBaseUrl(service); 132 - const handler = createSlingshotAwareHandler(normalizedService, fetchImpl); 133 - const rpc = new Client({ handler }); 134 - return { rpc, service: normalizedService, resolver }; 187 + const fetchImpl = bindFetch(opts.fetch); 188 + let service = opts.service; 189 + const resolver = new ServiceResolver({ ...opts, fetch: fetchImpl }); 190 + if (!service && opts.did) { 191 + service = await resolver.pdsEndpointForDid(opts.did); 192 + } 193 + if (!service) throw new Error("service or did required"); 194 + const normalizedService = normalizeBaseUrl(service); 195 + const slingshotUrl = resolver.getSlingshotUrl(); 196 + const handler = createSlingshotAwareHandler(normalizedService, slingshotUrl, fetchImpl); 197 + const rpc = new Client({ handler }); 198 + return { rpc, service: normalizedService, resolver }; 135 199 } 136 200 137 - export type AtprotoClient = Awaited<ReturnType<typeof createAtprotoClient>>['rpc']; 201 + export type AtprotoClient = Awaited< 202 + ReturnType<typeof createAtprotoClient> 203 + >["rpc"]; 138 204 139 205 const SLINGSHOT_RETRY_PATHS = [ 140 - '/xrpc/com.atproto.repo.getRecord', 141 - '/xrpc/com.atproto.identity.resolveHandle', 206 + "/xrpc/com.atproto.repo.getRecord", 207 + "/xrpc/com.atproto.identity.resolveHandle", 142 208 ]; 143 209 144 - function createSlingshotAwareHandler(service: string, fetchImpl: typeof fetch): FetchHandler { 145 - const primary = simpleFetchHandler({ service, fetch: fetchImpl }); 146 - const slingshot = simpleFetchHandler({ service: SLINGSHOT_BASE_URL, fetch: fetchImpl }); 147 - return async (pathname, init) => { 148 - const matched = SLINGSHOT_RETRY_PATHS.find(candidate => pathname === candidate || pathname.startsWith(`${candidate}?`)); 149 - if (matched) { 150 - try { 151 - const slingshotResponse = await slingshot(pathname, init); 152 - if (slingshotResponse.ok) { 153 - console.info(`[slingshot] cache hit for ${matched}`); 154 - return slingshotResponse; 155 - } 156 - const body = slingshotResponse.body; 157 - if (body) { 158 - body.cancel().catch(() => {}); 159 - } 160 - console.info(`[slingshot] cache miss ${slingshotResponse.status} for ${matched}, falling back to ${service}`); 161 - } catch (err) { 162 - if (err instanceof DOMException && err.name === 'AbortError') { 163 - throw err; 164 - } 165 - console.warn(`[slingshot] fetch error for ${matched}, falling back to ${service}`, err); 166 - } 167 - } 168 - return primary(pathname, init); 169 - }; 210 + function createSlingshotAwareHandler( 211 + service: string, 212 + slingshotBaseUrl: string, 213 + fetchImpl: typeof fetch, 214 + ): FetchHandler { 215 + const primary = simpleFetchHandler({ service, fetch: fetchImpl }); 216 + const slingshot = simpleFetchHandler({ 217 + service: slingshotBaseUrl, 218 + fetch: fetchImpl, 219 + }); 220 + return async (pathname, init) => { 221 + const matched = SLINGSHOT_RETRY_PATHS.find( 222 + (candidate) => 223 + pathname === candidate || pathname.startsWith(`${candidate}?`), 224 + ); 225 + if (matched) { 226 + try { 227 + const slingshotResponse = await slingshot(pathname, init); 228 + if (slingshotResponse.ok) { 229 + return slingshotResponse; 230 + } 231 + const body = slingshotResponse.body; 232 + if (body) { 233 + body.cancel().catch(() => {}); 234 + } 235 + } catch (err) { 236 + if (err instanceof DOMException && err.name === "AbortError") { 237 + throw err; 238 + } 239 + } 240 + } 241 + return primary(pathname, init); 242 + }; 170 243 } 171 244 172 245 function bindFetch(fetchImpl?: typeof fetch): typeof fetch { 173 - const impl = fetchImpl ?? globalThis.fetch; 174 - if (typeof impl !== 'function') { 175 - throw new Error('fetch implementation not available'); 176 - } 177 - return impl.bind(globalThis); 246 + const impl = fetchImpl ?? globalThis.fetch; 247 + if (typeof impl !== "function") { 248 + throw new Error("fetch implementation not available"); 249 + } 250 + return impl.bind(globalThis); 178 251 }
+37
lib/utils/blob.ts
··· 1 + import type { BlobWithCdn } from "../hooks/useBlueskyAppview"; 2 + 3 + /** 4 + * Type guard to check if a blob has a CDN URL from appview. 5 + */ 6 + export function isBlobWithCdn(value: unknown): value is BlobWithCdn { 7 + if (typeof value !== "object" || value === null) return false; 8 + const obj = value as Record<string, unknown>; 9 + return ( 10 + obj.$type === "blob" && 11 + typeof obj.cdnUrl === "string" && 12 + typeof obj.ref === "object" && 13 + obj.ref !== null && 14 + typeof (obj.ref as { $link?: unknown }).$link === "string" 15 + ); 16 + } 17 + 18 + /** 19 + * Extracts CID from a blob reference object. 20 + * Works with both legacy and modern blob formats. 21 + */ 22 + export function extractCidFromBlob(blob: unknown): string | undefined { 23 + if (typeof blob !== "object" || blob === null) return undefined; 24 + 25 + const blobObj = blob as { 26 + ref?: { $link?: string }; 27 + cid?: string; 28 + }; 29 + 30 + if (typeof blobObj.cid === "string") return blobObj.cid; 31 + if (typeof blobObj.ref === "object" && blobObj.ref !== null) { 32 + const link = blobObj.ref.$link; 33 + if (typeof link === "string") return link; 34 + } 35 + 36 + return undefined; 37 + }
+201 -22
lib/utils/cache.ts
··· 1 - import type { DidDocument } from '@atcute/identity'; 2 - import { ServiceResolver } from './atproto-client'; 1 + import type { DidDocument } from "@atcute/identity"; 2 + import { ServiceResolver } from "./atproto-client"; 3 3 4 4 interface DidCacheEntry { 5 5 did: string; ··· 7 7 doc?: DidDocument; 8 8 pdsEndpoint?: string; 9 9 timestamp: number; 10 + snapshot?: DidCacheSnapshot; // Memoized snapshot to prevent rerenders 10 11 } 11 12 12 13 export interface DidCacheSnapshot { ··· 16 17 pdsEndpoint?: string; 17 18 } 18 19 19 - const toSnapshot = (entry: DidCacheEntry | undefined): DidCacheSnapshot | undefined => { 20 + const toSnapshot = ( 21 + entry: DidCacheEntry | undefined, 22 + ): DidCacheSnapshot | undefined => { 20 23 if (!entry) return undefined; 24 + // Return memoized snapshot if it exists 25 + if (entry.snapshot) return entry.snapshot; 26 + // Create and cache new snapshot 21 27 const { did, handle, doc, pdsEndpoint } = entry; 22 - return { did, handle, doc, pdsEndpoint }; 28 + const snapshot = { did, handle, doc, pdsEndpoint }; 29 + entry.snapshot = snapshot; 30 + return snapshot; 23 31 }; 24 32 25 - const derivePdsEndpoint = (doc: DidDocument | undefined): string | undefined => { 33 + const derivePdsEndpoint = ( 34 + doc: DidDocument | undefined, 35 + ): string | undefined => { 26 36 if (!doc?.service) return undefined; 27 - const svc = doc.service.find(service => service.type === 'AtprotoPersonalDataServer'); 37 + const svc = doc.service.find( 38 + (service) => service.type === "AtprotoPersonalDataServer", 39 + ); 28 40 if (!svc) return undefined; 29 - const endpoint = typeof svc.serviceEndpoint === 'string' ? svc.serviceEndpoint : undefined; 41 + const endpoint = 42 + typeof svc.serviceEndpoint === "string" 43 + ? svc.serviceEndpoint 44 + : undefined; 30 45 if (!endpoint) return undefined; 31 - return endpoint.replace(/\/$/, ''); 46 + return endpoint.replace(/\/$/, ""); 32 47 }; 33 48 34 49 export class DidCache { ··· 48 63 return toSnapshot(this.byDid.get(did)); 49 64 } 50 65 51 - memoize(entry: { did: string; handle?: string; doc?: DidDocument; pdsEndpoint?: string }): DidCacheSnapshot { 66 + memoize(entry: { 67 + did: string; 68 + handle?: string; 69 + doc?: DidDocument; 70 + pdsEndpoint?: string; 71 + }): DidCacheSnapshot { 52 72 const did = entry.did; 53 73 const normalizedHandle = entry.handle?.toLowerCase(); 54 - const existing = this.byDid.get(did) ?? (normalizedHandle ? this.byHandle.get(normalizedHandle) : undefined); 74 + const existing = 75 + this.byDid.get(did) ?? 76 + (normalizedHandle 77 + ? this.byHandle.get(normalizedHandle) 78 + : undefined); 55 79 56 80 const doc = entry.doc ?? existing?.doc; 57 81 const handle = normalizedHandle ?? existing?.handle; 58 - const pdsEndpoint = entry.pdsEndpoint ?? derivePdsEndpoint(doc) ?? existing?.pdsEndpoint; 82 + const pdsEndpoint = 83 + entry.pdsEndpoint ?? 84 + derivePdsEndpoint(doc) ?? 85 + existing?.pdsEndpoint; 59 86 87 + // Check if data has changed - if not, reuse existing snapshot 88 + if ( 89 + existing && 90 + existing.did === did && 91 + existing.handle === handle && 92 + existing.doc === doc && 93 + existing.pdsEndpoint === pdsEndpoint 94 + ) { 95 + // Data unchanged, return existing memoized snapshot 96 + return toSnapshot(existing) as DidCacheSnapshot; 97 + } 98 + 99 + // Data changed, create new entry (snapshot will be created on first access) 60 100 const merged: DidCacheEntry = { 61 101 did, 62 102 handle, 63 103 doc, 64 104 pdsEndpoint, 65 105 timestamp: Date.now(), 106 + snapshot: undefined, // Will be created lazily by toSnapshot 66 107 }; 67 108 68 109 this.byDid.set(did, merged); ··· 73 114 return toSnapshot(merged) as DidCacheSnapshot; 74 115 } 75 116 76 - ensureHandle(resolver: ServiceResolver, handle: string): Promise<DidCacheSnapshot> { 117 + ensureHandle( 118 + resolver: ServiceResolver, 119 + handle: string, 120 + ): Promise<DidCacheSnapshot> { 77 121 const normalized = handle.toLowerCase(); 78 122 const cached = this.getByHandle(normalized); 79 123 if (cached?.did) return Promise.resolve(cached); ··· 81 125 if (pending) return pending; 82 126 const promise = resolver 83 127 .resolveHandle(normalized) 84 - .then(did => this.memoize({ did, handle: normalized })) 128 + .then((did) => this.memoize({ did, handle: normalized })) 85 129 .finally(() => { 86 130 this.handlePromises.delete(normalized); 87 131 }); ··· 89 133 return promise; 90 134 } 91 135 92 - ensureDidDoc(resolver: ServiceResolver, did: string): Promise<DidCacheSnapshot> { 136 + ensureDidDoc( 137 + resolver: ServiceResolver, 138 + did: string, 139 + ): Promise<DidCacheSnapshot> { 93 140 const cached = this.getByDid(did); 94 - if (cached?.doc && cached.handle !== undefined) return Promise.resolve(cached); 141 + if (cached?.doc && cached.handle !== undefined) 142 + return Promise.resolve(cached); 95 143 const pending = this.docPromises.get(did); 96 144 if (pending) return pending; 97 145 const promise = resolver 98 146 .resolveDidDoc(did) 99 - .then(doc => { 100 - const aka = doc.alsoKnownAs?.find(a => a.startsWith('at://')); 101 - const handle = aka ? aka.replace('at://', '').toLowerCase() : cached?.handle; 147 + .then((doc) => { 148 + const aka = doc.alsoKnownAs?.find((a) => a.startsWith("at://")); 149 + const handle = aka 150 + ? aka.replace("at://", "").toLowerCase() 151 + : cached?.handle; 102 152 return this.memoize({ did, handle, doc }); 103 153 }) 104 154 .finally(() => { ··· 108 158 return promise; 109 159 } 110 160 111 - ensurePdsEndpoint(resolver: ServiceResolver, did: string): Promise<DidCacheSnapshot> { 161 + ensurePdsEndpoint( 162 + resolver: ServiceResolver, 163 + did: string, 164 + ): Promise<DidCacheSnapshot> { 112 165 const cached = this.getByDid(did); 113 166 if (cached?.pdsEndpoint) return Promise.resolve(cached); 114 167 const pending = this.pdsPromises.get(did); 115 168 if (pending) return pending; 116 169 const promise = (async () => { 117 - const docSnapshot = await this.ensureDidDoc(resolver, did).catch(() => undefined); 170 + const docSnapshot = await this.ensureDidDoc(resolver, did).catch( 171 + () => undefined, 172 + ); 118 173 if (docSnapshot?.pdsEndpoint) return docSnapshot; 119 174 const endpoint = await resolver.pdsEndpointForDid(did); 120 175 return this.memoize({ did, pdsEndpoint: endpoint }); ··· 159 214 this.store.set(this.key(did, cid), { blob, timestamp: Date.now() }); 160 215 } 161 216 162 - ensure(did: string, cid: string, loader: () => { promise: Promise<Blob>; abort: () => void }): EnsureResult { 217 + ensure( 218 + did: string, 219 + cid: string, 220 + loader: () => { promise: Promise<Blob>; abort: () => void }, 221 + ): EnsureResult { 163 222 const cached = this.get(did, cid); 164 223 if (cached) { 165 224 return { promise: Promise.resolve(cached), release: () => {} }; ··· 176 235 } 177 236 178 237 const { promise, abort } = loader(); 179 - const wrapped = promise.then(blob => { 238 + const wrapped = promise.then((blob) => { 180 239 this.set(did, cid, blob); 181 240 return blob; 182 241 }); ··· 211 270 } 212 271 } 213 272 } 273 + 274 + interface RecordCacheEntry<T = unknown> { 275 + record: T; 276 + timestamp: number; 277 + } 278 + 279 + interface InFlightRecordEntry<T = unknown> { 280 + promise: Promise<T>; 281 + abort: () => void; 282 + refCount: number; 283 + } 284 + 285 + interface RecordEnsureResult<T = unknown> { 286 + promise: Promise<T>; 287 + release: () => void; 288 + } 289 + 290 + export class RecordCache { 291 + private store = new Map<string, RecordCacheEntry>(); 292 + private inFlight = new Map<string, InFlightRecordEntry>(); 293 + // Collections that should not be cached (e.g., status records that change frequently) 294 + private noCacheCollections = new Set<string>([ 295 + "fm.teal.alpha.actor.status", 296 + "fm.teal.alpha.feed.play", 297 + ]); 298 + 299 + private key(did: string, collection: string, rkey: string): string { 300 + return `${did}::${collection}::${rkey}`; 301 + } 302 + 303 + private shouldCache(collection: string): boolean { 304 + return !this.noCacheCollections.has(collection); 305 + } 306 + 307 + get<T = unknown>( 308 + did?: string, 309 + collection?: string, 310 + rkey?: string, 311 + ): T | undefined { 312 + if (!did || !collection || !rkey) return undefined; 313 + // Don't return cached data for non-cacheable collections 314 + if (!this.shouldCache(collection)) return undefined; 315 + return this.store.get(this.key(did, collection, rkey))?.record as 316 + | T 317 + | undefined; 318 + } 319 + 320 + set<T = unknown>( 321 + did: string, 322 + collection: string, 323 + rkey: string, 324 + record: T, 325 + ): void { 326 + // Don't cache records for non-cacheable collections 327 + if (!this.shouldCache(collection)) return; 328 + this.store.set(this.key(did, collection, rkey), { 329 + record, 330 + timestamp: Date.now(), 331 + }); 332 + } 333 + 334 + ensure<T = unknown>( 335 + did: string, 336 + collection: string, 337 + rkey: string, 338 + loader: () => { promise: Promise<T>; abort: () => void }, 339 + ): RecordEnsureResult<T> { 340 + const cached = this.get<T>(did, collection, rkey); 341 + if (cached !== undefined) { 342 + return { promise: Promise.resolve(cached), release: () => {} }; 343 + } 344 + 345 + const key = this.key(did, collection, rkey); 346 + const existing = this.inFlight.get(key) as 347 + | InFlightRecordEntry<T> 348 + | undefined; 349 + if (existing) { 350 + existing.refCount += 1; 351 + return { 352 + promise: existing.promise, 353 + release: () => this.release(key), 354 + }; 355 + } 356 + 357 + const { promise, abort } = loader(); 358 + const wrapped = promise.then((record) => { 359 + this.set(did, collection, rkey, record); 360 + return record; 361 + }); 362 + 363 + const entry: InFlightRecordEntry<T> = { 364 + promise: wrapped, 365 + abort, 366 + refCount: 1, 367 + }; 368 + 369 + this.inFlight.set(key, entry as InFlightRecordEntry); 370 + 371 + wrapped 372 + .catch(() => {}) 373 + .finally(() => { 374 + this.inFlight.delete(key); 375 + }); 376 + 377 + return { 378 + promise: wrapped, 379 + release: () => this.release(key), 380 + }; 381 + } 382 + 383 + private release(key: string) { 384 + const entry = this.inFlight.get(key); 385 + if (!entry) return; 386 + entry.refCount -= 1; 387 + if (entry.refCount <= 0) { 388 + this.inFlight.delete(key); 389 + entry.abort(); 390 + } 391 + } 392 + }
+5 -3
lib/utils/profile.ts
··· 1 - import type { ProfileRecord } from '../types/bluesky'; 1 + import type { ProfileRecord } from "../types/bluesky"; 2 2 3 3 interface LegacyBlobRef { 4 4 ref?: { $link?: string }; 5 5 cid?: string; 6 6 } 7 7 8 - export function getAvatarCid(record: ProfileRecord | undefined): string | undefined { 8 + export function getAvatarCid( 9 + record: ProfileRecord | undefined, 10 + ): string | undefined { 9 11 const avatar = record?.avatar as LegacyBlobRef | undefined; 10 12 if (!avatar) return undefined; 11 - if (typeof avatar.cid === 'string') return avatar.cid; 13 + if (typeof avatar.cid === "string") return avatar.cid; 12 14 return avatar.ref?.$link; 13 15 }
+120
lib/utils/richtext.ts
··· 1 + import type { AppBskyRichtextFacet } from "@atcute/bluesky"; 2 + 3 + export interface TextSegment { 4 + text: string; 5 + facet?: AppBskyRichtextFacet.Main; 6 + } 7 + 8 + /** 9 + * Converts a text string with facets into segments that can be rendered 10 + * with appropriate styling and interactivity. 11 + */ 12 + export function createTextSegments( 13 + text: string, 14 + facets?: AppBskyRichtextFacet.Main[], 15 + ): TextSegment[] { 16 + if (!facets || facets.length === 0) { 17 + return [{ text }]; 18 + } 19 + 20 + // Build byte-to-char index mapping 21 + const bytePrefix = buildBytePrefix(text); 22 + 23 + // Sort facets by start position 24 + const sortedFacets = [...facets].sort( 25 + (a, b) => a.index.byteStart - b.index.byteStart, 26 + ); 27 + 28 + const segments: TextSegment[] = []; 29 + let currentPos = 0; 30 + 31 + for (const facet of sortedFacets) { 32 + const startChar = byteOffsetToCharIndex(bytePrefix, facet.index.byteStart); 33 + const endChar = byteOffsetToCharIndex(bytePrefix, facet.index.byteEnd); 34 + 35 + // Add plain text before this facet 36 + if (startChar > currentPos) { 37 + segments.push({ 38 + text: sliceByCharRange(text, currentPos, startChar), 39 + }); 40 + } 41 + 42 + // Add the faceted text 43 + segments.push({ 44 + text: sliceByCharRange(text, startChar, endChar), 45 + facet, 46 + }); 47 + 48 + currentPos = endChar; 49 + } 50 + 51 + // Add remaining plain text 52 + if (currentPos < text.length) { 53 + segments.push({ 54 + text: sliceByCharRange(text, currentPos, text.length), 55 + }); 56 + } 57 + 58 + return segments; 59 + } 60 + 61 + /** 62 + * Builds a byte offset prefix array for UTF-8 encoded text. 63 + * This handles multi-byte characters correctly. 64 + */ 65 + function buildBytePrefix(text: string): number[] { 66 + const encoder = new TextEncoder(); 67 + const prefix: number[] = [0]; 68 + let byteCount = 0; 69 + 70 + for (let i = 0; i < text.length; ) { 71 + const codePoint = text.codePointAt(i); 72 + if (codePoint === undefined) break; 73 + 74 + const char = String.fromCodePoint(codePoint); 75 + const encoded = encoder.encode(char); 76 + byteCount += encoded.length; 77 + prefix.push(byteCount); 78 + 79 + // Handle surrogate pairs (emojis, etc.) 80 + i += codePoint > 0xffff ? 2 : 1; 81 + } 82 + 83 + return prefix; 84 + } 85 + 86 + /** 87 + * Converts a byte offset to a character index using the byte prefix array. 88 + */ 89 + function byteOffsetToCharIndex(prefix: number[], byteOffset: number): number { 90 + for (let i = 0; i < prefix.length; i++) { 91 + if (prefix[i] === byteOffset) return i; 92 + if (prefix[i] > byteOffset) return Math.max(0, i - 1); 93 + } 94 + return prefix.length - 1; 95 + } 96 + 97 + /** 98 + * Slices text by character range, handling multi-byte characters correctly. 99 + */ 100 + function sliceByCharRange(text: string, start: number, end: number): string { 101 + if (start <= 0 && end >= text.length) return text; 102 + 103 + let result = ""; 104 + let charIndex = 0; 105 + 106 + for (let i = 0; i < text.length && charIndex < end; ) { 107 + const codePoint = text.codePointAt(i); 108 + if (codePoint === undefined) break; 109 + 110 + const char = String.fromCodePoint(codePoint); 111 + if (charIndex >= start && charIndex < end) { 112 + result += char; 113 + } 114 + 115 + i += codePoint > 0xffff ? 2 : 1; 116 + charIndex++; 117 + } 118 + 119 + return result; 120 + }
+4073 -2861
package-lock.json
··· 1 1 { 2 - "name": "atproto-ui", 3 - "version": "0.2.0", 4 - "lockfileVersion": 3, 5 - "requires": true, 6 - "packages": { 7 - "": { 8 - "name": "atproto-ui", 9 - "version": "0.2.0", 10 - "dependencies": { 11 - "@atcute/atproto": "^3.1.7", 12 - "@atcute/bluesky": "^3.2.3", 13 - "@atcute/client": "^4.0.3", 14 - "@atcute/identity-resolver": "^1.1.3", 15 - "@atcute/tangled": "^1.0.6" 16 - }, 17 - "devDependencies": { 18 - "@eslint/js": "^9.36.0", 19 - "@types/node": "^24.6.0", 20 - "@types/react": "^19.1.16", 21 - "@types/react-dom": "^19.1.9", 22 - "@vitejs/plugin-react": "^5.0.4", 23 - "eslint": "^9.36.0", 24 - "eslint-plugin-react-hooks": "^5.2.0", 25 - "eslint-plugin-react-refresh": "^0.4.22", 26 - "globals": "^16.4.0", 27 - "react": "^19.1.1", 28 - "react-dom": "^19.1.1", 29 - "typescript": "~5.9.3", 30 - "typescript-eslint": "^8.45.0", 31 - "vite": "npm:rolldown-vite@7.1.14" 32 - }, 33 - "peerDependencies": { 34 - "react": "^18.2.0 || ^19.0.0", 35 - "react-dom": "^18.2.0 || ^19.0.0" 36 - }, 37 - "peerDependenciesMeta": { 38 - "react-dom": { 39 - "optional": true 40 - } 41 - } 42 - }, 43 - "node_modules/@atcute/atproto": { 44 - "version": "3.1.7", 45 - "resolved": "https://registry.npmjs.org/@atcute/atproto/-/atproto-3.1.7.tgz", 46 - "integrity": "sha512-3Ym8qaVZg2vf8qw0KO1aue39z/5oik5J+UDoSes1vr8ddw40UVLA5sV4bXSKmLnhzQHiLLgoVZXe4zaKfozPoQ==", 47 - "license": "0BSD", 48 - "dependencies": { 49 - "@atcute/lexicons": "^1.2.2" 50 - } 51 - }, 52 - "node_modules/@atcute/bluesky": { 53 - "version": "3.2.3", 54 - "resolved": "https://registry.npmjs.org/@atcute/bluesky/-/bluesky-3.2.3.tgz", 55 - "integrity": "sha512-IdPQQ54F1BLhW5z49k81ZUC/GQl/tVygZ+CzLHYvQySHA6GJRcvPzwEf8aV21u0SZOJF+yF4CWEGNgtryyxPmg==", 56 - "license": "0BSD", 57 - "dependencies": { 58 - "@atcute/atproto": "^3.1.4", 59 - "@atcute/lexicons": "^1.1.1" 60 - } 61 - }, 62 - "node_modules/@atcute/client": { 63 - "version": "4.0.3", 64 - "resolved": "https://registry.npmjs.org/@atcute/client/-/client-4.0.3.tgz", 65 - "integrity": "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==", 66 - "license": "MIT", 67 - "dependencies": { 68 - "@atcute/identity": "^1.0.2", 69 - "@atcute/lexicons": "^1.0.3" 70 - } 71 - }, 72 - "node_modules/@atcute/identity": { 73 - "version": "1.1.0", 74 - "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.1.0.tgz", 75 - "integrity": "sha512-6vRvRqJatDB+JUQsb+UswYmtBGQnSZcqC3a2y6H5DB/v5KcIh+6nFFtc17G0+3W9rxdk7k9M4KkgkdKf/YDNoQ==", 76 - "license": "0BSD", 77 - "dependencies": { 78 - "@atcute/lexicons": "^1.1.1", 79 - "@badrap/valita": "^0.4.5" 80 - } 81 - }, 82 - "node_modules/@atcute/identity-resolver": { 83 - "version": "1.1.4", 84 - "resolved": "https://registry.npmjs.org/@atcute/identity-resolver/-/identity-resolver-1.1.4.tgz", 85 - "integrity": "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA==", 86 - "license": "0BSD", 87 - "dependencies": { 88 - "@atcute/lexicons": "^1.2.2", 89 - "@atcute/util-fetch": "^1.0.3", 90 - "@badrap/valita": "^0.4.6" 91 - }, 92 - "peerDependencies": { 93 - "@atcute/identity": "^1.0.0" 94 - } 95 - }, 96 - "node_modules/@atcute/lexicons": { 97 - "version": "1.2.2", 98 - "resolved": "https://registry.npmjs.org/@atcute/lexicons/-/lexicons-1.2.2.tgz", 99 - "integrity": "sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA==", 100 - "license": "0BSD", 101 - "dependencies": { 102 - "@standard-schema/spec": "^1.0.0", 103 - "esm-env": "^1.2.2" 104 - } 105 - }, 106 - "node_modules/@atcute/tangled": { 107 - "version": "1.0.6", 108 - "resolved": "https://registry.npmjs.org/@atcute/tangled/-/tangled-1.0.6.tgz", 109 - "integrity": "sha512-eEOtrKRbjKfeLYtb5hmkhE45w8h4sV6mT4E2CQzJmhOMGCiK31GX7Vqfh59rhNLb9AlbW72RcQTV737pxx+ksw==", 110 - "license": "0BSD", 111 - "dependencies": { 112 - "@atcute/atproto": "^3.1.4", 113 - "@atcute/lexicons": "^1.1.1" 114 - } 115 - }, 116 - "node_modules/@atcute/util-fetch": { 117 - "version": "1.0.3", 118 - "resolved": "https://registry.npmjs.org/@atcute/util-fetch/-/util-fetch-1.0.3.tgz", 119 - "integrity": "sha512-f8zzTb/xlKIwv2OQ31DhShPUNCmIIleX6p7qIXwWwEUjX6x8skUtpdISSjnImq01LXpltGV5y8yhV4/Mlb7CRQ==", 120 - "license": "0BSD", 121 - "dependencies": { 122 - "@badrap/valita": "^0.4.6" 123 - } 124 - }, 125 - "node_modules/@babel/code-frame": { 126 - "version": "7.27.1", 127 - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 128 - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", 129 - "dev": true, 130 - "license": "MIT", 131 - "dependencies": { 132 - "@babel/helper-validator-identifier": "^7.27.1", 133 - "js-tokens": "^4.0.0", 134 - "picocolors": "^1.1.1" 135 - }, 136 - "engines": { 137 - "node": ">=6.9.0" 138 - } 139 - }, 140 - "node_modules/@babel/compat-data": { 141 - "version": "7.28.4", 142 - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", 143 - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", 144 - "dev": true, 145 - "license": "MIT", 146 - "engines": { 147 - "node": ">=6.9.0" 148 - } 149 - }, 150 - "node_modules/@babel/core": { 151 - "version": "7.28.4", 152 - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", 153 - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", 154 - "dev": true, 155 - "license": "MIT", 156 - "dependencies": { 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.4", 162 - "@babel/parser": "^7.28.4", 163 - "@babel/template": "^7.27.2", 164 - "@babel/traverse": "^7.28.4", 165 - "@babel/types": "^7.28.4", 166 - "@jridgewell/remapping": "^2.3.5", 167 - "convert-source-map": "^2.0.0", 168 - "debug": "^4.1.0", 169 - "gensync": "^1.0.0-beta.2", 170 - "json5": "^2.2.3", 171 - "semver": "^6.3.1" 172 - }, 173 - "engines": { 174 - "node": ">=6.9.0" 175 - }, 176 - "funding": { 177 - "type": "opencollective", 178 - "url": "https://opencollective.com/babel" 179 - } 180 - }, 181 - "node_modules/@babel/generator": { 182 - "version": "7.28.3", 183 - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", 184 - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", 185 - "dev": true, 186 - "license": "MIT", 187 - "dependencies": { 188 - "@babel/parser": "^7.28.3", 189 - "@babel/types": "^7.28.2", 190 - "@jridgewell/gen-mapping": "^0.3.12", 191 - "@jridgewell/trace-mapping": "^0.3.28", 192 - "jsesc": "^3.0.2" 193 - }, 194 - "engines": { 195 - "node": ">=6.9.0" 196 - } 197 - }, 198 - "node_modules/@babel/helper-compilation-targets": { 199 - "version": "7.27.2", 200 - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", 201 - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", 202 - "dev": true, 203 - "license": "MIT", 204 - "dependencies": { 205 - "@babel/compat-data": "^7.27.2", 206 - "@babel/helper-validator-option": "^7.27.1", 207 - "browserslist": "^4.24.0", 208 - "lru-cache": "^5.1.1", 209 - "semver": "^6.3.1" 210 - }, 211 - "engines": { 212 - "node": ">=6.9.0" 213 - } 214 - }, 215 - "node_modules/@babel/helper-globals": { 216 - "version": "7.28.0", 217 - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", 218 - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", 219 - "dev": true, 220 - "license": "MIT", 221 - "engines": { 222 - "node": ">=6.9.0" 223 - } 224 - }, 225 - "node_modules/@babel/helper-module-imports": { 226 - "version": "7.27.1", 227 - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", 228 - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", 229 - "dev": true, 230 - "license": "MIT", 231 - "dependencies": { 232 - "@babel/traverse": "^7.27.1", 233 - "@babel/types": "^7.27.1" 234 - }, 235 - "engines": { 236 - "node": ">=6.9.0" 237 - } 238 - }, 239 - "node_modules/@babel/helper-module-transforms": { 240 - "version": "7.28.3", 241 - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", 242 - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", 243 - "dev": true, 244 - "license": "MIT", 245 - "dependencies": { 246 - "@babel/helper-module-imports": "^7.27.1", 247 - "@babel/helper-validator-identifier": "^7.27.1", 248 - "@babel/traverse": "^7.28.3" 249 - }, 250 - "engines": { 251 - "node": ">=6.9.0" 252 - }, 253 - "peerDependencies": { 254 - "@babel/core": "^7.0.0" 255 - } 256 - }, 257 - "node_modules/@babel/helper-plugin-utils": { 258 - "version": "7.27.1", 259 - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", 260 - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", 261 - "dev": true, 262 - "license": "MIT", 263 - "engines": { 264 - "node": ">=6.9.0" 265 - } 266 - }, 267 - "node_modules/@babel/helper-string-parser": { 268 - "version": "7.27.1", 269 - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 270 - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 271 - "dev": true, 272 - "license": "MIT", 273 - "engines": { 274 - "node": ">=6.9.0" 275 - } 276 - }, 277 - "node_modules/@babel/helper-validator-identifier": { 278 - "version": "7.27.1", 279 - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", 280 - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", 281 - "dev": true, 282 - "license": "MIT", 283 - "engines": { 284 - "node": ">=6.9.0" 285 - } 286 - }, 287 - "node_modules/@babel/helper-validator-option": { 288 - "version": "7.27.1", 289 - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", 290 - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", 291 - "dev": true, 292 - "license": "MIT", 293 - "engines": { 294 - "node": ">=6.9.0" 295 - } 296 - }, 297 - "node_modules/@babel/helpers": { 298 - "version": "7.28.4", 299 - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", 300 - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", 301 - "dev": true, 302 - "license": "MIT", 303 - "dependencies": { 304 - "@babel/template": "^7.27.2", 305 - "@babel/types": "^7.28.4" 306 - }, 307 - "engines": { 308 - "node": ">=6.9.0" 309 - } 310 - }, 311 - "node_modules/@babel/parser": { 312 - "version": "7.28.4", 313 - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", 314 - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", 315 - "dev": true, 316 - "license": "MIT", 317 - "dependencies": { 318 - "@babel/types": "^7.28.4" 319 - }, 320 - "bin": { 321 - "parser": "bin/babel-parser.js" 322 - }, 323 - "engines": { 324 - "node": ">=6.0.0" 325 - } 326 - }, 327 - "node_modules/@babel/plugin-transform-react-jsx-self": { 328 - "version": "7.27.1", 329 - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", 330 - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", 331 - "dev": true, 332 - "license": "MIT", 333 - "dependencies": { 334 - "@babel/helper-plugin-utils": "^7.27.1" 335 - }, 336 - "engines": { 337 - "node": ">=6.9.0" 338 - }, 339 - "peerDependencies": { 340 - "@babel/core": "^7.0.0-0" 341 - } 342 - }, 343 - "node_modules/@babel/plugin-transform-react-jsx-source": { 344 - "version": "7.27.1", 345 - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", 346 - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", 347 - "dev": true, 348 - "license": "MIT", 349 - "dependencies": { 350 - "@babel/helper-plugin-utils": "^7.27.1" 351 - }, 352 - "engines": { 353 - "node": ">=6.9.0" 354 - }, 355 - "peerDependencies": { 356 - "@babel/core": "^7.0.0-0" 357 - } 358 - }, 359 - "node_modules/@babel/template": { 360 - "version": "7.27.2", 361 - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 362 - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 363 - "dev": true, 364 - "license": "MIT", 365 - "dependencies": { 366 - "@babel/code-frame": "^7.27.1", 367 - "@babel/parser": "^7.27.2", 368 - "@babel/types": "^7.27.1" 369 - }, 370 - "engines": { 371 - "node": ">=6.9.0" 372 - } 373 - }, 374 - "node_modules/@babel/traverse": { 375 - "version": "7.28.4", 376 - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", 377 - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", 378 - "dev": true, 379 - "license": "MIT", 380 - "dependencies": { 381 - "@babel/code-frame": "^7.27.1", 382 - "@babel/generator": "^7.28.3", 383 - "@babel/helper-globals": "^7.28.0", 384 - "@babel/parser": "^7.28.4", 385 - "@babel/template": "^7.27.2", 386 - "@babel/types": "^7.28.4", 387 - "debug": "^4.3.1" 388 - }, 389 - "engines": { 390 - "node": ">=6.9.0" 391 - } 392 - }, 393 - "node_modules/@babel/types": { 394 - "version": "7.28.4", 395 - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", 396 - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", 397 - "dev": true, 398 - "license": "MIT", 399 - "dependencies": { 400 - "@babel/helper-string-parser": "^7.27.1", 401 - "@babel/helper-validator-identifier": "^7.27.1" 402 - }, 403 - "engines": { 404 - "node": ">=6.9.0" 405 - } 406 - }, 407 - "node_modules/@badrap/valita": { 408 - "version": "0.4.6", 409 - "resolved": "https://registry.npmjs.org/@badrap/valita/-/valita-0.4.6.tgz", 410 - "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==", 411 - "license": "MIT", 412 - "engines": { 413 - "node": ">= 18" 414 - } 415 - }, 416 - "node_modules/@eslint-community/eslint-utils": { 417 - "version": "4.9.0", 418 - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", 419 - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", 420 - "dev": true, 421 - "license": "MIT", 422 - "dependencies": { 423 - "eslint-visitor-keys": "^3.4.3" 424 - }, 425 - "engines": { 426 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 427 - }, 428 - "funding": { 429 - "url": "https://opencollective.com/eslint" 430 - }, 431 - "peerDependencies": { 432 - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 433 - } 434 - }, 435 - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 436 - "version": "3.4.3", 437 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 438 - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 439 - "dev": true, 440 - "license": "Apache-2.0", 441 - "engines": { 442 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 443 - }, 444 - "funding": { 445 - "url": "https://opencollective.com/eslint" 446 - } 447 - }, 448 - "node_modules/@eslint-community/regexpp": { 449 - "version": "4.12.1", 450 - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 451 - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 452 - "dev": true, 453 - "license": "MIT", 454 - "engines": { 455 - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 456 - } 457 - }, 458 - "node_modules/@eslint/config-array": { 459 - "version": "0.21.0", 460 - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", 461 - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", 462 - "dev": true, 463 - "license": "Apache-2.0", 464 - "dependencies": { 465 - "@eslint/object-schema": "^2.1.6", 466 - "debug": "^4.3.1", 467 - "minimatch": "^3.1.2" 468 - }, 469 - "engines": { 470 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 471 - } 472 - }, 473 - "node_modules/@eslint/config-helpers": { 474 - "version": "0.4.0", 475 - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", 476 - "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", 477 - "dev": true, 478 - "license": "Apache-2.0", 479 - "dependencies": { 480 - "@eslint/core": "^0.16.0" 481 - }, 482 - "engines": { 483 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 484 - } 485 - }, 486 - "node_modules/@eslint/core": { 487 - "version": "0.16.0", 488 - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", 489 - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", 490 - "dev": true, 491 - "license": "Apache-2.0", 492 - "dependencies": { 493 - "@types/json-schema": "^7.0.15" 494 - }, 495 - "engines": { 496 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 497 - } 498 - }, 499 - "node_modules/@eslint/eslintrc": { 500 - "version": "3.3.1", 501 - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", 502 - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", 503 - "dev": true, 504 - "license": "MIT", 505 - "dependencies": { 506 - "ajv": "^6.12.4", 507 - "debug": "^4.3.2", 508 - "espree": "^10.0.1", 509 - "globals": "^14.0.0", 510 - "ignore": "^5.2.0", 511 - "import-fresh": "^3.2.1", 512 - "js-yaml": "^4.1.0", 513 - "minimatch": "^3.1.2", 514 - "strip-json-comments": "^3.1.1" 515 - }, 516 - "engines": { 517 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 518 - }, 519 - "funding": { 520 - "url": "https://opencollective.com/eslint" 521 - } 522 - }, 523 - "node_modules/@eslint/eslintrc/node_modules/globals": { 524 - "version": "14.0.0", 525 - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 526 - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 527 - "dev": true, 528 - "license": "MIT", 529 - "engines": { 530 - "node": ">=18" 531 - }, 532 - "funding": { 533 - "url": "https://github.com/sponsors/sindresorhus" 534 - } 535 - }, 536 - "node_modules/@eslint/js": { 537 - "version": "9.37.0", 538 - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", 539 - "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", 540 - "dev": true, 541 - "license": "MIT", 542 - "engines": { 543 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 544 - }, 545 - "funding": { 546 - "url": "https://eslint.org/donate" 547 - } 548 - }, 549 - "node_modules/@eslint/object-schema": { 550 - "version": "2.1.6", 551 - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", 552 - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 553 - "dev": true, 554 - "license": "Apache-2.0", 555 - "engines": { 556 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 557 - } 558 - }, 559 - "node_modules/@eslint/plugin-kit": { 560 - "version": "0.4.0", 561 - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", 562 - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", 563 - "dev": true, 564 - "license": "Apache-2.0", 565 - "dependencies": { 566 - "@eslint/core": "^0.16.0", 567 - "levn": "^0.4.1" 568 - }, 569 - "engines": { 570 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 571 - } 572 - }, 573 - "node_modules/@humanfs/core": { 574 - "version": "0.19.1", 575 - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 576 - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 577 - "dev": true, 578 - "license": "Apache-2.0", 579 - "engines": { 580 - "node": ">=18.18.0" 581 - } 582 - }, 583 - "node_modules/@humanfs/node": { 584 - "version": "0.16.7", 585 - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", 586 - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 587 - "dev": true, 588 - "license": "Apache-2.0", 589 - "dependencies": { 590 - "@humanfs/core": "^0.19.1", 591 - "@humanwhocodes/retry": "^0.4.0" 592 - }, 593 - "engines": { 594 - "node": ">=18.18.0" 595 - } 596 - }, 597 - "node_modules/@humanwhocodes/module-importer": { 598 - "version": "1.0.1", 599 - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 600 - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 601 - "dev": true, 602 - "license": "Apache-2.0", 603 - "engines": { 604 - "node": ">=12.22" 605 - }, 606 - "funding": { 607 - "type": "github", 608 - "url": "https://github.com/sponsors/nzakas" 609 - } 610 - }, 611 - "node_modules/@humanwhocodes/retry": { 612 - "version": "0.4.3", 613 - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", 614 - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 615 - "dev": true, 616 - "license": "Apache-2.0", 617 - "engines": { 618 - "node": ">=18.18" 619 - }, 620 - "funding": { 621 - "type": "github", 622 - "url": "https://github.com/sponsors/nzakas" 623 - } 624 - }, 625 - "node_modules/@jridgewell/gen-mapping": { 626 - "version": "0.3.13", 627 - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 628 - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 629 - "dev": true, 630 - "license": "MIT", 631 - "dependencies": { 632 - "@jridgewell/sourcemap-codec": "^1.5.0", 633 - "@jridgewell/trace-mapping": "^0.3.24" 634 - } 635 - }, 636 - "node_modules/@jridgewell/remapping": { 637 - "version": "2.3.5", 638 - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 639 - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 640 - "dev": true, 641 - "license": "MIT", 642 - "dependencies": { 643 - "@jridgewell/gen-mapping": "^0.3.5", 644 - "@jridgewell/trace-mapping": "^0.3.24" 645 - } 646 - }, 647 - "node_modules/@jridgewell/resolve-uri": { 648 - "version": "3.1.2", 649 - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 650 - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 651 - "dev": true, 652 - "license": "MIT", 653 - "engines": { 654 - "node": ">=6.0.0" 655 - } 656 - }, 657 - "node_modules/@jridgewell/sourcemap-codec": { 658 - "version": "1.5.5", 659 - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 660 - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 661 - "dev": true, 662 - "license": "MIT" 663 - }, 664 - "node_modules/@jridgewell/trace-mapping": { 665 - "version": "0.3.31", 666 - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 667 - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 668 - "dev": true, 669 - "license": "MIT", 670 - "dependencies": { 671 - "@jridgewell/resolve-uri": "^3.1.0", 672 - "@jridgewell/sourcemap-codec": "^1.4.14" 673 - } 674 - }, 675 - "node_modules/@nodelib/fs.scandir": { 676 - "version": "2.1.5", 677 - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 678 - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 679 - "dev": true, 680 - "license": "MIT", 681 - "dependencies": { 682 - "@nodelib/fs.stat": "2.0.5", 683 - "run-parallel": "^1.1.9" 684 - }, 685 - "engines": { 686 - "node": ">= 8" 687 - } 688 - }, 689 - "node_modules/@nodelib/fs.stat": { 690 - "version": "2.0.5", 691 - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 692 - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 693 - "dev": true, 694 - "license": "MIT", 695 - "engines": { 696 - "node": ">= 8" 697 - } 698 - }, 699 - "node_modules/@nodelib/fs.walk": { 700 - "version": "1.2.8", 701 - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 702 - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 703 - "dev": true, 704 - "license": "MIT", 705 - "dependencies": { 706 - "@nodelib/fs.scandir": "2.1.5", 707 - "fastq": "^1.6.0" 708 - }, 709 - "engines": { 710 - "node": ">= 8" 711 - } 712 - }, 713 - "node_modules/@oxc-project/runtime": { 714 - "version": "0.92.0", 715 - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.92.0.tgz", 716 - "integrity": "sha512-Z7x2dZOmznihvdvCvLKMl+nswtOSVxS2H2ocar+U9xx6iMfTp0VGIrX6a4xB1v80IwOPC7dT1LXIJrY70Xu3Jw==", 717 - "dev": true, 718 - "license": "MIT", 719 - "engines": { 720 - "node": "^20.19.0 || >=22.12.0" 721 - } 722 - }, 723 - "node_modules/@oxc-project/types": { 724 - "version": "0.93.0", 725 - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.93.0.tgz", 726 - "integrity": "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==", 727 - "dev": true, 728 - "license": "MIT", 729 - "funding": { 730 - "url": "https://github.com/sponsors/Boshen" 731 - } 732 - }, 733 - "node_modules/@rolldown/binding-darwin-arm64": { 734 - "version": "1.0.0-beta.41", 735 - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.41.tgz", 736 - "integrity": "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==", 737 - "cpu": [ 738 - "arm64" 739 - ], 740 - "dev": true, 741 - "license": "MIT", 742 - "optional": true, 743 - "os": [ 744 - "darwin" 745 - ], 746 - "engines": { 747 - "node": "^20.19.0 || >=22.12.0" 748 - } 749 - }, 750 - "node_modules/@rolldown/pluginutils": { 751 - "version": "1.0.0-beta.38", 752 - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", 753 - "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", 754 - "dev": true, 755 - "license": "MIT" 756 - }, 757 - "node_modules/@standard-schema/spec": { 758 - "version": "1.0.0", 759 - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", 760 - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", 761 - "license": "MIT" 762 - }, 763 - "node_modules/@types/babel__core": { 764 - "version": "7.20.5", 765 - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", 766 - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", 767 - "dev": true, 768 - "license": "MIT", 769 - "dependencies": { 770 - "@babel/parser": "^7.20.7", 771 - "@babel/types": "^7.20.7", 772 - "@types/babel__generator": "*", 773 - "@types/babel__template": "*", 774 - "@types/babel__traverse": "*" 775 - } 776 - }, 777 - "node_modules/@types/babel__generator": { 778 - "version": "7.27.0", 779 - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", 780 - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", 781 - "dev": true, 782 - "license": "MIT", 783 - "dependencies": { 784 - "@babel/types": "^7.0.0" 785 - } 786 - }, 787 - "node_modules/@types/babel__template": { 788 - "version": "7.4.4", 789 - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", 790 - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", 791 - "dev": true, 792 - "license": "MIT", 793 - "dependencies": { 794 - "@babel/parser": "^7.1.0", 795 - "@babel/types": "^7.0.0" 796 - } 797 - }, 798 - "node_modules/@types/babel__traverse": { 799 - "version": "7.28.0", 800 - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", 801 - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", 802 - "dev": true, 803 - "license": "MIT", 804 - "dependencies": { 805 - "@babel/types": "^7.28.2" 806 - } 807 - }, 808 - "node_modules/@types/estree": { 809 - "version": "1.0.8", 810 - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 811 - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 812 - "dev": true, 813 - "license": "MIT" 814 - }, 815 - "node_modules/@types/json-schema": { 816 - "version": "7.0.15", 817 - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 818 - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 819 - "dev": true, 820 - "license": "MIT" 821 - }, 822 - "node_modules/@types/node": { 823 - "version": "24.7.0", 824 - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", 825 - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", 826 - "dev": true, 827 - "license": "MIT", 828 - "dependencies": { 829 - "undici-types": "~7.14.0" 830 - } 831 - }, 832 - "node_modules/@types/react": { 833 - "version": "19.2.2", 834 - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", 835 - "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", 836 - "dev": true, 837 - "license": "MIT", 838 - "dependencies": { 839 - "csstype": "^3.0.2" 840 - } 841 - }, 842 - "node_modules/@types/react-dom": { 843 - "version": "19.2.1", 844 - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.1.tgz", 845 - "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", 846 - "dev": true, 847 - "license": "MIT", 848 - "peerDependencies": { 849 - "@types/react": "^19.2.0" 850 - } 851 - }, 852 - "node_modules/@typescript-eslint/eslint-plugin": { 853 - "version": "8.46.0", 854 - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", 855 - "integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==", 856 - "dev": true, 857 - "license": "MIT", 858 - "dependencies": { 859 - "@eslint-community/regexpp": "^4.10.0", 860 - "@typescript-eslint/scope-manager": "8.46.0", 861 - "@typescript-eslint/type-utils": "8.46.0", 862 - "@typescript-eslint/utils": "8.46.0", 863 - "@typescript-eslint/visitor-keys": "8.46.0", 864 - "graphemer": "^1.4.0", 865 - "ignore": "^7.0.0", 866 - "natural-compare": "^1.4.0", 867 - "ts-api-utils": "^2.1.0" 868 - }, 869 - "engines": { 870 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 871 - }, 872 - "funding": { 873 - "type": "opencollective", 874 - "url": "https://opencollective.com/typescript-eslint" 875 - }, 876 - "peerDependencies": { 877 - "@typescript-eslint/parser": "^8.46.0", 878 - "eslint": "^8.57.0 || ^9.0.0", 879 - "typescript": ">=4.8.4 <6.0.0" 880 - } 881 - }, 882 - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 883 - "version": "7.0.5", 884 - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", 885 - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", 886 - "dev": true, 887 - "license": "MIT", 888 - "engines": { 889 - "node": ">= 4" 890 - } 891 - }, 892 - "node_modules/@typescript-eslint/parser": { 893 - "version": "8.46.0", 894 - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz", 895 - "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", 896 - "dev": true, 897 - "license": "MIT", 898 - "dependencies": { 899 - "@typescript-eslint/scope-manager": "8.46.0", 900 - "@typescript-eslint/types": "8.46.0", 901 - "@typescript-eslint/typescript-estree": "8.46.0", 902 - "@typescript-eslint/visitor-keys": "8.46.0", 903 - "debug": "^4.3.4" 904 - }, 905 - "engines": { 906 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 907 - }, 908 - "funding": { 909 - "type": "opencollective", 910 - "url": "https://opencollective.com/typescript-eslint" 911 - }, 912 - "peerDependencies": { 913 - "eslint": "^8.57.0 || ^9.0.0", 914 - "typescript": ">=4.8.4 <6.0.0" 915 - } 916 - }, 917 - "node_modules/@typescript-eslint/project-service": { 918 - "version": "8.46.0", 919 - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz", 920 - "integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==", 921 - "dev": true, 922 - "license": "MIT", 923 - "dependencies": { 924 - "@typescript-eslint/tsconfig-utils": "^8.46.0", 925 - "@typescript-eslint/types": "^8.46.0", 926 - "debug": "^4.3.4" 927 - }, 928 - "engines": { 929 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 930 - }, 931 - "funding": { 932 - "type": "opencollective", 933 - "url": "https://opencollective.com/typescript-eslint" 934 - }, 935 - "peerDependencies": { 936 - "typescript": ">=4.8.4 <6.0.0" 937 - } 938 - }, 939 - "node_modules/@typescript-eslint/scope-manager": { 940 - "version": "8.46.0", 941 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz", 942 - "integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==", 943 - "dev": true, 944 - "license": "MIT", 945 - "dependencies": { 946 - "@typescript-eslint/types": "8.46.0", 947 - "@typescript-eslint/visitor-keys": "8.46.0" 948 - }, 949 - "engines": { 950 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 951 - }, 952 - "funding": { 953 - "type": "opencollective", 954 - "url": "https://opencollective.com/typescript-eslint" 955 - } 956 - }, 957 - "node_modules/@typescript-eslint/tsconfig-utils": { 958 - "version": "8.46.0", 959 - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz", 960 - "integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==", 961 - "dev": true, 962 - "license": "MIT", 963 - "engines": { 964 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 965 - }, 966 - "funding": { 967 - "type": "opencollective", 968 - "url": "https://opencollective.com/typescript-eslint" 969 - }, 970 - "peerDependencies": { 971 - "typescript": ">=4.8.4 <6.0.0" 972 - } 973 - }, 974 - "node_modules/@typescript-eslint/type-utils": { 975 - "version": "8.46.0", 976 - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz", 977 - "integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==", 978 - "dev": true, 979 - "license": "MIT", 980 - "dependencies": { 981 - "@typescript-eslint/types": "8.46.0", 982 - "@typescript-eslint/typescript-estree": "8.46.0", 983 - "@typescript-eslint/utils": "8.46.0", 984 - "debug": "^4.3.4", 985 - "ts-api-utils": "^2.1.0" 986 - }, 987 - "engines": { 988 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 989 - }, 990 - "funding": { 991 - "type": "opencollective", 992 - "url": "https://opencollective.com/typescript-eslint" 993 - }, 994 - "peerDependencies": { 995 - "eslint": "^8.57.0 || ^9.0.0", 996 - "typescript": ">=4.8.4 <6.0.0" 997 - } 998 - }, 999 - "node_modules/@typescript-eslint/types": { 1000 - "version": "8.46.0", 1001 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz", 1002 - "integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==", 1003 - "dev": true, 1004 - "license": "MIT", 1005 - "engines": { 1006 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1007 - }, 1008 - "funding": { 1009 - "type": "opencollective", 1010 - "url": "https://opencollective.com/typescript-eslint" 1011 - } 1012 - }, 1013 - "node_modules/@typescript-eslint/typescript-estree": { 1014 - "version": "8.46.0", 1015 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz", 1016 - "integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==", 1017 - "dev": true, 1018 - "license": "MIT", 1019 - "dependencies": { 1020 - "@typescript-eslint/project-service": "8.46.0", 1021 - "@typescript-eslint/tsconfig-utils": "8.46.0", 1022 - "@typescript-eslint/types": "8.46.0", 1023 - "@typescript-eslint/visitor-keys": "8.46.0", 1024 - "debug": "^4.3.4", 1025 - "fast-glob": "^3.3.2", 1026 - "is-glob": "^4.0.3", 1027 - "minimatch": "^9.0.4", 1028 - "semver": "^7.6.0", 1029 - "ts-api-utils": "^2.1.0" 1030 - }, 1031 - "engines": { 1032 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1033 - }, 1034 - "funding": { 1035 - "type": "opencollective", 1036 - "url": "https://opencollective.com/typescript-eslint" 1037 - }, 1038 - "peerDependencies": { 1039 - "typescript": ">=4.8.4 <6.0.0" 1040 - } 1041 - }, 1042 - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 1043 - "version": "2.0.2", 1044 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 1045 - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 1046 - "dev": true, 1047 - "license": "MIT", 1048 - "dependencies": { 1049 - "balanced-match": "^1.0.0" 1050 - } 1051 - }, 1052 - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1053 - "version": "9.0.5", 1054 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 1055 - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1056 - "dev": true, 1057 - "license": "ISC", 1058 - "dependencies": { 1059 - "brace-expansion": "^2.0.1" 1060 - }, 1061 - "engines": { 1062 - "node": ">=16 || 14 >=14.17" 1063 - }, 1064 - "funding": { 1065 - "url": "https://github.com/sponsors/isaacs" 1066 - } 1067 - }, 1068 - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1069 - "version": "7.7.3", 1070 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 1071 - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 1072 - "dev": true, 1073 - "license": "ISC", 1074 - "bin": { 1075 - "semver": "bin/semver.js" 1076 - }, 1077 - "engines": { 1078 - "node": ">=10" 1079 - } 1080 - }, 1081 - "node_modules/@typescript-eslint/utils": { 1082 - "version": "8.46.0", 1083 - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz", 1084 - "integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==", 1085 - "dev": true, 1086 - "license": "MIT", 1087 - "dependencies": { 1088 - "@eslint-community/eslint-utils": "^4.7.0", 1089 - "@typescript-eslint/scope-manager": "8.46.0", 1090 - "@typescript-eslint/types": "8.46.0", 1091 - "@typescript-eslint/typescript-estree": "8.46.0" 1092 - }, 1093 - "engines": { 1094 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1095 - }, 1096 - "funding": { 1097 - "type": "opencollective", 1098 - "url": "https://opencollective.com/typescript-eslint" 1099 - }, 1100 - "peerDependencies": { 1101 - "eslint": "^8.57.0 || ^9.0.0", 1102 - "typescript": ">=4.8.4 <6.0.0" 1103 - } 1104 - }, 1105 - "node_modules/@typescript-eslint/visitor-keys": { 1106 - "version": "8.46.0", 1107 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz", 1108 - "integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==", 1109 - "dev": true, 1110 - "license": "MIT", 1111 - "dependencies": { 1112 - "@typescript-eslint/types": "8.46.0", 1113 - "eslint-visitor-keys": "^4.2.1" 1114 - }, 1115 - "engines": { 1116 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1117 - }, 1118 - "funding": { 1119 - "type": "opencollective", 1120 - "url": "https://opencollective.com/typescript-eslint" 1121 - } 1122 - }, 1123 - "node_modules/@vitejs/plugin-react": { 1124 - "version": "5.0.4", 1125 - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz", 1126 - "integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==", 1127 - "dev": true, 1128 - "license": "MIT", 1129 - "dependencies": { 1130 - "@babel/core": "^7.28.4", 1131 - "@babel/plugin-transform-react-jsx-self": "^7.27.1", 1132 - "@babel/plugin-transform-react-jsx-source": "^7.27.1", 1133 - "@rolldown/pluginutils": "1.0.0-beta.38", 1134 - "@types/babel__core": "^7.20.5", 1135 - "react-refresh": "^0.17.0" 1136 - }, 1137 - "engines": { 1138 - "node": "^20.19.0 || >=22.12.0" 1139 - }, 1140 - "peerDependencies": { 1141 - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" 1142 - } 1143 - }, 1144 - "node_modules/acorn": { 1145 - "version": "8.15.0", 1146 - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 1147 - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 1148 - "dev": true, 1149 - "license": "MIT", 1150 - "bin": { 1151 - "acorn": "bin/acorn" 1152 - }, 1153 - "engines": { 1154 - "node": ">=0.4.0" 1155 - } 1156 - }, 1157 - "node_modules/acorn-jsx": { 1158 - "version": "5.3.2", 1159 - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 1160 - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 1161 - "dev": true, 1162 - "license": "MIT", 1163 - "peerDependencies": { 1164 - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 1165 - } 1166 - }, 1167 - "node_modules/ajv": { 1168 - "version": "6.12.6", 1169 - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 1170 - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 1171 - "dev": true, 1172 - "license": "MIT", 1173 - "dependencies": { 1174 - "fast-deep-equal": "^3.1.1", 1175 - "fast-json-stable-stringify": "^2.0.0", 1176 - "json-schema-traverse": "^0.4.1", 1177 - "uri-js": "^4.2.2" 1178 - }, 1179 - "funding": { 1180 - "type": "github", 1181 - "url": "https://github.com/sponsors/epoberezkin" 1182 - } 1183 - }, 1184 - "node_modules/ansi-styles": { 1185 - "version": "4.3.0", 1186 - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1187 - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1188 - "dev": true, 1189 - "license": "MIT", 1190 - "dependencies": { 1191 - "color-convert": "^2.0.1" 1192 - }, 1193 - "engines": { 1194 - "node": ">=8" 1195 - }, 1196 - "funding": { 1197 - "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1198 - } 1199 - }, 1200 - "node_modules/ansis": { 1201 - "version": "4.2.0", 1202 - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", 1203 - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", 1204 - "dev": true, 1205 - "license": "ISC", 1206 - "engines": { 1207 - "node": ">=14" 1208 - } 1209 - }, 1210 - "node_modules/argparse": { 1211 - "version": "2.0.1", 1212 - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1213 - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1214 - "dev": true, 1215 - "license": "Python-2.0" 1216 - }, 1217 - "node_modules/balanced-match": { 1218 - "version": "1.0.2", 1219 - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1220 - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1221 - "dev": true, 1222 - "license": "MIT" 1223 - }, 1224 - "node_modules/baseline-browser-mapping": { 1225 - "version": "2.8.13", 1226 - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz", 1227 - "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==", 1228 - "dev": true, 1229 - "license": "Apache-2.0", 1230 - "bin": { 1231 - "baseline-browser-mapping": "dist/cli.js" 1232 - } 1233 - }, 1234 - "node_modules/brace-expansion": { 1235 - "version": "1.1.12", 1236 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 1237 - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 1238 - "dev": true, 1239 - "license": "MIT", 1240 - "dependencies": { 1241 - "balanced-match": "^1.0.0", 1242 - "concat-map": "0.0.1" 1243 - } 1244 - }, 1245 - "node_modules/braces": { 1246 - "version": "3.0.3", 1247 - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 1248 - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 1249 - "dev": true, 1250 - "license": "MIT", 1251 - "dependencies": { 1252 - "fill-range": "^7.1.1" 1253 - }, 1254 - "engines": { 1255 - "node": ">=8" 1256 - } 1257 - }, 1258 - "node_modules/browserslist": { 1259 - "version": "4.26.3", 1260 - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", 1261 - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", 1262 - "dev": true, 1263 - "funding": [ 1264 - { 1265 - "type": "opencollective", 1266 - "url": "https://opencollective.com/browserslist" 1267 - }, 1268 - { 1269 - "type": "tidelift", 1270 - "url": "https://tidelift.com/funding/github/npm/browserslist" 1271 - }, 1272 - { 1273 - "type": "github", 1274 - "url": "https://github.com/sponsors/ai" 1275 - } 1276 - ], 1277 - "license": "MIT", 1278 - "dependencies": { 1279 - "baseline-browser-mapping": "^2.8.9", 1280 - "caniuse-lite": "^1.0.30001746", 1281 - "electron-to-chromium": "^1.5.227", 1282 - "node-releases": "^2.0.21", 1283 - "update-browserslist-db": "^1.1.3" 1284 - }, 1285 - "bin": { 1286 - "browserslist": "cli.js" 1287 - }, 1288 - "engines": { 1289 - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 1290 - } 1291 - }, 1292 - "node_modules/callsites": { 1293 - "version": "3.1.0", 1294 - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 1295 - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 1296 - "dev": true, 1297 - "license": "MIT", 1298 - "engines": { 1299 - "node": ">=6" 1300 - } 1301 - }, 1302 - "node_modules/caniuse-lite": { 1303 - "version": "1.0.30001748", 1304 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz", 1305 - "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==", 1306 - "dev": true, 1307 - "funding": [ 1308 - { 1309 - "type": "opencollective", 1310 - "url": "https://opencollective.com/browserslist" 1311 - }, 1312 - { 1313 - "type": "tidelift", 1314 - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 1315 - }, 1316 - { 1317 - "type": "github", 1318 - "url": "https://github.com/sponsors/ai" 1319 - } 1320 - ], 1321 - "license": "CC-BY-4.0" 1322 - }, 1323 - "node_modules/chalk": { 1324 - "version": "4.1.2", 1325 - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1326 - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1327 - "dev": true, 1328 - "license": "MIT", 1329 - "dependencies": { 1330 - "ansi-styles": "^4.1.0", 1331 - "supports-color": "^7.1.0" 1332 - }, 1333 - "engines": { 1334 - "node": ">=10" 1335 - }, 1336 - "funding": { 1337 - "url": "https://github.com/chalk/chalk?sponsor=1" 1338 - } 1339 - }, 1340 - "node_modules/color-convert": { 1341 - "version": "2.0.1", 1342 - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1343 - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1344 - "dev": true, 1345 - "license": "MIT", 1346 - "dependencies": { 1347 - "color-name": "~1.1.4" 1348 - }, 1349 - "engines": { 1350 - "node": ">=7.0.0" 1351 - } 1352 - }, 1353 - "node_modules/color-name": { 1354 - "version": "1.1.4", 1355 - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1356 - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1357 - "dev": true, 1358 - "license": "MIT" 1359 - }, 1360 - "node_modules/concat-map": { 1361 - "version": "0.0.1", 1362 - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1363 - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1364 - "dev": true, 1365 - "license": "MIT" 1366 - }, 1367 - "node_modules/convert-source-map": { 1368 - "version": "2.0.0", 1369 - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 1370 - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 1371 - "dev": true, 1372 - "license": "MIT" 1373 - }, 1374 - "node_modules/cross-spawn": { 1375 - "version": "7.0.6", 1376 - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 1377 - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 1378 - "dev": true, 1379 - "license": "MIT", 1380 - "dependencies": { 1381 - "path-key": "^3.1.0", 1382 - "shebang-command": "^2.0.0", 1383 - "which": "^2.0.1" 1384 - }, 1385 - "engines": { 1386 - "node": ">= 8" 1387 - } 1388 - }, 1389 - "node_modules/csstype": { 1390 - "version": "3.1.3", 1391 - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 1392 - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 1393 - "dev": true, 1394 - "license": "MIT" 1395 - }, 1396 - "node_modules/debug": { 1397 - "version": "4.4.3", 1398 - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 1399 - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 1400 - "dev": true, 1401 - "license": "MIT", 1402 - "dependencies": { 1403 - "ms": "^2.1.3" 1404 - }, 1405 - "engines": { 1406 - "node": ">=6.0" 1407 - }, 1408 - "peerDependenciesMeta": { 1409 - "supports-color": { 1410 - "optional": true 1411 - } 1412 - } 1413 - }, 1414 - "node_modules/deep-is": { 1415 - "version": "0.1.4", 1416 - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 1417 - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 1418 - "dev": true, 1419 - "license": "MIT" 1420 - }, 1421 - "node_modules/detect-libc": { 1422 - "version": "2.1.2", 1423 - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1424 - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1425 - "dev": true, 1426 - "license": "Apache-2.0", 1427 - "engines": { 1428 - "node": ">=8" 1429 - } 1430 - }, 1431 - "node_modules/electron-to-chromium": { 1432 - "version": "1.5.232", 1433 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz", 1434 - "integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==", 1435 - "dev": true, 1436 - "license": "ISC" 1437 - }, 1438 - "node_modules/escalade": { 1439 - "version": "3.2.0", 1440 - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 1441 - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 1442 - "dev": true, 1443 - "license": "MIT", 1444 - "engines": { 1445 - "node": ">=6" 1446 - } 1447 - }, 1448 - "node_modules/escape-string-regexp": { 1449 - "version": "4.0.0", 1450 - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 1451 - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 1452 - "dev": true, 1453 - "license": "MIT", 1454 - "engines": { 1455 - "node": ">=10" 1456 - }, 1457 - "funding": { 1458 - "url": "https://github.com/sponsors/sindresorhus" 1459 - } 1460 - }, 1461 - "node_modules/eslint": { 1462 - "version": "9.37.0", 1463 - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", 1464 - "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", 1465 - "dev": true, 1466 - "license": "MIT", 1467 - "dependencies": { 1468 - "@eslint-community/eslint-utils": "^4.8.0", 1469 - "@eslint-community/regexpp": "^4.12.1", 1470 - "@eslint/config-array": "^0.21.0", 1471 - "@eslint/config-helpers": "^0.4.0", 1472 - "@eslint/core": "^0.16.0", 1473 - "@eslint/eslintrc": "^3.3.1", 1474 - "@eslint/js": "9.37.0", 1475 - "@eslint/plugin-kit": "^0.4.0", 1476 - "@humanfs/node": "^0.16.6", 1477 - "@humanwhocodes/module-importer": "^1.0.1", 1478 - "@humanwhocodes/retry": "^0.4.2", 1479 - "@types/estree": "^1.0.6", 1480 - "@types/json-schema": "^7.0.15", 1481 - "ajv": "^6.12.4", 1482 - "chalk": "^4.0.0", 1483 - "cross-spawn": "^7.0.6", 1484 - "debug": "^4.3.2", 1485 - "escape-string-regexp": "^4.0.0", 1486 - "eslint-scope": "^8.4.0", 1487 - "eslint-visitor-keys": "^4.2.1", 1488 - "espree": "^10.4.0", 1489 - "esquery": "^1.5.0", 1490 - "esutils": "^2.0.2", 1491 - "fast-deep-equal": "^3.1.3", 1492 - "file-entry-cache": "^8.0.0", 1493 - "find-up": "^5.0.0", 1494 - "glob-parent": "^6.0.2", 1495 - "ignore": "^5.2.0", 1496 - "imurmurhash": "^0.1.4", 1497 - "is-glob": "^4.0.0", 1498 - "json-stable-stringify-without-jsonify": "^1.0.1", 1499 - "lodash.merge": "^4.6.2", 1500 - "minimatch": "^3.1.2", 1501 - "natural-compare": "^1.4.0", 1502 - "optionator": "^0.9.3" 1503 - }, 1504 - "bin": { 1505 - "eslint": "bin/eslint.js" 1506 - }, 1507 - "engines": { 1508 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1509 - }, 1510 - "funding": { 1511 - "url": "https://eslint.org/donate" 1512 - }, 1513 - "peerDependencies": { 1514 - "jiti": "*" 1515 - }, 1516 - "peerDependenciesMeta": { 1517 - "jiti": { 1518 - "optional": true 1519 - } 1520 - } 1521 - }, 1522 - "node_modules/eslint-plugin-react-hooks": { 1523 - "version": "5.2.0", 1524 - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", 1525 - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", 1526 - "dev": true, 1527 - "license": "MIT", 1528 - "engines": { 1529 - "node": ">=10" 1530 - }, 1531 - "peerDependencies": { 1532 - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" 1533 - } 1534 - }, 1535 - "node_modules/eslint-plugin-react-refresh": { 1536 - "version": "0.4.23", 1537 - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.23.tgz", 1538 - "integrity": "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA==", 1539 - "dev": true, 1540 - "license": "MIT", 1541 - "peerDependencies": { 1542 - "eslint": ">=8.40" 1543 - } 1544 - }, 1545 - "node_modules/eslint-scope": { 1546 - "version": "8.4.0", 1547 - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", 1548 - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 1549 - "dev": true, 1550 - "license": "BSD-2-Clause", 1551 - "dependencies": { 1552 - "esrecurse": "^4.3.0", 1553 - "estraverse": "^5.2.0" 1554 - }, 1555 - "engines": { 1556 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1557 - }, 1558 - "funding": { 1559 - "url": "https://opencollective.com/eslint" 1560 - } 1561 - }, 1562 - "node_modules/eslint-visitor-keys": { 1563 - "version": "4.2.1", 1564 - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", 1565 - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", 1566 - "dev": true, 1567 - "license": "Apache-2.0", 1568 - "engines": { 1569 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1570 - }, 1571 - "funding": { 1572 - "url": "https://opencollective.com/eslint" 1573 - } 1574 - }, 1575 - "node_modules/esm-env": { 1576 - "version": "1.2.2", 1577 - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 1578 - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 1579 - "license": "MIT" 1580 - }, 1581 - "node_modules/espree": { 1582 - "version": "10.4.0", 1583 - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", 1584 - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 1585 - "dev": true, 1586 - "license": "BSD-2-Clause", 1587 - "dependencies": { 1588 - "acorn": "^8.15.0", 1589 - "acorn-jsx": "^5.3.2", 1590 - "eslint-visitor-keys": "^4.2.1" 1591 - }, 1592 - "engines": { 1593 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1594 - }, 1595 - "funding": { 1596 - "url": "https://opencollective.com/eslint" 1597 - } 1598 - }, 1599 - "node_modules/esquery": { 1600 - "version": "1.6.0", 1601 - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 1602 - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 1603 - "dev": true, 1604 - "license": "BSD-3-Clause", 1605 - "dependencies": { 1606 - "estraverse": "^5.1.0" 1607 - }, 1608 - "engines": { 1609 - "node": ">=0.10" 1610 - } 1611 - }, 1612 - "node_modules/esrecurse": { 1613 - "version": "4.3.0", 1614 - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 1615 - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 1616 - "dev": true, 1617 - "license": "BSD-2-Clause", 1618 - "dependencies": { 1619 - "estraverse": "^5.2.0" 1620 - }, 1621 - "engines": { 1622 - "node": ">=4.0" 1623 - } 1624 - }, 1625 - "node_modules/estraverse": { 1626 - "version": "5.3.0", 1627 - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1628 - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1629 - "dev": true, 1630 - "license": "BSD-2-Clause", 1631 - "engines": { 1632 - "node": ">=4.0" 1633 - } 1634 - }, 1635 - "node_modules/esutils": { 1636 - "version": "2.0.3", 1637 - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1638 - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1639 - "dev": true, 1640 - "license": "BSD-2-Clause", 1641 - "engines": { 1642 - "node": ">=0.10.0" 1643 - } 1644 - }, 1645 - "node_modules/fast-deep-equal": { 1646 - "version": "3.1.3", 1647 - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1648 - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1649 - "dev": true, 1650 - "license": "MIT" 1651 - }, 1652 - "node_modules/fast-glob": { 1653 - "version": "3.3.3", 1654 - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 1655 - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 1656 - "dev": true, 1657 - "license": "MIT", 1658 - "dependencies": { 1659 - "@nodelib/fs.stat": "^2.0.2", 1660 - "@nodelib/fs.walk": "^1.2.3", 1661 - "glob-parent": "^5.1.2", 1662 - "merge2": "^1.3.0", 1663 - "micromatch": "^4.0.8" 1664 - }, 1665 - "engines": { 1666 - "node": ">=8.6.0" 1667 - } 1668 - }, 1669 - "node_modules/fast-glob/node_modules/glob-parent": { 1670 - "version": "5.1.2", 1671 - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1672 - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1673 - "dev": true, 1674 - "license": "ISC", 1675 - "dependencies": { 1676 - "is-glob": "^4.0.1" 1677 - }, 1678 - "engines": { 1679 - "node": ">= 6" 1680 - } 1681 - }, 1682 - "node_modules/fast-json-stable-stringify": { 1683 - "version": "2.1.0", 1684 - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1685 - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 1686 - "dev": true, 1687 - "license": "MIT" 1688 - }, 1689 - "node_modules/fast-levenshtein": { 1690 - "version": "2.0.6", 1691 - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 1692 - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 1693 - "dev": true, 1694 - "license": "MIT" 1695 - }, 1696 - "node_modules/fastq": { 1697 - "version": "1.19.1", 1698 - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 1699 - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 1700 - "dev": true, 1701 - "license": "ISC", 1702 - "dependencies": { 1703 - "reusify": "^1.0.4" 1704 - } 1705 - }, 1706 - "node_modules/file-entry-cache": { 1707 - "version": "8.0.0", 1708 - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 1709 - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 1710 - "dev": true, 1711 - "license": "MIT", 1712 - "dependencies": { 1713 - "flat-cache": "^4.0.0" 1714 - }, 1715 - "engines": { 1716 - "node": ">=16.0.0" 1717 - } 1718 - }, 1719 - "node_modules/fill-range": { 1720 - "version": "7.1.1", 1721 - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 1722 - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 1723 - "dev": true, 1724 - "license": "MIT", 1725 - "dependencies": { 1726 - "to-regex-range": "^5.0.1" 1727 - }, 1728 - "engines": { 1729 - "node": ">=8" 1730 - } 1731 - }, 1732 - "node_modules/find-up": { 1733 - "version": "5.0.0", 1734 - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1735 - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1736 - "dev": true, 1737 - "license": "MIT", 1738 - "dependencies": { 1739 - "locate-path": "^6.0.0", 1740 - "path-exists": "^4.0.0" 1741 - }, 1742 - "engines": { 1743 - "node": ">=10" 1744 - }, 1745 - "funding": { 1746 - "url": "https://github.com/sponsors/sindresorhus" 1747 - } 1748 - }, 1749 - "node_modules/flat-cache": { 1750 - "version": "4.0.1", 1751 - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 1752 - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 1753 - "dev": true, 1754 - "license": "MIT", 1755 - "dependencies": { 1756 - "flatted": "^3.2.9", 1757 - "keyv": "^4.5.4" 1758 - }, 1759 - "engines": { 1760 - "node": ">=16" 1761 - } 1762 - }, 1763 - "node_modules/flatted": { 1764 - "version": "3.3.3", 1765 - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 1766 - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 1767 - "dev": true, 1768 - "license": "ISC" 1769 - }, 1770 - "node_modules/fsevents": { 1771 - "version": "2.3.3", 1772 - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1773 - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1774 - "dev": true, 1775 - "hasInstallScript": true, 1776 - "license": "MIT", 1777 - "optional": true, 1778 - "os": [ 1779 - "darwin" 1780 - ], 1781 - "engines": { 1782 - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1783 - } 1784 - }, 1785 - "node_modules/gensync": { 1786 - "version": "1.0.0-beta.2", 1787 - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 1788 - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 1789 - "dev": true, 1790 - "license": "MIT", 1791 - "engines": { 1792 - "node": ">=6.9.0" 1793 - } 1794 - }, 1795 - "node_modules/glob-parent": { 1796 - "version": "6.0.2", 1797 - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1798 - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1799 - "dev": true, 1800 - "license": "ISC", 1801 - "dependencies": { 1802 - "is-glob": "^4.0.3" 1803 - }, 1804 - "engines": { 1805 - "node": ">=10.13.0" 1806 - } 1807 - }, 1808 - "node_modules/globals": { 1809 - "version": "16.4.0", 1810 - "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", 1811 - "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", 1812 - "dev": true, 1813 - "license": "MIT", 1814 - "engines": { 1815 - "node": ">=18" 1816 - }, 1817 - "funding": { 1818 - "url": "https://github.com/sponsors/sindresorhus" 1819 - } 1820 - }, 1821 - "node_modules/graphemer": { 1822 - "version": "1.4.0", 1823 - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 1824 - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 1825 - "dev": true, 1826 - "license": "MIT" 1827 - }, 1828 - "node_modules/has-flag": { 1829 - "version": "4.0.0", 1830 - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1831 - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1832 - "dev": true, 1833 - "license": "MIT", 1834 - "engines": { 1835 - "node": ">=8" 1836 - } 1837 - }, 1838 - "node_modules/ignore": { 1839 - "version": "5.3.2", 1840 - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 1841 - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 1842 - "dev": true, 1843 - "license": "MIT", 1844 - "engines": { 1845 - "node": ">= 4" 1846 - } 1847 - }, 1848 - "node_modules/import-fresh": { 1849 - "version": "3.3.1", 1850 - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 1851 - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 1852 - "dev": true, 1853 - "license": "MIT", 1854 - "dependencies": { 1855 - "parent-module": "^1.0.0", 1856 - "resolve-from": "^4.0.0" 1857 - }, 1858 - "engines": { 1859 - "node": ">=6" 1860 - }, 1861 - "funding": { 1862 - "url": "https://github.com/sponsors/sindresorhus" 1863 - } 1864 - }, 1865 - "node_modules/imurmurhash": { 1866 - "version": "0.1.4", 1867 - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1868 - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1869 - "dev": true, 1870 - "license": "MIT", 1871 - "engines": { 1872 - "node": ">=0.8.19" 1873 - } 1874 - }, 1875 - "node_modules/is-extglob": { 1876 - "version": "2.1.1", 1877 - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1878 - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1879 - "dev": true, 1880 - "license": "MIT", 1881 - "engines": { 1882 - "node": ">=0.10.0" 1883 - } 1884 - }, 1885 - "node_modules/is-glob": { 1886 - "version": "4.0.3", 1887 - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1888 - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1889 - "dev": true, 1890 - "license": "MIT", 1891 - "dependencies": { 1892 - "is-extglob": "^2.1.1" 1893 - }, 1894 - "engines": { 1895 - "node": ">=0.10.0" 1896 - } 1897 - }, 1898 - "node_modules/is-number": { 1899 - "version": "7.0.0", 1900 - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1901 - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1902 - "dev": true, 1903 - "license": "MIT", 1904 - "engines": { 1905 - "node": ">=0.12.0" 1906 - } 1907 - }, 1908 - "node_modules/isexe": { 1909 - "version": "2.0.0", 1910 - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1911 - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1912 - "dev": true, 1913 - "license": "ISC" 1914 - }, 1915 - "node_modules/js-tokens": { 1916 - "version": "4.0.0", 1917 - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1918 - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1919 - "dev": true, 1920 - "license": "MIT" 1921 - }, 1922 - "node_modules/js-yaml": { 1923 - "version": "4.1.0", 1924 - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1925 - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1926 - "dev": true, 1927 - "license": "MIT", 1928 - "dependencies": { 1929 - "argparse": "^2.0.1" 1930 - }, 1931 - "bin": { 1932 - "js-yaml": "bin/js-yaml.js" 1933 - } 1934 - }, 1935 - "node_modules/jsesc": { 1936 - "version": "3.1.0", 1937 - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", 1938 - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", 1939 - "dev": true, 1940 - "license": "MIT", 1941 - "bin": { 1942 - "jsesc": "bin/jsesc" 1943 - }, 1944 - "engines": { 1945 - "node": ">=6" 1946 - } 1947 - }, 1948 - "node_modules/json-buffer": { 1949 - "version": "3.0.1", 1950 - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 1951 - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 1952 - "dev": true, 1953 - "license": "MIT" 1954 - }, 1955 - "node_modules/json-schema-traverse": { 1956 - "version": "0.4.1", 1957 - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1958 - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1959 - "dev": true, 1960 - "license": "MIT" 1961 - }, 1962 - "node_modules/json-stable-stringify-without-jsonify": { 1963 - "version": "1.0.1", 1964 - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1965 - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 1966 - "dev": true, 1967 - "license": "MIT" 1968 - }, 1969 - "node_modules/json5": { 1970 - "version": "2.2.3", 1971 - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 1972 - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 1973 - "dev": true, 1974 - "license": "MIT", 1975 - "bin": { 1976 - "json5": "lib/cli.js" 1977 - }, 1978 - "engines": { 1979 - "node": ">=6" 1980 - } 1981 - }, 1982 - "node_modules/keyv": { 1983 - "version": "4.5.4", 1984 - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 1985 - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 1986 - "dev": true, 1987 - "license": "MIT", 1988 - "dependencies": { 1989 - "json-buffer": "3.0.1" 1990 - } 1991 - }, 1992 - "node_modules/levn": { 1993 - "version": "0.4.1", 1994 - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1995 - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1996 - "dev": true, 1997 - "license": "MIT", 1998 - "dependencies": { 1999 - "prelude-ls": "^1.2.1", 2000 - "type-check": "~0.4.0" 2001 - }, 2002 - "engines": { 2003 - "node": ">= 0.8.0" 2004 - } 2005 - }, 2006 - "node_modules/lightningcss": { 2007 - "version": "1.30.2", 2008 - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", 2009 - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", 2010 - "dev": true, 2011 - "license": "MPL-2.0", 2012 - "dependencies": { 2013 - "detect-libc": "^2.0.3" 2014 - }, 2015 - "engines": { 2016 - "node": ">= 12.0.0" 2017 - }, 2018 - "funding": { 2019 - "type": "opencollective", 2020 - "url": "https://opencollective.com/parcel" 2021 - }, 2022 - "optionalDependencies": { 2023 - "lightningcss-android-arm64": "1.30.2", 2024 - "lightningcss-darwin-arm64": "1.30.2", 2025 - "lightningcss-darwin-x64": "1.30.2", 2026 - "lightningcss-freebsd-x64": "1.30.2", 2027 - "lightningcss-linux-arm-gnueabihf": "1.30.2", 2028 - "lightningcss-linux-arm64-gnu": "1.30.2", 2029 - "lightningcss-linux-arm64-musl": "1.30.2", 2030 - "lightningcss-linux-x64-gnu": "1.30.2", 2031 - "lightningcss-linux-x64-musl": "1.30.2", 2032 - "lightningcss-win32-arm64-msvc": "1.30.2", 2033 - "lightningcss-win32-x64-msvc": "1.30.2" 2034 - } 2035 - }, 2036 - "node_modules/lightningcss-darwin-arm64": { 2037 - "version": "1.30.2", 2038 - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", 2039 - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", 2040 - "cpu": [ 2041 - "arm64" 2042 - ], 2043 - "dev": true, 2044 - "license": "MPL-2.0", 2045 - "optional": true, 2046 - "os": [ 2047 - "darwin" 2048 - ], 2049 - "engines": { 2050 - "node": ">= 12.0.0" 2051 - }, 2052 - "funding": { 2053 - "type": "opencollective", 2054 - "url": "https://opencollective.com/parcel" 2055 - } 2056 - }, 2057 - "node_modules/locate-path": { 2058 - "version": "6.0.0", 2059 - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 2060 - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 2061 - "dev": true, 2062 - "license": "MIT", 2063 - "dependencies": { 2064 - "p-locate": "^5.0.0" 2065 - }, 2066 - "engines": { 2067 - "node": ">=10" 2068 - }, 2069 - "funding": { 2070 - "url": "https://github.com/sponsors/sindresorhus" 2071 - } 2072 - }, 2073 - "node_modules/lodash.merge": { 2074 - "version": "4.6.2", 2075 - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 2076 - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 2077 - "dev": true, 2078 - "license": "MIT" 2079 - }, 2080 - "node_modules/lru-cache": { 2081 - "version": "5.1.1", 2082 - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 2083 - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 2084 - "dev": true, 2085 - "license": "ISC", 2086 - "dependencies": { 2087 - "yallist": "^3.0.2" 2088 - } 2089 - }, 2090 - "node_modules/merge2": { 2091 - "version": "1.4.1", 2092 - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 2093 - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 2094 - "dev": true, 2095 - "license": "MIT", 2096 - "engines": { 2097 - "node": ">= 8" 2098 - } 2099 - }, 2100 - "node_modules/micromatch": { 2101 - "version": "4.0.8", 2102 - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 2103 - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 2104 - "dev": true, 2105 - "license": "MIT", 2106 - "dependencies": { 2107 - "braces": "^3.0.3", 2108 - "picomatch": "^2.3.1" 2109 - }, 2110 - "engines": { 2111 - "node": ">=8.6" 2112 - } 2113 - }, 2114 - "node_modules/minimatch": { 2115 - "version": "3.1.2", 2116 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 2117 - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 2118 - "dev": true, 2119 - "license": "ISC", 2120 - "dependencies": { 2121 - "brace-expansion": "^1.1.7" 2122 - }, 2123 - "engines": { 2124 - "node": "*" 2125 - } 2126 - }, 2127 - "node_modules/ms": { 2128 - "version": "2.1.3", 2129 - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2130 - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 2131 - "dev": true, 2132 - "license": "MIT" 2133 - }, 2134 - "node_modules/nanoid": { 2135 - "version": "3.3.11", 2136 - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 2137 - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 2138 - "dev": true, 2139 - "funding": [ 2140 - { 2141 - "type": "github", 2142 - "url": "https://github.com/sponsors/ai" 2143 - } 2144 - ], 2145 - "license": "MIT", 2146 - "bin": { 2147 - "nanoid": "bin/nanoid.cjs" 2148 - }, 2149 - "engines": { 2150 - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2151 - } 2152 - }, 2153 - "node_modules/natural-compare": { 2154 - "version": "1.4.0", 2155 - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 2156 - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 2157 - "dev": true, 2158 - "license": "MIT" 2159 - }, 2160 - "node_modules/node-releases": { 2161 - "version": "2.0.23", 2162 - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", 2163 - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", 2164 - "dev": true, 2165 - "license": "MIT" 2166 - }, 2167 - "node_modules/optionator": { 2168 - "version": "0.9.4", 2169 - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 2170 - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 2171 - "dev": true, 2172 - "license": "MIT", 2173 - "dependencies": { 2174 - "deep-is": "^0.1.3", 2175 - "fast-levenshtein": "^2.0.6", 2176 - "levn": "^0.4.1", 2177 - "prelude-ls": "^1.2.1", 2178 - "type-check": "^0.4.0", 2179 - "word-wrap": "^1.2.5" 2180 - }, 2181 - "engines": { 2182 - "node": ">= 0.8.0" 2183 - } 2184 - }, 2185 - "node_modules/p-limit": { 2186 - "version": "3.1.0", 2187 - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 2188 - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 2189 - "dev": true, 2190 - "license": "MIT", 2191 - "dependencies": { 2192 - "yocto-queue": "^0.1.0" 2193 - }, 2194 - "engines": { 2195 - "node": ">=10" 2196 - }, 2197 - "funding": { 2198 - "url": "https://github.com/sponsors/sindresorhus" 2199 - } 2200 - }, 2201 - "node_modules/p-locate": { 2202 - "version": "5.0.0", 2203 - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 2204 - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 2205 - "dev": true, 2206 - "license": "MIT", 2207 - "dependencies": { 2208 - "p-limit": "^3.0.2" 2209 - }, 2210 - "engines": { 2211 - "node": ">=10" 2212 - }, 2213 - "funding": { 2214 - "url": "https://github.com/sponsors/sindresorhus" 2215 - } 2216 - }, 2217 - "node_modules/parent-module": { 2218 - "version": "1.0.1", 2219 - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 2220 - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 2221 - "dev": true, 2222 - "license": "MIT", 2223 - "dependencies": { 2224 - "callsites": "^3.0.0" 2225 - }, 2226 - "engines": { 2227 - "node": ">=6" 2228 - } 2229 - }, 2230 - "node_modules/path-exists": { 2231 - "version": "4.0.0", 2232 - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 2233 - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 2234 - "dev": true, 2235 - "license": "MIT", 2236 - "engines": { 2237 - "node": ">=8" 2238 - } 2239 - }, 2240 - "node_modules/path-key": { 2241 - "version": "3.1.1", 2242 - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 2243 - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 2244 - "dev": true, 2245 - "license": "MIT", 2246 - "engines": { 2247 - "node": ">=8" 2248 - } 2249 - }, 2250 - "node_modules/picocolors": { 2251 - "version": "1.1.1", 2252 - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 2253 - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 2254 - "dev": true, 2255 - "license": "ISC" 2256 - }, 2257 - "node_modules/picomatch": { 2258 - "version": "2.3.1", 2259 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 2260 - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 2261 - "dev": true, 2262 - "license": "MIT", 2263 - "engines": { 2264 - "node": ">=8.6" 2265 - }, 2266 - "funding": { 2267 - "url": "https://github.com/sponsors/jonschlinkert" 2268 - } 2269 - }, 2270 - "node_modules/postcss": { 2271 - "version": "8.5.6", 2272 - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", 2273 - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 2274 - "dev": true, 2275 - "funding": [ 2276 - { 2277 - "type": "opencollective", 2278 - "url": "https://opencollective.com/postcss/" 2279 - }, 2280 - { 2281 - "type": "tidelift", 2282 - "url": "https://tidelift.com/funding/github/npm/postcss" 2283 - }, 2284 - { 2285 - "type": "github", 2286 - "url": "https://github.com/sponsors/ai" 2287 - } 2288 - ], 2289 - "license": "MIT", 2290 - "dependencies": { 2291 - "nanoid": "^3.3.11", 2292 - "picocolors": "^1.1.1", 2293 - "source-map-js": "^1.2.1" 2294 - }, 2295 - "engines": { 2296 - "node": "^10 || ^12 || >=14" 2297 - } 2298 - }, 2299 - "node_modules/prelude-ls": { 2300 - "version": "1.2.1", 2301 - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 2302 - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 2303 - "dev": true, 2304 - "license": "MIT", 2305 - "engines": { 2306 - "node": ">= 0.8.0" 2307 - } 2308 - }, 2309 - "node_modules/punycode": { 2310 - "version": "2.3.1", 2311 - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 2312 - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 2313 - "dev": true, 2314 - "license": "MIT", 2315 - "engines": { 2316 - "node": ">=6" 2317 - } 2318 - }, 2319 - "node_modules/queue-microtask": { 2320 - "version": "1.2.3", 2321 - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2322 - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 2323 - "dev": true, 2324 - "funding": [ 2325 - { 2326 - "type": "github", 2327 - "url": "https://github.com/sponsors/feross" 2328 - }, 2329 - { 2330 - "type": "patreon", 2331 - "url": "https://www.patreon.com/feross" 2332 - }, 2333 - { 2334 - "type": "consulting", 2335 - "url": "https://feross.org/support" 2336 - } 2337 - ], 2338 - "license": "MIT" 2339 - }, 2340 - "node_modules/react": { 2341 - "version": "19.2.0", 2342 - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", 2343 - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", 2344 - "dev": true, 2345 - "license": "MIT", 2346 - "engines": { 2347 - "node": ">=0.10.0" 2348 - } 2349 - }, 2350 - "node_modules/react-dom": { 2351 - "version": "19.2.0", 2352 - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", 2353 - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", 2354 - "dev": true, 2355 - "license": "MIT", 2356 - "dependencies": { 2357 - "scheduler": "^0.27.0" 2358 - }, 2359 - "peerDependencies": { 2360 - "react": "^19.2.0" 2361 - } 2362 - }, 2363 - "node_modules/react-refresh": { 2364 - "version": "0.17.0", 2365 - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", 2366 - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", 2367 - "dev": true, 2368 - "license": "MIT", 2369 - "engines": { 2370 - "node": ">=0.10.0" 2371 - } 2372 - }, 2373 - "node_modules/resolve-from": { 2374 - "version": "4.0.0", 2375 - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 2376 - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 2377 - "dev": true, 2378 - "license": "MIT", 2379 - "engines": { 2380 - "node": ">=4" 2381 - } 2382 - }, 2383 - "node_modules/reusify": { 2384 - "version": "1.1.0", 2385 - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 2386 - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 2387 - "dev": true, 2388 - "license": "MIT", 2389 - "engines": { 2390 - "iojs": ">=1.0.0", 2391 - "node": ">=0.10.0" 2392 - } 2393 - }, 2394 - "node_modules/rolldown": { 2395 - "version": "1.0.0-beta.41", 2396 - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.41.tgz", 2397 - "integrity": "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg==", 2398 - "dev": true, 2399 - "license": "MIT", 2400 - "dependencies": { 2401 - "@oxc-project/types": "=0.93.0", 2402 - "@rolldown/pluginutils": "1.0.0-beta.41", 2403 - "ansis": "=4.2.0" 2404 - }, 2405 - "bin": { 2406 - "rolldown": "bin/cli.mjs" 2407 - }, 2408 - "engines": { 2409 - "node": "^20.19.0 || >=22.12.0" 2410 - }, 2411 - "optionalDependencies": { 2412 - "@rolldown/binding-android-arm64": "1.0.0-beta.41", 2413 - "@rolldown/binding-darwin-arm64": "1.0.0-beta.41", 2414 - "@rolldown/binding-darwin-x64": "1.0.0-beta.41", 2415 - "@rolldown/binding-freebsd-x64": "1.0.0-beta.41", 2416 - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.41", 2417 - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.41", 2418 - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.41", 2419 - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.41", 2420 - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.41", 2421 - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.41", 2422 - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.41", 2423 - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.41", 2424 - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.41", 2425 - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.41" 2426 - } 2427 - }, 2428 - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { 2429 - "version": "1.0.0-beta.41", 2430 - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.41.tgz", 2431 - "integrity": "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==", 2432 - "dev": true, 2433 - "license": "MIT" 2434 - }, 2435 - "node_modules/run-parallel": { 2436 - "version": "1.2.0", 2437 - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 2438 - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 2439 - "dev": true, 2440 - "funding": [ 2441 - { 2442 - "type": "github", 2443 - "url": "https://github.com/sponsors/feross" 2444 - }, 2445 - { 2446 - "type": "patreon", 2447 - "url": "https://www.patreon.com/feross" 2448 - }, 2449 - { 2450 - "type": "consulting", 2451 - "url": "https://feross.org/support" 2452 - } 2453 - ], 2454 - "license": "MIT", 2455 - "dependencies": { 2456 - "queue-microtask": "^1.2.2" 2457 - } 2458 - }, 2459 - "node_modules/scheduler": { 2460 - "version": "0.27.0", 2461 - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", 2462 - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", 2463 - "dev": true, 2464 - "license": "MIT" 2465 - }, 2466 - "node_modules/semver": { 2467 - "version": "6.3.1", 2468 - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 2469 - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 2470 - "dev": true, 2471 - "license": "ISC", 2472 - "bin": { 2473 - "semver": "bin/semver.js" 2474 - } 2475 - }, 2476 - "node_modules/shebang-command": { 2477 - "version": "2.0.0", 2478 - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2479 - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2480 - "dev": true, 2481 - "license": "MIT", 2482 - "dependencies": { 2483 - "shebang-regex": "^3.0.0" 2484 - }, 2485 - "engines": { 2486 - "node": ">=8" 2487 - } 2488 - }, 2489 - "node_modules/shebang-regex": { 2490 - "version": "3.0.0", 2491 - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2492 - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2493 - "dev": true, 2494 - "license": "MIT", 2495 - "engines": { 2496 - "node": ">=8" 2497 - } 2498 - }, 2499 - "node_modules/source-map-js": { 2500 - "version": "1.2.1", 2501 - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 2502 - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 2503 - "dev": true, 2504 - "license": "BSD-3-Clause", 2505 - "engines": { 2506 - "node": ">=0.10.0" 2507 - } 2508 - }, 2509 - "node_modules/strip-json-comments": { 2510 - "version": "3.1.1", 2511 - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 2512 - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 2513 - "dev": true, 2514 - "license": "MIT", 2515 - "engines": { 2516 - "node": ">=8" 2517 - }, 2518 - "funding": { 2519 - "url": "https://github.com/sponsors/sindresorhus" 2520 - } 2521 - }, 2522 - "node_modules/supports-color": { 2523 - "version": "7.2.0", 2524 - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2525 - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2526 - "dev": true, 2527 - "license": "MIT", 2528 - "dependencies": { 2529 - "has-flag": "^4.0.0" 2530 - }, 2531 - "engines": { 2532 - "node": ">=8" 2533 - } 2534 - }, 2535 - "node_modules/tinyglobby": { 2536 - "version": "0.2.15", 2537 - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 2538 - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 2539 - "dev": true, 2540 - "license": "MIT", 2541 - "dependencies": { 2542 - "fdir": "^6.5.0", 2543 - "picomatch": "^4.0.3" 2544 - }, 2545 - "engines": { 2546 - "node": ">=12.0.0" 2547 - }, 2548 - "funding": { 2549 - "url": "https://github.com/sponsors/SuperchupuDev" 2550 - } 2551 - }, 2552 - "node_modules/tinyglobby/node_modules/fdir": { 2553 - "version": "6.5.0", 2554 - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 2555 - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 2556 - "dev": true, 2557 - "license": "MIT", 2558 - "engines": { 2559 - "node": ">=12.0.0" 2560 - }, 2561 - "peerDependencies": { 2562 - "picomatch": "^3 || ^4" 2563 - }, 2564 - "peerDependenciesMeta": { 2565 - "picomatch": { 2566 - "optional": true 2567 - } 2568 - } 2569 - }, 2570 - "node_modules/tinyglobby/node_modules/picomatch": { 2571 - "version": "4.0.3", 2572 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 2573 - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 2574 - "dev": true, 2575 - "license": "MIT", 2576 - "engines": { 2577 - "node": ">=12" 2578 - }, 2579 - "funding": { 2580 - "url": "https://github.com/sponsors/jonschlinkert" 2581 - } 2582 - }, 2583 - "node_modules/to-regex-range": { 2584 - "version": "5.0.1", 2585 - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2586 - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2587 - "dev": true, 2588 - "license": "MIT", 2589 - "dependencies": { 2590 - "is-number": "^7.0.0" 2591 - }, 2592 - "engines": { 2593 - "node": ">=8.0" 2594 - } 2595 - }, 2596 - "node_modules/ts-api-utils": { 2597 - "version": "2.1.0", 2598 - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", 2599 - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", 2600 - "dev": true, 2601 - "license": "MIT", 2602 - "engines": { 2603 - "node": ">=18.12" 2604 - }, 2605 - "peerDependencies": { 2606 - "typescript": ">=4.8.4" 2607 - } 2608 - }, 2609 - "node_modules/type-check": { 2610 - "version": "0.4.0", 2611 - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 2612 - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 2613 - "dev": true, 2614 - "license": "MIT", 2615 - "dependencies": { 2616 - "prelude-ls": "^1.2.1" 2617 - }, 2618 - "engines": { 2619 - "node": ">= 0.8.0" 2620 - } 2621 - }, 2622 - "node_modules/typescript": { 2623 - "version": "5.9.3", 2624 - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 2625 - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 2626 - "dev": true, 2627 - "license": "Apache-2.0", 2628 - "bin": { 2629 - "tsc": "bin/tsc", 2630 - "tsserver": "bin/tsserver" 2631 - }, 2632 - "engines": { 2633 - "node": ">=14.17" 2634 - } 2635 - }, 2636 - "node_modules/typescript-eslint": { 2637 - "version": "8.46.0", 2638 - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.0.tgz", 2639 - "integrity": "sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==", 2640 - "dev": true, 2641 - "license": "MIT", 2642 - "dependencies": { 2643 - "@typescript-eslint/eslint-plugin": "8.46.0", 2644 - "@typescript-eslint/parser": "8.46.0", 2645 - "@typescript-eslint/typescript-estree": "8.46.0", 2646 - "@typescript-eslint/utils": "8.46.0" 2647 - }, 2648 - "engines": { 2649 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2650 - }, 2651 - "funding": { 2652 - "type": "opencollective", 2653 - "url": "https://opencollective.com/typescript-eslint" 2654 - }, 2655 - "peerDependencies": { 2656 - "eslint": "^8.57.0 || ^9.0.0", 2657 - "typescript": ">=4.8.4 <6.0.0" 2658 - } 2659 - }, 2660 - "node_modules/undici-types": { 2661 - "version": "7.14.0", 2662 - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", 2663 - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", 2664 - "dev": true, 2665 - "license": "MIT" 2666 - }, 2667 - "node_modules/update-browserslist-db": { 2668 - "version": "1.1.3", 2669 - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", 2670 - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", 2671 - "dev": true, 2672 - "funding": [ 2673 - { 2674 - "type": "opencollective", 2675 - "url": "https://opencollective.com/browserslist" 2676 - }, 2677 - { 2678 - "type": "tidelift", 2679 - "url": "https://tidelift.com/funding/github/npm/browserslist" 2680 - }, 2681 - { 2682 - "type": "github", 2683 - "url": "https://github.com/sponsors/ai" 2684 - } 2685 - ], 2686 - "license": "MIT", 2687 - "dependencies": { 2688 - "escalade": "^3.2.0", 2689 - "picocolors": "^1.1.1" 2690 - }, 2691 - "bin": { 2692 - "update-browserslist-db": "cli.js" 2693 - }, 2694 - "peerDependencies": { 2695 - "browserslist": ">= 4.21.0" 2696 - } 2697 - }, 2698 - "node_modules/uri-js": { 2699 - "version": "4.4.1", 2700 - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 2701 - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 2702 - "dev": true, 2703 - "license": "BSD-2-Clause", 2704 - "dependencies": { 2705 - "punycode": "^2.1.0" 2706 - } 2707 - }, 2708 - "node_modules/vite": { 2709 - "name": "rolldown-vite", 2710 - "version": "7.1.14", 2711 - "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz", 2712 - "integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==", 2713 - "dev": true, 2714 - "license": "MIT", 2715 - "dependencies": { 2716 - "@oxc-project/runtime": "0.92.0", 2717 - "fdir": "^6.5.0", 2718 - "lightningcss": "^1.30.1", 2719 - "picomatch": "^4.0.3", 2720 - "postcss": "^8.5.6", 2721 - "rolldown": "1.0.0-beta.41", 2722 - "tinyglobby": "^0.2.15" 2723 - }, 2724 - "bin": { 2725 - "vite": "bin/vite.js" 2726 - }, 2727 - "engines": { 2728 - "node": "^20.19.0 || >=22.12.0" 2729 - }, 2730 - "funding": { 2731 - "url": "https://github.com/vitejs/vite?sponsor=1" 2732 - }, 2733 - "optionalDependencies": { 2734 - "fsevents": "~2.3.3" 2735 - }, 2736 - "peerDependencies": { 2737 - "@types/node": "^20.19.0 || >=22.12.0", 2738 - "esbuild": "^0.25.0", 2739 - "jiti": ">=1.21.0", 2740 - "less": "^4.0.0", 2741 - "sass": "^1.70.0", 2742 - "sass-embedded": "^1.70.0", 2743 - "stylus": ">=0.54.8", 2744 - "sugarss": "^5.0.0", 2745 - "terser": "^5.16.0", 2746 - "tsx": "^4.8.1", 2747 - "yaml": "^2.4.2" 2748 - }, 2749 - "peerDependenciesMeta": { 2750 - "@types/node": { 2751 - "optional": true 2752 - }, 2753 - "esbuild": { 2754 - "optional": true 2755 - }, 2756 - "jiti": { 2757 - "optional": true 2758 - }, 2759 - "less": { 2760 - "optional": true 2761 - }, 2762 - "sass": { 2763 - "optional": true 2764 - }, 2765 - "sass-embedded": { 2766 - "optional": true 2767 - }, 2768 - "stylus": { 2769 - "optional": true 2770 - }, 2771 - "sugarss": { 2772 - "optional": true 2773 - }, 2774 - "terser": { 2775 - "optional": true 2776 - }, 2777 - "tsx": { 2778 - "optional": true 2779 - }, 2780 - "yaml": { 2781 - "optional": true 2782 - } 2783 - } 2784 - }, 2785 - "node_modules/vite/node_modules/fdir": { 2786 - "version": "6.5.0", 2787 - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 2788 - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 2789 - "dev": true, 2790 - "license": "MIT", 2791 - "engines": { 2792 - "node": ">=12.0.0" 2793 - }, 2794 - "peerDependencies": { 2795 - "picomatch": "^3 || ^4" 2796 - }, 2797 - "peerDependenciesMeta": { 2798 - "picomatch": { 2799 - "optional": true 2800 - } 2801 - } 2802 - }, 2803 - "node_modules/vite/node_modules/picomatch": { 2804 - "version": "4.0.3", 2805 - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 2806 - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 2807 - "dev": true, 2808 - "license": "MIT", 2809 - "engines": { 2810 - "node": ">=12" 2811 - }, 2812 - "funding": { 2813 - "url": "https://github.com/sponsors/jonschlinkert" 2814 - } 2815 - }, 2816 - "node_modules/which": { 2817 - "version": "2.0.2", 2818 - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2819 - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2820 - "dev": true, 2821 - "license": "ISC", 2822 - "dependencies": { 2823 - "isexe": "^2.0.0" 2824 - }, 2825 - "bin": { 2826 - "node-which": "bin/node-which" 2827 - }, 2828 - "engines": { 2829 - "node": ">= 8" 2830 - } 2831 - }, 2832 - "node_modules/word-wrap": { 2833 - "version": "1.2.5", 2834 - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 2835 - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 2836 - "dev": true, 2837 - "license": "MIT", 2838 - "engines": { 2839 - "node": ">=0.10.0" 2840 - } 2841 - }, 2842 - "node_modules/yallist": { 2843 - "version": "3.1.1", 2844 - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 2845 - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 2846 - "dev": true, 2847 - "license": "ISC" 2848 - }, 2849 - "node_modules/yocto-queue": { 2850 - "version": "0.1.0", 2851 - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 2852 - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 2853 - "dev": true, 2854 - "license": "MIT", 2855 - "engines": { 2856 - "node": ">=10" 2857 - }, 2858 - "funding": { 2859 - "url": "https://github.com/sponsors/sindresorhus" 2860 - } 2861 - } 2862 - } 2 + "name": "atproto-ui", 3 + "version": "0.12", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "atproto-ui", 9 + "version": "0.12", 10 + "dependencies": { 11 + "@atcute/atproto": "^3.1.7", 12 + "@atcute/bluesky": "^3.2.3", 13 + "@atcute/client": "^4.0.3", 14 + "@atcute/identity-resolver": "^1.1.3", 15 + "@atcute/tangled": "^1.0.10" 16 + }, 17 + "devDependencies": { 18 + "@eslint/js": "^9.36.0", 19 + "@microsoft/api-extractor": "^7.53.1", 20 + "@types/node": "^24.6.0", 21 + "@types/react": "^19.1.16", 22 + "@types/react-dom": "^19.1.9", 23 + "@vitejs/plugin-react": "^5.0.4", 24 + "eslint": "^9.36.0", 25 + "eslint-plugin-react-hooks": "^5.2.0", 26 + "eslint-plugin-react-refresh": "^0.4.22", 27 + "globals": "^16.4.0", 28 + "react": "^19.1.1", 29 + "react-dom": "^19.1.1", 30 + "rollup-plugin-typescript2": "^0.36.0", 31 + "typescript": "~5.9.3", 32 + "typescript-eslint": "^8.45.0", 33 + "unplugin-dts": "^1.0.0-beta.6", 34 + "vite": "npm:rolldown-vite@7.1.14" 35 + }, 36 + "peerDependencies": { 37 + "react": "^18.2.0 || ^19.0.0", 38 + "react-dom": "^18.2.0 || ^19.0.0" 39 + }, 40 + "peerDependenciesMeta": { 41 + "react-dom": { 42 + "optional": true 43 + } 44 + } 45 + }, 46 + "node_modules/@atcute/atproto": { 47 + "version": "3.1.9", 48 + "license": "0BSD", 49 + "dependencies": { 50 + "@atcute/lexicons": "^1.2.2" 51 + } 52 + }, 53 + "node_modules/@atcute/bluesky": { 54 + "version": "3.2.11", 55 + "license": "0BSD", 56 + "dependencies": { 57 + "@atcute/atproto": "^3.1.9", 58 + "@atcute/lexicons": "^1.2.5" 59 + } 60 + }, 61 + "node_modules/@atcute/client": { 62 + "version": "4.1.0", 63 + "license": "0BSD", 64 + "dependencies": { 65 + "@atcute/identity": "^1.1.3", 66 + "@atcute/lexicons": "^1.2.5" 67 + } 68 + }, 69 + "node_modules/@atcute/identity": { 70 + "version": "1.1.3", 71 + "license": "0BSD", 72 + "peer": true, 73 + "dependencies": { 74 + "@atcute/lexicons": "^1.2.4", 75 + "@badrap/valita": "^0.4.6" 76 + } 77 + }, 78 + "node_modules/@atcute/identity-resolver": { 79 + "version": "1.1.4", 80 + "license": "0BSD", 81 + "dependencies": { 82 + "@atcute/lexicons": "^1.2.2", 83 + "@atcute/util-fetch": "^1.0.3", 84 + "@badrap/valita": "^0.4.6" 85 + }, 86 + "peerDependencies": { 87 + "@atcute/identity": "^1.0.0" 88 + } 89 + }, 90 + "node_modules/@atcute/lexicons": { 91 + "version": "1.2.5", 92 + "license": "0BSD", 93 + "dependencies": { 94 + "@standard-schema/spec": "^1.0.0", 95 + "esm-env": "^1.2.2" 96 + } 97 + }, 98 + "node_modules/@atcute/tangled": { 99 + "version": "1.0.12", 100 + "license": "0BSD", 101 + "dependencies": { 102 + "@atcute/atproto": "^3.1.9", 103 + "@atcute/lexicons": "^1.2.3" 104 + } 105 + }, 106 + "node_modules/@atcute/util-fetch": { 107 + "version": "1.0.4", 108 + "license": "0BSD", 109 + "dependencies": { 110 + "@badrap/valita": "^0.4.6" 111 + } 112 + }, 113 + "node_modules/@babel/code-frame": { 114 + "version": "7.27.1", 115 + "dev": true, 116 + "license": "MIT", 117 + "dependencies": { 118 + "@babel/helper-validator-identifier": "^7.27.1", 119 + "js-tokens": "^4.0.0", 120 + "picocolors": "^1.1.1" 121 + }, 122 + "engines": { 123 + "node": ">=6.9.0" 124 + } 125 + }, 126 + "node_modules/@babel/compat-data": { 127 + "version": "7.28.5", 128 + "dev": true, 129 + "license": "MIT", 130 + "engines": { 131 + "node": ">=6.9.0" 132 + } 133 + }, 134 + "node_modules/@babel/core": { 135 + "version": "7.28.5", 136 + "dev": true, 137 + "license": "MIT", 138 + "peer": true, 139 + "dependencies": { 140 + "@babel/code-frame": "^7.27.1", 141 + "@babel/generator": "^7.28.5", 142 + "@babel/helper-compilation-targets": "^7.27.2", 143 + "@babel/helper-module-transforms": "^7.28.3", 144 + "@babel/helpers": "^7.28.4", 145 + "@babel/parser": "^7.28.5", 146 + "@babel/template": "^7.27.2", 147 + "@babel/traverse": "^7.28.5", 148 + "@babel/types": "^7.28.5", 149 + "@jridgewell/remapping": "^2.3.5", 150 + "convert-source-map": "^2.0.0", 151 + "debug": "^4.1.0", 152 + "gensync": "^1.0.0-beta.2", 153 + "json5": "^2.2.3", 154 + "semver": "^6.3.1" 155 + }, 156 + "engines": { 157 + "node": ">=6.9.0" 158 + }, 159 + "funding": { 160 + "type": "opencollective", 161 + "url": "https://opencollective.com/babel" 162 + } 163 + }, 164 + "node_modules/@babel/core/node_modules/semver": { 165 + "version": "6.3.1", 166 + "dev": true, 167 + "license": "ISC", 168 + "bin": { 169 + "semver": "bin/semver.js" 170 + } 171 + }, 172 + "node_modules/@babel/generator": { 173 + "version": "7.28.5", 174 + "dev": true, 175 + "license": "MIT", 176 + "dependencies": { 177 + "@babel/parser": "^7.28.5", 178 + "@babel/types": "^7.28.5", 179 + "@jridgewell/gen-mapping": "^0.3.12", 180 + "@jridgewell/trace-mapping": "^0.3.28", 181 + "jsesc": "^3.0.2" 182 + }, 183 + "engines": { 184 + "node": ">=6.9.0" 185 + } 186 + }, 187 + "node_modules/@babel/helper-compilation-targets": { 188 + "version": "7.27.2", 189 + "dev": true, 190 + "license": "MIT", 191 + "dependencies": { 192 + "@babel/compat-data": "^7.27.2", 193 + "@babel/helper-validator-option": "^7.27.1", 194 + "browserslist": "^4.24.0", 195 + "lru-cache": "^5.1.1", 196 + "semver": "^6.3.1" 197 + }, 198 + "engines": { 199 + "node": ">=6.9.0" 200 + } 201 + }, 202 + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { 203 + "version": "5.1.1", 204 + "dev": true, 205 + "license": "ISC", 206 + "dependencies": { 207 + "yallist": "^3.0.2" 208 + } 209 + }, 210 + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache/node_modules/yallist": { 211 + "version": "3.1.1", 212 + "dev": true, 213 + "license": "ISC" 214 + }, 215 + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { 216 + "version": "6.3.1", 217 + "dev": true, 218 + "license": "ISC", 219 + "bin": { 220 + "semver": "bin/semver.js" 221 + } 222 + }, 223 + "node_modules/@babel/helper-globals": { 224 + "version": "7.28.0", 225 + "dev": true, 226 + "license": "MIT", 227 + "engines": { 228 + "node": ">=6.9.0" 229 + } 230 + }, 231 + "node_modules/@babel/helper-module-imports": { 232 + "version": "7.27.1", 233 + "dev": true, 234 + "license": "MIT", 235 + "dependencies": { 236 + "@babel/traverse": "^7.27.1", 237 + "@babel/types": "^7.27.1" 238 + }, 239 + "engines": { 240 + "node": ">=6.9.0" 241 + } 242 + }, 243 + "node_modules/@babel/helper-module-transforms": { 244 + "version": "7.28.3", 245 + "dev": true, 246 + "license": "MIT", 247 + "dependencies": { 248 + "@babel/helper-module-imports": "^7.27.1", 249 + "@babel/helper-validator-identifier": "^7.27.1", 250 + "@babel/traverse": "^7.28.3" 251 + }, 252 + "engines": { 253 + "node": ">=6.9.0" 254 + }, 255 + "peerDependencies": { 256 + "@babel/core": "^7.0.0" 257 + } 258 + }, 259 + "node_modules/@babel/helper-plugin-utils": { 260 + "version": "7.27.1", 261 + "dev": true, 262 + "license": "MIT", 263 + "engines": { 264 + "node": ">=6.9.0" 265 + } 266 + }, 267 + "node_modules/@babel/helper-string-parser": { 268 + "version": "7.27.1", 269 + "dev": true, 270 + "license": "MIT", 271 + "engines": { 272 + "node": ">=6.9.0" 273 + } 274 + }, 275 + "node_modules/@babel/helper-validator-identifier": { 276 + "version": "7.28.5", 277 + "dev": true, 278 + "license": "MIT", 279 + "engines": { 280 + "node": ">=6.9.0" 281 + } 282 + }, 283 + "node_modules/@babel/helper-validator-option": { 284 + "version": "7.27.1", 285 + "dev": true, 286 + "license": "MIT", 287 + "engines": { 288 + "node": ">=6.9.0" 289 + } 290 + }, 291 + "node_modules/@babel/helpers": { 292 + "version": "7.28.4", 293 + "dev": true, 294 + "license": "MIT", 295 + "dependencies": { 296 + "@babel/template": "^7.27.2", 297 + "@babel/types": "^7.28.4" 298 + }, 299 + "engines": { 300 + "node": ">=6.9.0" 301 + } 302 + }, 303 + "node_modules/@babel/parser": { 304 + "version": "7.28.5", 305 + "dev": true, 306 + "license": "MIT", 307 + "dependencies": { 308 + "@babel/types": "^7.28.5" 309 + }, 310 + "bin": { 311 + "parser": "bin/babel-parser.js" 312 + }, 313 + "engines": { 314 + "node": ">=6.0.0" 315 + } 316 + }, 317 + "node_modules/@babel/plugin-transform-react-jsx-self": { 318 + "version": "7.27.1", 319 + "dev": true, 320 + "license": "MIT", 321 + "dependencies": { 322 + "@babel/helper-plugin-utils": "^7.27.1" 323 + }, 324 + "engines": { 325 + "node": ">=6.9.0" 326 + }, 327 + "peerDependencies": { 328 + "@babel/core": "^7.0.0-0" 329 + } 330 + }, 331 + "node_modules/@babel/plugin-transform-react-jsx-source": { 332 + "version": "7.27.1", 333 + "dev": true, 334 + "license": "MIT", 335 + "dependencies": { 336 + "@babel/helper-plugin-utils": "^7.27.1" 337 + }, 338 + "engines": { 339 + "node": ">=6.9.0" 340 + }, 341 + "peerDependencies": { 342 + "@babel/core": "^7.0.0-0" 343 + } 344 + }, 345 + "node_modules/@babel/template": { 346 + "version": "7.27.2", 347 + "dev": true, 348 + "license": "MIT", 349 + "dependencies": { 350 + "@babel/code-frame": "^7.27.1", 351 + "@babel/parser": "^7.27.2", 352 + "@babel/types": "^7.27.1" 353 + }, 354 + "engines": { 355 + "node": ">=6.9.0" 356 + } 357 + }, 358 + "node_modules/@babel/traverse": { 359 + "version": "7.28.5", 360 + "dev": true, 361 + "license": "MIT", 362 + "dependencies": { 363 + "@babel/code-frame": "^7.27.1", 364 + "@babel/generator": "^7.28.5", 365 + "@babel/helper-globals": "^7.28.0", 366 + "@babel/parser": "^7.28.5", 367 + "@babel/template": "^7.27.2", 368 + "@babel/types": "^7.28.5", 369 + "debug": "^4.3.1" 370 + }, 371 + "engines": { 372 + "node": ">=6.9.0" 373 + } 374 + }, 375 + "node_modules/@babel/types": { 376 + "version": "7.28.5", 377 + "dev": true, 378 + "license": "MIT", 379 + "dependencies": { 380 + "@babel/helper-string-parser": "^7.27.1", 381 + "@babel/helper-validator-identifier": "^7.28.5" 382 + }, 383 + "engines": { 384 + "node": ">=6.9.0" 385 + } 386 + }, 387 + "node_modules/@badrap/valita": { 388 + "version": "0.4.6", 389 + "license": "MIT", 390 + "engines": { 391 + "node": ">= 18" 392 + } 393 + }, 394 + "node_modules/@emnapi/core": { 395 + "version": "1.7.1", 396 + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", 397 + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", 398 + "dev": true, 399 + "license": "MIT", 400 + "optional": true, 401 + "dependencies": { 402 + "@emnapi/wasi-threads": "1.1.0", 403 + "tslib": "^2.4.0" 404 + } 405 + }, 406 + "node_modules/@emnapi/runtime": { 407 + "version": "1.7.1", 408 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", 409 + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", 410 + "dev": true, 411 + "license": "MIT", 412 + "optional": true, 413 + "dependencies": { 414 + "tslib": "^2.4.0" 415 + } 416 + }, 417 + "node_modules/@emnapi/wasi-threads": { 418 + "version": "1.1.0", 419 + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", 420 + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", 421 + "dev": true, 422 + "license": "MIT", 423 + "optional": true, 424 + "dependencies": { 425 + "tslib": "^2.4.0" 426 + } 427 + }, 428 + "node_modules/@eslint-community/eslint-utils": { 429 + "version": "4.9.0", 430 + "dev": true, 431 + "license": "MIT", 432 + "dependencies": { 433 + "eslint-visitor-keys": "^3.4.3" 434 + }, 435 + "engines": { 436 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 437 + }, 438 + "funding": { 439 + "url": "https://opencollective.com/eslint" 440 + }, 441 + "peerDependencies": { 442 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 443 + } 444 + }, 445 + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 446 + "version": "3.4.3", 447 + "dev": true, 448 + "license": "Apache-2.0", 449 + "engines": { 450 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 451 + }, 452 + "funding": { 453 + "url": "https://opencollective.com/eslint" 454 + } 455 + }, 456 + "node_modules/@eslint-community/regexpp": { 457 + "version": "4.12.2", 458 + "dev": true, 459 + "license": "MIT", 460 + "engines": { 461 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 462 + } 463 + }, 464 + "node_modules/@eslint/config-array": { 465 + "version": "0.21.1", 466 + "dev": true, 467 + "license": "Apache-2.0", 468 + "dependencies": { 469 + "@eslint/object-schema": "^2.1.7", 470 + "debug": "^4.3.1", 471 + "minimatch": "^3.1.2" 472 + }, 473 + "engines": { 474 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 475 + } 476 + }, 477 + "node_modules/@eslint/config-array/node_modules/minimatch": { 478 + "version": "3.1.2", 479 + "dev": true, 480 + "license": "ISC", 481 + "dependencies": { 482 + "brace-expansion": "^1.1.7" 483 + }, 484 + "engines": { 485 + "node": "*" 486 + } 487 + }, 488 + "node_modules/@eslint/config-helpers": { 489 + "version": "0.4.2", 490 + "dev": true, 491 + "license": "Apache-2.0", 492 + "dependencies": { 493 + "@eslint/core": "^0.17.0" 494 + }, 495 + "engines": { 496 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 497 + } 498 + }, 499 + "node_modules/@eslint/core": { 500 + "version": "0.17.0", 501 + "dev": true, 502 + "license": "Apache-2.0", 503 + "dependencies": { 504 + "@types/json-schema": "^7.0.15" 505 + }, 506 + "engines": { 507 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 508 + } 509 + }, 510 + "node_modules/@eslint/eslintrc": { 511 + "version": "3.3.3", 512 + "dev": true, 513 + "license": "MIT", 514 + "dependencies": { 515 + "ajv": "^6.12.4", 516 + "debug": "^4.3.2", 517 + "espree": "^10.0.1", 518 + "globals": "^14.0.0", 519 + "ignore": "^5.2.0", 520 + "import-fresh": "^3.2.1", 521 + "js-yaml": "^4.1.1", 522 + "minimatch": "^3.1.2", 523 + "strip-json-comments": "^3.1.1" 524 + }, 525 + "engines": { 526 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 527 + }, 528 + "funding": { 529 + "url": "https://opencollective.com/eslint" 530 + } 531 + }, 532 + "node_modules/@eslint/eslintrc/node_modules/ajv": { 533 + "version": "6.12.6", 534 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 535 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 536 + "dev": true, 537 + "license": "MIT", 538 + "dependencies": { 539 + "fast-deep-equal": "^3.1.1", 540 + "fast-json-stable-stringify": "^2.0.0", 541 + "json-schema-traverse": "^0.4.1", 542 + "uri-js": "^4.2.2" 543 + }, 544 + "funding": { 545 + "type": "github", 546 + "url": "https://github.com/sponsors/epoberezkin" 547 + } 548 + }, 549 + "node_modules/@eslint/eslintrc/node_modules/globals": { 550 + "version": "14.0.0", 551 + "dev": true, 552 + "license": "MIT", 553 + "engines": { 554 + "node": ">=18" 555 + }, 556 + "funding": { 557 + "url": "https://github.com/sponsors/sindresorhus" 558 + } 559 + }, 560 + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { 561 + "version": "0.4.1", 562 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 563 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 564 + "dev": true, 565 + "license": "MIT" 566 + }, 567 + "node_modules/@eslint/eslintrc/node_modules/minimatch": { 568 + "version": "3.1.2", 569 + "dev": true, 570 + "license": "ISC", 571 + "dependencies": { 572 + "brace-expansion": "^1.1.7" 573 + }, 574 + "engines": { 575 + "node": "*" 576 + } 577 + }, 578 + "node_modules/@eslint/js": { 579 + "version": "9.39.1", 580 + "dev": true, 581 + "license": "MIT", 582 + "engines": { 583 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 584 + }, 585 + "funding": { 586 + "url": "https://eslint.org/donate" 587 + } 588 + }, 589 + "node_modules/@eslint/object-schema": { 590 + "version": "2.1.7", 591 + "dev": true, 592 + "license": "Apache-2.0", 593 + "engines": { 594 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 595 + } 596 + }, 597 + "node_modules/@eslint/plugin-kit": { 598 + "version": "0.4.1", 599 + "dev": true, 600 + "license": "Apache-2.0", 601 + "dependencies": { 602 + "@eslint/core": "^0.17.0", 603 + "levn": "^0.4.1" 604 + }, 605 + "engines": { 606 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 607 + } 608 + }, 609 + "node_modules/@humanfs/core": { 610 + "version": "0.19.1", 611 + "dev": true, 612 + "license": "Apache-2.0", 613 + "engines": { 614 + "node": ">=18.18.0" 615 + } 616 + }, 617 + "node_modules/@humanfs/node": { 618 + "version": "0.16.7", 619 + "dev": true, 620 + "license": "Apache-2.0", 621 + "dependencies": { 622 + "@humanfs/core": "^0.19.1", 623 + "@humanwhocodes/retry": "^0.4.0" 624 + }, 625 + "engines": { 626 + "node": ">=18.18.0" 627 + } 628 + }, 629 + "node_modules/@humanwhocodes/module-importer": { 630 + "version": "1.0.1", 631 + "dev": true, 632 + "license": "Apache-2.0", 633 + "engines": { 634 + "node": ">=12.22" 635 + }, 636 + "funding": { 637 + "type": "github", 638 + "url": "https://github.com/sponsors/nzakas" 639 + } 640 + }, 641 + "node_modules/@humanwhocodes/retry": { 642 + "version": "0.4.3", 643 + "dev": true, 644 + "license": "Apache-2.0", 645 + "engines": { 646 + "node": ">=18.18" 647 + }, 648 + "funding": { 649 + "type": "github", 650 + "url": "https://github.com/sponsors/nzakas" 651 + } 652 + }, 653 + "node_modules/@isaacs/balanced-match": { 654 + "version": "4.0.1", 655 + "dev": true, 656 + "license": "MIT", 657 + "engines": { 658 + "node": "20 || >=22" 659 + } 660 + }, 661 + "node_modules/@isaacs/brace-expansion": { 662 + "version": "5.0.0", 663 + "dev": true, 664 + "license": "MIT", 665 + "dependencies": { 666 + "@isaacs/balanced-match": "^4.0.1" 667 + }, 668 + "engines": { 669 + "node": "20 || >=22" 670 + } 671 + }, 672 + "node_modules/@jridgewell/gen-mapping": { 673 + "version": "0.3.13", 674 + "dev": true, 675 + "license": "MIT", 676 + "dependencies": { 677 + "@jridgewell/sourcemap-codec": "^1.5.0", 678 + "@jridgewell/trace-mapping": "^0.3.24" 679 + } 680 + }, 681 + "node_modules/@jridgewell/remapping": { 682 + "version": "2.3.5", 683 + "dev": true, 684 + "license": "MIT", 685 + "dependencies": { 686 + "@jridgewell/gen-mapping": "^0.3.5", 687 + "@jridgewell/trace-mapping": "^0.3.24" 688 + } 689 + }, 690 + "node_modules/@jridgewell/resolve-uri": { 691 + "version": "3.1.2", 692 + "dev": true, 693 + "license": "MIT", 694 + "engines": { 695 + "node": ">=6.0.0" 696 + } 697 + }, 698 + "node_modules/@jridgewell/source-map": { 699 + "version": "0.3.11", 700 + "dev": true, 701 + "license": "MIT", 702 + "optional": true, 703 + "dependencies": { 704 + "@jridgewell/gen-mapping": "^0.3.5", 705 + "@jridgewell/trace-mapping": "^0.3.25" 706 + } 707 + }, 708 + "node_modules/@jridgewell/sourcemap-codec": { 709 + "version": "1.5.5", 710 + "dev": true, 711 + "license": "MIT" 712 + }, 713 + "node_modules/@jridgewell/trace-mapping": { 714 + "version": "0.3.31", 715 + "dev": true, 716 + "license": "MIT", 717 + "dependencies": { 718 + "@jridgewell/resolve-uri": "^3.1.0", 719 + "@jridgewell/sourcemap-codec": "^1.4.14" 720 + } 721 + }, 722 + "node_modules/@microsoft/api-extractor": { 723 + "version": "7.55.1", 724 + "dev": true, 725 + "license": "MIT", 726 + "dependencies": { 727 + "@microsoft/api-extractor-model": "7.32.1", 728 + "@microsoft/tsdoc": "~0.16.0", 729 + "@microsoft/tsdoc-config": "~0.18.0", 730 + "@rushstack/node-core-library": "5.19.0", 731 + "@rushstack/rig-package": "0.6.0", 732 + "@rushstack/terminal": "0.19.4", 733 + "@rushstack/ts-command-line": "5.1.4", 734 + "diff": "~8.0.2", 735 + "lodash": "~4.17.15", 736 + "minimatch": "10.0.3", 737 + "resolve": "~1.22.1", 738 + "semver": "~7.5.4", 739 + "source-map": "~0.6.1", 740 + "typescript": "5.8.2" 741 + }, 742 + "bin": { 743 + "api-extractor": "bin/api-extractor" 744 + } 745 + }, 746 + "node_modules/@microsoft/api-extractor-model": { 747 + "version": "7.32.1", 748 + "dev": true, 749 + "license": "MIT", 750 + "dependencies": { 751 + "@microsoft/tsdoc": "~0.16.0", 752 + "@microsoft/tsdoc-config": "~0.18.0", 753 + "@rushstack/node-core-library": "5.19.0" 754 + } 755 + }, 756 + "node_modules/@microsoft/api-extractor/node_modules/typescript": { 757 + "version": "5.8.2", 758 + "dev": true, 759 + "license": "Apache-2.0", 760 + "bin": { 761 + "tsc": "bin/tsc", 762 + "tsserver": "bin/tsserver" 763 + }, 764 + "engines": { 765 + "node": ">=14.17" 766 + } 767 + }, 768 + "node_modules/@microsoft/tsdoc": { 769 + "version": "0.16.0", 770 + "dev": true, 771 + "license": "MIT" 772 + }, 773 + "node_modules/@microsoft/tsdoc-config": { 774 + "version": "0.18.0", 775 + "dev": true, 776 + "license": "MIT", 777 + "dependencies": { 778 + "@microsoft/tsdoc": "0.16.0", 779 + "ajv": "~8.12.0", 780 + "jju": "~1.4.0", 781 + "resolve": "~1.22.2" 782 + } 783 + }, 784 + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { 785 + "version": "8.12.0", 786 + "dev": true, 787 + "license": "MIT", 788 + "dependencies": { 789 + "fast-deep-equal": "^3.1.1", 790 + "json-schema-traverse": "^1.0.0", 791 + "require-from-string": "^2.0.2", 792 + "uri-js": "^4.2.2" 793 + }, 794 + "funding": { 795 + "type": "github", 796 + "url": "https://github.com/sponsors/epoberezkin" 797 + } 798 + }, 799 + "node_modules/@napi-rs/wasm-runtime": { 800 + "version": "1.1.0", 801 + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", 802 + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", 803 + "dev": true, 804 + "license": "MIT", 805 + "optional": true, 806 + "dependencies": { 807 + "@emnapi/core": "^1.7.1", 808 + "@emnapi/runtime": "^1.7.1", 809 + "@tybys/wasm-util": "^0.10.1" 810 + } 811 + }, 812 + "node_modules/@oxc-project/runtime": { 813 + "version": "0.92.0", 814 + "dev": true, 815 + "license": "MIT", 816 + "engines": { 817 + "node": "^20.19.0 || >=22.12.0" 818 + } 819 + }, 820 + "node_modules/@oxc-project/types": { 821 + "version": "0.93.0", 822 + "dev": true, 823 + "license": "MIT", 824 + "funding": { 825 + "url": "https://github.com/sponsors/Boshen" 826 + } 827 + }, 828 + "node_modules/@rolldown/binding-android-arm64": { 829 + "version": "1.0.0-beta.41", 830 + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.41.tgz", 831 + "integrity": "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ==", 832 + "cpu": [ 833 + "arm64" 834 + ], 835 + "dev": true, 836 + "license": "MIT", 837 + "optional": true, 838 + "os": [ 839 + "android" 840 + ], 841 + "engines": { 842 + "node": "^20.19.0 || >=22.12.0" 843 + } 844 + }, 845 + "node_modules/@rolldown/binding-darwin-arm64": { 846 + "version": "1.0.0-beta.41", 847 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.41.tgz", 848 + "integrity": "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==", 849 + "cpu": [ 850 + "arm64" 851 + ], 852 + "dev": true, 853 + "license": "MIT", 854 + "optional": true, 855 + "os": [ 856 + "darwin" 857 + ], 858 + "engines": { 859 + "node": "^20.19.0 || >=22.12.0" 860 + } 861 + }, 862 + "node_modules/@rolldown/binding-darwin-x64": { 863 + "version": "1.0.0-beta.41", 864 + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.41.tgz", 865 + "integrity": "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ==", 866 + "cpu": [ 867 + "x64" 868 + ], 869 + "dev": true, 870 + "license": "MIT", 871 + "optional": true, 872 + "os": [ 873 + "darwin" 874 + ], 875 + "engines": { 876 + "node": "^20.19.0 || >=22.12.0" 877 + } 878 + }, 879 + "node_modules/@rolldown/binding-freebsd-x64": { 880 + "version": "1.0.0-beta.41", 881 + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.41.tgz", 882 + "integrity": "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g==", 883 + "cpu": [ 884 + "x64" 885 + ], 886 + "dev": true, 887 + "license": "MIT", 888 + "optional": true, 889 + "os": [ 890 + "freebsd" 891 + ], 892 + "engines": { 893 + "node": "^20.19.0 || >=22.12.0" 894 + } 895 + }, 896 + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { 897 + "version": "1.0.0-beta.41", 898 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.41.tgz", 899 + "integrity": "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA==", 900 + "cpu": [ 901 + "arm" 902 + ], 903 + "dev": true, 904 + "license": "MIT", 905 + "optional": true, 906 + "os": [ 907 + "linux" 908 + ], 909 + "engines": { 910 + "node": "^20.19.0 || >=22.12.0" 911 + } 912 + }, 913 + "node_modules/@rolldown/binding-linux-arm64-gnu": { 914 + "version": "1.0.0-beta.41", 915 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.41.tgz", 916 + "integrity": "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg==", 917 + "cpu": [ 918 + "arm64" 919 + ], 920 + "dev": true, 921 + "license": "MIT", 922 + "optional": true, 923 + "os": [ 924 + "linux" 925 + ], 926 + "engines": { 927 + "node": "^20.19.0 || >=22.12.0" 928 + } 929 + }, 930 + "node_modules/@rolldown/binding-linux-arm64-musl": { 931 + "version": "1.0.0-beta.41", 932 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.41.tgz", 933 + "integrity": "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg==", 934 + "cpu": [ 935 + "arm64" 936 + ], 937 + "dev": true, 938 + "license": "MIT", 939 + "optional": true, 940 + "os": [ 941 + "linux" 942 + ], 943 + "engines": { 944 + "node": "^20.19.0 || >=22.12.0" 945 + } 946 + }, 947 + "node_modules/@rolldown/binding-linux-x64-gnu": { 948 + "version": "1.0.0-beta.41", 949 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.41.tgz", 950 + "integrity": "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g==", 951 + "cpu": [ 952 + "x64" 953 + ], 954 + "dev": true, 955 + "license": "MIT", 956 + "optional": true, 957 + "os": [ 958 + "linux" 959 + ], 960 + "engines": { 961 + "node": "^20.19.0 || >=22.12.0" 962 + } 963 + }, 964 + "node_modules/@rolldown/binding-linux-x64-musl": { 965 + "version": "1.0.0-beta.41", 966 + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.41.tgz", 967 + "integrity": "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q==", 968 + "cpu": [ 969 + "x64" 970 + ], 971 + "dev": true, 972 + "license": "MIT", 973 + "optional": true, 974 + "os": [ 975 + "linux" 976 + ], 977 + "engines": { 978 + "node": "^20.19.0 || >=22.12.0" 979 + } 980 + }, 981 + "node_modules/@rolldown/binding-openharmony-arm64": { 982 + "version": "1.0.0-beta.41", 983 + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.41.tgz", 984 + "integrity": "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg==", 985 + "cpu": [ 986 + "arm64" 987 + ], 988 + "dev": true, 989 + "license": "MIT", 990 + "optional": true, 991 + "os": [ 992 + "openharmony" 993 + ], 994 + "engines": { 995 + "node": "^20.19.0 || >=22.12.0" 996 + } 997 + }, 998 + "node_modules/@rolldown/binding-wasm32-wasi": { 999 + "version": "1.0.0-beta.41", 1000 + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.41.tgz", 1001 + "integrity": "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ==", 1002 + "cpu": [ 1003 + "wasm32" 1004 + ], 1005 + "dev": true, 1006 + "license": "MIT", 1007 + "optional": true, 1008 + "dependencies": { 1009 + "@napi-rs/wasm-runtime": "^1.0.5" 1010 + }, 1011 + "engines": { 1012 + "node": ">=14.0.0" 1013 + } 1014 + }, 1015 + "node_modules/@rolldown/binding-win32-arm64-msvc": { 1016 + "version": "1.0.0-beta.41", 1017 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.41.tgz", 1018 + "integrity": "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==", 1019 + "cpu": [ 1020 + "arm64" 1021 + ], 1022 + "dev": true, 1023 + "license": "MIT", 1024 + "optional": true, 1025 + "os": [ 1026 + "win32" 1027 + ], 1028 + "engines": { 1029 + "node": "^20.19.0 || >=22.12.0" 1030 + } 1031 + }, 1032 + "node_modules/@rolldown/binding-win32-ia32-msvc": { 1033 + "version": "1.0.0-beta.41", 1034 + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.41.tgz", 1035 + "integrity": "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==", 1036 + "cpu": [ 1037 + "ia32" 1038 + ], 1039 + "dev": true, 1040 + "license": "MIT", 1041 + "optional": true, 1042 + "os": [ 1043 + "win32" 1044 + ], 1045 + "engines": { 1046 + "node": "^20.19.0 || >=22.12.0" 1047 + } 1048 + }, 1049 + "node_modules/@rolldown/binding-win32-x64-msvc": { 1050 + "version": "1.0.0-beta.41", 1051 + "cpu": [ 1052 + "x64" 1053 + ], 1054 + "dev": true, 1055 + "license": "MIT", 1056 + "optional": true, 1057 + "os": [ 1058 + "win32" 1059 + ], 1060 + "engines": { 1061 + "node": "^20.19.0 || >=22.12.0" 1062 + } 1063 + }, 1064 + "node_modules/@rolldown/pluginutils": { 1065 + "version": "1.0.0-beta.47", 1066 + "dev": true, 1067 + "license": "MIT" 1068 + }, 1069 + "node_modules/@rollup/pluginutils": { 1070 + "version": "4.2.1", 1071 + "dev": true, 1072 + "license": "MIT", 1073 + "dependencies": { 1074 + "estree-walker": "^2.0.1", 1075 + "picomatch": "^2.2.2" 1076 + }, 1077 + "engines": { 1078 + "node": ">= 8.0.0" 1079 + } 1080 + }, 1081 + "node_modules/@rollup/pluginutils/node_modules/picomatch": { 1082 + "version": "2.3.1", 1083 + "dev": true, 1084 + "license": "MIT", 1085 + "engines": { 1086 + "node": ">=8.6" 1087 + }, 1088 + "funding": { 1089 + "url": "https://github.com/sponsors/jonschlinkert" 1090 + } 1091 + }, 1092 + "node_modules/@rollup/rollup-android-arm-eabi": { 1093 + "version": "4.53.3", 1094 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", 1095 + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", 1096 + "cpu": [ 1097 + "arm" 1098 + ], 1099 + "dev": true, 1100 + "license": "MIT", 1101 + "optional": true, 1102 + "os": [ 1103 + "android" 1104 + ] 1105 + }, 1106 + "node_modules/@rollup/rollup-android-arm64": { 1107 + "version": "4.53.3", 1108 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", 1109 + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", 1110 + "cpu": [ 1111 + "arm64" 1112 + ], 1113 + "dev": true, 1114 + "license": "MIT", 1115 + "optional": true, 1116 + "os": [ 1117 + "android" 1118 + ] 1119 + }, 1120 + "node_modules/@rollup/rollup-darwin-arm64": { 1121 + "version": "4.53.3", 1122 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", 1123 + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", 1124 + "cpu": [ 1125 + "arm64" 1126 + ], 1127 + "dev": true, 1128 + "license": "MIT", 1129 + "optional": true, 1130 + "os": [ 1131 + "darwin" 1132 + ] 1133 + }, 1134 + "node_modules/@rollup/rollup-darwin-x64": { 1135 + "version": "4.53.3", 1136 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", 1137 + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", 1138 + "cpu": [ 1139 + "x64" 1140 + ], 1141 + "dev": true, 1142 + "license": "MIT", 1143 + "optional": true, 1144 + "os": [ 1145 + "darwin" 1146 + ] 1147 + }, 1148 + "node_modules/@rollup/rollup-freebsd-arm64": { 1149 + "version": "4.53.3", 1150 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", 1151 + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", 1152 + "cpu": [ 1153 + "arm64" 1154 + ], 1155 + "dev": true, 1156 + "license": "MIT", 1157 + "optional": true, 1158 + "os": [ 1159 + "freebsd" 1160 + ] 1161 + }, 1162 + "node_modules/@rollup/rollup-freebsd-x64": { 1163 + "version": "4.53.3", 1164 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", 1165 + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", 1166 + "cpu": [ 1167 + "x64" 1168 + ], 1169 + "dev": true, 1170 + "license": "MIT", 1171 + "optional": true, 1172 + "os": [ 1173 + "freebsd" 1174 + ] 1175 + }, 1176 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1177 + "version": "4.53.3", 1178 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", 1179 + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", 1180 + "cpu": [ 1181 + "arm" 1182 + ], 1183 + "dev": true, 1184 + "license": "MIT", 1185 + "optional": true, 1186 + "os": [ 1187 + "linux" 1188 + ] 1189 + }, 1190 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1191 + "version": "4.53.3", 1192 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", 1193 + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", 1194 + "cpu": [ 1195 + "arm" 1196 + ], 1197 + "dev": true, 1198 + "license": "MIT", 1199 + "optional": true, 1200 + "os": [ 1201 + "linux" 1202 + ] 1203 + }, 1204 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 1205 + "version": "4.53.3", 1206 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", 1207 + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", 1208 + "cpu": [ 1209 + "arm64" 1210 + ], 1211 + "dev": true, 1212 + "license": "MIT", 1213 + "optional": true, 1214 + "os": [ 1215 + "linux" 1216 + ] 1217 + }, 1218 + "node_modules/@rollup/rollup-linux-arm64-musl": { 1219 + "version": "4.53.3", 1220 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", 1221 + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", 1222 + "cpu": [ 1223 + "arm64" 1224 + ], 1225 + "dev": true, 1226 + "license": "MIT", 1227 + "optional": true, 1228 + "os": [ 1229 + "linux" 1230 + ] 1231 + }, 1232 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 1233 + "version": "4.53.3", 1234 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", 1235 + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", 1236 + "cpu": [ 1237 + "loong64" 1238 + ], 1239 + "dev": true, 1240 + "license": "MIT", 1241 + "optional": true, 1242 + "os": [ 1243 + "linux" 1244 + ] 1245 + }, 1246 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 1247 + "version": "4.53.3", 1248 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", 1249 + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", 1250 + "cpu": [ 1251 + "ppc64" 1252 + ], 1253 + "dev": true, 1254 + "license": "MIT", 1255 + "optional": true, 1256 + "os": [ 1257 + "linux" 1258 + ] 1259 + }, 1260 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1261 + "version": "4.53.3", 1262 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", 1263 + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", 1264 + "cpu": [ 1265 + "riscv64" 1266 + ], 1267 + "dev": true, 1268 + "license": "MIT", 1269 + "optional": true, 1270 + "os": [ 1271 + "linux" 1272 + ] 1273 + }, 1274 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 1275 + "version": "4.53.3", 1276 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", 1277 + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", 1278 + "cpu": [ 1279 + "riscv64" 1280 + ], 1281 + "dev": true, 1282 + "license": "MIT", 1283 + "optional": true, 1284 + "os": [ 1285 + "linux" 1286 + ] 1287 + }, 1288 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1289 + "version": "4.53.3", 1290 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", 1291 + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", 1292 + "cpu": [ 1293 + "s390x" 1294 + ], 1295 + "dev": true, 1296 + "license": "MIT", 1297 + "optional": true, 1298 + "os": [ 1299 + "linux" 1300 + ] 1301 + }, 1302 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1303 + "version": "4.53.3", 1304 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", 1305 + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", 1306 + "cpu": [ 1307 + "x64" 1308 + ], 1309 + "dev": true, 1310 + "license": "MIT", 1311 + "optional": true, 1312 + "os": [ 1313 + "linux" 1314 + ] 1315 + }, 1316 + "node_modules/@rollup/rollup-linux-x64-musl": { 1317 + "version": "4.53.3", 1318 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", 1319 + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", 1320 + "cpu": [ 1321 + "x64" 1322 + ], 1323 + "dev": true, 1324 + "license": "MIT", 1325 + "optional": true, 1326 + "os": [ 1327 + "linux" 1328 + ] 1329 + }, 1330 + "node_modules/@rollup/rollup-openharmony-arm64": { 1331 + "version": "4.53.3", 1332 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", 1333 + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", 1334 + "cpu": [ 1335 + "arm64" 1336 + ], 1337 + "dev": true, 1338 + "license": "MIT", 1339 + "optional": true, 1340 + "os": [ 1341 + "openharmony" 1342 + ] 1343 + }, 1344 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1345 + "version": "4.53.3", 1346 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", 1347 + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", 1348 + "cpu": [ 1349 + "arm64" 1350 + ], 1351 + "dev": true, 1352 + "license": "MIT", 1353 + "optional": true, 1354 + "os": [ 1355 + "win32" 1356 + ] 1357 + }, 1358 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1359 + "version": "4.53.3", 1360 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", 1361 + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", 1362 + "cpu": [ 1363 + "ia32" 1364 + ], 1365 + "dev": true, 1366 + "license": "MIT", 1367 + "optional": true, 1368 + "os": [ 1369 + "win32" 1370 + ] 1371 + }, 1372 + "node_modules/@rollup/rollup-win32-x64-gnu": { 1373 + "version": "4.53.3", 1374 + "cpu": [ 1375 + "x64" 1376 + ], 1377 + "dev": true, 1378 + "license": "MIT", 1379 + "optional": true, 1380 + "os": [ 1381 + "win32" 1382 + ] 1383 + }, 1384 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1385 + "version": "4.53.3", 1386 + "cpu": [ 1387 + "x64" 1388 + ], 1389 + "dev": true, 1390 + "license": "MIT", 1391 + "optional": true, 1392 + "os": [ 1393 + "win32" 1394 + ] 1395 + }, 1396 + "node_modules/@rushstack/node-core-library": { 1397 + "version": "5.19.0", 1398 + "dev": true, 1399 + "license": "MIT", 1400 + "dependencies": { 1401 + "ajv": "~8.13.0", 1402 + "ajv-draft-04": "~1.0.0", 1403 + "ajv-formats": "~3.0.1", 1404 + "fs-extra": "~11.3.0", 1405 + "import-lazy": "~4.0.0", 1406 + "jju": "~1.4.0", 1407 + "resolve": "~1.22.1", 1408 + "semver": "~7.5.4" 1409 + }, 1410 + "peerDependencies": { 1411 + "@types/node": "*" 1412 + }, 1413 + "peerDependenciesMeta": { 1414 + "@types/node": { 1415 + "optional": true 1416 + } 1417 + } 1418 + }, 1419 + "node_modules/@rushstack/node-core-library/node_modules/ajv": { 1420 + "version": "8.13.0", 1421 + "dev": true, 1422 + "license": "MIT", 1423 + "dependencies": { 1424 + "fast-deep-equal": "^3.1.3", 1425 + "json-schema-traverse": "^1.0.0", 1426 + "require-from-string": "^2.0.2", 1427 + "uri-js": "^4.4.1" 1428 + }, 1429 + "funding": { 1430 + "type": "github", 1431 + "url": "https://github.com/sponsors/epoberezkin" 1432 + } 1433 + }, 1434 + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { 1435 + "version": "11.3.2", 1436 + "dev": true, 1437 + "license": "MIT", 1438 + "dependencies": { 1439 + "graceful-fs": "^4.2.0", 1440 + "jsonfile": "^6.0.1", 1441 + "universalify": "^2.0.0" 1442 + }, 1443 + "engines": { 1444 + "node": ">=14.14" 1445 + } 1446 + }, 1447 + "node_modules/@rushstack/problem-matcher": { 1448 + "version": "0.1.1", 1449 + "dev": true, 1450 + "license": "MIT", 1451 + "peerDependencies": { 1452 + "@types/node": "*" 1453 + }, 1454 + "peerDependenciesMeta": { 1455 + "@types/node": { 1456 + "optional": true 1457 + } 1458 + } 1459 + }, 1460 + "node_modules/@rushstack/rig-package": { 1461 + "version": "0.6.0", 1462 + "dev": true, 1463 + "license": "MIT", 1464 + "dependencies": { 1465 + "resolve": "~1.22.1", 1466 + "strip-json-comments": "~3.1.1" 1467 + } 1468 + }, 1469 + "node_modules/@rushstack/terminal": { 1470 + "version": "0.19.4", 1471 + "dev": true, 1472 + "license": "MIT", 1473 + "dependencies": { 1474 + "@rushstack/node-core-library": "5.19.0", 1475 + "@rushstack/problem-matcher": "0.1.1", 1476 + "supports-color": "~8.1.1" 1477 + }, 1478 + "peerDependencies": { 1479 + "@types/node": "*" 1480 + }, 1481 + "peerDependenciesMeta": { 1482 + "@types/node": { 1483 + "optional": true 1484 + } 1485 + } 1486 + }, 1487 + "node_modules/@rushstack/ts-command-line": { 1488 + "version": "5.1.4", 1489 + "dev": true, 1490 + "license": "MIT", 1491 + "dependencies": { 1492 + "@rushstack/terminal": "0.19.4", 1493 + "@types/argparse": "1.0.38", 1494 + "argparse": "~1.0.9", 1495 + "string-argv": "~0.3.1" 1496 + } 1497 + }, 1498 + "node_modules/@standard-schema/spec": { 1499 + "version": "1.0.0", 1500 + "license": "MIT" 1501 + }, 1502 + "node_modules/@tybys/wasm-util": { 1503 + "version": "0.10.1", 1504 + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", 1505 + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", 1506 + "dev": true, 1507 + "license": "MIT", 1508 + "optional": true, 1509 + "dependencies": { 1510 + "tslib": "^2.4.0" 1511 + } 1512 + }, 1513 + "node_modules/@types/argparse": { 1514 + "version": "1.0.38", 1515 + "dev": true, 1516 + "license": "MIT" 1517 + }, 1518 + "node_modules/@types/babel__core": { 1519 + "version": "7.20.5", 1520 + "dev": true, 1521 + "license": "MIT", 1522 + "dependencies": { 1523 + "@babel/parser": "^7.20.7", 1524 + "@babel/types": "^7.20.7", 1525 + "@types/babel__generator": "*", 1526 + "@types/babel__template": "*", 1527 + "@types/babel__traverse": "*" 1528 + } 1529 + }, 1530 + "node_modules/@types/babel__generator": { 1531 + "version": "7.27.0", 1532 + "dev": true, 1533 + "license": "MIT", 1534 + "dependencies": { 1535 + "@babel/types": "^7.0.0" 1536 + } 1537 + }, 1538 + "node_modules/@types/babel__template": { 1539 + "version": "7.4.4", 1540 + "dev": true, 1541 + "license": "MIT", 1542 + "dependencies": { 1543 + "@babel/parser": "^7.1.0", 1544 + "@babel/types": "^7.0.0" 1545 + } 1546 + }, 1547 + "node_modules/@types/babel__traverse": { 1548 + "version": "7.28.0", 1549 + "dev": true, 1550 + "license": "MIT", 1551 + "dependencies": { 1552 + "@babel/types": "^7.28.2" 1553 + } 1554 + }, 1555 + "node_modules/@types/estree": { 1556 + "version": "1.0.8", 1557 + "dev": true, 1558 + "license": "MIT" 1559 + }, 1560 + "node_modules/@types/json-schema": { 1561 + "version": "7.0.15", 1562 + "dev": true, 1563 + "license": "MIT" 1564 + }, 1565 + "node_modules/@types/node": { 1566 + "version": "24.10.1", 1567 + "dev": true, 1568 + "license": "MIT", 1569 + "peer": true, 1570 + "dependencies": { 1571 + "undici-types": "~7.16.0" 1572 + } 1573 + }, 1574 + "node_modules/@types/react": { 1575 + "version": "19.2.7", 1576 + "dev": true, 1577 + "license": "MIT", 1578 + "peer": true, 1579 + "dependencies": { 1580 + "csstype": "^3.2.2" 1581 + } 1582 + }, 1583 + "node_modules/@types/react-dom": { 1584 + "version": "19.2.3", 1585 + "dev": true, 1586 + "license": "MIT", 1587 + "peerDependencies": { 1588 + "@types/react": "^19.2.0" 1589 + } 1590 + }, 1591 + "node_modules/@typescript-eslint/eslint-plugin": { 1592 + "version": "8.48.1", 1593 + "dev": true, 1594 + "license": "MIT", 1595 + "dependencies": { 1596 + "@eslint-community/regexpp": "^4.10.0", 1597 + "@typescript-eslint/scope-manager": "8.48.1", 1598 + "@typescript-eslint/type-utils": "8.48.1", 1599 + "@typescript-eslint/utils": "8.48.1", 1600 + "@typescript-eslint/visitor-keys": "8.48.1", 1601 + "graphemer": "^1.4.0", 1602 + "ignore": "^7.0.0", 1603 + "natural-compare": "^1.4.0", 1604 + "ts-api-utils": "^2.1.0" 1605 + }, 1606 + "engines": { 1607 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1608 + }, 1609 + "funding": { 1610 + "type": "opencollective", 1611 + "url": "https://opencollective.com/typescript-eslint" 1612 + }, 1613 + "peerDependencies": { 1614 + "@typescript-eslint/parser": "^8.48.1", 1615 + "eslint": "^8.57.0 || ^9.0.0", 1616 + "typescript": ">=4.8.4 <6.0.0" 1617 + } 1618 + }, 1619 + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 1620 + "version": "7.0.5", 1621 + "dev": true, 1622 + "license": "MIT", 1623 + "engines": { 1624 + "node": ">= 4" 1625 + } 1626 + }, 1627 + "node_modules/@typescript-eslint/parser": { 1628 + "version": "8.48.1", 1629 + "dev": true, 1630 + "license": "MIT", 1631 + "peer": true, 1632 + "dependencies": { 1633 + "@typescript-eslint/scope-manager": "8.48.1", 1634 + "@typescript-eslint/types": "8.48.1", 1635 + "@typescript-eslint/typescript-estree": "8.48.1", 1636 + "@typescript-eslint/visitor-keys": "8.48.1", 1637 + "debug": "^4.3.4" 1638 + }, 1639 + "engines": { 1640 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1641 + }, 1642 + "funding": { 1643 + "type": "opencollective", 1644 + "url": "https://opencollective.com/typescript-eslint" 1645 + }, 1646 + "peerDependencies": { 1647 + "eslint": "^8.57.0 || ^9.0.0", 1648 + "typescript": ">=4.8.4 <6.0.0" 1649 + } 1650 + }, 1651 + "node_modules/@typescript-eslint/project-service": { 1652 + "version": "8.48.1", 1653 + "dev": true, 1654 + "license": "MIT", 1655 + "dependencies": { 1656 + "@typescript-eslint/tsconfig-utils": "^8.48.1", 1657 + "@typescript-eslint/types": "^8.48.1", 1658 + "debug": "^4.3.4" 1659 + }, 1660 + "engines": { 1661 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1662 + }, 1663 + "funding": { 1664 + "type": "opencollective", 1665 + "url": "https://opencollective.com/typescript-eslint" 1666 + }, 1667 + "peerDependencies": { 1668 + "typescript": ">=4.8.4 <6.0.0" 1669 + } 1670 + }, 1671 + "node_modules/@typescript-eslint/scope-manager": { 1672 + "version": "8.48.1", 1673 + "dev": true, 1674 + "license": "MIT", 1675 + "dependencies": { 1676 + "@typescript-eslint/types": "8.48.1", 1677 + "@typescript-eslint/visitor-keys": "8.48.1" 1678 + }, 1679 + "engines": { 1680 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1681 + }, 1682 + "funding": { 1683 + "type": "opencollective", 1684 + "url": "https://opencollective.com/typescript-eslint" 1685 + } 1686 + }, 1687 + "node_modules/@typescript-eslint/tsconfig-utils": { 1688 + "version": "8.48.1", 1689 + "dev": true, 1690 + "license": "MIT", 1691 + "engines": { 1692 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1693 + }, 1694 + "funding": { 1695 + "type": "opencollective", 1696 + "url": "https://opencollective.com/typescript-eslint" 1697 + }, 1698 + "peerDependencies": { 1699 + "typescript": ">=4.8.4 <6.0.0" 1700 + } 1701 + }, 1702 + "node_modules/@typescript-eslint/type-utils": { 1703 + "version": "8.48.1", 1704 + "dev": true, 1705 + "license": "MIT", 1706 + "dependencies": { 1707 + "@typescript-eslint/types": "8.48.1", 1708 + "@typescript-eslint/typescript-estree": "8.48.1", 1709 + "@typescript-eslint/utils": "8.48.1", 1710 + "debug": "^4.3.4", 1711 + "ts-api-utils": "^2.1.0" 1712 + }, 1713 + "engines": { 1714 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1715 + }, 1716 + "funding": { 1717 + "type": "opencollective", 1718 + "url": "https://opencollective.com/typescript-eslint" 1719 + }, 1720 + "peerDependencies": { 1721 + "eslint": "^8.57.0 || ^9.0.0", 1722 + "typescript": ">=4.8.4 <6.0.0" 1723 + } 1724 + }, 1725 + "node_modules/@typescript-eslint/types": { 1726 + "version": "8.48.1", 1727 + "dev": true, 1728 + "license": "MIT", 1729 + "engines": { 1730 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1731 + }, 1732 + "funding": { 1733 + "type": "opencollective", 1734 + "url": "https://opencollective.com/typescript-eslint" 1735 + } 1736 + }, 1737 + "node_modules/@typescript-eslint/typescript-estree": { 1738 + "version": "8.48.1", 1739 + "dev": true, 1740 + "license": "MIT", 1741 + "dependencies": { 1742 + "@typescript-eslint/project-service": "8.48.1", 1743 + "@typescript-eslint/tsconfig-utils": "8.48.1", 1744 + "@typescript-eslint/types": "8.48.1", 1745 + "@typescript-eslint/visitor-keys": "8.48.1", 1746 + "debug": "^4.3.4", 1747 + "minimatch": "^9.0.4", 1748 + "semver": "^7.6.0", 1749 + "tinyglobby": "^0.2.15", 1750 + "ts-api-utils": "^2.1.0" 1751 + }, 1752 + "engines": { 1753 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1754 + }, 1755 + "funding": { 1756 + "type": "opencollective", 1757 + "url": "https://opencollective.com/typescript-eslint" 1758 + }, 1759 + "peerDependencies": { 1760 + "typescript": ">=4.8.4 <6.0.0" 1761 + } 1762 + }, 1763 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 1764 + "version": "9.0.5", 1765 + "dev": true, 1766 + "license": "ISC", 1767 + "dependencies": { 1768 + "brace-expansion": "^2.0.1" 1769 + }, 1770 + "engines": { 1771 + "node": ">=16 || 14 >=14.17" 1772 + }, 1773 + "funding": { 1774 + "url": "https://github.com/sponsors/isaacs" 1775 + } 1776 + }, 1777 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { 1778 + "version": "2.0.2", 1779 + "dev": true, 1780 + "license": "MIT", 1781 + "dependencies": { 1782 + "balanced-match": "^1.0.0" 1783 + } 1784 + }, 1785 + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 1786 + "version": "7.7.3", 1787 + "dev": true, 1788 + "license": "ISC", 1789 + "bin": { 1790 + "semver": "bin/semver.js" 1791 + }, 1792 + "engines": { 1793 + "node": ">=10" 1794 + } 1795 + }, 1796 + "node_modules/@typescript-eslint/utils": { 1797 + "version": "8.48.1", 1798 + "dev": true, 1799 + "license": "MIT", 1800 + "dependencies": { 1801 + "@eslint-community/eslint-utils": "^4.7.0", 1802 + "@typescript-eslint/scope-manager": "8.48.1", 1803 + "@typescript-eslint/types": "8.48.1", 1804 + "@typescript-eslint/typescript-estree": "8.48.1" 1805 + }, 1806 + "engines": { 1807 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1808 + }, 1809 + "funding": { 1810 + "type": "opencollective", 1811 + "url": "https://opencollective.com/typescript-eslint" 1812 + }, 1813 + "peerDependencies": { 1814 + "eslint": "^8.57.0 || ^9.0.0", 1815 + "typescript": ">=4.8.4 <6.0.0" 1816 + } 1817 + }, 1818 + "node_modules/@typescript-eslint/visitor-keys": { 1819 + "version": "8.48.1", 1820 + "dev": true, 1821 + "license": "MIT", 1822 + "dependencies": { 1823 + "@typescript-eslint/types": "8.48.1", 1824 + "eslint-visitor-keys": "^4.2.1" 1825 + }, 1826 + "engines": { 1827 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1828 + }, 1829 + "funding": { 1830 + "type": "opencollective", 1831 + "url": "https://opencollective.com/typescript-eslint" 1832 + } 1833 + }, 1834 + "node_modules/@vitejs/plugin-react": { 1835 + "version": "5.1.1", 1836 + "dev": true, 1837 + "license": "MIT", 1838 + "dependencies": { 1839 + "@babel/core": "^7.28.5", 1840 + "@babel/plugin-transform-react-jsx-self": "^7.27.1", 1841 + "@babel/plugin-transform-react-jsx-source": "^7.27.1", 1842 + "@rolldown/pluginutils": "1.0.0-beta.47", 1843 + "@types/babel__core": "^7.20.5", 1844 + "react-refresh": "^0.18.0" 1845 + }, 1846 + "engines": { 1847 + "node": "^20.19.0 || >=22.12.0" 1848 + }, 1849 + "peerDependencies": { 1850 + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" 1851 + } 1852 + }, 1853 + "node_modules/@volar/language-core": { 1854 + "version": "2.4.26", 1855 + "dev": true, 1856 + "license": "MIT", 1857 + "dependencies": { 1858 + "@volar/source-map": "2.4.26" 1859 + } 1860 + }, 1861 + "node_modules/@volar/source-map": { 1862 + "version": "2.4.26", 1863 + "dev": true, 1864 + "license": "MIT" 1865 + }, 1866 + "node_modules/@volar/typescript": { 1867 + "version": "2.4.26", 1868 + "dev": true, 1869 + "license": "MIT", 1870 + "dependencies": { 1871 + "@volar/language-core": "2.4.26", 1872 + "path-browserify": "^1.0.1", 1873 + "vscode-uri": "^3.0.8" 1874 + } 1875 + }, 1876 + "node_modules/acorn": { 1877 + "version": "8.15.0", 1878 + "dev": true, 1879 + "license": "MIT", 1880 + "peer": true, 1881 + "bin": { 1882 + "acorn": "bin/acorn" 1883 + }, 1884 + "engines": { 1885 + "node": ">=0.4.0" 1886 + } 1887 + }, 1888 + "node_modules/acorn-jsx": { 1889 + "version": "5.3.2", 1890 + "dev": true, 1891 + "license": "MIT", 1892 + "peerDependencies": { 1893 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 1894 + } 1895 + }, 1896 + "node_modules/ajv": { 1897 + "version": "8.17.1", 1898 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", 1899 + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", 1900 + "dev": true, 1901 + "license": "MIT", 1902 + "peer": true, 1903 + "dependencies": { 1904 + "fast-deep-equal": "^3.1.3", 1905 + "fast-uri": "^3.0.1", 1906 + "json-schema-traverse": "^1.0.0", 1907 + "require-from-string": "^2.0.2" 1908 + }, 1909 + "funding": { 1910 + "type": "github", 1911 + "url": "https://github.com/sponsors/epoberezkin" 1912 + } 1913 + }, 1914 + "node_modules/ajv-draft-04": { 1915 + "version": "1.0.0", 1916 + "dev": true, 1917 + "license": "MIT", 1918 + "peerDependencies": { 1919 + "ajv": "^8.5.0" 1920 + }, 1921 + "peerDependenciesMeta": { 1922 + "ajv": { 1923 + "optional": true 1924 + } 1925 + } 1926 + }, 1927 + "node_modules/ajv-formats": { 1928 + "version": "3.0.1", 1929 + "dev": true, 1930 + "license": "MIT", 1931 + "dependencies": { 1932 + "ajv": "^8.0.0" 1933 + }, 1934 + "peerDependencies": { 1935 + "ajv": "^8.0.0" 1936 + }, 1937 + "peerDependenciesMeta": { 1938 + "ajv": { 1939 + "optional": true 1940 + } 1941 + } 1942 + }, 1943 + "node_modules/ansi-styles": { 1944 + "version": "4.3.0", 1945 + "dev": true, 1946 + "license": "MIT", 1947 + "dependencies": { 1948 + "color-convert": "^2.0.1" 1949 + }, 1950 + "engines": { 1951 + "node": ">=8" 1952 + }, 1953 + "funding": { 1954 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1955 + } 1956 + }, 1957 + "node_modules/ansis": { 1958 + "version": "4.2.0", 1959 + "dev": true, 1960 + "license": "ISC", 1961 + "engines": { 1962 + "node": ">=14" 1963 + } 1964 + }, 1965 + "node_modules/argparse": { 1966 + "version": "1.0.10", 1967 + "dev": true, 1968 + "license": "MIT", 1969 + "dependencies": { 1970 + "sprintf-js": "~1.0.2" 1971 + } 1972 + }, 1973 + "node_modules/balanced-match": { 1974 + "version": "1.0.2", 1975 + "dev": true, 1976 + "license": "MIT" 1977 + }, 1978 + "node_modules/baseline-browser-mapping": { 1979 + "version": "2.8.32", 1980 + "dev": true, 1981 + "license": "Apache-2.0", 1982 + "bin": { 1983 + "baseline-browser-mapping": "dist/cli.js" 1984 + } 1985 + }, 1986 + "node_modules/brace-expansion": { 1987 + "version": "1.1.12", 1988 + "dev": true, 1989 + "license": "MIT", 1990 + "dependencies": { 1991 + "balanced-match": "^1.0.0", 1992 + "concat-map": "0.0.1" 1993 + } 1994 + }, 1995 + "node_modules/browserslist": { 1996 + "version": "4.28.0", 1997 + "dev": true, 1998 + "funding": [ 1999 + { 2000 + "type": "opencollective", 2001 + "url": "https://opencollective.com/browserslist" 2002 + }, 2003 + { 2004 + "type": "tidelift", 2005 + "url": "https://tidelift.com/funding/github/npm/browserslist" 2006 + }, 2007 + { 2008 + "type": "github", 2009 + "url": "https://github.com/sponsors/ai" 2010 + } 2011 + ], 2012 + "license": "MIT", 2013 + "peer": true, 2014 + "dependencies": { 2015 + "baseline-browser-mapping": "^2.8.25", 2016 + "caniuse-lite": "^1.0.30001754", 2017 + "electron-to-chromium": "^1.5.249", 2018 + "node-releases": "^2.0.27", 2019 + "update-browserslist-db": "^1.1.4" 2020 + }, 2021 + "bin": { 2022 + "browserslist": "cli.js" 2023 + }, 2024 + "engines": { 2025 + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 2026 + } 2027 + }, 2028 + "node_modules/buffer-from": { 2029 + "version": "1.1.2", 2030 + "dev": true, 2031 + "license": "MIT", 2032 + "optional": true 2033 + }, 2034 + "node_modules/callsites": { 2035 + "version": "3.1.0", 2036 + "dev": true, 2037 + "license": "MIT", 2038 + "engines": { 2039 + "node": ">=6" 2040 + } 2041 + }, 2042 + "node_modules/caniuse-lite": { 2043 + "version": "1.0.30001759", 2044 + "dev": true, 2045 + "funding": [ 2046 + { 2047 + "type": "opencollective", 2048 + "url": "https://opencollective.com/browserslist" 2049 + }, 2050 + { 2051 + "type": "tidelift", 2052 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 2053 + }, 2054 + { 2055 + "type": "github", 2056 + "url": "https://github.com/sponsors/ai" 2057 + } 2058 + ], 2059 + "license": "CC-BY-4.0" 2060 + }, 2061 + "node_modules/chalk": { 2062 + "version": "4.1.2", 2063 + "dev": true, 2064 + "license": "MIT", 2065 + "dependencies": { 2066 + "ansi-styles": "^4.1.0", 2067 + "supports-color": "^7.1.0" 2068 + }, 2069 + "engines": { 2070 + "node": ">=10" 2071 + }, 2072 + "funding": { 2073 + "url": "https://github.com/chalk/chalk?sponsor=1" 2074 + } 2075 + }, 2076 + "node_modules/chalk/node_modules/supports-color": { 2077 + "version": "7.2.0", 2078 + "dev": true, 2079 + "license": "MIT", 2080 + "dependencies": { 2081 + "has-flag": "^4.0.0" 2082 + }, 2083 + "engines": { 2084 + "node": ">=8" 2085 + } 2086 + }, 2087 + "node_modules/color-convert": { 2088 + "version": "2.0.1", 2089 + "dev": true, 2090 + "license": "MIT", 2091 + "dependencies": { 2092 + "color-name": "~1.1.4" 2093 + }, 2094 + "engines": { 2095 + "node": ">=7.0.0" 2096 + } 2097 + }, 2098 + "node_modules/color-name": { 2099 + "version": "1.1.4", 2100 + "dev": true, 2101 + "license": "MIT" 2102 + }, 2103 + "node_modules/commander": { 2104 + "version": "2.20.3", 2105 + "dev": true, 2106 + "license": "MIT", 2107 + "optional": true 2108 + }, 2109 + "node_modules/commondir": { 2110 + "version": "1.0.1", 2111 + "dev": true, 2112 + "license": "MIT" 2113 + }, 2114 + "node_modules/compare-versions": { 2115 + "version": "6.1.1", 2116 + "dev": true, 2117 + "license": "MIT" 2118 + }, 2119 + "node_modules/concat-map": { 2120 + "version": "0.0.1", 2121 + "dev": true, 2122 + "license": "MIT" 2123 + }, 2124 + "node_modules/confbox": { 2125 + "version": "0.2.2", 2126 + "dev": true, 2127 + "license": "MIT" 2128 + }, 2129 + "node_modules/convert-source-map": { 2130 + "version": "2.0.0", 2131 + "dev": true, 2132 + "license": "MIT" 2133 + }, 2134 + "node_modules/cross-spawn": { 2135 + "version": "7.0.6", 2136 + "dev": true, 2137 + "license": "MIT", 2138 + "dependencies": { 2139 + "path-key": "^3.1.0", 2140 + "shebang-command": "^2.0.0", 2141 + "which": "^2.0.1" 2142 + }, 2143 + "engines": { 2144 + "node": ">= 8" 2145 + } 2146 + }, 2147 + "node_modules/csstype": { 2148 + "version": "3.2.3", 2149 + "dev": true, 2150 + "license": "MIT" 2151 + }, 2152 + "node_modules/debug": { 2153 + "version": "4.4.3", 2154 + "dev": true, 2155 + "license": "MIT", 2156 + "dependencies": { 2157 + "ms": "^2.1.3" 2158 + }, 2159 + "engines": { 2160 + "node": ">=6.0" 2161 + }, 2162 + "peerDependenciesMeta": { 2163 + "supports-color": { 2164 + "optional": true 2165 + } 2166 + } 2167 + }, 2168 + "node_modules/deep-is": { 2169 + "version": "0.1.4", 2170 + "dev": true, 2171 + "license": "MIT" 2172 + }, 2173 + "node_modules/detect-libc": { 2174 + "version": "2.1.2", 2175 + "dev": true, 2176 + "license": "Apache-2.0", 2177 + "engines": { 2178 + "node": ">=8" 2179 + } 2180 + }, 2181 + "node_modules/diff": { 2182 + "version": "8.0.2", 2183 + "dev": true, 2184 + "license": "BSD-3-Clause", 2185 + "engines": { 2186 + "node": ">=0.3.1" 2187 + } 2188 + }, 2189 + "node_modules/electron-to-chromium": { 2190 + "version": "1.5.263", 2191 + "dev": true, 2192 + "license": "ISC" 2193 + }, 2194 + "node_modules/escalade": { 2195 + "version": "3.2.0", 2196 + "dev": true, 2197 + "license": "MIT", 2198 + "engines": { 2199 + "node": ">=6" 2200 + } 2201 + }, 2202 + "node_modules/escape-string-regexp": { 2203 + "version": "4.0.0", 2204 + "dev": true, 2205 + "license": "MIT", 2206 + "engines": { 2207 + "node": ">=10" 2208 + }, 2209 + "funding": { 2210 + "url": "https://github.com/sponsors/sindresorhus" 2211 + } 2212 + }, 2213 + "node_modules/eslint": { 2214 + "version": "9.39.1", 2215 + "dev": true, 2216 + "license": "MIT", 2217 + "peer": true, 2218 + "dependencies": { 2219 + "@eslint-community/eslint-utils": "^4.8.0", 2220 + "@eslint-community/regexpp": "^4.12.1", 2221 + "@eslint/config-array": "^0.21.1", 2222 + "@eslint/config-helpers": "^0.4.2", 2223 + "@eslint/core": "^0.17.0", 2224 + "@eslint/eslintrc": "^3.3.1", 2225 + "@eslint/js": "9.39.1", 2226 + "@eslint/plugin-kit": "^0.4.1", 2227 + "@humanfs/node": "^0.16.6", 2228 + "@humanwhocodes/module-importer": "^1.0.1", 2229 + "@humanwhocodes/retry": "^0.4.2", 2230 + "@types/estree": "^1.0.6", 2231 + "ajv": "^6.12.4", 2232 + "chalk": "^4.0.0", 2233 + "cross-spawn": "^7.0.6", 2234 + "debug": "^4.3.2", 2235 + "escape-string-regexp": "^4.0.0", 2236 + "eslint-scope": "^8.4.0", 2237 + "eslint-visitor-keys": "^4.2.1", 2238 + "espree": "^10.4.0", 2239 + "esquery": "^1.5.0", 2240 + "esutils": "^2.0.2", 2241 + "fast-deep-equal": "^3.1.3", 2242 + "file-entry-cache": "^8.0.0", 2243 + "find-up": "^5.0.0", 2244 + "glob-parent": "^6.0.2", 2245 + "ignore": "^5.2.0", 2246 + "imurmurhash": "^0.1.4", 2247 + "is-glob": "^4.0.0", 2248 + "json-stable-stringify-without-jsonify": "^1.0.1", 2249 + "lodash.merge": "^4.6.2", 2250 + "minimatch": "^3.1.2", 2251 + "natural-compare": "^1.4.0", 2252 + "optionator": "^0.9.3" 2253 + }, 2254 + "bin": { 2255 + "eslint": "bin/eslint.js" 2256 + }, 2257 + "engines": { 2258 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2259 + }, 2260 + "funding": { 2261 + "url": "https://eslint.org/donate" 2262 + }, 2263 + "peerDependencies": { 2264 + "jiti": "*" 2265 + }, 2266 + "peerDependenciesMeta": { 2267 + "jiti": { 2268 + "optional": true 2269 + } 2270 + } 2271 + }, 2272 + "node_modules/eslint-plugin-react-hooks": { 2273 + "version": "5.2.0", 2274 + "dev": true, 2275 + "license": "MIT", 2276 + "engines": { 2277 + "node": ">=10" 2278 + }, 2279 + "peerDependencies": { 2280 + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" 2281 + } 2282 + }, 2283 + "node_modules/eslint-plugin-react-refresh": { 2284 + "version": "0.4.24", 2285 + "dev": true, 2286 + "license": "MIT", 2287 + "peerDependencies": { 2288 + "eslint": ">=8.40" 2289 + } 2290 + }, 2291 + "node_modules/eslint-scope": { 2292 + "version": "8.4.0", 2293 + "dev": true, 2294 + "license": "BSD-2-Clause", 2295 + "dependencies": { 2296 + "esrecurse": "^4.3.0", 2297 + "estraverse": "^5.2.0" 2298 + }, 2299 + "engines": { 2300 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2301 + }, 2302 + "funding": { 2303 + "url": "https://opencollective.com/eslint" 2304 + } 2305 + }, 2306 + "node_modules/eslint-visitor-keys": { 2307 + "version": "4.2.1", 2308 + "dev": true, 2309 + "license": "Apache-2.0", 2310 + "engines": { 2311 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2312 + }, 2313 + "funding": { 2314 + "url": "https://opencollective.com/eslint" 2315 + } 2316 + }, 2317 + "node_modules/eslint/node_modules/ajv": { 2318 + "version": "6.12.6", 2319 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 2320 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 2321 + "dev": true, 2322 + "license": "MIT", 2323 + "dependencies": { 2324 + "fast-deep-equal": "^3.1.1", 2325 + "fast-json-stable-stringify": "^2.0.0", 2326 + "json-schema-traverse": "^0.4.1", 2327 + "uri-js": "^4.2.2" 2328 + }, 2329 + "funding": { 2330 + "type": "github", 2331 + "url": "https://github.com/sponsors/epoberezkin" 2332 + } 2333 + }, 2334 + "node_modules/eslint/node_modules/json-schema-traverse": { 2335 + "version": "0.4.1", 2336 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 2337 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 2338 + "dev": true, 2339 + "license": "MIT" 2340 + }, 2341 + "node_modules/eslint/node_modules/minimatch": { 2342 + "version": "3.1.2", 2343 + "dev": true, 2344 + "license": "ISC", 2345 + "dependencies": { 2346 + "brace-expansion": "^1.1.7" 2347 + }, 2348 + "engines": { 2349 + "node": "*" 2350 + } 2351 + }, 2352 + "node_modules/esm-env": { 2353 + "version": "1.2.2", 2354 + "license": "MIT" 2355 + }, 2356 + "node_modules/espree": { 2357 + "version": "10.4.0", 2358 + "dev": true, 2359 + "license": "BSD-2-Clause", 2360 + "dependencies": { 2361 + "acorn": "^8.15.0", 2362 + "acorn-jsx": "^5.3.2", 2363 + "eslint-visitor-keys": "^4.2.1" 2364 + }, 2365 + "engines": { 2366 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2367 + }, 2368 + "funding": { 2369 + "url": "https://opencollective.com/eslint" 2370 + } 2371 + }, 2372 + "node_modules/esquery": { 2373 + "version": "1.6.0", 2374 + "dev": true, 2375 + "license": "BSD-3-Clause", 2376 + "dependencies": { 2377 + "estraverse": "^5.1.0" 2378 + }, 2379 + "engines": { 2380 + "node": ">=0.10" 2381 + } 2382 + }, 2383 + "node_modules/esrecurse": { 2384 + "version": "4.3.0", 2385 + "dev": true, 2386 + "license": "BSD-2-Clause", 2387 + "dependencies": { 2388 + "estraverse": "^5.2.0" 2389 + }, 2390 + "engines": { 2391 + "node": ">=4.0" 2392 + } 2393 + }, 2394 + "node_modules/estraverse": { 2395 + "version": "5.3.0", 2396 + "dev": true, 2397 + "license": "BSD-2-Clause", 2398 + "engines": { 2399 + "node": ">=4.0" 2400 + } 2401 + }, 2402 + "node_modules/estree-walker": { 2403 + "version": "2.0.2", 2404 + "dev": true, 2405 + "license": "MIT" 2406 + }, 2407 + "node_modules/esutils": { 2408 + "version": "2.0.3", 2409 + "dev": true, 2410 + "license": "BSD-2-Clause", 2411 + "engines": { 2412 + "node": ">=0.10.0" 2413 + } 2414 + }, 2415 + "node_modules/exsolve": { 2416 + "version": "1.0.8", 2417 + "dev": true, 2418 + "license": "MIT" 2419 + }, 2420 + "node_modules/fast-deep-equal": { 2421 + "version": "3.1.3", 2422 + "dev": true, 2423 + "license": "MIT" 2424 + }, 2425 + "node_modules/fast-json-stable-stringify": { 2426 + "version": "2.1.0", 2427 + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 2428 + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 2429 + "dev": true, 2430 + "license": "MIT" 2431 + }, 2432 + "node_modules/fast-levenshtein": { 2433 + "version": "2.0.6", 2434 + "dev": true, 2435 + "license": "MIT" 2436 + }, 2437 + "node_modules/fast-uri": { 2438 + "version": "3.1.0", 2439 + "dev": true, 2440 + "funding": [ 2441 + { 2442 + "type": "github", 2443 + "url": "https://github.com/sponsors/fastify" 2444 + }, 2445 + { 2446 + "type": "opencollective", 2447 + "url": "https://opencollective.com/fastify" 2448 + } 2449 + ], 2450 + "license": "BSD-3-Clause" 2451 + }, 2452 + "node_modules/fdir": { 2453 + "version": "6.5.0", 2454 + "dev": true, 2455 + "license": "MIT", 2456 + "engines": { 2457 + "node": ">=12.0.0" 2458 + }, 2459 + "peerDependencies": { 2460 + "picomatch": "^3 || ^4" 2461 + }, 2462 + "peerDependenciesMeta": { 2463 + "picomatch": { 2464 + "optional": true 2465 + } 2466 + } 2467 + }, 2468 + "node_modules/file-entry-cache": { 2469 + "version": "8.0.0", 2470 + "dev": true, 2471 + "license": "MIT", 2472 + "dependencies": { 2473 + "flat-cache": "^4.0.0" 2474 + }, 2475 + "engines": { 2476 + "node": ">=16.0.0" 2477 + } 2478 + }, 2479 + "node_modules/find-cache-dir": { 2480 + "version": "3.3.2", 2481 + "dev": true, 2482 + "license": "MIT", 2483 + "dependencies": { 2484 + "commondir": "^1.0.1", 2485 + "make-dir": "^3.0.2", 2486 + "pkg-dir": "^4.1.0" 2487 + }, 2488 + "engines": { 2489 + "node": ">=8" 2490 + }, 2491 + "funding": { 2492 + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" 2493 + } 2494 + }, 2495 + "node_modules/find-up": { 2496 + "version": "5.0.0", 2497 + "dev": true, 2498 + "license": "MIT", 2499 + "dependencies": { 2500 + "locate-path": "^6.0.0", 2501 + "path-exists": "^4.0.0" 2502 + }, 2503 + "engines": { 2504 + "node": ">=10" 2505 + }, 2506 + "funding": { 2507 + "url": "https://github.com/sponsors/sindresorhus" 2508 + } 2509 + }, 2510 + "node_modules/flat-cache": { 2511 + "version": "4.0.1", 2512 + "dev": true, 2513 + "license": "MIT", 2514 + "dependencies": { 2515 + "flatted": "^3.2.9", 2516 + "keyv": "^4.5.4" 2517 + }, 2518 + "engines": { 2519 + "node": ">=16" 2520 + } 2521 + }, 2522 + "node_modules/flatted": { 2523 + "version": "3.3.3", 2524 + "dev": true, 2525 + "license": "ISC" 2526 + }, 2527 + "node_modules/fs-extra": { 2528 + "version": "10.1.0", 2529 + "dev": true, 2530 + "license": "MIT", 2531 + "dependencies": { 2532 + "graceful-fs": "^4.2.0", 2533 + "jsonfile": "^6.0.1", 2534 + "universalify": "^2.0.0" 2535 + }, 2536 + "engines": { 2537 + "node": ">=12" 2538 + } 2539 + }, 2540 + "node_modules/fsevents": { 2541 + "version": "2.3.3", 2542 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 2543 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 2544 + "dev": true, 2545 + "hasInstallScript": true, 2546 + "license": "MIT", 2547 + "optional": true, 2548 + "os": [ 2549 + "darwin" 2550 + ], 2551 + "engines": { 2552 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2553 + } 2554 + }, 2555 + "node_modules/function-bind": { 2556 + "version": "1.1.2", 2557 + "dev": true, 2558 + "license": "MIT", 2559 + "funding": { 2560 + "url": "https://github.com/sponsors/ljharb" 2561 + } 2562 + }, 2563 + "node_modules/gensync": { 2564 + "version": "1.0.0-beta.2", 2565 + "dev": true, 2566 + "license": "MIT", 2567 + "engines": { 2568 + "node": ">=6.9.0" 2569 + } 2570 + }, 2571 + "node_modules/glob-parent": { 2572 + "version": "6.0.2", 2573 + "dev": true, 2574 + "license": "ISC", 2575 + "dependencies": { 2576 + "is-glob": "^4.0.3" 2577 + }, 2578 + "engines": { 2579 + "node": ">=10.13.0" 2580 + } 2581 + }, 2582 + "node_modules/globals": { 2583 + "version": "16.5.0", 2584 + "dev": true, 2585 + "license": "MIT", 2586 + "engines": { 2587 + "node": ">=18" 2588 + }, 2589 + "funding": { 2590 + "url": "https://github.com/sponsors/sindresorhus" 2591 + } 2592 + }, 2593 + "node_modules/graceful-fs": { 2594 + "version": "4.2.11", 2595 + "dev": true, 2596 + "license": "ISC" 2597 + }, 2598 + "node_modules/graphemer": { 2599 + "version": "1.4.0", 2600 + "dev": true, 2601 + "license": "MIT" 2602 + }, 2603 + "node_modules/has-flag": { 2604 + "version": "4.0.0", 2605 + "dev": true, 2606 + "license": "MIT", 2607 + "engines": { 2608 + "node": ">=8" 2609 + } 2610 + }, 2611 + "node_modules/hasown": { 2612 + "version": "2.0.2", 2613 + "dev": true, 2614 + "license": "MIT", 2615 + "dependencies": { 2616 + "function-bind": "^1.1.2" 2617 + }, 2618 + "engines": { 2619 + "node": ">= 0.4" 2620 + } 2621 + }, 2622 + "node_modules/ignore": { 2623 + "version": "5.3.2", 2624 + "dev": true, 2625 + "license": "MIT", 2626 + "engines": { 2627 + "node": ">= 4" 2628 + } 2629 + }, 2630 + "node_modules/import-fresh": { 2631 + "version": "3.3.1", 2632 + "dev": true, 2633 + "license": "MIT", 2634 + "dependencies": { 2635 + "parent-module": "^1.0.0", 2636 + "resolve-from": "^4.0.0" 2637 + }, 2638 + "engines": { 2639 + "node": ">=6" 2640 + }, 2641 + "funding": { 2642 + "url": "https://github.com/sponsors/sindresorhus" 2643 + } 2644 + }, 2645 + "node_modules/import-lazy": { 2646 + "version": "4.0.0", 2647 + "dev": true, 2648 + "license": "MIT", 2649 + "engines": { 2650 + "node": ">=8" 2651 + } 2652 + }, 2653 + "node_modules/imurmurhash": { 2654 + "version": "0.1.4", 2655 + "dev": true, 2656 + "license": "MIT", 2657 + "engines": { 2658 + "node": ">=0.8.19" 2659 + } 2660 + }, 2661 + "node_modules/is-core-module": { 2662 + "version": "2.16.1", 2663 + "dev": true, 2664 + "license": "MIT", 2665 + "dependencies": { 2666 + "hasown": "^2.0.2" 2667 + }, 2668 + "engines": { 2669 + "node": ">= 0.4" 2670 + }, 2671 + "funding": { 2672 + "url": "https://github.com/sponsors/ljharb" 2673 + } 2674 + }, 2675 + "node_modules/is-extglob": { 2676 + "version": "2.1.1", 2677 + "dev": true, 2678 + "license": "MIT", 2679 + "engines": { 2680 + "node": ">=0.10.0" 2681 + } 2682 + }, 2683 + "node_modules/is-glob": { 2684 + "version": "4.0.3", 2685 + "dev": true, 2686 + "license": "MIT", 2687 + "dependencies": { 2688 + "is-extglob": "^2.1.1" 2689 + }, 2690 + "engines": { 2691 + "node": ">=0.10.0" 2692 + } 2693 + }, 2694 + "node_modules/isexe": { 2695 + "version": "2.0.0", 2696 + "dev": true, 2697 + "license": "ISC" 2698 + }, 2699 + "node_modules/jju": { 2700 + "version": "1.4.0", 2701 + "dev": true, 2702 + "license": "MIT" 2703 + }, 2704 + "node_modules/js-tokens": { 2705 + "version": "4.0.0", 2706 + "dev": true, 2707 + "license": "MIT" 2708 + }, 2709 + "node_modules/js-yaml": { 2710 + "version": "4.1.1", 2711 + "dev": true, 2712 + "license": "MIT", 2713 + "dependencies": { 2714 + "argparse": "^2.0.1" 2715 + }, 2716 + "bin": { 2717 + "js-yaml": "bin/js-yaml.js" 2718 + } 2719 + }, 2720 + "node_modules/js-yaml/node_modules/argparse": { 2721 + "version": "2.0.1", 2722 + "dev": true, 2723 + "license": "Python-2.0" 2724 + }, 2725 + "node_modules/jsesc": { 2726 + "version": "3.1.0", 2727 + "dev": true, 2728 + "license": "MIT", 2729 + "bin": { 2730 + "jsesc": "bin/jsesc" 2731 + }, 2732 + "engines": { 2733 + "node": ">=6" 2734 + } 2735 + }, 2736 + "node_modules/json-buffer": { 2737 + "version": "3.0.1", 2738 + "dev": true, 2739 + "license": "MIT" 2740 + }, 2741 + "node_modules/json-schema-traverse": { 2742 + "version": "1.0.0", 2743 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 2744 + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 2745 + "dev": true, 2746 + "license": "MIT" 2747 + }, 2748 + "node_modules/json-stable-stringify-without-jsonify": { 2749 + "version": "1.0.1", 2750 + "dev": true, 2751 + "license": "MIT" 2752 + }, 2753 + "node_modules/json5": { 2754 + "version": "2.2.3", 2755 + "dev": true, 2756 + "license": "MIT", 2757 + "bin": { 2758 + "json5": "lib/cli.js" 2759 + }, 2760 + "engines": { 2761 + "node": ">=6" 2762 + } 2763 + }, 2764 + "node_modules/jsonfile": { 2765 + "version": "6.2.0", 2766 + "dev": true, 2767 + "license": "MIT", 2768 + "dependencies": { 2769 + "universalify": "^2.0.0" 2770 + }, 2771 + "optionalDependencies": { 2772 + "graceful-fs": "^4.1.6" 2773 + } 2774 + }, 2775 + "node_modules/keyv": { 2776 + "version": "4.5.4", 2777 + "dev": true, 2778 + "license": "MIT", 2779 + "dependencies": { 2780 + "json-buffer": "3.0.1" 2781 + } 2782 + }, 2783 + "node_modules/kolorist": { 2784 + "version": "1.8.0", 2785 + "dev": true, 2786 + "license": "MIT" 2787 + }, 2788 + "node_modules/levn": { 2789 + "version": "0.4.1", 2790 + "dev": true, 2791 + "license": "MIT", 2792 + "dependencies": { 2793 + "prelude-ls": "^1.2.1", 2794 + "type-check": "~0.4.0" 2795 + }, 2796 + "engines": { 2797 + "node": ">= 0.8.0" 2798 + } 2799 + }, 2800 + "node_modules/lightningcss": { 2801 + "version": "1.30.2", 2802 + "dev": true, 2803 + "license": "MPL-2.0", 2804 + "dependencies": { 2805 + "detect-libc": "^2.0.3" 2806 + }, 2807 + "engines": { 2808 + "node": ">= 12.0.0" 2809 + }, 2810 + "funding": { 2811 + "type": "opencollective", 2812 + "url": "https://opencollective.com/parcel" 2813 + }, 2814 + "optionalDependencies": { 2815 + "lightningcss-android-arm64": "1.30.2", 2816 + "lightningcss-darwin-arm64": "1.30.2", 2817 + "lightningcss-darwin-x64": "1.30.2", 2818 + "lightningcss-freebsd-x64": "1.30.2", 2819 + "lightningcss-linux-arm-gnueabihf": "1.30.2", 2820 + "lightningcss-linux-arm64-gnu": "1.30.2", 2821 + "lightningcss-linux-arm64-musl": "1.30.2", 2822 + "lightningcss-linux-x64-gnu": "1.30.2", 2823 + "lightningcss-linux-x64-musl": "1.30.2", 2824 + "lightningcss-win32-arm64-msvc": "1.30.2", 2825 + "lightningcss-win32-x64-msvc": "1.30.2" 2826 + } 2827 + }, 2828 + "node_modules/lightningcss-android-arm64": { 2829 + "version": "1.30.2", 2830 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", 2831 + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", 2832 + "cpu": [ 2833 + "arm64" 2834 + ], 2835 + "dev": true, 2836 + "license": "MPL-2.0", 2837 + "optional": true, 2838 + "os": [ 2839 + "android" 2840 + ], 2841 + "engines": { 2842 + "node": ">= 12.0.0" 2843 + }, 2844 + "funding": { 2845 + "type": "opencollective", 2846 + "url": "https://opencollective.com/parcel" 2847 + } 2848 + }, 2849 + "node_modules/lightningcss-darwin-arm64": { 2850 + "version": "1.30.2", 2851 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", 2852 + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", 2853 + "cpu": [ 2854 + "arm64" 2855 + ], 2856 + "dev": true, 2857 + "license": "MPL-2.0", 2858 + "optional": true, 2859 + "os": [ 2860 + "darwin" 2861 + ], 2862 + "engines": { 2863 + "node": ">= 12.0.0" 2864 + }, 2865 + "funding": { 2866 + "type": "opencollective", 2867 + "url": "https://opencollective.com/parcel" 2868 + } 2869 + }, 2870 + "node_modules/lightningcss-darwin-x64": { 2871 + "version": "1.30.2", 2872 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", 2873 + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", 2874 + "cpu": [ 2875 + "x64" 2876 + ], 2877 + "dev": true, 2878 + "license": "MPL-2.0", 2879 + "optional": true, 2880 + "os": [ 2881 + "darwin" 2882 + ], 2883 + "engines": { 2884 + "node": ">= 12.0.0" 2885 + }, 2886 + "funding": { 2887 + "type": "opencollective", 2888 + "url": "https://opencollective.com/parcel" 2889 + } 2890 + }, 2891 + "node_modules/lightningcss-freebsd-x64": { 2892 + "version": "1.30.2", 2893 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", 2894 + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", 2895 + "cpu": [ 2896 + "x64" 2897 + ], 2898 + "dev": true, 2899 + "license": "MPL-2.0", 2900 + "optional": true, 2901 + "os": [ 2902 + "freebsd" 2903 + ], 2904 + "engines": { 2905 + "node": ">= 12.0.0" 2906 + }, 2907 + "funding": { 2908 + "type": "opencollective", 2909 + "url": "https://opencollective.com/parcel" 2910 + } 2911 + }, 2912 + "node_modules/lightningcss-linux-arm-gnueabihf": { 2913 + "version": "1.30.2", 2914 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", 2915 + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", 2916 + "cpu": [ 2917 + "arm" 2918 + ], 2919 + "dev": true, 2920 + "license": "MPL-2.0", 2921 + "optional": true, 2922 + "os": [ 2923 + "linux" 2924 + ], 2925 + "engines": { 2926 + "node": ">= 12.0.0" 2927 + }, 2928 + "funding": { 2929 + "type": "opencollective", 2930 + "url": "https://opencollective.com/parcel" 2931 + } 2932 + }, 2933 + "node_modules/lightningcss-linux-arm64-gnu": { 2934 + "version": "1.30.2", 2935 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", 2936 + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", 2937 + "cpu": [ 2938 + "arm64" 2939 + ], 2940 + "dev": true, 2941 + "license": "MPL-2.0", 2942 + "optional": true, 2943 + "os": [ 2944 + "linux" 2945 + ], 2946 + "engines": { 2947 + "node": ">= 12.0.0" 2948 + }, 2949 + "funding": { 2950 + "type": "opencollective", 2951 + "url": "https://opencollective.com/parcel" 2952 + } 2953 + }, 2954 + "node_modules/lightningcss-linux-arm64-musl": { 2955 + "version": "1.30.2", 2956 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", 2957 + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", 2958 + "cpu": [ 2959 + "arm64" 2960 + ], 2961 + "dev": true, 2962 + "license": "MPL-2.0", 2963 + "optional": true, 2964 + "os": [ 2965 + "linux" 2966 + ], 2967 + "engines": { 2968 + "node": ">= 12.0.0" 2969 + }, 2970 + "funding": { 2971 + "type": "opencollective", 2972 + "url": "https://opencollective.com/parcel" 2973 + } 2974 + }, 2975 + "node_modules/lightningcss-linux-x64-gnu": { 2976 + "version": "1.30.2", 2977 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", 2978 + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", 2979 + "cpu": [ 2980 + "x64" 2981 + ], 2982 + "dev": true, 2983 + "license": "MPL-2.0", 2984 + "optional": true, 2985 + "os": [ 2986 + "linux" 2987 + ], 2988 + "engines": { 2989 + "node": ">= 12.0.0" 2990 + }, 2991 + "funding": { 2992 + "type": "opencollective", 2993 + "url": "https://opencollective.com/parcel" 2994 + } 2995 + }, 2996 + "node_modules/lightningcss-linux-x64-musl": { 2997 + "version": "1.30.2", 2998 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", 2999 + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", 3000 + "cpu": [ 3001 + "x64" 3002 + ], 3003 + "dev": true, 3004 + "license": "MPL-2.0", 3005 + "optional": true, 3006 + "os": [ 3007 + "linux" 3008 + ], 3009 + "engines": { 3010 + "node": ">= 12.0.0" 3011 + }, 3012 + "funding": { 3013 + "type": "opencollective", 3014 + "url": "https://opencollective.com/parcel" 3015 + } 3016 + }, 3017 + "node_modules/lightningcss-win32-arm64-msvc": { 3018 + "version": "1.30.2", 3019 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", 3020 + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", 3021 + "cpu": [ 3022 + "arm64" 3023 + ], 3024 + "dev": true, 3025 + "license": "MPL-2.0", 3026 + "optional": true, 3027 + "os": [ 3028 + "win32" 3029 + ], 3030 + "engines": { 3031 + "node": ">= 12.0.0" 3032 + }, 3033 + "funding": { 3034 + "type": "opencollective", 3035 + "url": "https://opencollective.com/parcel" 3036 + } 3037 + }, 3038 + "node_modules/lightningcss-win32-x64-msvc": { 3039 + "version": "1.30.2", 3040 + "cpu": [ 3041 + "x64" 3042 + ], 3043 + "dev": true, 3044 + "license": "MPL-2.0", 3045 + "optional": true, 3046 + "os": [ 3047 + "win32" 3048 + ], 3049 + "engines": { 3050 + "node": ">= 12.0.0" 3051 + }, 3052 + "funding": { 3053 + "type": "opencollective", 3054 + "url": "https://opencollective.com/parcel" 3055 + } 3056 + }, 3057 + "node_modules/local-pkg": { 3058 + "version": "1.1.2", 3059 + "dev": true, 3060 + "license": "MIT", 3061 + "dependencies": { 3062 + "mlly": "^1.7.4", 3063 + "pkg-types": "^2.3.0", 3064 + "quansync": "^0.2.11" 3065 + }, 3066 + "engines": { 3067 + "node": ">=14" 3068 + }, 3069 + "funding": { 3070 + "url": "https://github.com/sponsors/antfu" 3071 + } 3072 + }, 3073 + "node_modules/locate-path": { 3074 + "version": "6.0.0", 3075 + "dev": true, 3076 + "license": "MIT", 3077 + "dependencies": { 3078 + "p-locate": "^5.0.0" 3079 + }, 3080 + "engines": { 3081 + "node": ">=10" 3082 + }, 3083 + "funding": { 3084 + "url": "https://github.com/sponsors/sindresorhus" 3085 + } 3086 + }, 3087 + "node_modules/lodash": { 3088 + "version": "4.17.21", 3089 + "dev": true, 3090 + "license": "MIT" 3091 + }, 3092 + "node_modules/lodash.merge": { 3093 + "version": "4.6.2", 3094 + "dev": true, 3095 + "license": "MIT" 3096 + }, 3097 + "node_modules/lru-cache": { 3098 + "version": "6.0.0", 3099 + "dev": true, 3100 + "license": "ISC", 3101 + "dependencies": { 3102 + "yallist": "^4.0.0" 3103 + }, 3104 + "engines": { 3105 + "node": ">=10" 3106 + } 3107 + }, 3108 + "node_modules/magic-string": { 3109 + "version": "0.30.21", 3110 + "dev": true, 3111 + "license": "MIT", 3112 + "dependencies": { 3113 + "@jridgewell/sourcemap-codec": "^1.5.5" 3114 + } 3115 + }, 3116 + "node_modules/make-dir": { 3117 + "version": "3.1.0", 3118 + "dev": true, 3119 + "license": "MIT", 3120 + "dependencies": { 3121 + "semver": "^6.0.0" 3122 + }, 3123 + "engines": { 3124 + "node": ">=8" 3125 + }, 3126 + "funding": { 3127 + "url": "https://github.com/sponsors/sindresorhus" 3128 + } 3129 + }, 3130 + "node_modules/make-dir/node_modules/semver": { 3131 + "version": "6.3.1", 3132 + "dev": true, 3133 + "license": "ISC", 3134 + "bin": { 3135 + "semver": "bin/semver.js" 3136 + } 3137 + }, 3138 + "node_modules/minimatch": { 3139 + "version": "10.0.3", 3140 + "dev": true, 3141 + "license": "ISC", 3142 + "dependencies": { 3143 + "@isaacs/brace-expansion": "^5.0.0" 3144 + }, 3145 + "engines": { 3146 + "node": "20 || >=22" 3147 + }, 3148 + "funding": { 3149 + "url": "https://github.com/sponsors/isaacs" 3150 + } 3151 + }, 3152 + "node_modules/mlly": { 3153 + "version": "1.8.0", 3154 + "dev": true, 3155 + "license": "MIT", 3156 + "dependencies": { 3157 + "acorn": "^8.15.0", 3158 + "pathe": "^2.0.3", 3159 + "pkg-types": "^1.3.1", 3160 + "ufo": "^1.6.1" 3161 + } 3162 + }, 3163 + "node_modules/mlly/node_modules/pkg-types": { 3164 + "version": "1.3.1", 3165 + "dev": true, 3166 + "license": "MIT", 3167 + "dependencies": { 3168 + "confbox": "^0.1.8", 3169 + "mlly": "^1.7.4", 3170 + "pathe": "^2.0.1" 3171 + } 3172 + }, 3173 + "node_modules/mlly/node_modules/pkg-types/node_modules/confbox": { 3174 + "version": "0.1.8", 3175 + "dev": true, 3176 + "license": "MIT" 3177 + }, 3178 + "node_modules/ms": { 3179 + "version": "2.1.3", 3180 + "dev": true, 3181 + "license": "MIT" 3182 + }, 3183 + "node_modules/nanoid": { 3184 + "version": "3.3.11", 3185 + "dev": true, 3186 + "funding": [ 3187 + { 3188 + "type": "github", 3189 + "url": "https://github.com/sponsors/ai" 3190 + } 3191 + ], 3192 + "license": "MIT", 3193 + "bin": { 3194 + "nanoid": "bin/nanoid.cjs" 3195 + }, 3196 + "engines": { 3197 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 3198 + } 3199 + }, 3200 + "node_modules/natural-compare": { 3201 + "version": "1.4.0", 3202 + "dev": true, 3203 + "license": "MIT" 3204 + }, 3205 + "node_modules/node-releases": { 3206 + "version": "2.0.27", 3207 + "dev": true, 3208 + "license": "MIT" 3209 + }, 3210 + "node_modules/optionator": { 3211 + "version": "0.9.4", 3212 + "dev": true, 3213 + "license": "MIT", 3214 + "dependencies": { 3215 + "deep-is": "^0.1.3", 3216 + "fast-levenshtein": "^2.0.6", 3217 + "levn": "^0.4.1", 3218 + "prelude-ls": "^1.2.1", 3219 + "type-check": "^0.4.0", 3220 + "word-wrap": "^1.2.5" 3221 + }, 3222 + "engines": { 3223 + "node": ">= 0.8.0" 3224 + } 3225 + }, 3226 + "node_modules/p-limit": { 3227 + "version": "3.1.0", 3228 + "dev": true, 3229 + "license": "MIT", 3230 + "dependencies": { 3231 + "yocto-queue": "^0.1.0" 3232 + }, 3233 + "engines": { 3234 + "node": ">=10" 3235 + }, 3236 + "funding": { 3237 + "url": "https://github.com/sponsors/sindresorhus" 3238 + } 3239 + }, 3240 + "node_modules/p-locate": { 3241 + "version": "5.0.0", 3242 + "dev": true, 3243 + "license": "MIT", 3244 + "dependencies": { 3245 + "p-limit": "^3.0.2" 3246 + }, 3247 + "engines": { 3248 + "node": ">=10" 3249 + }, 3250 + "funding": { 3251 + "url": "https://github.com/sponsors/sindresorhus" 3252 + } 3253 + }, 3254 + "node_modules/p-try": { 3255 + "version": "2.2.0", 3256 + "dev": true, 3257 + "license": "MIT", 3258 + "engines": { 3259 + "node": ">=6" 3260 + } 3261 + }, 3262 + "node_modules/parent-module": { 3263 + "version": "1.0.1", 3264 + "dev": true, 3265 + "license": "MIT", 3266 + "dependencies": { 3267 + "callsites": "^3.0.0" 3268 + }, 3269 + "engines": { 3270 + "node": ">=6" 3271 + } 3272 + }, 3273 + "node_modules/path-browserify": { 3274 + "version": "1.0.1", 3275 + "dev": true, 3276 + "license": "MIT" 3277 + }, 3278 + "node_modules/path-exists": { 3279 + "version": "4.0.0", 3280 + "dev": true, 3281 + "license": "MIT", 3282 + "engines": { 3283 + "node": ">=8" 3284 + } 3285 + }, 3286 + "node_modules/path-key": { 3287 + "version": "3.1.1", 3288 + "dev": true, 3289 + "license": "MIT", 3290 + "engines": { 3291 + "node": ">=8" 3292 + } 3293 + }, 3294 + "node_modules/path-parse": { 3295 + "version": "1.0.7", 3296 + "dev": true, 3297 + "license": "MIT" 3298 + }, 3299 + "node_modules/pathe": { 3300 + "version": "2.0.3", 3301 + "dev": true, 3302 + "license": "MIT" 3303 + }, 3304 + "node_modules/picocolors": { 3305 + "version": "1.1.1", 3306 + "dev": true, 3307 + "license": "ISC" 3308 + }, 3309 + "node_modules/picomatch": { 3310 + "version": "4.0.3", 3311 + "dev": true, 3312 + "license": "MIT", 3313 + "engines": { 3314 + "node": ">=12" 3315 + }, 3316 + "funding": { 3317 + "url": "https://github.com/sponsors/jonschlinkert" 3318 + } 3319 + }, 3320 + "node_modules/pkg-dir": { 3321 + "version": "4.2.0", 3322 + "dev": true, 3323 + "license": "MIT", 3324 + "dependencies": { 3325 + "find-up": "^4.0.0" 3326 + }, 3327 + "engines": { 3328 + "node": ">=8" 3329 + } 3330 + }, 3331 + "node_modules/pkg-dir/node_modules/find-up": { 3332 + "version": "4.1.0", 3333 + "dev": true, 3334 + "license": "MIT", 3335 + "dependencies": { 3336 + "locate-path": "^5.0.0", 3337 + "path-exists": "^4.0.0" 3338 + }, 3339 + "engines": { 3340 + "node": ">=8" 3341 + } 3342 + }, 3343 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path": { 3344 + "version": "5.0.0", 3345 + "dev": true, 3346 + "license": "MIT", 3347 + "dependencies": { 3348 + "p-locate": "^4.1.0" 3349 + }, 3350 + "engines": { 3351 + "node": ">=8" 3352 + } 3353 + }, 3354 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path/node_modules/p-locate": { 3355 + "version": "4.1.0", 3356 + "dev": true, 3357 + "license": "MIT", 3358 + "dependencies": { 3359 + "p-limit": "^2.2.0" 3360 + }, 3361 + "engines": { 3362 + "node": ">=8" 3363 + } 3364 + }, 3365 + "node_modules/pkg-dir/node_modules/find-up/node_modules/locate-path/node_modules/p-locate/node_modules/p-limit": { 3366 + "version": "2.3.0", 3367 + "dev": true, 3368 + "license": "MIT", 3369 + "dependencies": { 3370 + "p-try": "^2.0.0" 3371 + }, 3372 + "engines": { 3373 + "node": ">=6" 3374 + }, 3375 + "funding": { 3376 + "url": "https://github.com/sponsors/sindresorhus" 3377 + } 3378 + }, 3379 + "node_modules/pkg-types": { 3380 + "version": "2.3.0", 3381 + "dev": true, 3382 + "license": "MIT", 3383 + "dependencies": { 3384 + "confbox": "^0.2.2", 3385 + "exsolve": "^1.0.7", 3386 + "pathe": "^2.0.3" 3387 + } 3388 + }, 3389 + "node_modules/postcss": { 3390 + "version": "8.5.6", 3391 + "dev": true, 3392 + "funding": [ 3393 + { 3394 + "type": "opencollective", 3395 + "url": "https://opencollective.com/postcss/" 3396 + }, 3397 + { 3398 + "type": "tidelift", 3399 + "url": "https://tidelift.com/funding/github/npm/postcss" 3400 + }, 3401 + { 3402 + "type": "github", 3403 + "url": "https://github.com/sponsors/ai" 3404 + } 3405 + ], 3406 + "license": "MIT", 3407 + "dependencies": { 3408 + "nanoid": "^3.3.11", 3409 + "picocolors": "^1.1.1", 3410 + "source-map-js": "^1.2.1" 3411 + }, 3412 + "engines": { 3413 + "node": "^10 || ^12 || >=14" 3414 + } 3415 + }, 3416 + "node_modules/prelude-ls": { 3417 + "version": "1.2.1", 3418 + "dev": true, 3419 + "license": "MIT", 3420 + "engines": { 3421 + "node": ">= 0.8.0" 3422 + } 3423 + }, 3424 + "node_modules/punycode": { 3425 + "version": "2.3.1", 3426 + "dev": true, 3427 + "license": "MIT", 3428 + "engines": { 3429 + "node": ">=6" 3430 + } 3431 + }, 3432 + "node_modules/quansync": { 3433 + "version": "0.2.11", 3434 + "dev": true, 3435 + "funding": [ 3436 + { 3437 + "type": "individual", 3438 + "url": "https://github.com/sponsors/antfu" 3439 + }, 3440 + { 3441 + "type": "individual", 3442 + "url": "https://github.com/sponsors/sxzz" 3443 + } 3444 + ], 3445 + "license": "MIT" 3446 + }, 3447 + "node_modules/react": { 3448 + "version": "19.2.0", 3449 + "dev": true, 3450 + "license": "MIT", 3451 + "peer": true, 3452 + "engines": { 3453 + "node": ">=0.10.0" 3454 + } 3455 + }, 3456 + "node_modules/react-dom": { 3457 + "version": "19.2.0", 3458 + "dev": true, 3459 + "license": "MIT", 3460 + "dependencies": { 3461 + "scheduler": "^0.27.0" 3462 + }, 3463 + "peerDependencies": { 3464 + "react": "^19.2.0" 3465 + } 3466 + }, 3467 + "node_modules/react-refresh": { 3468 + "version": "0.18.0", 3469 + "dev": true, 3470 + "license": "MIT", 3471 + "engines": { 3472 + "node": ">=0.10.0" 3473 + } 3474 + }, 3475 + "node_modules/require-from-string": { 3476 + "version": "2.0.2", 3477 + "dev": true, 3478 + "license": "MIT", 3479 + "engines": { 3480 + "node": ">=0.10.0" 3481 + } 3482 + }, 3483 + "node_modules/resolve": { 3484 + "version": "1.22.11", 3485 + "dev": true, 3486 + "license": "MIT", 3487 + "dependencies": { 3488 + "is-core-module": "^2.16.1", 3489 + "path-parse": "^1.0.7", 3490 + "supports-preserve-symlinks-flag": "^1.0.0" 3491 + }, 3492 + "bin": { 3493 + "resolve": "bin/resolve" 3494 + }, 3495 + "engines": { 3496 + "node": ">= 0.4" 3497 + }, 3498 + "funding": { 3499 + "url": "https://github.com/sponsors/ljharb" 3500 + } 3501 + }, 3502 + "node_modules/resolve-from": { 3503 + "version": "4.0.0", 3504 + "dev": true, 3505 + "license": "MIT", 3506 + "engines": { 3507 + "node": ">=4" 3508 + } 3509 + }, 3510 + "node_modules/rolldown": { 3511 + "version": "1.0.0-beta.41", 3512 + "dev": true, 3513 + "license": "MIT", 3514 + "peer": true, 3515 + "dependencies": { 3516 + "@oxc-project/types": "=0.93.0", 3517 + "@rolldown/pluginutils": "1.0.0-beta.41", 3518 + "ansis": "=4.2.0" 3519 + }, 3520 + "bin": { 3521 + "rolldown": "bin/cli.mjs" 3522 + }, 3523 + "engines": { 3524 + "node": "^20.19.0 || >=22.12.0" 3525 + }, 3526 + "optionalDependencies": { 3527 + "@rolldown/binding-android-arm64": "1.0.0-beta.41", 3528 + "@rolldown/binding-darwin-arm64": "1.0.0-beta.41", 3529 + "@rolldown/binding-darwin-x64": "1.0.0-beta.41", 3530 + "@rolldown/binding-freebsd-x64": "1.0.0-beta.41", 3531 + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.41", 3532 + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.41", 3533 + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.41", 3534 + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.41", 3535 + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.41", 3536 + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.41", 3537 + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.41", 3538 + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.41", 3539 + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.41", 3540 + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.41" 3541 + } 3542 + }, 3543 + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { 3544 + "version": "1.0.0-beta.41", 3545 + "dev": true, 3546 + "license": "MIT" 3547 + }, 3548 + "node_modules/rollup": { 3549 + "version": "4.53.3", 3550 + "dev": true, 3551 + "license": "MIT", 3552 + "peer": true, 3553 + "dependencies": { 3554 + "@types/estree": "1.0.8" 3555 + }, 3556 + "bin": { 3557 + "rollup": "dist/bin/rollup" 3558 + }, 3559 + "engines": { 3560 + "node": ">=18.0.0", 3561 + "npm": ">=8.0.0" 3562 + }, 3563 + "optionalDependencies": { 3564 + "@rollup/rollup-android-arm-eabi": "4.53.3", 3565 + "@rollup/rollup-android-arm64": "4.53.3", 3566 + "@rollup/rollup-darwin-arm64": "4.53.3", 3567 + "@rollup/rollup-darwin-x64": "4.53.3", 3568 + "@rollup/rollup-freebsd-arm64": "4.53.3", 3569 + "@rollup/rollup-freebsd-x64": "4.53.3", 3570 + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", 3571 + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", 3572 + "@rollup/rollup-linux-arm64-gnu": "4.53.3", 3573 + "@rollup/rollup-linux-arm64-musl": "4.53.3", 3574 + "@rollup/rollup-linux-loong64-gnu": "4.53.3", 3575 + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", 3576 + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", 3577 + "@rollup/rollup-linux-riscv64-musl": "4.53.3", 3578 + "@rollup/rollup-linux-s390x-gnu": "4.53.3", 3579 + "@rollup/rollup-linux-x64-gnu": "4.53.3", 3580 + "@rollup/rollup-linux-x64-musl": "4.53.3", 3581 + "@rollup/rollup-openharmony-arm64": "4.53.3", 3582 + "@rollup/rollup-win32-arm64-msvc": "4.53.3", 3583 + "@rollup/rollup-win32-ia32-msvc": "4.53.3", 3584 + "@rollup/rollup-win32-x64-gnu": "4.53.3", 3585 + "@rollup/rollup-win32-x64-msvc": "4.53.3", 3586 + "fsevents": "~2.3.2" 3587 + } 3588 + }, 3589 + "node_modules/rollup-plugin-typescript2": { 3590 + "version": "0.36.0", 3591 + "dev": true, 3592 + "license": "MIT", 3593 + "dependencies": { 3594 + "@rollup/pluginutils": "^4.1.2", 3595 + "find-cache-dir": "^3.3.2", 3596 + "fs-extra": "^10.0.0", 3597 + "semver": "^7.5.4", 3598 + "tslib": "^2.6.2" 3599 + }, 3600 + "peerDependencies": { 3601 + "rollup": ">=1.26.3", 3602 + "typescript": ">=2.4.0" 3603 + } 3604 + }, 3605 + "node_modules/rollup-plugin-typescript2/node_modules/semver": { 3606 + "version": "7.7.3", 3607 + "dev": true, 3608 + "license": "ISC", 3609 + "bin": { 3610 + "semver": "bin/semver.js" 3611 + }, 3612 + "engines": { 3613 + "node": ">=10" 3614 + } 3615 + }, 3616 + "node_modules/scheduler": { 3617 + "version": "0.27.0", 3618 + "dev": true, 3619 + "license": "MIT" 3620 + }, 3621 + "node_modules/semver": { 3622 + "version": "7.5.4", 3623 + "dev": true, 3624 + "license": "ISC", 3625 + "dependencies": { 3626 + "lru-cache": "^6.0.0" 3627 + }, 3628 + "bin": { 3629 + "semver": "bin/semver.js" 3630 + }, 3631 + "engines": { 3632 + "node": ">=10" 3633 + } 3634 + }, 3635 + "node_modules/shebang-command": { 3636 + "version": "2.0.0", 3637 + "dev": true, 3638 + "license": "MIT", 3639 + "dependencies": { 3640 + "shebang-regex": "^3.0.0" 3641 + }, 3642 + "engines": { 3643 + "node": ">=8" 3644 + } 3645 + }, 3646 + "node_modules/shebang-regex": { 3647 + "version": "3.0.0", 3648 + "dev": true, 3649 + "license": "MIT", 3650 + "engines": { 3651 + "node": ">=8" 3652 + } 3653 + }, 3654 + "node_modules/source-map": { 3655 + "version": "0.6.1", 3656 + "dev": true, 3657 + "license": "BSD-3-Clause", 3658 + "engines": { 3659 + "node": ">=0.10.0" 3660 + } 3661 + }, 3662 + "node_modules/source-map-js": { 3663 + "version": "1.2.1", 3664 + "dev": true, 3665 + "license": "BSD-3-Clause", 3666 + "engines": { 3667 + "node": ">=0.10.0" 3668 + } 3669 + }, 3670 + "node_modules/source-map-support": { 3671 + "version": "0.5.21", 3672 + "dev": true, 3673 + "license": "MIT", 3674 + "optional": true, 3675 + "dependencies": { 3676 + "buffer-from": "^1.0.0", 3677 + "source-map": "^0.6.0" 3678 + } 3679 + }, 3680 + "node_modules/sprintf-js": { 3681 + "version": "1.0.3", 3682 + "dev": true, 3683 + "license": "BSD-3-Clause" 3684 + }, 3685 + "node_modules/string-argv": { 3686 + "version": "0.3.2", 3687 + "dev": true, 3688 + "license": "MIT", 3689 + "engines": { 3690 + "node": ">=0.6.19" 3691 + } 3692 + }, 3693 + "node_modules/strip-json-comments": { 3694 + "version": "3.1.1", 3695 + "dev": true, 3696 + "license": "MIT", 3697 + "engines": { 3698 + "node": ">=8" 3699 + }, 3700 + "funding": { 3701 + "url": "https://github.com/sponsors/sindresorhus" 3702 + } 3703 + }, 3704 + "node_modules/supports-color": { 3705 + "version": "8.1.1", 3706 + "dev": true, 3707 + "license": "MIT", 3708 + "dependencies": { 3709 + "has-flag": "^4.0.0" 3710 + }, 3711 + "engines": { 3712 + "node": ">=10" 3713 + }, 3714 + "funding": { 3715 + "url": "https://github.com/chalk/supports-color?sponsor=1" 3716 + } 3717 + }, 3718 + "node_modules/supports-preserve-symlinks-flag": { 3719 + "version": "1.0.0", 3720 + "dev": true, 3721 + "license": "MIT", 3722 + "engines": { 3723 + "node": ">= 0.4" 3724 + }, 3725 + "funding": { 3726 + "url": "https://github.com/sponsors/ljharb" 3727 + } 3728 + }, 3729 + "node_modules/tinyglobby": { 3730 + "version": "0.2.15", 3731 + "dev": true, 3732 + "license": "MIT", 3733 + "dependencies": { 3734 + "fdir": "^6.5.0", 3735 + "picomatch": "^4.0.3" 3736 + }, 3737 + "engines": { 3738 + "node": ">=12.0.0" 3739 + }, 3740 + "funding": { 3741 + "url": "https://github.com/sponsors/SuperchupuDev" 3742 + } 3743 + }, 3744 + "node_modules/ts-api-utils": { 3745 + "version": "2.1.0", 3746 + "dev": true, 3747 + "license": "MIT", 3748 + "engines": { 3749 + "node": ">=18.12" 3750 + }, 3751 + "peerDependencies": { 3752 + "typescript": ">=4.8.4" 3753 + } 3754 + }, 3755 + "node_modules/tslib": { 3756 + "version": "2.8.1", 3757 + "dev": true, 3758 + "license": "0BSD" 3759 + }, 3760 + "node_modules/type-check": { 3761 + "version": "0.4.0", 3762 + "dev": true, 3763 + "license": "MIT", 3764 + "dependencies": { 3765 + "prelude-ls": "^1.2.1" 3766 + }, 3767 + "engines": { 3768 + "node": ">= 0.8.0" 3769 + } 3770 + }, 3771 + "node_modules/typescript": { 3772 + "version": "5.9.3", 3773 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 3774 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 3775 + "dev": true, 3776 + "license": "Apache-2.0", 3777 + "peer": true, 3778 + "bin": { 3779 + "tsc": "bin/tsc", 3780 + "tsserver": "bin/tsserver" 3781 + }, 3782 + "engines": { 3783 + "node": ">=14.17" 3784 + } 3785 + }, 3786 + "node_modules/typescript-eslint": { 3787 + "version": "8.48.1", 3788 + "dev": true, 3789 + "license": "MIT", 3790 + "dependencies": { 3791 + "@typescript-eslint/eslint-plugin": "8.48.1", 3792 + "@typescript-eslint/parser": "8.48.1", 3793 + "@typescript-eslint/typescript-estree": "8.48.1", 3794 + "@typescript-eslint/utils": "8.48.1" 3795 + }, 3796 + "engines": { 3797 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3798 + }, 3799 + "funding": { 3800 + "type": "opencollective", 3801 + "url": "https://opencollective.com/typescript-eslint" 3802 + }, 3803 + "peerDependencies": { 3804 + "eslint": "^8.57.0 || ^9.0.0", 3805 + "typescript": ">=4.8.4 <6.0.0" 3806 + } 3807 + }, 3808 + "node_modules/ufo": { 3809 + "version": "1.6.1", 3810 + "dev": true, 3811 + "license": "MIT" 3812 + }, 3813 + "node_modules/undici-types": { 3814 + "version": "7.16.0", 3815 + "dev": true, 3816 + "license": "MIT" 3817 + }, 3818 + "node_modules/universalify": { 3819 + "version": "2.0.1", 3820 + "dev": true, 3821 + "license": "MIT", 3822 + "engines": { 3823 + "node": ">= 10.0.0" 3824 + } 3825 + }, 3826 + "node_modules/unplugin": { 3827 + "version": "2.3.11", 3828 + "dev": true, 3829 + "license": "MIT", 3830 + "dependencies": { 3831 + "@jridgewell/remapping": "^2.3.5", 3832 + "acorn": "^8.15.0", 3833 + "picomatch": "^4.0.3", 3834 + "webpack-virtual-modules": "^0.6.2" 3835 + }, 3836 + "engines": { 3837 + "node": ">=18.12.0" 3838 + } 3839 + }, 3840 + "node_modules/unplugin-dts": { 3841 + "version": "1.0.0-beta.6", 3842 + "dev": true, 3843 + "license": "MIT", 3844 + "dependencies": { 3845 + "@rollup/pluginutils": "^5.1.4", 3846 + "@volar/typescript": "^2.4.17", 3847 + "compare-versions": "^6.1.1", 3848 + "debug": "^4.4.0", 3849 + "kolorist": "^1.8.0", 3850 + "local-pkg": "^1.1.1", 3851 + "magic-string": "^0.30.17", 3852 + "unplugin": "^2.3.2" 3853 + }, 3854 + "peerDependencies": { 3855 + "@microsoft/api-extractor": ">=7", 3856 + "@rspack/core": "^1", 3857 + "@vue/language-core": "~3.0.1", 3858 + "esbuild": "*", 3859 + "rolldown": "*", 3860 + "rollup": ">=3", 3861 + "typescript": ">=4", 3862 + "vite": ">=3", 3863 + "webpack": "^4 || ^5" 3864 + }, 3865 + "peerDependenciesMeta": { 3866 + "@microsoft/api-extractor": { 3867 + "optional": true 3868 + }, 3869 + "@rspack/core": { 3870 + "optional": true 3871 + }, 3872 + "@vue/language-core": { 3873 + "optional": true 3874 + }, 3875 + "esbuild": { 3876 + "optional": true 3877 + }, 3878 + "rolldown": { 3879 + "optional": true 3880 + }, 3881 + "rollup": { 3882 + "optional": true 3883 + }, 3884 + "vite": { 3885 + "optional": true 3886 + }, 3887 + "webpack": { 3888 + "optional": true 3889 + } 3890 + } 3891 + }, 3892 + "node_modules/unplugin-dts/node_modules/@rollup/pluginutils": { 3893 + "version": "5.3.0", 3894 + "dev": true, 3895 + "license": "MIT", 3896 + "dependencies": { 3897 + "@types/estree": "^1.0.0", 3898 + "estree-walker": "^2.0.2", 3899 + "picomatch": "^4.0.2" 3900 + }, 3901 + "engines": { 3902 + "node": ">=14.0.0" 3903 + }, 3904 + "peerDependencies": { 3905 + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 3906 + }, 3907 + "peerDependenciesMeta": { 3908 + "rollup": { 3909 + "optional": true 3910 + } 3911 + } 3912 + }, 3913 + "node_modules/update-browserslist-db": { 3914 + "version": "1.1.4", 3915 + "dev": true, 3916 + "funding": [ 3917 + { 3918 + "type": "opencollective", 3919 + "url": "https://opencollective.com/browserslist" 3920 + }, 3921 + { 3922 + "type": "tidelift", 3923 + "url": "https://tidelift.com/funding/github/npm/browserslist" 3924 + }, 3925 + { 3926 + "type": "github", 3927 + "url": "https://github.com/sponsors/ai" 3928 + } 3929 + ], 3930 + "license": "MIT", 3931 + "dependencies": { 3932 + "escalade": "^3.2.0", 3933 + "picocolors": "^1.1.1" 3934 + }, 3935 + "bin": { 3936 + "update-browserslist-db": "cli.js" 3937 + }, 3938 + "peerDependencies": { 3939 + "browserslist": ">= 4.21.0" 3940 + } 3941 + }, 3942 + "node_modules/uri-js": { 3943 + "version": "4.4.1", 3944 + "dev": true, 3945 + "license": "BSD-2-Clause", 3946 + "dependencies": { 3947 + "punycode": "^2.1.0" 3948 + } 3949 + }, 3950 + "node_modules/vite": { 3951 + "name": "rolldown-vite", 3952 + "version": "7.1.14", 3953 + "dev": true, 3954 + "license": "MIT", 3955 + "peer": true, 3956 + "dependencies": { 3957 + "@oxc-project/runtime": "0.92.0", 3958 + "fdir": "^6.5.0", 3959 + "lightningcss": "^1.30.1", 3960 + "picomatch": "^4.0.3", 3961 + "postcss": "^8.5.6", 3962 + "rolldown": "1.0.0-beta.41", 3963 + "tinyglobby": "^0.2.15" 3964 + }, 3965 + "bin": { 3966 + "vite": "bin/vite.js" 3967 + }, 3968 + "engines": { 3969 + "node": "^20.19.0 || >=22.12.0" 3970 + }, 3971 + "funding": { 3972 + "url": "https://github.com/vitejs/vite?sponsor=1" 3973 + }, 3974 + "optionalDependencies": { 3975 + "fsevents": "~2.3.3" 3976 + }, 3977 + "peerDependencies": { 3978 + "@types/node": "^20.19.0 || >=22.12.0", 3979 + "esbuild": "^0.25.0", 3980 + "jiti": ">=1.21.0", 3981 + "less": "^4.0.0", 3982 + "sass": "^1.70.0", 3983 + "sass-embedded": "^1.70.0", 3984 + "stylus": ">=0.54.8", 3985 + "sugarss": "^5.0.0", 3986 + "terser": "^5.16.0", 3987 + "tsx": "^4.8.1", 3988 + "yaml": "^2.4.2" 3989 + }, 3990 + "peerDependenciesMeta": { 3991 + "@types/node": { 3992 + "optional": true 3993 + }, 3994 + "esbuild": { 3995 + "optional": true 3996 + }, 3997 + "jiti": { 3998 + "optional": true 3999 + }, 4000 + "less": { 4001 + "optional": true 4002 + }, 4003 + "sass": { 4004 + "optional": true 4005 + }, 4006 + "sass-embedded": { 4007 + "optional": true 4008 + }, 4009 + "stylus": { 4010 + "optional": true 4011 + }, 4012 + "sugarss": { 4013 + "optional": true 4014 + }, 4015 + "terser": { 4016 + "optional": true 4017 + }, 4018 + "tsx": { 4019 + "optional": true 4020 + }, 4021 + "yaml": { 4022 + "optional": true 4023 + } 4024 + } 4025 + }, 4026 + "node_modules/vscode-uri": { 4027 + "version": "3.1.0", 4028 + "dev": true, 4029 + "license": "MIT" 4030 + }, 4031 + "node_modules/webpack-virtual-modules": { 4032 + "version": "0.6.2", 4033 + "dev": true, 4034 + "license": "MIT" 4035 + }, 4036 + "node_modules/which": { 4037 + "version": "2.0.2", 4038 + "dev": true, 4039 + "license": "ISC", 4040 + "dependencies": { 4041 + "isexe": "^2.0.0" 4042 + }, 4043 + "bin": { 4044 + "node-which": "bin/node-which" 4045 + }, 4046 + "engines": { 4047 + "node": ">= 8" 4048 + } 4049 + }, 4050 + "node_modules/word-wrap": { 4051 + "version": "1.2.5", 4052 + "dev": true, 4053 + "license": "MIT", 4054 + "engines": { 4055 + "node": ">=0.10.0" 4056 + } 4057 + }, 4058 + "node_modules/yallist": { 4059 + "version": "4.0.0", 4060 + "dev": true, 4061 + "license": "ISC" 4062 + }, 4063 + "node_modules/yocto-queue": { 4064 + "version": "0.1.0", 4065 + "dev": true, 4066 + "license": "MIT", 4067 + "engines": { 4068 + "node": ">=10" 4069 + }, 4070 + "funding": { 4071 + "url": "https://github.com/sponsors/sindresorhus" 4072 + } 4073 + } 4074 + } 2863 4075 }
+65 -65
package.json
··· 1 1 { 2 - "name": "atproto-ui", 3 - "version": "0.3.0", 4 - "type": "module", 5 - "description": "React components and hooks for rendering AT Protocol records.", 6 - "main": "./lib-dist/index.js", 7 - "module": "./lib-dist/index.js", 8 - "types": "./lib-dist/index.d.ts", 9 - "exports": { 10 - ".": { 11 - "types": "./lib-dist/index.d.ts", 12 - "import": "./lib-dist/index.js", 13 - "default": "./lib-dist/index.js" 14 - }, 15 - "./styles/highlight.css": "./lib/styles/highlight.css" 16 - }, 17 - "files": [ 18 - "lib-dist", 19 - "lib/styles", 20 - "README.md" 21 - ], 22 - "sideEffects": [ 23 - "./lib/styles/highlight.css" 24 - ], 25 - "scripts": { 26 - "dev": "vite", 27 - "build": "tsc -b && vite build", 28 - "lint": "eslint .", 29 - "preview": "vite preview", 30 - "prepublishOnly": "npm run build" 31 - }, 32 - "peerDependencies": { 33 - "react": "^18.2.0 || ^19.0.0", 34 - "react-dom": "^18.2.0 || ^19.0.0" 35 - }, 36 - "peerDependenciesMeta": { 37 - "react-dom": { 38 - "optional": true 39 - } 40 - }, 41 - "dependencies": { 42 - "@atcute/atproto": "^3.1.7", 43 - "@atcute/bluesky": "^3.2.3", 44 - "@atcute/client": "^4.0.3", 45 - "@atcute/identity-resolver": "^1.1.3", 46 - "@atcute/tangled": "^1.0.6" 47 - }, 48 - "devDependencies": { 49 - "@eslint/js": "^9.36.0", 50 - "@types/node": "^24.6.0", 51 - "@types/react": "^19.1.16", 52 - "@types/react-dom": "^19.1.9", 53 - "@vitejs/plugin-react": "^5.0.4", 54 - "eslint": "^9.36.0", 55 - "eslint-plugin-react-hooks": "^5.2.0", 56 - "eslint-plugin-react-refresh": "^0.4.22", 57 - "globals": "^16.4.0", 58 - "react": "^19.1.1", 59 - "react-dom": "^19.1.1", 60 - "typescript": "~5.9.3", 61 - "typescript-eslint": "^8.45.0", 62 - "vite": "npm:rolldown-vite@7.1.14" 63 - }, 64 - "overrides": { 65 - "vite": "npm:rolldown-vite@7.1.14" 66 - } 2 + "name": "atproto-ui", 3 + "version": "0.12.0", 4 + "type": "module", 5 + "description": "React components and hooks for rendering AT Protocol records.", 6 + "main": "./lib-dist/index.js", 7 + "module": "./lib-dist/index.js", 8 + "types": "./lib-dist/index.d.ts", 9 + "exports": { 10 + ".": { 11 + "import": "./lib-dist/index.js", 12 + "require": "./lib-dist/index.js" 13 + }, 14 + "./styles.css": "./lib-dist/styles.css" 15 + }, 16 + "files": [ 17 + "lib-dist", 18 + "README.md" 19 + ], 20 + "sideEffects": [ 21 + "./lib-dist/styles.css" 22 + ], 23 + "scripts": { 24 + "dev": "vite", 25 + "build": "vite build && tsc -b", 26 + "build:demo": "BUILD_TARGET=demo vite build", 27 + "build:all": "npm run build && npm run build:demo", 28 + "lint": "eslint .", 29 + "preview": "vite preview", 30 + "prepublishOnly": "npm run build" 31 + }, 32 + "peerDependencies": { 33 + "react": "^18.2.0 || ^19.0.0", 34 + "react-dom": "^18.2.0 || ^19.0.0" 35 + }, 36 + "peerDependenciesMeta": { 37 + "react-dom": { 38 + "optional": true 39 + } 40 + }, 41 + "dependencies": { 42 + "@atcute/atproto": "^3.1.7", 43 + "@atcute/bluesky": "^3.2.3", 44 + "@atcute/client": "^4.0.3", 45 + "@atcute/identity-resolver": "^1.1.3", 46 + "@atcute/tangled": "^1.0.10" 47 + }, 48 + "devDependencies": { 49 + "@eslint/js": "^9.36.0", 50 + "@microsoft/api-extractor": "^7.53.1", 51 + "@types/node": "^24.6.0", 52 + "@types/react": "^19.1.16", 53 + "@types/react-dom": "^19.1.9", 54 + "@vitejs/plugin-react": "^5.0.4", 55 + "eslint": "^9.36.0", 56 + "eslint-plugin-react-hooks": "^5.2.0", 57 + "eslint-plugin-react-refresh": "^0.4.22", 58 + "globals": "^16.4.0", 59 + "react": "^19.1.1", 60 + "react-dom": "^19.1.1", 61 + "rollup-plugin-typescript2": "^0.36.0", 62 + "typescript": "~5.9.3", 63 + "typescript-eslint": "^8.45.0", 64 + "unplugin-dts": "^1.0.0-beta.6", 65 + "vite": "npm:rolldown-vite@7.1.14" 66 + } 67 67 }
+59
src/App.css
··· 1 + /** 2 + * Demo app styles - separate from atproto-ui component styles 3 + * This demonstrates that atproto-ui components work well within 4 + * apps that have their own theming system. 5 + */ 6 + 7 + /* Root styles for the demo app */ 8 + body { 9 + margin: 0; 10 + padding: 0; 11 + background: var(--demo-bg); 12 + color: var(--demo-text); 13 + transition: background-color 200ms ease, color 200ms ease; 14 + } 15 + 16 + :root { 17 + /* Light theme for demo app */ 18 + --demo-bg: #eeeeee; 19 + --demo-text: #1a1a1a; 20 + --demo-text-secondary: #666; 21 + --demo-border: #ddd; 22 + --demo-input-bg: #fff; 23 + --demo-button-bg: #0066cc; 24 + --demo-button-text: #fff; 25 + --demo-code-bg: #f5f5f5; 26 + --demo-code-border: #e0e0e0; 27 + --demo-hr: #e0e0e0; 28 + } 29 + 30 + /* Dark theme for demo app */ 31 + [data-theme="dark"] { 32 + --demo-bg: #1a1a1a; 33 + --demo-text: #e0e0e0; 34 + --demo-text-secondary: #999; 35 + --demo-border: #444; 36 + --demo-input-bg: #2a2a2a; 37 + --demo-button-bg: #0066cc; 38 + --demo-button-text: #fff; 39 + --demo-code-bg: #2a2a2a; 40 + --demo-code-border: #444; 41 + --demo-hr: #444; 42 + } 43 + 44 + /* System preference dark mode */ 45 + @media (prefers-color-scheme: dark) { 46 + :root:not([data-theme]), 47 + :root[data-theme="system"] { 48 + --demo-bg: #1a1a1a; 49 + --demo-text: #e0e0e0; 50 + --demo-text-secondary: #999; 51 + --demo-border: #444; 52 + --demo-input-bg: #2a2a2a; 53 + --demo-button-bg: #0066cc; 54 + --demo-button-text: #fff; 55 + --demo-code-bg: #2a2a2a; 56 + --demo-code-border: #444; 57 + --demo-hr: #444; 58 + } 59 + }
+590 -346
src/App.tsx
··· 1 - import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'; 2 - import { AtProtoProvider } from '../lib/providers/AtProtoProvider'; 3 - import { AtProtoRecord } from '../lib/core/AtProtoRecord'; 4 - import { TangledString } from '../lib/components/TangledString'; 5 - import { LeafletDocument } from '../lib/components/LeafletDocument'; 6 - import { BlueskyProfile } from '../lib/components/BlueskyProfile'; 7 - import { BlueskyPost, BLUESKY_POST_COLLECTION } from '../lib/components/BlueskyPost'; 8 - import { BlueskyPostList } from '../lib/components/BlueskyPostList'; 9 - import { BlueskyQuotePost } from '../lib/components/BlueskyQuotePost'; 10 - import { useDidResolution } from '../lib/hooks/useDidResolution'; 11 - import { useLatestRecord } from '../lib/hooks/useLatestRecord'; 12 - import { ColorSchemeToggle } from '../lib/components/ColorSchemeToggle.tsx'; 13 - import { useColorScheme, type ColorSchemePreference } from '../lib/hooks/useColorScheme'; 14 - import type { FeedPostRecord } from '../lib/types/bluesky'; 1 + import React, { useState, useCallback, useRef } from "react"; 2 + import { AtProtoProvider, TangledRepo } from "../lib"; 3 + import "../lib/styles.css"; 4 + import "./App.css"; 15 5 16 - const COLOR_SCHEME_STORAGE_KEY = 'atproto-ui-color-scheme'; 6 + import { TangledString } from "../lib/components/TangledString"; 7 + import { LeafletDocument } from "../lib/components/LeafletDocument"; 8 + import { BlueskyProfile } from "../lib/components/BlueskyProfile"; 9 + import { 10 + BlueskyPost, 11 + BLUESKY_POST_COLLECTION, 12 + } from "../lib/components/BlueskyPost"; 13 + import { BlueskyPostList } from "../lib/components/BlueskyPostList"; 14 + import { BlueskyQuotePost } from "../lib/components/BlueskyQuotePost"; 15 + import { GrainGallery } from "../lib/components/GrainGallery"; 16 + import { CurrentlyPlaying } from "../lib/components/CurrentlyPlaying"; 17 + import { LastPlayed } from "../lib/components/LastPlayed"; 18 + import { SongHistoryList } from "../lib/components/SongHistoryList"; 19 + import { useDidResolution } from "../lib/hooks/useDidResolution"; 20 + import { useLatestRecord } from "../lib/hooks/useLatestRecord"; 21 + import type { FeedPostRecord } from "../lib/types/bluesky"; 17 22 18 23 const basicUsageSnippet = `import { AtProtoProvider, BlueskyPost } from 'atproto-ui'; 19 24 ··· 25 30 ); 26 31 }`; 27 32 28 - const customComponentSnippet = `import { useLatestRecord, useColorScheme, AtProtoRecord } from 'atproto-ui'; 33 + const prefetchedDataSnippet = `import { BlueskyPost, useLatestRecord } from 'atproto-ui'; 29 34 import type { FeedPostRecord } from 'atproto-ui'; 30 35 31 - const LatestPostSummary: React.FC<{ did: string }> = ({ did }) => { 32 - const scheme = useColorScheme('system'); 33 - const { rkey, loading, error } = useLatestRecord<FeedPostRecord>(did, 'app.bsky.feed.post'); 36 + const LatestPostWithPrefetch: React.FC<{ did: string }> = ({ did }) => { 37 + // Fetch once with the hook 38 + const { record, rkey, loading } = useLatestRecord<FeedPostRecord>( 39 + did, 40 + 'app.bsky.feed.post' 41 + ); 34 42 35 43 if (loading) return <span>Loadingโ€ฆ</span>; 36 - if (error || !rkey) return <span>No post yet.</span>; 44 + if (!record || !rkey) return <span>No posts yet.</span>; 37 45 38 - return ( 39 - <AtProtoRecord<FeedPostRecord> 40 - did={did} 41 - collection="app.bsky.feed.post" 42 - rkey={rkey} 43 - renderer={({ record }) => ( 44 - <article data-color-scheme={scheme}> 45 - <strong>{record?.text ?? 'Empty post'}</strong> 46 - </article> 47 - )} 48 - /> 49 - ); 46 + // Pass prefetched recordโ€”BlueskyPost won't re-fetch it 47 + return <BlueskyPost did={did} rkey={rkey} record={record} />; 50 48 };`; 51 49 52 - const codeBlockBase: React.CSSProperties = { 53 - fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace, monospace', 54 - fontSize: 12, 55 - whiteSpace: 'pre', 56 - overflowX: 'auto', 57 - borderRadius: 10, 58 - padding: '12px 14px', 59 - lineHeight: 1.6 60 - }; 50 + const atcuteUsageSnippet = `import { Client, simpleFetchHandler, ok } from '@atcute/client'; 51 + import type { AppBskyFeedPost } from '@atcute/bluesky'; 52 + import { BlueskyPost } from 'atproto-ui'; 61 53 62 - const FullDemo: React.FC = () => { 63 - const handleInputRef = useRef<HTMLInputElement | null>(null); 64 - const [submitted, setSubmitted] = useState<string | null>(null); 65 - const [colorSchemePreference, setColorSchemePreference] = useState<ColorSchemePreference>(() => { 66 - if (typeof window === 'undefined') return 'system'; 67 - try { 68 - const stored = window.localStorage.getItem(COLOR_SCHEME_STORAGE_KEY); 69 - if (stored === 'light' || stored === 'dark' || stored === 'system') return stored; 70 - } catch { 71 - /* ignore */ 72 - } 73 - return 'system'; 74 - }); 75 - const scheme = useColorScheme(colorSchemePreference); 76 - const { did, loading: resolvingDid } = useDidResolution(submitted ?? undefined); 77 - const onSubmit = useCallback<React.FormEventHandler>((e) => { 78 - e.preventDefault(); 79 - const rawValue = handleInputRef.current?.value; 80 - const nextValue = rawValue?.trim(); 81 - if (!nextValue) return; 82 - if (handleInputRef.current) { 83 - handleInputRef.current.value = nextValue; 84 - } 85 - setSubmitted(nextValue); 86 - }, []); 54 + // Create atcute client 55 + const client = new Client({ 56 + handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 57 + }); 87 58 88 - useEffect(() => { 89 - if (typeof window === 'undefined') return; 90 - try { 91 - window.localStorage.setItem(COLOR_SCHEME_STORAGE_KEY, colorSchemePreference); 92 - } catch { 93 - /* ignore */ 59 + // Fetch a record 60 + const data = await ok( 61 + client.get('com.atproto.repo.getRecord', { 62 + params: { 63 + repo: 'did:plc:ttdrpj45ibqunmfhdsb4zdwq', 64 + collection: 'app.bsky.feed.post', 65 + rkey: '3m45rq4sjes2h' 94 66 } 95 - }, [colorSchemePreference]); 67 + }) 68 + ); 96 69 97 - useEffect(() => { 98 - if (typeof document === 'undefined') return; 99 - const root = document.documentElement; 100 - const body = document.body; 101 - const prevScheme = root.dataset.colorScheme; 102 - const prevBg = body.style.backgroundColor; 103 - const prevColor = body.style.color; 104 - root.dataset.colorScheme = scheme; 105 - body.style.backgroundColor = scheme === 'dark' ? '#020617' : '#f8fafc'; 106 - body.style.color = scheme === 'dark' ? '#e2e8f0' : '#0f172a'; 107 - return () => { 108 - root.dataset.colorScheme = prevScheme ?? ''; 109 - body.style.backgroundColor = prevBg; 110 - body.style.color = prevColor; 111 - }; 112 - }, [scheme]); 70 + const record = data.value as AppBskyFeedPost.Main; 113 71 114 - const showHandle = submitted && !submitted.startsWith('did:') ? submitted : undefined; 72 + // Pass atcute record directly to component! 73 + <BlueskyPost 74 + did="did:plc:ttdrpj45ibqunmfhdsb4zdwq" 75 + rkey="3m45rq4sjes2h" 76 + record={record} 77 + />`; 115 78 116 - const mutedTextColor = useMemo(() => (scheme === 'dark' ? '#94a3b8' : '#555'), [scheme]); 117 - const panelStyle = useMemo<React.CSSProperties>(() => ({ 118 - display: 'flex', 119 - flexDirection: 'column', 120 - gap: 8, 121 - padding: 10, 122 - borderRadius: 12, 123 - borderColor: scheme === 'dark' ? '#1e293b' : '#e2e8f0', 124 - }), [scheme]); 125 - const baseTextColor = useMemo(() => (scheme === 'dark' ? '#e2e8f0' : '#0f172a'), [scheme]); 126 - const gistPanelStyle = useMemo<React.CSSProperties>(() => ({ 127 - ...panelStyle, 128 - padding: 0, 129 - border: 'none', 130 - background: 'transparent', 131 - backdropFilter: 'none', 132 - marginTop: 32 133 - }), [panelStyle]); 134 - const leafletPanelStyle = useMemo<React.CSSProperties>(() => ({ 135 - ...panelStyle, 136 - padding: 0, 137 - border: 'none', 138 - background: 'transparent', 139 - backdropFilter: 'none', 140 - marginTop: 32, 141 - alignItems: 'center' 142 - }), [panelStyle]); 143 - const primaryGridStyle = useMemo<React.CSSProperties>(() => ({ 144 - display: 'grid', 145 - gap: 32, 146 - gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))' 147 - }), []); 148 - const columnStackStyle = useMemo<React.CSSProperties>(() => ({ 149 - display: 'flex', 150 - flexDirection: 'column', 151 - gap: 32 152 - }), []); 153 - const codeBlockStyle = useMemo<React.CSSProperties>(() => ({ 154 - ...codeBlockBase, 155 - background: scheme === 'dark' ? '#0b1120' : '#f1f5f9', 156 - border: `1px solid ${scheme === 'dark' ? '#1e293b' : '#e2e8f0'}` 157 - }), [scheme]); 158 - const codeTextStyle = useMemo<React.CSSProperties>(() => ({ 159 - margin: 0, 160 - display: 'block', 161 - fontFamily: codeBlockBase.fontFamily, 162 - fontSize: 12, 163 - lineHeight: 1.6, 164 - whiteSpace: 'pre' 165 - }), []); 166 - const basicCodeRef = useRef<HTMLElement | null>(null); 167 - const customCodeRef = useRef<HTMLElement | null>(null); 79 + const codeBlockBase: React.CSSProperties = { 80 + fontFamily: 'Menlo, Consolas, "SFMono-Regular", ui-monospace, monospace', 81 + fontSize: 12, 82 + whiteSpace: "pre", 83 + overflowX: "auto", 84 + borderRadius: 10, 85 + padding: "12px 14px", 86 + lineHeight: 1.6, 87 + }; 168 88 169 - // Latest Bluesky post 170 - const { 171 - rkey: latestPostRkey, 172 - loading: loadingLatestPost, 173 - empty: noPosts, 174 - error: latestPostError 175 - } = useLatestRecord<unknown>(did, BLUESKY_POST_COLLECTION); 89 + const ThemeSwitcher: React.FC = () => { 90 + const [theme, setTheme] = useState<"light" | "dark" | "system">("system"); 176 91 177 - const quoteSampleDid = 'did:plc:ttdrpj45ibqunmfhdsb4zdwq'; 178 - const quoteSampleRkey = '3m2prlq6xxc2v'; 92 + const toggle = () => { 93 + const schemes: ("light" | "dark" | "system")[] = [ 94 + "light", 95 + "dark", 96 + "system", 97 + ]; 98 + const currentIndex = schemes.indexOf(theme); 99 + const nextIndex = (currentIndex + 1) % schemes.length; 100 + const nextTheme = schemes[nextIndex]; 101 + setTheme(nextTheme); 179 102 180 - return ( 181 - <div style={{ display: 'flex', flexDirection: 'column', gap: 20, color: baseTextColor }}> 182 - <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center', justifyContent: 'space-between' }}> 183 - <form onSubmit={onSubmit} style={{ display: 'flex', gap: 8, flexWrap: 'wrap', flex: '1 1 320px' }}> 184 - <input 185 - placeholder="Handle or DID (e.g. alice.bsky.social or did:plc:...)" 186 - ref={handleInputRef} 187 - style={{ flex: '1 1 260px', padding: '6px 8px', borderRadius: 8, border: '1px solid', borderColor: scheme === 'dark' ? '#1e293b' : '#cbd5f5', background: scheme === 'dark' ? '#0b1120' : '#fff', color: scheme === 'dark' ? '#e2e8f0' : '#0f172a' }} 188 - /> 189 - <button type="submit" style={{ padding: '6px 16px', borderRadius: 8, border: 'none', background: '#2563eb', color: '#fff', cursor: 'pointer' }}>Load</button> 190 - </form> 191 - <ColorSchemeToggle value={colorSchemePreference} onChange={setColorSchemePreference} scheme={scheme} /> 192 - </div> 193 - {!submitted && <p style={{ color: mutedTextColor }}>Enter a handle to fetch your profile, latest Bluesky post, a Tangled string, and a Leaflet document.</p>} 194 - {submitted && resolvingDid && <p style={{ color: mutedTextColor }}>Resolving DIDโ€ฆ</p>} 195 - {did && ( 196 - <> 197 - <div style={primaryGridStyle}> 198 - <div style={columnStackStyle}> 199 - <section style={panelStyle}> 200 - <h3 style={sectionHeaderStyle}>Profile</h3> 201 - <BlueskyProfile did={did} handle={showHandle} colorScheme={colorSchemePreference} /> 202 - </section> 203 - <section style={panelStyle}> 204 - <h3 style={sectionHeaderStyle}>Recent Posts</h3> 205 - <BlueskyPostList did={did} colorScheme={colorSchemePreference} /> 206 - </section> 207 - </div> 208 - <div style={columnStackStyle}> 209 - <section style={panelStyle}> 210 - <h3 style={sectionHeaderStyle}>Latest Bluesky Post</h3> 211 - {loadingLatestPost && <div style={loadingBox}>Loading latest postโ€ฆ</div>} 212 - {latestPostError && <div style={errorBox}>Failed to load latest post.</div>} 213 - {noPosts && <div style={{ ...infoBox, color: mutedTextColor }}>No posts found.</div>} 214 - {!loadingLatestPost && latestPostRkey && ( 215 - <BlueskyPost did={did} rkey={latestPostRkey} colorScheme={colorSchemePreference} /> 216 - )} 217 - </section> 218 - <section style={panelStyle}> 219 - <h3 style={sectionHeaderStyle}>Quote Post Demo</h3> 220 - <BlueskyQuotePost did={quoteSampleDid} rkey={quoteSampleRkey} colorScheme={colorSchemePreference} /> 221 - </section> 222 - </div> 223 - </div> 224 - <section style={gistPanelStyle}> 225 - <h3 style={sectionHeaderStyle}>A Tangled String</h3> 226 - <TangledString did="nekomimi.pet" rkey="3m2p4gjptg522" colorScheme={colorSchemePreference} /> 227 - </section> 228 - <section style={leafletPanelStyle}> 229 - <h3 style={sectionHeaderStyle}>A Leaflet Document.</h3> 230 - <div style={{ width: '100%', display: 'flex', justifyContent: 'center' }}> 231 - <LeafletDocument did={"did:plc:ttdrpj45ibqunmfhdsb4zdwq"} rkey={"3m2seagm2222c"} colorScheme={colorSchemePreference} /> 232 - </div> 233 - </section> 234 - </> 235 - )} 236 - <section style={{ ...panelStyle, marginTop: 32 }}> 237 - <h3 style={sectionHeaderStyle}>Build your own component</h3> 238 - <p style={{ color: mutedTextColor, margin: '4px 0 8px' }}> 239 - Wrap your app with the provider once and drop the ready-made components wherever you need them. 240 - </p> 241 - <pre style={codeBlockStyle}> 242 - <code ref={basicCodeRef} className="language-tsx" style={codeTextStyle}>{basicUsageSnippet}</code> 243 - </pre> 244 - <p style={{ color: mutedTextColor, margin: '16px 0 8px' }}> 245 - Need to make your own component? Compose your own renderer with the hooks and utilities that ship with the library. 246 - </p> 247 - <pre style={codeBlockStyle}> 248 - <code ref={customCodeRef} className="language-tsx" style={codeTextStyle}>{customComponentSnippet}</code> 249 - </pre> 250 - {did && ( 251 - <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 12 }}> 252 - <p style={{ color: mutedTextColor, margin: 0 }}> 253 - Live example with your handle: 254 - </p> 255 - <LatestPostSummary did={did} handle={showHandle} colorScheme={colorSchemePreference} /> 256 - </div> 257 - )} 258 - </section> 259 - </div> 260 - ); 103 + // Update the data-theme attribute on the document element 104 + if (nextTheme === "system") { 105 + document.documentElement.removeAttribute("data-theme"); 106 + } else { 107 + document.documentElement.setAttribute("data-theme", nextTheme); 108 + } 109 + }; 110 + 111 + return ( 112 + <button 113 + onClick={toggle} 114 + style={{ 115 + padding: "8px 12px", 116 + borderRadius: 8, 117 + border: "1px solid var(--demo-border)", 118 + background: "var(--demo-input-bg)", 119 + color: "var(--demo-text)", 120 + cursor: "pointer", 121 + }} 122 + > 123 + Theme: {theme} 124 + </button> 125 + ); 261 126 }; 262 127 263 - const LatestPostSummary: React.FC<{ did: string; handle?: string; colorScheme: ColorSchemePreference }> = ({ did, colorScheme }) => { 264 - const { record, rkey, loading, error } = useLatestRecord<FeedPostRecord>(did, BLUESKY_POST_COLLECTION); 265 - const scheme = useColorScheme(colorScheme); 266 - const palette = scheme === 'dark' ? latestSummaryPalette.dark : latestSummaryPalette.light; 128 + const FullDemo: React.FC = () => { 129 + const handleInputRef = useRef<HTMLInputElement | null>(null); 130 + const [submitted, setSubmitted] = useState<string | null>(null); 267 131 268 - if (loading) return <div style={palette.muted}>Loading summaryโ€ฆ</div>; 269 - if (error) return <div style={palette.error}>Failed to load the latest post.</div>; 270 - if (!rkey) return <div style={palette.muted}>No posts published yet.</div>; 132 + const { did, loading: resolvingDid } = useDidResolution( 133 + submitted ?? undefined, 134 + ); 135 + const onSubmit = useCallback<React.FormEventHandler>((e) => { 136 + e.preventDefault(); 137 + const rawValue = handleInputRef.current?.value; 138 + const nextValue = rawValue?.trim(); 139 + if (!nextValue) return; 140 + if (handleInputRef.current) { 141 + handleInputRef.current.value = nextValue; 142 + } 143 + setSubmitted(nextValue); 144 + }, []); 271 145 272 - const atProtoProps = record 273 - ? { record } 274 - : { did, collection: 'app.bsky.feed.post', rkey }; 146 + const showHandle = 147 + submitted && !submitted.startsWith("did:") ? submitted : undefined; 148 + 149 + const panelStyle: React.CSSProperties = { 150 + display: "flex", 151 + flexDirection: "column", 152 + gap: 8, 153 + padding: 10, 154 + borderRadius: 12, 155 + border: `1px solid var(--demo-border)`, 156 + }; 157 + 158 + const gistPanelStyle: React.CSSProperties = { 159 + ...panelStyle, 160 + padding: 0, 161 + border: "none", 162 + background: "transparent", 163 + backdropFilter: "none", 164 + marginTop: 32, 165 + }; 166 + const leafletPanelStyle: React.CSSProperties = { 167 + ...panelStyle, 168 + padding: 0, 169 + border: "none", 170 + background: "transparent", 171 + backdropFilter: "none", 172 + marginTop: 32, 173 + alignItems: "center", 174 + }; 175 + const primaryGridStyle: React.CSSProperties = { 176 + display: "grid", 177 + gap: 32, 178 + gridTemplateColumns: "repeat(auto-fit, minmax(320px, 1fr))", 179 + }; 180 + const columnStackStyle: React.CSSProperties = { 181 + display: "flex", 182 + flexDirection: "column", 183 + gap: 32, 184 + }; 185 + const codeBlockStyle: React.CSSProperties = { 186 + ...codeBlockBase, 187 + background: `var(--demo-code-bg)`, 188 + border: `1px solid var(--demo-code-border)`, 189 + color: `var(--demo-text)`, 190 + }; 191 + const codeTextStyle: React.CSSProperties = { 192 + margin: 0, 193 + display: "block", 194 + fontFamily: codeBlockBase.fontFamily, 195 + fontSize: 12, 196 + lineHeight: 1.6, 197 + whiteSpace: "pre", 198 + }; 199 + const basicCodeRef = useRef<HTMLElement | null>(null); 200 + const customCodeRef = useRef<HTMLElement | null>(null); 201 + 202 + // Latest Bluesky post - fetch with record for prefetch demo 203 + const { 204 + record: latestPostRecord, 205 + rkey: latestPostRkey, 206 + loading: loadingLatestPost, 207 + empty: noPosts, 208 + error: latestPostError, 209 + } = useLatestRecord<FeedPostRecord>(did, BLUESKY_POST_COLLECTION); 210 + 211 + const quoteSampleDid = "did:plc:ttdrpj45ibqunmfhdsb4zdwq"; 212 + const quoteSampleRkey = "3m2prlq6xxc2v"; 275 213 276 - return ( 277 - <AtProtoRecord<FeedPostRecord> 278 - {...atProtoProps} 279 - renderer={({ record: resolvedRecord }) => ( 280 - <article data-color-scheme={scheme}> 281 - <strong>{resolvedRecord?.text ?? 'Empty post'}</strong> 282 - </article> 283 - )} 284 - /> 285 - ); 214 + return ( 215 + <div 216 + style={{ 217 + display: "flex", 218 + flexDirection: "column", 219 + gap: 20, 220 + }} 221 + > 222 + <div 223 + style={{ 224 + display: "flex", 225 + flexWrap: "wrap", 226 + gap: 12, 227 + alignItems: "center", 228 + justifyContent: "space-between", 229 + }} 230 + > 231 + <form 232 + onSubmit={onSubmit} 233 + style={{ 234 + display: "flex", 235 + gap: 8, 236 + flexWrap: "wrap", 237 + flex: "1 1 320px", 238 + }} 239 + > 240 + <input 241 + placeholder="Handle or DID (e.g. alice.bsky.social or did:plc:...)" 242 + ref={handleInputRef} 243 + style={{ 244 + flex: "1 1 260px", 245 + padding: "6px 8px", 246 + borderRadius: 8, 247 + border: `1px solid var(--demo-border)`, 248 + background: `var(--demo-input-bg)`, 249 + color: `var(--demo-text)`, 250 + }} 251 + /> 252 + <button 253 + type="submit" 254 + style={{ 255 + padding: "6px 16px", 256 + borderRadius: 8, 257 + border: "none", 258 + background: `var(--demo-button-bg)`, 259 + color: `var(--demo-button-text)`, 260 + cursor: "pointer", 261 + }} 262 + > 263 + Load 264 + </button> 265 + </form> 266 + <ThemeSwitcher /> 267 + </div> 268 + {!submitted && ( 269 + <p style={{ color: `var(--demo-text-secondary)` }}> 270 + Enter a handle to fetch your profile, latest Bluesky post, a 271 + Tangled string, and a Leaflet document. 272 + </p> 273 + )} 274 + {submitted && resolvingDid && ( 275 + <p style={{ color: `var(--demo-text-secondary)` }}> 276 + Resolving DIDโ€ฆ 277 + </p> 278 + )} 279 + {did && ( 280 + <> 281 + <div style={primaryGridStyle}> 282 + <div style={columnStackStyle}> 283 + <section style={panelStyle}> 284 + <h3 style={sectionHeaderStyle}>Profile</h3> 285 + <BlueskyProfile did={did} handle={showHandle} /> 286 + </section> 287 + <section style={panelStyle}> 288 + <h3 style={sectionHeaderStyle}>Recent Posts</h3> 289 + <BlueskyPostList did={did} /> 290 + </section> 291 + <section style={panelStyle}> 292 + <h3 style={sectionHeaderStyle}> 293 + grain.social Gallery Demo 294 + </h3> 295 + <p 296 + style={{ 297 + fontSize: 12, 298 + color: `var(--demo-text-secondary)`, 299 + margin: "0 0 8px", 300 + }} 301 + > 302 + Instagram-style photo gallery from grain.social 303 + </p> 304 + <GrainGallery 305 + did="kat.meangirls.online" 306 + rkey="3m2e2qikseq2f" 307 + /> 308 + </section> 309 + <section style={panelStyle}> 310 + <h3 style={sectionHeaderStyle}> 311 + teal.fm Currently Playing 312 + </h3> 313 + <p 314 + style={{ 315 + fontSize: 12, 316 + color: `var(--demo-text-secondary)`, 317 + margin: "0 0 8px", 318 + }} 319 + > 320 + Currently playing track from teal.fm (refreshes every 15s) 321 + </p> 322 + <CurrentlyPlaying did="nekomimi.pet" /> 323 + </section> 324 + <section style={panelStyle}> 325 + <h3 style={sectionHeaderStyle}> 326 + teal.fm Last Played 327 + </h3> 328 + <p 329 + style={{ 330 + fontSize: 12, 331 + color: `var(--demo-text-secondary)`, 332 + margin: "0 0 8px", 333 + }} 334 + > 335 + Most recent play from teal.fm feed 336 + </p> 337 + <LastPlayed did="nekomimi.pet" /> 338 + </section> 339 + <section style={panelStyle}> 340 + <h3 style={sectionHeaderStyle}> 341 + teal.fm Song History 342 + </h3> 343 + <p 344 + style={{ 345 + fontSize: 12, 346 + color: `var(--demo-text-secondary)`, 347 + margin: "0 0 8px", 348 + }} 349 + > 350 + Listening history with album art focus 351 + </p> 352 + <SongHistoryList did="nekomimi.pet" limit={6} /> 353 + </section> 354 + </div> 355 + <div style={columnStackStyle}> 356 + <section style={panelStyle}> 357 + <h3 style={sectionHeaderStyle}> 358 + Latest Post (Prefetched Data) 359 + </h3> 360 + <p 361 + style={{ 362 + fontSize: 12, 363 + color: `var(--demo-text-secondary)`, 364 + margin: "0 0 8px", 365 + }} 366 + > 367 + Using{" "} 368 + <code 369 + style={{ 370 + background: `var(--demo-code-bg)`, 371 + padding: "2px 4px", 372 + borderRadius: 3, 373 + color: "var(--demo-text)", 374 + }} 375 + > 376 + useLatestRecord 377 + </code>{" "} 378 + to fetch once, then passing{" "} 379 + <code 380 + style={{ 381 + background: `var(--demo-code-bg)`, 382 + padding: "2px 4px", 383 + borderRadius: 3, 384 + color: "var(--demo-text)", 385 + }} 386 + > 387 + record 388 + </code>{" "} 389 + propโ€”no re-fetch! 390 + </p> 391 + {loadingLatestPost && ( 392 + <div style={loadingBox}> 393 + Loading latest postโ€ฆ 394 + </div> 395 + )} 396 + {latestPostError && ( 397 + <div style={errorBox}> 398 + Failed to load latest post. 399 + </div> 400 + )} 401 + {noPosts && ( 402 + <div style={infoBox}>No posts found.</div> 403 + )} 404 + {!loadingLatestPost && 405 + latestPostRkey && 406 + latestPostRecord && ( 407 + <BlueskyPost 408 + did={did} 409 + rkey={latestPostRkey} 410 + record={latestPostRecord} 411 + /> 412 + )} 413 + </section> 414 + <section style={panelStyle}> 415 + <h3 style={sectionHeaderStyle}> 416 + Quote Post Demo 417 + </h3> 418 + <BlueskyQuotePost 419 + did={quoteSampleDid} 420 + rkey={quoteSampleRkey} 421 + /> 422 + </section> 423 + <section style={panelStyle}> 424 + <h3 style={sectionHeaderStyle}> 425 + Reply Post Demo 426 + </h3> 427 + <BlueskyPost 428 + did="did:plc:xwhsmuozq3mlsp56dyd7copv" 429 + rkey="3m3je5ydg4s2o" 430 + showParent={true} 431 + recursiveParent={true} 432 + /> 433 + </section> 434 + <section style={panelStyle}> 435 + <h3 style={sectionHeaderStyle}> 436 + Rich Text Facets Demo 437 + </h3> 438 + <p 439 + style={{ 440 + fontSize: 12, 441 + color: `var(--demo-text-secondary)`, 442 + margin: "0 0 8px", 443 + }} 444 + > 445 + Post with mentions, links, and hashtags 446 + </p> 447 + <BlueskyPost 448 + did="nekomimi.pet" 449 + rkey="3m45s553cys22" 450 + showParent={false} 451 + /> 452 + </section> 453 + <section style={panelStyle}> 454 + <TangledRepo 455 + did="did:plc:ttdrpj45ibqunmfhdsb4zdwq" 456 + rkey="3m2sx5zpxzs22" 457 + /> 458 + </section> 459 + <section style={panelStyle}> 460 + <h3 style={sectionHeaderStyle}> 461 + Custom Themed Post 462 + </h3> 463 + <p 464 + style={{ 465 + fontSize: 12, 466 + color: `var(--demo-text-secondary)`, 467 + margin: "0 0 8px", 468 + }} 469 + > 470 + Wrapping a component in a div with custom 471 + CSS variables to override the theme! 472 + </p> 473 + <div 474 + style={ 475 + { 476 + "--atproto-color-bg": 477 + "var(--demo-secondary-bg)", 478 + "--atproto-color-bg-elevated": 479 + "var(--demo-input-bg)", 480 + "--atproto-color-bg-secondary": 481 + "var(--demo-code-bg)", 482 + "--atproto-color-text": 483 + "var(--demo-text)", 484 + "--atproto-color-text-secondary": 485 + "var(--demo-text-secondary)", 486 + "--atproto-color-text-muted": 487 + "var(--demo-text-secondary)", 488 + "--atproto-color-border": 489 + "var(--demo-border)", 490 + "--atproto-color-border-subtle": 491 + "var(--demo-border)", 492 + "--atproto-color-link": 493 + "var(--demo-button-bg)", 494 + } as React.CSSProperties 495 + } 496 + > 497 + <BlueskyPost 498 + did="nekomimi.pet" 499 + rkey="3m2dgvyws7k27" 500 + /> 501 + </div> 502 + </section> 503 + </div> 504 + </div> 505 + <section style={gistPanelStyle}> 506 + <h3 style={sectionHeaderStyle}>A Tangled String</h3> 507 + <TangledString 508 + did="nekomimi.pet" 509 + rkey="3m2p4gjptg522" 510 + /> 511 + </section> 512 + <section style={leafletPanelStyle}> 513 + <h3 style={sectionHeaderStyle}>A Leaflet Document.</h3> 514 + <div 515 + style={{ 516 + width: "100%", 517 + display: "flex", 518 + justifyContent: "center", 519 + }} 520 + > 521 + <LeafletDocument 522 + did={"did:plc:ttdrpj45ibqunmfhdsb4zdwq"} 523 + rkey={"3m2seagm2222c"} 524 + /> 525 + </div> 526 + </section> 527 + </> 528 + )} 529 + <section style={{ ...panelStyle, marginTop: 32 }}> 530 + <h3 style={sectionHeaderStyle}>Code Examples</h3> 531 + <p 532 + style={{ 533 + color: `var(--demo-text-secondary)`, 534 + margin: "4px 0 8px", 535 + }} 536 + > 537 + Wrap your app with the provider once and drop the ready-made 538 + components wherever you need them. 539 + </p> 540 + <pre style={codeBlockStyle}> 541 + <code 542 + ref={basicCodeRef} 543 + className="language-tsx" 544 + style={codeTextStyle} 545 + > 546 + {basicUsageSnippet} 547 + </code> 548 + </pre> 549 + <p 550 + style={{ 551 + color: `var(--demo-text-secondary)`, 552 + margin: "16px 0 8px", 553 + }} 554 + > 555 + Pass prefetched data to components to skip API callsโ€”perfect 556 + for SSR or caching. 557 + </p> 558 + <pre style={codeBlockStyle}> 559 + <code 560 + ref={customCodeRef} 561 + className="language-tsx" 562 + style={codeTextStyle} 563 + > 564 + {prefetchedDataSnippet} 565 + </code> 566 + </pre> 567 + <p 568 + style={{ 569 + color: `var(--demo-text-secondary)`, 570 + margin: "16px 0 8px", 571 + }} 572 + > 573 + Use atcute directly to construct records and pass them to 574 + componentsโ€”fully compatible! 575 + </p> 576 + <pre style={codeBlockStyle}> 577 + <code className="language-tsx" style={codeTextStyle}> 578 + {atcuteUsageSnippet} 579 + </code> 580 + </pre> 581 + </section> 582 + </div> 583 + ); 286 584 }; 287 585 288 - const sectionHeaderStyle: React.CSSProperties = { margin: '4px 0', fontSize: 16 }; 586 + const sectionHeaderStyle: React.CSSProperties = { 587 + margin: "4px 0", 588 + fontSize: 16, 589 + color: "var(--demo-text)", 590 + }; 289 591 const loadingBox: React.CSSProperties = { padding: 8 }; 290 - const errorBox: React.CSSProperties = { padding: 8, color: 'crimson' }; 291 - const infoBox: React.CSSProperties = { padding: 8, color: '#555' }; 292 - 293 - const latestSummaryPalette = { 294 - light: { 295 - card: { 296 - border: '1px solid #e2e8f0', 297 - background: '#ffffff', 298 - borderRadius: 12, 299 - padding: 12, 300 - display: 'flex', 301 - flexDirection: 'column', 302 - gap: 8 303 - } satisfies React.CSSProperties, 304 - header: { 305 - display: 'flex', 306 - alignItems: 'baseline', 307 - justifyContent: 'space-between', 308 - gap: 12, 309 - color: '#0f172a' 310 - } satisfies React.CSSProperties, 311 - time: { 312 - fontSize: 12, 313 - color: '#64748b' 314 - } satisfies React.CSSProperties, 315 - text: { 316 - margin: 0, 317 - color: '#1f2937', 318 - whiteSpace: 'pre-wrap' 319 - } satisfies React.CSSProperties, 320 - link: { 321 - color: '#2563eb', 322 - fontWeight: 600, 323 - fontSize: 12, 324 - textDecoration: 'none' 325 - } satisfies React.CSSProperties, 326 - muted: { 327 - color: '#64748b' 328 - } satisfies React.CSSProperties, 329 - error: { 330 - color: 'crimson' 331 - } satisfies React.CSSProperties 332 - }, 333 - dark: { 334 - card: { 335 - border: '1px solid #1e293b', 336 - background: '#0f172a', 337 - borderRadius: 12, 338 - padding: 12, 339 - display: 'flex', 340 - flexDirection: 'column', 341 - gap: 8 342 - } satisfies React.CSSProperties, 343 - header: { 344 - display: 'flex', 345 - alignItems: 'baseline', 346 - justifyContent: 'space-between', 347 - gap: 12, 348 - color: '#e2e8f0' 349 - } satisfies React.CSSProperties, 350 - time: { 351 - fontSize: 12, 352 - color: '#cbd5f5' 353 - } satisfies React.CSSProperties, 354 - text: { 355 - margin: 0, 356 - color: '#e2e8f0', 357 - whiteSpace: 'pre-wrap' 358 - } satisfies React.CSSProperties, 359 - link: { 360 - color: '#38bdf8', 361 - fontWeight: 600, 362 - fontSize: 12, 363 - textDecoration: 'none' 364 - } satisfies React.CSSProperties, 365 - muted: { 366 - color: '#94a3b8' 367 - } satisfies React.CSSProperties, 368 - error: { 369 - color: '#f472b6' 370 - } satisfies React.CSSProperties 371 - } 372 - } as const; 592 + const errorBox: React.CSSProperties = { padding: 8, color: "crimson" }; 593 + const infoBox: React.CSSProperties = { 594 + padding: 8, 595 + color: "var(--demo-text-secondary)", 596 + }; 373 597 374 598 export const App: React.FC = () => { 375 - return ( 376 - <AtProtoProvider> 377 - <div style={{ maxWidth: 860, margin: '40px auto', padding: '0 20px', fontFamily: 'system-ui, sans-serif' }}> 378 - <h1 style={{ marginTop: 0 }}>atproto-ui Demo</h1> 379 - <p style={{ lineHeight: 1.4 }}>A component library for rendering common AT Protocol records for applications such as Bluesky and Tangled.</p> 380 - <hr style={{ margin: '32px 0' }} /> 381 - <FullDemo /> 382 - </div> 383 - </AtProtoProvider> 384 - ); 599 + return ( 600 + <AtProtoProvider> 601 + <div 602 + style={{ 603 + maxWidth: 860, 604 + margin: "40px auto", 605 + padding: "0 20px", 606 + fontFamily: "system-ui, sans-serif", 607 + minHeight: "100vh", 608 + }} 609 + > 610 + <h1 style={{ marginTop: 0, color: "var(--demo-text)" }}> 611 + atproto-ui Demo 612 + </h1> 613 + <p 614 + style={{ 615 + lineHeight: 1.4, 616 + color: "var(--demo-text-secondary)", 617 + }} 618 + > 619 + A component library for rendering common AT Protocol records 620 + for applications such as Bluesky and Tangled. 621 + </p> 622 + <hr 623 + style={{ margin: "32px 0", borderColor: "var(--demo-hr)" }} 624 + /> 625 + <FullDemo /> 626 + </div> 627 + </AtProtoProvider> 628 + ); 385 629 }; 386 630 387 631 export default App;
+5 -5
src/main.tsx
··· 1 - import { createRoot } from 'react-dom/client'; 2 - import App from './App'; 1 + import { createRoot } from "react-dom/client"; 2 + import App from "./App"; 3 3 4 - const el = document.getElementById('root'); 4 + const el = document.getElementById("root"); 5 5 if (el) { 6 - const root = createRoot(el); 7 - root.render(<App />); 6 + const root = createRoot(el); 7 + root.render(<App />); 8 8 }
+3
tsconfig.app.json
··· 14 14 "verbatimModuleSyntax": true, 15 15 "moduleDetection": "force", 16 16 "noEmit": true, 17 + "emitDeclarationOnly": true, 18 + "declaration": true, 19 + "declarationDir": "dist-lib", 17 20 "jsx": "react-jsx", 18 21 19 22 /* Linting */
+6 -3
tsconfig.lib.json
··· 12 12 "allowSyntheticDefaultImports": true, 13 13 "esModuleInterop": true, 14 14 "resolveJsonModule": true, 15 + "noEmit": true, 16 + "emitDeclarationOnly": true, 15 17 "declaration": true, 16 - "declarationMap": false, 17 - "sourceMap": false, 18 + "declarationDir": "dist-lib", 19 + "sourceMap": true, 18 20 "outDir": "./lib-dist", 19 - "rootDir": "./lib" 21 + "rootDir": "./lib", 22 + "types": ["@atcute/bluesky", "@atcute/tangled"] 20 23 }, 21 24 "include": ["lib/**/*.ts", "lib/**/*.tsx"] 22 25 }
-1
tsconfig.lib.tsbuildinfo
··· 1 - {"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/@types/react/jsx-runtime.d.ts","./node_modules/@atcute/lexicons/dist/syntax/did.d.ts","./node_modules/@atcute/lexicons/dist/syntax/handle.d.ts","./node_modules/@atcute/lexicons/dist/syntax/at-identifier.d.ts","./node_modules/@atcute/lexicons/dist/syntax/nsid.d.ts","./node_modules/@atcute/lexicons/dist/syntax/record-key.d.ts","./node_modules/@atcute/lexicons/dist/utils.d.ts","./node_modules/@atcute/lexicons/dist/syntax/at-uri.d.ts","./node_modules/@atcute/lexicons/dist/syntax/cid.d.ts","./node_modules/@atcute/lexicons/dist/syntax/datetime.d.ts","./node_modules/@atcute/lexicons/dist/syntax/language.d.ts","./node_modules/@atcute/lexicons/dist/syntax/tid.d.ts","./node_modules/@atcute/lexicons/dist/syntax/uri.d.ts","./node_modules/@atcute/lexicons/dist/interfaces/cid-link.d.ts","./node_modules/@atcute/lexicons/dist/interfaces/blob.d.ts","./node_modules/@atcute/lexicons/dist/interfaces/bytes.d.ts","./node_modules/@atcute/lexicons/dist/types/brand.d.ts","./node_modules/@standard-schema/spec/dist/index.d.ts","./node_modules/@atcute/lexicons/dist/syntax/index.d.ts","./node_modules/@atcute/lexicons/dist/interfaces/index.d.ts","./node_modules/@atcute/lexicons/dist/validations/index.d.ts","./node_modules/@atcute/lexicons/dist/index.d.ts","./node_modules/@atcute/lexicons/dist/ambient.d.ts","./node_modules/@atcute/client/dist/fetch-handler.d.ts","./node_modules/@atcute/client/dist/client.d.ts","./node_modules/@atcute/client/dist/credential-manager.d.ts","./node_modules/@atcute/client/dist/index.d.ts","./node_modules/@badrap/valita/dist/mjs/index.d.mts","./node_modules/@atcute/identity/dist/types.d.ts","./node_modules/@atcute/identity/dist/typedefs.d.ts","./node_modules/@atcute/identity/dist/utils.d.ts","./node_modules/@atcute/identity/dist/did.d.ts","./node_modules/@atcute/identity/dist/methods/key.d.ts","./node_modules/@atcute/identity/dist/methods/plc.d.ts","./node_modules/@atcute/identity/dist/methods/web.d.ts","./node_modules/@atcute/identity/dist/index.d.ts","./node_modules/@atcute/identity-resolver/dist/types.d.ts","./node_modules/@atcute/identity-resolver/dist/did/composite.d.ts","./node_modules/@atcute/identity-resolver/dist/did/methods/plc.d.ts","./node_modules/@atcute/identity-resolver/dist/did/methods/web.d.ts","./node_modules/@atcute/identity-resolver/dist/did/methods/xrpc.d.ts","./node_modules/@atcute/identity-resolver/dist/handle/composite.d.ts","./node_modules/@atcute/identity-resolver/dist/handle/methods/doh-json.d.ts","./node_modules/@atcute/identity-resolver/dist/handle/methods/well-known.d.ts","./node_modules/@atcute/identity-resolver/dist/handle/methods/xrpc.d.ts","./node_modules/@atcute/identity-resolver/dist/errors.d.ts","./node_modules/@atcute/identity-resolver/dist/index.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/actor/profile.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/feed/reaction.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/feed/star.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/git/refupdate.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/graph/follow.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/knot.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/knot/listkeys.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/knot/member.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/knot/version.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/owner.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/pipeline.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/pipeline/status.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/publickey.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/addsecret.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/archive.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/artifact.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/blob.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/branch.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/branches.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/collaborator.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/compare.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/create.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/delete.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/diff.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/forkstatus.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/forksync.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/getdefaultbranch.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/hiddenref.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/issue.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/issue/comment.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/issue/state.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/issue/state/closed.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/issue/state/open.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/languages.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/listsecrets.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/log.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/merge.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/mergecheck.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/pull.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/pull/comment.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/pull/status.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/pull/status/closed.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/pull/status/merged.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/pull/status/open.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/removesecret.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/setdefaultbranch.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/tags.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/repo/tree.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/spindle.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/spindle/member.d.ts","./node_modules/@atcute/tangled/dist/lexicons/types/sh/tangled/string.d.ts","./node_modules/@atcute/tangled/dist/lexicons/index.d.ts","./node_modules/@atcute/tangled/dist/index.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/defs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/defs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/deleteaccount.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/disableaccountinvites.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/disableinvitecodes.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/enableaccountinvites.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/getaccountinfo.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/getaccountinfos.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/getinvitecodes.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/strongref.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/getsubjectstatus.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/searchaccounts.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/sendemail.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/updateaccountemail.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/updateaccounthandle.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/updateaccountpassword.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/updateaccountsigningkey.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/admin/updatesubjectstatus.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/defs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/getrecommendeddidcredentials.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/refreshidentity.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/requestplcoperationsignature.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/resolvedid.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/resolvehandle.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/resolveidentity.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/signplcoperation.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/submitplcoperation.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/identity/updatehandle.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/label/defs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/label/querylabels.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/label/subscribelabels.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/lexicon/schema.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/moderation/defs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/moderation/createreport.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/defs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/applywrites.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/createrecord.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/deleterecord.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/describerepo.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/getrecord.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/importrepo.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/listmissingblobs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/listrecords.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/putrecord.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/repo/uploadblob.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/activateaccount.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/checkaccountstatus.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/confirmemail.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/createaccount.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/createapppassword.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/createinvitecode.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/createinvitecodes.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/createsession.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/deactivateaccount.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/deleteaccount.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/deletesession.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/describeserver.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/getaccountinvitecodes.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/getserviceauth.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/getsession.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/listapppasswords.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/refreshsession.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/requestaccountdelete.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/requestemailconfirmation.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/requestemailupdate.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/requestpasswordreset.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/reservesigningkey.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/resetpassword.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/revokeapppassword.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/server/updateemail.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/defs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/getblob.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/getblocks.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/getcheckout.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/gethead.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/gethoststatus.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/getlatestcommit.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/getrecord.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/getrepo.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/getrepostatus.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/listblobs.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/listhosts.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/listrepos.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/listreposbycollection.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/notifyofupdate.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/requestcrawl.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/sync/subscriberepos.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/temp/addreservedhandle.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/temp/checkhandleavailability.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/temp/checksignupqueue.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/temp/dereferencescope.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/temp/fetchlabels.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/temp/requestphoneverification.d.ts","./node_modules/@atcute/atproto/dist/lexicons/types/com/atproto/temp/revokeaccountcredentials.d.ts","./node_modules/@atcute/atproto/dist/lexicons/index.d.ts","./node_modules/@atcute/atproto/dist/index.d.ts","./lib/utils/atproto-client.ts","./lib/utils/cache.ts","./lib/providers/atprotoprovider.tsx","./lib/hooks/usedidresolution.ts","./lib/hooks/usepdsendpoint.ts","./lib/hooks/useatprotorecord.ts","./lib/core/atprotorecord.tsx","./lib/components/blueskyicon.tsx","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/embed/external.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/postgate.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/threadgate.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/embed/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/embed/images.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/embed/video.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/embed/recordwithmedia.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/labeler/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/embed/record.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/richtext/facet.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/getpreferences.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/getprofile.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/getprofiles.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/getsuggestions.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/profile.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/putpreferences.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/searchactors.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/searchactorstypeahead.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/actor/status.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/bookmark/createbookmark.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/bookmark/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/bookmark/deletebookmark.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/bookmark/getbookmarks.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/describefeedgenerator.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/generator.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getactorfeeds.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getactorlikes.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getauthorfeed.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getfeed.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getfeedgenerator.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getfeedgenerators.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getfeedskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getlikes.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getlistfeed.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getpostthread.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getposts.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getquotes.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getrepostedby.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/getsuggestedfeeds.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/gettimeline.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/like.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/post.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/repost.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/searchposts.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/feed/sendinteractions.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/block.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/follow.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getactorstarterpacks.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getblocks.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getfollowers.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getfollows.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getknownfollowers.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getlist.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getlistblocks.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getlistmutes.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getlists.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getlistswithmembership.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getmutes.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getrelationships.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getstarterpack.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getstarterpacks.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getstarterpackswithmembership.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/getsuggestedfollowsbyactor.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/list.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/listblock.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/listitem.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/muteactor.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/muteactorlist.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/mutethread.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/searchstarterpacks.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/starterpack.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/unmuteactor.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/unmuteactorlist.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/unmutethread.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/graph/verification.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/labeler/getservices.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/labeler/service.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/declaration.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/getpreferences.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/getunreadcount.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/listactivitysubscriptions.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/listnotifications.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/putactivitysubscription.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/putpreferences.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/putpreferencesv2.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/registerpush.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/unregisterpush.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/notification/updateseen.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getageassurancestate.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getconfig.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getpopularfeedgenerators.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getpostthreadotherv2.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getpostthreadv2.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getsuggestedfeeds.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getsuggestedfeedsskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getsuggestedstarterpacks.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getsuggestedstarterpacksskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getsuggestedusers.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getsuggestedusersskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/getsuggestionsskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/gettaggedsuggestions.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/gettrendingtopics.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/gettrends.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/gettrendsskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/initageassurance.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/searchactorsskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/searchpostsskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/unspecced/searchstarterpacksskeleton.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/video/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/video/getjobstatus.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/video/getuploadlimits.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/app/bsky/video/uploadvideo.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/actor/declaration.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/actor/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/actor/deleteaccount.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/actor/exportaccountdata.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/acceptconvo.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/defs.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/addreaction.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/deletemessageforself.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/getconvo.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/getconvoavailability.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/getconvoformembers.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/getlog.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/getmessages.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/leaveconvo.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/listconvos.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/muteconvo.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/removereaction.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/sendmessage.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/sendmessagebatch.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/unmuteconvo.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/updateallread.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/convo/updateread.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/moderation/getactormetadata.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/moderation/getmessagecontext.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/types/chat/bsky/moderation/updateactoraccess.d.ts","./node_modules/@atcute/bluesky/dist/lexicons/index.d.ts","./node_modules/@atcute/bluesky/dist/utilities/embeds.d.ts","./node_modules/@atcute/bluesky/dist/utilities/list.d.ts","./node_modules/@atcute/bluesky/dist/utilities/profile.d.ts","./node_modules/@atcute/bluesky/dist/utilities/starterpack.d.ts","./node_modules/@atcute/bluesky/dist/index.d.ts","./lib/types/bluesky.ts","./lib/hooks/usecolorscheme.ts","./lib/utils/at-uri.ts","./lib/hooks/useblob.ts","./lib/renderers/blueskypostrenderer.tsx","./lib/renderers/blueskyprofilerenderer.tsx","./lib/utils/profile.ts","./lib/components/blueskyprofile.tsx","./lib/components/blueskypost.tsx","./lib/hooks/usepaginatedrecords.ts","./lib/components/blueskypostlist.tsx","./lib/components/blueskyquotepost.tsx","./lib/components/colorschemetoggle.tsx","./lib/types/leaflet.ts","./lib/renderers/leafletdocumentrenderer.tsx","./lib/components/leafletdocument.tsx","./lib/renderers/tangledstringrenderer.tsx","./lib/components/tangledstring.tsx","./lib/hooks/useblueskyprofile.ts","./lib/hooks/uselatestrecord.ts","./lib/index.ts","./node_modules/@babel/types/lib/index.d.ts","./node_modules/@types/babel__generator/index.d.ts","./node_modules/@babel/parser/typings/babel-parser.d.ts","./node_modules/@types/babel__template/index.d.ts","./node_modules/@types/babel__traverse/index.d.ts","./node_modules/@types/babel__core/index.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/@types/json-schema/index.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/@types/react-dom/index.d.ts"],"fileIdsList":[[52,53,437,491,508,509],[52,53,253,255,256,406,408,409,410,412,413,437,491,508,509],[52,53,253,257,406,407,415,437,491,508,509],[52,53,253,256,406,408,409,411,412,437,491,508,509],[52,53,408,410,414,437,491,508,509],[52,53,407,437,491,508,509],[52,53,255,256,407,408,419,420,437,491,508,509],[52,53,256,422,437,491,508,509],[52,53,255,437,491,508,509],[52,53,250,253,254,437,491,508,509],[52,53,252,253,254,437,491,508,509],[52,53,250,254,437,491,508,509],[52,53,252,437,491,508,509],[53,250,252,253,254,255,256,257,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,437,491,508,509],[52,53,250,251,437,491,508,509],[52,53,253,257,406,407,408,409,437,491,508,509],[52,53,257,406,407,437,491,508,509],[52,53,253,407,408,409,414,419,437,491,508,509],[52,53,153,407,437,491,508,509],[53,405,437,491,508,509],[53,437,491,508,509],[53,71,79,88,99,153,249,437,491,508,509],[53,88,250,437,491,508,509],[53,406,437,491,508,509],[248,437,491,508,509],[154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,437,491,508,509],[73,154,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,155,156,157,158,159,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,155,156,157,158,159,160,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,154,156,157,158,159,160,161,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,155,156,157,158,159,160,161,162,163,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,155,156,157,158,159,160,161,162,164,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,172,173,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,172,173,174,175,176,177,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,186,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,188,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,188,189,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,188,189,190,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,188,189,190,191,192,193,194,195,196,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,154,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[400,401,402,403,404,437,491,508,509],[258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,163,182,258,259,260,269,270,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,258,259,260,272,273,274,275,276,277,278,279,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,163,268,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,282,283,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,261,437,491,508,509],[73,163,182,258,262,263,264,265,268,269,271,437,491,508,509],[73,258,262,263,266,437,491,508,509],[73,182,258,262,263,264,266,267,269,271,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,267,272,273,274,275,276,277,278,279,280,281,283,284,285,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,258,259,260,262,263,264,266,267,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,182,267,268,271,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,267,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,267,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,182,186,271,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,265,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,265,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,270,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,182,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,270,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,270,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,268,271,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,268,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,269,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,182,271,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,266,267,376,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,389,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,389,390,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,437,491,508,509],[74,264,266,268,303,437,491,508,509],[400,437,491,508,509],[73,74,75,76,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[74,76,437,491,508,509],[437,491,508,509],[76,77,78,437,491,508,509],[71,88,89,437,491,508,509],[71,437,491,508,509],[71,89,437,491,508,509],[89,90,91,92,93,94,95,96,97,98,437,491,508,509],[71,88,437,491,508,509],[81,82,83,84,85,86,87,437,491,508,509],[74,437,491,508,509],[80,81,437,491,508,509],[74,81,437,491,508,509],[54,55,56,57,58,60,61,62,63,64,65,66,67,68,69,73,437,491,508,509],[61,66,437,491,508,509],[61,437,491,508,509],[66,67,68,437,491,508,509],[54,55,437,491,508,509],[54,56,57,58,59,437,491,508,509],[54,55,56,57,58,60,61,62,63,64,65,437,491,508,509],[69,70,71,72,437,491,508,509],[152,437,491,508,509],[100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,437,491,508,509],[73,75,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,134,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,135,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,136,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,137,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,138,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,139,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,140,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,141,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,145,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,146,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,147,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,148,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,149,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,150,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,151,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[73,75,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,134,135,136,137,138,139,140,141,145,146,147,148,149,150,156,157,158,159,160,161,162,164,165,166,167,168,169,170,171,173,174,175,176,177,178,179,180,181,183,184,185,187,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,259,260,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,437,491,508,509],[427,437,491,508,509],[427,428,429,430,431,437,491,508,509],[427,429,437,491,508,509],[437,488,489,491,508,509],[437,490,491,508,509],[491,508,509],[437,491,496,508,509,526],[437,491,492,497,502,508,509,511,523,534],[437,491,492,493,502,508,509,511],[437,491,494,508,509,535],[437,491,495,496,503,508,509,512],[437,491,496,508,509,523,531],[437,491,497,499,502,508,509,511],[437,490,491,498,508,509],[437,491,499,500,508,509],[437,491,501,502,508,509],[437,490,491,502,508,509],[437,491,502,503,504,508,509,523,534],[437,491,502,503,504,508,509,518,523,526],[437,483,491,499,502,505,508,509,511,523,534],[437,491,502,503,505,506,508,509,511,523,531,534],[437,491,505,507,508,509,523,531,534],[435,436,437,438,439,440,441,442,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540],[437,491,502,508,509],[437,491,508,509,510,534],[437,491,499,502,508,509,511,523],[437,491,508,509,512],[437,491,508,509,513],[437,490,491,508,509,514],[437,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540],[437,491,508,509,516],[437,491,508,509,517],[437,491,502,508,509,518,519],[437,491,508,509,518,520,535,537],[437,491,502,508,509,523,524,526],[437,491,508,509,525,526],[437,491,508,509,523,524],[437,491,508,509,526],[437,491,508,509,527],[437,488,491,508,509,523,528],[437,491,502,508,509,529,530],[437,491,508,509,529,530],[437,491,496,508,509,511,523,531],[437,491,508,509,532],[437,491,508,509,511,533],[437,491,505,508,509,517,534],[437,491,496,508,509,535],[437,491,508,509,523,536],[437,491,508,509,510,537],[437,491,508,509,538],[437,491,496,508,509],[437,483,491,508,509],[437,491,508,509,539],[437,483,491,502,504,508,509,514,523,526,534,536,537,539],[437,491,508,509,523,540],[52,437,491,508,509],[50,51,437,491,508,509],[437,449,452,455,456,491,508,509,534],[437,452,491,508,509,523,534],[437,452,456,491,508,509,534],[437,491,508,509,523],[437,446,491,508,509],[437,450,491,508,509],[437,448,449,452,491,508,509,534],[437,491,508,509,511,531],[437,491,508,509,541],[437,446,491,508,509,541],[437,448,452,491,508,509,511,534],[437,443,444,445,447,451,491,502,508,509,523,534],[437,452,460,468,491,508,509],[437,444,450,491,508,509],[437,452,477,478,491,508,509],[437,444,447,452,491,508,509,526,534,541],[437,452,491,508,509],[437,448,452,491,508,509,534],[437,443,491,508,509],[437,446,447,448,450,451,452,453,454,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,478,479,480,481,482,491,508,509],[437,452,470,473,491,499,508,509],[437,452,460,461,462,491,508,509],[437,450,452,461,463,491,508,509],[437,451,491,508,509],[437,444,446,452,491,508,509],[437,452,456,461,463,491,508,509],[437,456,491,508,509],[437,450,452,455,491,508,509,534],[437,444,448,452,460,491,508,509],[437,452,470,491,508,509],[437,463,491,508,509],[437,446,452,477,491,508,509,526,539,541]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"170d4db14678c68178ee8a3d5a990d5afb759ecb6ec44dbd885c50f6da6204f6","affectsGlobalScope":true,"impliedFormat":1},{"version":"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9","impliedFormat":1},{"version":"0ff1b165090b491f5e1407ae680b9a0bc3806dc56827ec85f93c57390491e732","impliedFormat":1},{"version":"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3","impliedFormat":1},{"version":"ea72cc9550b89d2fd7b8ac2ab4a6ad5b179bf897de7859bba0dc7a934bbca734","impliedFormat":99},{"version":"aa45d08c94931e0b7a9a4c418b33ab895aaf240e192839b36866ad84198c062a","impliedFormat":99},{"version":"dbddbfd48c1d54d619891c895f6e8ff16d30717f4a0952e701387e8040d99f85","impliedFormat":99},{"version":"3361e3db8cb33aa91beaf33b3c79c108f2410e6414498e79d2c7da1838fdfc4d","impliedFormat":99},{"version":"4d54ec33bb701c533741e8abc4233d5f378805166d3a5999d234ae189702d93f","impliedFormat":99},{"version":"f1eed69ccd2798f31d0996306c51d648e19e6e09088f9a7f57c3dcb4367cef51","impliedFormat":99},{"version":"da276fcfe4fb73d74245eee5d1dfb7776bd450d77c591f6e03b54d60be5299b4","impliedFormat":99},{"version":"b402b88cf99c4dc208e5fd337e198e9ee703f48b239dbc8d08ec3b6389e5b55b","impliedFormat":99},{"version":"6f99fbd5ea27a199f84fbc41105525d207d8c6838d62babcbe12e5be0c4a0271","impliedFormat":99},{"version":"528939385e62838eb5631e339475ba93c2ce3ba7e3819cacd42d8ba9adfe5025","impliedFormat":99},{"version":"ef7d56a3fff2aa6476c694027c4844905d40ecc7e8b775dd8743dfd8378353fe","impliedFormat":99},{"version":"3894b5833c452b46d1c59f7d58a8c45e55bcc75a0248af65e1c9c9dd88afeac7","impliedFormat":99},{"version":"1ef2ed1e48da7d651f705bc0131d1028725841d4a97652ef0905367360ba3f73","impliedFormat":99},{"version":"47c221ec0b8802eb5887d6a1be6d7a33ddcd10116185a15c36803e0ae1c0539c","impliedFormat":99},{"version":"0d42fe6c9f1fab8c308ab1ec0ec2ad814a1bd2fb685e2f12cabc0c7732c089b9","impliedFormat":99},{"version":"74967284243720aad05a852617f2891ae384d7a6470c824169a351ad31cab9fb","impliedFormat":99},{"version":"76af14c3cce62da183aaf30375e3a4613109d16c7f16d30702f16d625a95e62c","impliedFormat":99},{"version":"19bc69921209ad7269f886c69e5f812499e133ce318e1aa5d53ac82427c1a477","impliedFormat":99},{"version":"31c17a52735cb1f6cc5a4c452857f568341f95424f07fdb5ee2fbcd6a4a4c094","impliedFormat":99},{"version":"06b419067158787fe33af288531a1df891b452a11e485896bded780b2ef70732","impliedFormat":99},{"version":"44c976b666ef022c74dc1405abb2b167346f9b8ebed3ab4bd9464711c8caa1f5","impliedFormat":99},{"version":"8e52008b516a7a7301f9d43884dc899019695eab471312b72be8a72f89850327","impliedFormat":99},{"version":"f47d865499c8b58d931d7f16e4432c69a3501cb324ead037804e8f292ff195a3","impliedFormat":99},{"version":"1e2f699820214b98d8d16c57137c7adb04b13f3ac573b4f0bbe7b7a601d09495","impliedFormat":99},{"version":"3fc902025baa4cbc0b4648466ac6be6117dc4b0e5b56d245d959839e58fdca8f","impliedFormat":99},{"version":"8ca7d6aee90a5a1d40d356c70c8d5b0ca55ab31a3d17ba73ffb51604674c48f4","impliedFormat":99},{"version":"8de5552b84830fba2143c18d43ebfc3c1289c67402c51a378c32daa7ff7adf32","impliedFormat":99},{"version":"651b59286248bd10267c630f728172866ae427f4036372bcd63f1502532454f8","impliedFormat":99},{"version":"1be150449268e28edc1a1d31acb2b9fa030aee87897a41856d50ddccc124ea53","impliedFormat":99},{"version":"ae0bff9b2245fc399295d0572519a2833ff1a47550b7fdddf96e648eca5271ea","impliedFormat":99},{"version":"0b903757cdc9fe17029ff023c454ae06941499fbf9ae0f733c5e7314555797aa","impliedFormat":99},{"version":"f7ff68ca4ababc2bea9fe21a476be0f6096b8f9f54d34f3408f921850b770e24","impliedFormat":99},{"version":"fa689046e96fc6bd92d2f6edcdb9c0c45efedcfe23443ae1fd4a470bf6e9eda3","impliedFormat":99},{"version":"cb54ec232cad4e0c94cebfd4f560de0907e65c1b55b62a7c1063ab73fe2b014d","impliedFormat":99},{"version":"39f0d3b66804b002c2f462c9ee61bfca709b607559893cccb4ade202f01028d6","impliedFormat":99},{"version":"1f0316a5da01034b17aed99fdd5ec8fe805bb65c5181587c8da353e7c1c31466","impliedFormat":99},{"version":"56442d4cc6dbee39651e056fa1497720913f1f801791b84e50e892cfbe078bd4","impliedFormat":99},{"version":"03cd861598cd715ca2bac8148c04a5f0ed2d21e90bf8db13de771be632f28c66","impliedFormat":99},{"version":"bd1578e4ca78d8de0d3dc05f95d147e45b4dfda47186ce874b3c0d7539e2098f","impliedFormat":99},{"version":"735c117db157cdfd07b4a18b777995e5ce23e535f49cbb12f5fd2c9111c5db18","impliedFormat":99},{"version":"303e57dc66fb6f7e6a79344e5135d48c73ed024cb311d2babf4df5b89fbcf3c8","impliedFormat":99},{"version":"d334f6d54e3907bf5a76f95119c4cc3f91220e96eb113487062d416c3bb70667","impliedFormat":99},{"version":"41178e3f5b546481e0a44eb7aaee78751843dffd525360d953550e8704880445","impliedFormat":99},{"version":"6d0cffb06c980b1ded7ed62302284696b2e9c88ed2608a36acee3ae30cebf88c","impliedFormat":99},{"version":"c57d47d923b6851e8880b395d0935d0b3bccc5c3691e7ce33abb247195b262ba","impliedFormat":99},{"version":"aedbbff1a84bb4cc113b3aa603afd19ea5a53e505744f971b715c412dae0505d","impliedFormat":99},{"version":"e04ad05711a15421294f32c07c87f23a99e792e44a2109700cec6e031038e5a8","impliedFormat":99},{"version":"05b4b78a0fe3f591b9f46700396ec760bf4785c238f8a27cbf78037357615994","impliedFormat":99},{"version":"54469d80797325cac80ddf053a12afe121953b9f327a3865925c1ea5fedc1e04","impliedFormat":99},{"version":"8ac43b3a5564c1b963aa0598ce3fe1d2b5673f220a2d0900a05cc03a2098433c","impliedFormat":99},{"version":"9555fc148c875680bb94cf7afd8c96cb6bd39575833f4d57755a3b8e3639937e","impliedFormat":99},{"version":"2dd5d9fe6995942adf82fff82e624e5aac2965a605587cc777c852981670f7c7","impliedFormat":99},{"version":"09ded16b94109529ea27304a7292084332eea0fec7960361650feaa57f738763","impliedFormat":99},{"version":"89bfc04207450b25e880200f4b1548391b539fc60bc2f2945fc0edfb23e23634","impliedFormat":99},{"version":"7c9600f96f56cb5356fb26046a60d5ea86f4e3a48709d316244395a8ca007029","impliedFormat":99},{"version":"8b10bff76988431e8f68b2a0dbb79df3ee0331753c4fac29b40ab62801f2aa06","impliedFormat":99},{"version":"724ce9a87bbacc89ff4ab317df599f77a55ee6545d76e056d8b36bb55f24c58b","impliedFormat":99},{"version":"79ddbd060c4878d83b491a4f8469794c8184f59ec9d1b022bc5b14c29215f590","impliedFormat":99},{"version":"c80b21e0bc14336675fe80924a468b9ecbda995c0b4705988f53c38dddf41922","impliedFormat":99},{"version":"aefa7fd02617fd165f8f06e921b8c0d8e60867af6634a622ddd017c158c5a505","impliedFormat":99},{"version":"bab81fad10b35ea3ec3886895b7b2e44716d7c73e009b1b3c6aabcff8dd28b42","impliedFormat":99},{"version":"78c2b0c973739c69dc21745ccb2c8dfaabd0bc97f27675a11eec5ddcb44f04e2","impliedFormat":99},{"version":"8883a6e862b5f07b07fccf8a0100ca73d35a2117fa2656d4368fbe089dc45ad5","impliedFormat":99},{"version":"9262edf59574bb4b69221a2289c08e9d8d9b83f51ac9838191dc43c176e26312","impliedFormat":99},{"version":"8f3e70c49f9abb6115f25ad78fc1d65edaa463f2d6d5a5a1554cc83bae5fb284","impliedFormat":99},{"version":"17aa7e05470aee99f362246e3b2fbccb3833d155b103bc85fcf3cd0166e8385d","impliedFormat":99},{"version":"c47eedce9426b4a330be6cc94f31ab0430557bf3d66b2f6fdaa8bc2a9fe3dc63","impliedFormat":99},{"version":"8fc134de25055444c3f7cf24895245a20009342b7d27fefe046a23f06050775d","impliedFormat":99},{"version":"5f291db565a8a521639a3f6414c1d7484b1692ced3fab25d5f6d9c7622412239","impliedFormat":99},{"version":"9d97d4c094e8fb4a39dc3c9c61a5ab3750df7f640f10d7cc1f0cc95254867a46","impliedFormat":99},{"version":"13b381b958471357ea0e5d55331fe8da40ef6dd444c765620f5ac6ed8604fea5","impliedFormat":99},{"version":"c7b04352ef5338a362cb852b254bcef5fd9f299611830722ca96bef2272a727d","impliedFormat":99},{"version":"5b7cc254748cfad68a29f1708899c3c053ab74088d08ab4a55488eb2a2a6ffc3","impliedFormat":99},{"version":"32d7684d69e6fb0d6b0b9526c81791c866e379d890969248ec033b76014e5ae3","impliedFormat":99},{"version":"3004fd440a0ce893bde0047ded249354f17fc5bf5f0e18cb443ad5875ce624af","impliedFormat":99},{"version":"616bd5a947e0f6c9b3b1115ff93e6bf6bde37f0f8874ad22ce1b94972210ecbb","impliedFormat":99},{"version":"e93070a564ab221196230b074e5316c1780fd97e32d134984662d77686a03da0","impliedFormat":99},{"version":"f14fb34a1756869066dcf0101061dc28a3125165a19033870b99da489aef7b8a","impliedFormat":99},{"version":"d8c6ddb39e8570cfe28b4bff78b60c3b0f8989b18f1cff11b65b3bce72cc8b7b","impliedFormat":99},{"version":"bc676aee1c0487b866e6c4879c44b4022726bd4aabdda10d8a869bfff2fc4fd1","impliedFormat":99},{"version":"94fdd74bb75c869bb49cff84e2cc0d96847e54550d07f190e6aa2f66f48df3cd","impliedFormat":99},{"version":"df87131c4e90121f3735d75f8687fd6593a2cf532f584f1c007929b2a30a5f9b","impliedFormat":99},{"version":"ede4c4fa5909299b5ef08f59bdca60f233b267dbd1cba1dad89977aca2d2a832","impliedFormat":99},{"version":"64e5054bb3a621b29863c2af6682d288c14635b2d14ffb5b446a17a63b88dd87","impliedFormat":99},{"version":"bb1fb4df5b57dd20edc2b411f0b0149f15ab5a106cdc8a953b4209f645034785","impliedFormat":99},{"version":"060c85a4fc9199e1f44449c4496e50da8803b007aa3fdd20f2285978060c33c0","impliedFormat":99},{"version":"0acc2626aeba6dec96b73ec578bc3bc823af3fce72c12c5309ac1863604c076b","impliedFormat":99},{"version":"9a761443a08143aab9c4de9693e72f6d8d23247186eb8889bcd10393dc3532c0","impliedFormat":99},{"version":"da1417971dd43589cf2f64e5f39da372a9413bbdaf4fc535b0a3bb132b1657e3","impliedFormat":99},{"version":"8889a09c6af3130c89f3447054fa5460754471e0a1f98a527e71fdfde899cb2f","impliedFormat":99},{"version":"ba77a7192b018f6e461de533baed7b4c7e574d708bb65217c42201c2ed10abfb","impliedFormat":99},{"version":"78ac78b6372510eeba34d59c12baf2f2d3e711b466161dce30d73c935340a8f7","impliedFormat":99},{"version":"a8d4f94ba30bf367db014cbef8d6ece4cb664e69aeba758567acb8c02b0bfe2c","impliedFormat":99},{"version":"8f489b6a9e3d21271875f468482bc0418341e23a0688734c3ee7e6b9ff74d7c4","impliedFormat":99},{"version":"787633e9a13f49fe7403439b33388a99637c899236a6cfbf381a063cdbe92542","impliedFormat":99},{"version":"e78023dc028d670a73c72661acbed753764a505e2f464d9d7e118046508c26bb","impliedFormat":99},{"version":"b0486c6fe4f9e68f2105cabed1c388aaf94c06774305d712013fdb5043e46bb7","impliedFormat":99},{"version":"b617febcd2b87748a5896415458836838cc7a047c9ba3c1f93e145ba972117bc","impliedFormat":99},{"version":"c2ed7a65f90e182168b8946ec12ff5aec58c10e9927b04745875843c0568846f","impliedFormat":99},{"version":"e4813e26203d7c6caee2675461cb8694d0a5a2a692fe955b3becdffb1b229726","impliedFormat":99},{"version":"90740a4740eb94a7cfa4c346de4ce38329d161423444f13d86153ad503571cc8","impliedFormat":99},{"version":"f419f04277f873a6c9cddb78465d66a7fc89453768283bc66610f4d563c1fa16","impliedFormat":99},{"version":"85c11e5e250abb4a591509a61e9db2202e7a186cadbd4184d77357f6d768ed1d","impliedFormat":99},{"version":"5fe57966bc656055ea2e4c1657dcc16edc32a08ef87dc30c09671004bdd4cc14","impliedFormat":99},{"version":"ead716385d47a7d5ee147bd5a7fdc0d156c989b4c8806a76f220649c6accde95","impliedFormat":99},{"version":"bc16ae537239eb39d5020afb0958f401befba9d0aa17bb8f895984c6c688569f","impliedFormat":99},{"version":"e551cfaaa5020ea281dbe01991d644059e60cb22b1dcc9634192db9238542723","impliedFormat":99},{"version":"21fc8b8d6387a8a5cc9e62c31fee3c8da8249fb3bc4a8ba79c5092057c892f7d","impliedFormat":99},{"version":"19082f3df1f25f7fb304a911d1a153c9391f464e3001080a524c995e4028a3c4","impliedFormat":99},{"version":"e0f6aafed1acaba5854688e6fdaf8d124383a9282c111b41e22c63f3eb3634ce","impliedFormat":99},{"version":"6562b25cdd9cf562a6d9c614d6294a5b4a5d53013a337b684198672aaa1d61e7","impliedFormat":99},{"version":"3a99f401d0459a24124f6510c9fd84c3ddc94cc9dda88bc092b2c328a2316f10","impliedFormat":99},{"version":"25f8badc9dcd1b4ed1b670442a5cb3bce3855aa2273600123ca82f7105cbd05f","impliedFormat":99},{"version":"df348ecd79b09037176daee7995ddd749a1862a11ed6ac4b0c8a3e7a27cfad27","impliedFormat":99},{"version":"36b1f70dafde83b308cf0d9605fe43900cd340320a5943c79085e39b7ce9c344","impliedFormat":99},{"version":"d5631a56aeb2279698572bf985113de7e1deee43c56cd9dd85aa2be1a41bb998","impliedFormat":99},{"version":"108a85619ed468d15c2f5ea49aa19be365c18a123d2c096cdb682533cafddee5","impliedFormat":99},{"version":"e8e65217abc06cbbd6f5d331cc2fecc278491350423f03ac47aec6978456716b","impliedFormat":99},{"version":"0ae17e04bf71b5eaa46a509bccf6d58cf793a33e39c27077820fb010d4f9521a","impliedFormat":99},{"version":"60d25f66f4b6ef80e0f66f3b2ffbdc88b9ee98d925838d5964fccaa792bb7bc6","impliedFormat":99},{"version":"fce7a2dfbc5f0e8cc50b843e3b4dc606a98832cd6d4a5ebe6ddd054a92dc2597","impliedFormat":99},{"version":"c30274fdea2366b5a293415a6a09124c85b42cc6c02b8d0fc72af5a05351f55e","impliedFormat":99},{"version":"5dd427cc3c26d8e97c7dc0b287e06297717f7a148829fb7fc5875e93bf40956c","impliedFormat":99},{"version":"3abfe64fea01a095950c9f1782ca2ef01ae5391fe5b7fe6443222e5a6adc5d08","impliedFormat":99},{"version":"17a168032c270ec761481e277ac16efeac6530b01870720c17ea6649c9db518b","impliedFormat":99},{"version":"b9e54097ce2e07b1911e42f1c578d9304ca3e2c824f989759bc2199d54e4db0a","impliedFormat":99},{"version":"52256083681cca90fcbf3796937308d94e1c8e3ced04a7974b49ec4209f2ab4f","impliedFormat":99},{"version":"7086e03c6bbd58177835da26104c3124e985afbd9a91b3a148e0cb88fbadcbf0","impliedFormat":99},{"version":"d46dc9978eefe051bc8f6679dc6b6a8be7594247effd2563ac488afee31ce9c7","impliedFormat":99},{"version":"2baba9d417f5388ecf1f2b1a9a58a4b6d4ed7df15509d60de9d7caf1f0043ba1","impliedFormat":99},{"version":"fa412c9f26d7be8ccbbd56ab3f1fad78eb9cf87af63fd8c6fb29dd0450b47622","impliedFormat":99},{"version":"4da7dcbb288a72cb7488728930efa47e46fb3dc042f25aa7d50468f0949bd1a0","impliedFormat":99},{"version":"b0b15e47e8d7e2875b28284ea08678baf6f4dcd420241046fc91743c905d0947","impliedFormat":99},{"version":"7eef51f067c640ef085973e8e13608a0b91c912297514e3264fa6596f328c9fe","impliedFormat":99},{"version":"4afc590f38f8051eec35231e6946ecf2151d7a23ad4e138047b01c800d94d9d0","impliedFormat":99},{"version":"4c42c37e1f02b327024ecb1edf1b5a12e4d45092bc51b405985a791ce32598e0","impliedFormat":99},{"version":"8adbcb728b2d3bd8282e1a657a45f6f9103a7eb43f6fbe36c554bfef17df9656","impliedFormat":99},{"version":"ae061bc7af2f9ea15a742b07e471c27fc4d82e2b940509f9e7da3f129595e03e","impliedFormat":99},{"version":"d15f78dca6c2afbdd2596cb17983fc60a79d95dc0142408d224d671f8b6564dc","impliedFormat":99},{"version":"2307f448951cae8a6b9bf7b09c114369e42d590c53fb99c99fb1cc34ead0f0dc","impliedFormat":99},{"version":"71ceec981852d2a8b0fb281799199344a6ec90aa55e13302916f38b48debd10a","impliedFormat":99},{"version":"5c943d5009f2f31841d04d8ba9e853a13201a9e81fb96234c90191f00a50a713","impliedFormat":99},{"version":"cf7f50328f9916c083247e28d938b986a9d9fd60ba956a90ce540ff676e3deff","impliedFormat":99},{"version":"9005e5cc014620c428d72c3333d6430f4be9ab113c7c41400ea4babe458907fb","impliedFormat":99},{"version":"5040c1eb043d900faf3cbc181831b7aefda2fa75bc08cac59897baefe2666e43","impliedFormat":99},{"version":"83e5ae91867c4b4d94ef5c43b9652401642b80f3a4b86af579390dc2fa97ed10","impliedFormat":99},{"version":"b87b16115bb301284968c766678ee98ab1c2276f1bc4e437945db172718a22b3","impliedFormat":99},{"version":"ec78470f53dc3905513b87992cae127a1d5b793ea42cd48c8ff809ebf576f19a","impliedFormat":99},{"version":"a22df7d38217bf9917cd51c7df7da7965d6a5f57b56b5e612f48ecb34a693c1e","impliedFormat":99},{"version":"df740e512060283334dc9f4d608bb7ddf9097b6e1613dd2b3145d1455bc4f73d","impliedFormat":99},{"version":"b7eea3116456adebb14baa549db35cd82759bdf2f5b161e8ba12768e634b10e8","impliedFormat":99},{"version":"930612d51e2a0cb1402f15a758d8bd025ee29b4c47a13d038faa3fc8ee16e3ba","impliedFormat":99},{"version":"da4d4b60a3c4a12f155b1ca908ff6500c81262168dd39bf6b7f2a632661beb4a","impliedFormat":99},{"version":"1c7a163091d67b9af010e10f5c43ceccde78320486d2901053b4d5af2b751b2a","impliedFormat":99},{"version":"6fb3541ebbb5e2a930fea38603d4c0cbfb4dfbbb7df8999396a8a90939131ef1","impliedFormat":99},{"version":"545dc958d8101a8e3c9932370a7b6c2f8599df0cd53449114fadd7f59fa9d6f6","impliedFormat":99},{"version":"cc8b39d1f46ad8e39ebbc27d5664594a82625199459a9ddbe54ef17b4239ce24","impliedFormat":99},{"version":"08c3b01490d49ce93d06bcb7593ef9ad02dc2d91cd583eb9cd0f3d2fa75e440a","impliedFormat":99},{"version":"6ba19623201e7dda0bda2eb91bc7d1ee6ed719be167f999564a42ad8535e2f9b","impliedFormat":99},{"version":"482f77de2d4972e66c7c6a3aa53b524e20eca97cdaa3220cd2ab7acd3a7a54f3","impliedFormat":99},{"version":"634708acac0aa7d170f03a37a9d8cf6a9febefad220477649f6ec3e1ac1f7adc","impliedFormat":99},{"version":"89b2f499dba3c24fdb44acef871860328553cc46a4802874e17de01d530056f8","impliedFormat":99},{"version":"0288e5f29c1d2b678afce4d682a7ce94c323b86c479f28a4a33928261a5fe4ef","impliedFormat":99},{"version":"4e0c84783c17101df536fa4018dad2c210ba3a41daca0c1df4957f7e8dbabbaf","impliedFormat":99},{"version":"bf17c4d237818052f7f3bdd00fa2433e5ee151e1dbbaa1700edd2d229bacfb03","impliedFormat":99},{"version":"862052995b61cdf46e9fdf9b83acb076975dcfe2f44e45c07b23ffa70f9dde0a","impliedFormat":99},{"version":"8a85e856fe6d7a45e4acac5406bf440ec274ac46171d08567d38a7ff36796c3b","impliedFormat":99},{"version":"b48976ad99fc88001f7c2757d84a6bcc5e8830f7e95fb3c73d774e7710b51aa0","impliedFormat":99},{"version":"40ec8dd574cfdd80de30ba61a0dda32c83d6b60b783ddd5c9afe21187a8b0353","impliedFormat":99},{"version":"4ef67ffb70b66a188eb391c50965ce59a997e5995a060cb0eb803e7937f80cc1","impliedFormat":99},{"version":"bd3f53621504b6b6fb460130652d71bedc7b48c4fe26e81a62f9976de519700a","impliedFormat":99},{"version":"1a94fffe1807952952587e8d392df98582a90f034abdce57217cdf8409a0a16d","impliedFormat":99},{"version":"b7b4608cddbb582012556d2016b1704bb77c9b320770cbde8b98ebbaccdc6056","impliedFormat":99},{"version":"7ff126c2720906e2edc4917be8a00ff52597dd050a0ba8deb82ae1dfff440432","impliedFormat":99},{"version":"53aaccfcf252817f557afc4e9aa18084dcc90e5c096411b9f60b6b7de4fcc41c","impliedFormat":99},{"version":"a7f0bf6a7172f6c4a33877693190c8dca6b5ca0bf0deb5130bfad368b61e16c8","impliedFormat":99},{"version":"fe428bc5c0d6ce416defb7a7e67fa6a10b182b7ee2c16b42419f9538cac0fe10","impliedFormat":99},{"version":"5f19171239190089286ae47ff6d0c9d6fd2b29342dfd0acf6cc6f66f9d1bb4d5","impliedFormat":99},{"version":"d2a502e2c30b775339bfea8c1d51d3dd5244980a3cd5063ba12227cfe03fef44","impliedFormat":99},{"version":"a64913d689476c4743b020c801672718d1a736ab243907396312b832b65cbdc4","impliedFormat":99},{"version":"2d7bd36015925a348793ebfbc2e2a1fbd390bf335be8b9ec780696ed6dde26d4","impliedFormat":99},{"version":"336e86ff9f1abe22105776c7e757e9580446aac47cf8429ca78e6ce980f284e9","impliedFormat":99},{"version":"1416b07f727c918e532f4134a45e3b07b0a116d9def6d9d347a00c50dc01060e","impliedFormat":99},{"version":"a44330e93e4a7f8759a73736215eef4590b74b3421d955d980ba3db8e6b5a7fe","impliedFormat":99},{"version":"ac84b7a6fdece6cbb0ee141a09b505890c3f11c83446f6304ab9d8e972eee60b","impliedFormat":99},{"version":"97ce0d7c831f1fa7934651c878f1af14090ba5d9ee471db035f465a7dc113202","impliedFormat":99},{"version":"cbcc2f6b5fa06c91e2a35cec35066fd14dacccf185dc8c64c5143381bc2d4b30","impliedFormat":99},{"version":"02bfa3730c6288cac5928a0fb08f01f8c236992b0ef57806c478f0f7bdb6eb83","impliedFormat":99},{"version":"5af40b9066886960f300ad8efd6dd8f77eddeb475903989841cf0385a1edf4c7","impliedFormat":99},{"version":"09d52052be4f3e0f4c8fd5d4954cb215a96e20bf6fade80f95706fbdd5cee318","impliedFormat":99},{"version":"f61822dc28d52652fd897fa846364a0c6e2f9d18c4488c104f4887b4627b3fb4","impliedFormat":99},{"version":"55a4f8c04961c6cb99a6d2c79a375e4ef107339c3ccfabd8d986f2a177dface2","impliedFormat":99},{"version":"a5433e2b1359ac2672f3dd287d58e0733fe778ef7dd71a4f913b01e794104119","impliedFormat":99},{"version":"19d3750d9f8f570936cea4be2a6feac27e1cb88391d7c54e3209f479019c1bb1","impliedFormat":99},{"version":"acc3dd66b849dbe92d07581baef23754c6bd3d4cc453d6d8f5d52e261304a3bc","impliedFormat":99},{"version":"e4813e26203d7c6caee2675461cb8694d0a5a2a692fe955b3becdffb1b229726","impliedFormat":99},{"version":"63e92d208753fa0737072880b8ebfc8a054b8bd4c7af89b620605b7438d7a9d7","signature":"0c1a5de0fe6a22e6065eea7dcbb344229ab4e0f07e5f92f14a9db66cf751df8d"},{"version":"d135fc23cbc0d02c33b0f8ed2be7df4311515064ffb370e826cd53b0d8622524","signature":"3651678bdeba3e39d96bcde883de468401f51943e3c7dc3220c4b7be5b0b52bb"},{"version":"246bf4998a6cef47739b4976189abe1afda3f566be0c6efdb3771b619e21dbde","signature":"f283ffd4999bb8027d29a0963b5e9796c80d42b774e452f38c95f2fb00747708"},{"version":"981ac80c705c3344e59e7901a57cefc7266812f5f5f99b10f1a85af715d58d3a","signature":"1f009a3450b7fda74839cda2709909e4954d55e56def759d215f937733eaf3f4"},{"version":"c3899ca78872ccb9619967ef2b8fae5bf8ee1e4fdafc89c3ba9ec0fe253d918e","signature":"201d60dd52e618046236c6d3fcbc68b5cc0265267c6e3cf680f414666b21ab44"},{"version":"47d32cc25f24eb3f46ae95b5da96e7c4d5e200d440d1ceec7af730d997ce8bbf","signature":"58485c1d698072f89d92094b20b5174913100efa379b990788cdf23eeceaf9a8"},{"version":"2863ff2c955646e3942389fecaf8188c76e5478163b0f2025ff3bb64b2918698","signature":"b6d9d5a43d89e36c56bf8c0c753809f3cd6c6bbcccee0c684d37596376470141"},{"version":"959734acd7d29267ec7e15a4975ac091e0e806c65fe3367c19c791412720bd99","signature":"fb151c164a917f6ada3b9783fbee8518a8ea80695189b66920aa777aa76419af"},{"version":"b06d9380eb2fb5c35f16d03445b1963e8fef5647c592642f5d8dfbb11cebc1d0","impliedFormat":99},{"version":"33e160560eddf2de63ec16a76b8c1d91cba0f5906ff2f29819e07521d6382f6c","impliedFormat":99},{"version":"c35a50ae47731c15c54b6f359590d24776fdd74374bc85031afb800212181af0","impliedFormat":99},{"version":"f34a1a6965c0bbfa374873432849ec319413414ec186261e97ea70d5dc4d4654","impliedFormat":99},{"version":"a3f4b311af4127148f5dbb1a1ea80468a20725f10ae78001a8dada6e4a618696","impliedFormat":99},{"version":"790d03a802369d9a8ff78db3ece0cab418ec75a120dd5ab9aaeeb1c534493d3a","impliedFormat":99},{"version":"2aba871159bc6021308c5f1d05d7917e0c896c7436dcc3619038f381859fbd68","impliedFormat":99},{"version":"adaa0d941bbf9d3f11262c3739579aef70fea7af193718f8fd67e10a771fff8b","impliedFormat":99},{"version":"f178ad620e827581f2baf23c9c7fabb77d36ead92c15e149012934a30151dcf3","impliedFormat":99},{"version":"ef73187b0072b9b148b03acb62dd12615d704589772ce88e2e2e1bbf0255c802","impliedFormat":99},{"version":"417f252d9e1048c6b47d8a8a5a8e7ff5e9a4de37d6d3468152f2e8eb35b88c29","impliedFormat":99},{"version":"f3b2ef02288170cb72b7d412a7cc67dfd85dd24c168b78b77554f1ec515b5701","impliedFormat":99},{"version":"590460ef9a2cf9ab9e34504aba42d6daffe4ff7c347ded7bd8d9a989e0920c3b","impliedFormat":99},{"version":"da45f9cde46eaf3ef2d3135b5354bd3739897d72b406e0f0ec1f5455108580a1","impliedFormat":99},{"version":"cdfaab58a31962eb873192959e14bca891669fa0edfd469c9c35babc36e490a3","impliedFormat":99},{"version":"206d171772d21c82353069668d680a40df7489bde0446fb70f378256ad9d33ff","impliedFormat":99},{"version":"b6104978f6a9921d214c58df950900dc2a9319815a843d354881fd1bad7e62f0","impliedFormat":99},{"version":"1202e02184a9fcfafad7333bfac2da836eaff83653845e00c9495d9c82c9f635","impliedFormat":99},{"version":"6899200d51b7d7491d15521da878142c5fe50d711afe22e60e359f6db6c98088","impliedFormat":99},{"version":"c5e79b84695297cc596924039842c2ab7821470f1c97c4b1aa9d6a7f5f2d2e3f","impliedFormat":99},{"version":"630927db336d1990af0f696dec46f8844c44c507e3a72069f50754541dfcdf5b","impliedFormat":99},{"version":"b4e7867fcdc8d3c7a23b45cd2d705d2dc576a7f7a70156b17aadd7e01f334315","impliedFormat":99},{"version":"c89ac8740afdfc27b2166cad337e716fe843eba690167cbec823bd689f613e6b","impliedFormat":99},{"version":"23c280c11bce285e8f022b0009e083a1bdfc3839f348a38df9dde0c3c571820c","impliedFormat":99},{"version":"dd5be7220598df4a07b6281e01170d0dad5fcd74bcd27daad19619b1fdacc6e0","impliedFormat":99},{"version":"c526474a2770a981d08d9f00644e2a390475b0f9910888d426a6b2e6686b77ec","impliedFormat":99},{"version":"d5cd7c1300ee1f45cc7ef5afef746a1a34c701b94958532a88d265a9f0068063","impliedFormat":99},{"version":"a01c5aaacdd5362cda056105f2acea8b8c4699ce28ccfe641399dd0b49239419","impliedFormat":99},{"version":"6ca50db42bff8c322c4a7449cb09545ff575a1c4bd4fe6d32a9adb563f3fac2b","impliedFormat":99},{"version":"2a45ce8c09f2f343ac47fede405b833cd0ccb325f9b313fd2febfff8ccebd8bd","impliedFormat":99},{"version":"c67e3783cb681d6277dec8ada124cea535c8c7f4d99f5d6f0fed69bca38f3807","impliedFormat":99},{"version":"76ff4e5304fd22a8ebe6cb01ca297feaa0dbe14988ff43746345b55ab3504e19","impliedFormat":99},{"version":"361cb330385d846fbabf3d4db59be3655d45d7c76a483a03c71b0082014f4e9d","impliedFormat":99},{"version":"43b526d382577c18840fb6c10030753b82eb60a377c50233959437dc6493b047","impliedFormat":99},{"version":"7529eb5b698818410c1858e22ddd689fc3fb6f86d54af06872e40423574d15ab","impliedFormat":99},{"version":"3ee6957c2293a69d9f7ff232da8425d396e3115e9ba261a3e532d350e2bebdb0","impliedFormat":99},{"version":"6f70230fab4be1aebb2ef95828669b89dda35280965c55ce88aecaf94848f036","impliedFormat":99},{"version":"8d87cfe48ed90323bac6da16f46aff7fa293e32057cee38d14aab11e9f66e4c5","impliedFormat":99},{"version":"eaa88006a1ba314595330bf47f08eb4a468e7764a1060d6e133183aed7babe13","impliedFormat":99},{"version":"0645cd3e93a26c8a7c16595dc63a169d137f6b58bf50cca98449ea23b333475f","impliedFormat":99},{"version":"1586cdd29126d1a54dee78b46eae7b26c7d473bfd7eaa8bbfc5bf2a76946c545","impliedFormat":99},{"version":"accf489fbfb2dac5d38e71b2fe81687fd44d64b21b5631f6084b9acff57b4d48","impliedFormat":99},{"version":"0c5390e201ce2722d720a82c2105e07606576903e599ed3af15fb8e4e45ea2f4","impliedFormat":99},{"version":"b556cdad15c625405519814fac6ef355484a25c1cc9344e6e5dc110d57a8cdff","impliedFormat":99},{"version":"3e810c39b288e419bc933c293cf5703b948b73bbf07050527c9d73f7107c0c4e","impliedFormat":99},{"version":"9278bac0ed6d0d99fcee6f0f217399ec12edc1d10a9b3c0873c7b97a25223384","impliedFormat":99},{"version":"6f3dccb219fdfb3816c02e9a99d959c853c855c7b7beadecd3a3d3b0a374d953","impliedFormat":99},{"version":"2eeaf63552fd9c5d3ab821464d8f4a5db0de7132331acafafce577f58d871a39","impliedFormat":99},{"version":"9025d4254c18b31d9220e4d33dad5035507ee1e863d1858891e56ffe0eb2ff13","impliedFormat":99},{"version":"001a2571401e839a4b68cec0af11c6188f766ebba01cc1e1895027e3d35030b1","impliedFormat":99},{"version":"846715a4679c9be48434e2b6dfcc466588301a733f51ba0ea15e348fc7312528","impliedFormat":99},{"version":"0b77894d2bbddb9024a230e9d00eb9df633c8d190887f92959d9683a81286c39","impliedFormat":99},{"version":"fe438b40de8ec7fb4a1734f8a3c8315aafcc90dcd15512484e7dcafac310e856","impliedFormat":99},{"version":"96d53f66b4fa3ab1ef688b676450895730863e298e62dc6bc051442195aa1a5f","impliedFormat":99},{"version":"877f538909bb2120565ca0c39e93341e5a4a737bc5fda014c5148c3676fec8df","impliedFormat":99},{"version":"fba10fca31d18ed5fafacb21e2ed389ae5b347b552c5b7a69023a563e27fdf5d","impliedFormat":99},{"version":"600beaefece9011d792a4e165db46b9de5563e025e941aebd069b95cb30e7af5","impliedFormat":99},{"version":"bdc387fdccb98bd48da4cd4cbc8f29342586c2f8ac7215396547442c3b0ff8be","impliedFormat":99},{"version":"7ed291bf246832b2745528de9e68b2bd3e2fe7cdf32631665a326740c3905836","impliedFormat":99},{"version":"e61bc77843da1980c2b6851df589e730bbbdada172986ecb47101974f44aeb5a","impliedFormat":99},{"version":"00d79566e3d5682a968b44aed9eba1557b1f62c884546fee2bca88be8c477104","impliedFormat":99},{"version":"f82d26f23d5e3093ca86458544b54befaf0957a5535149f673976187a60f9e6b","impliedFormat":99},{"version":"9f0d621f43198d0db510779ba65ca3d1a8fbca37f408efa5e25ac5ca01f74875","impliedFormat":99},{"version":"9bc53a206914661c0f98bb869eeda7214778367683a5e3e0fe7c6eaa6c474556","impliedFormat":99},{"version":"71987002d6409ea81e705fbfb97afc644851c16d9fbb5e90fcb3bc3e00e4ac5d","impliedFormat":99},{"version":"70e86eda2938040292fac398debcbe47114a3b9bd6eb8f632a89dcd23ac0e2f0","impliedFormat":99},{"version":"6241345f59b24be6e85c159db51cfd937f853aa8020b38b2aac5345813b25a8d","impliedFormat":99},{"version":"583eb3b56fc84b730641e623070f6eefa3fbacae4e15a6a3f19e1b09c81a7685","impliedFormat":99},{"version":"1c96afc9b678d9f15ec1b045864ea6cff25c134fd594cdddd42d66d99c8663b5","impliedFormat":99},{"version":"cd37c2470a4ac1ad7e1bdbd29bc8953589a4da6a7326a3bfd885bb7b220ffcde","impliedFormat":99},{"version":"ce82368397e5c926481222b9adb2547e4f9e0206982ac71c301284d107e77ebb","impliedFormat":99},{"version":"602a7beeec46d5f481d8f017adc1e01314101dea37f8f9ba3debcf88c83a44d5","impliedFormat":99},{"version":"d4e62d466b6302ad6419cf38669dda1eabe141200b15928a120ec32423904fdb","impliedFormat":99},{"version":"7baac7cf35ef7ef1b049fdfaa215f66891fd677bd617ec7e9a89ca9d430406d1","impliedFormat":99},{"version":"7869a702c21d4fe08180df0919c6365d4662cbdd9e0cbfd17bae7222d10fae58","impliedFormat":99},{"version":"e2944368a3fc08ea1ecde61384f2aea35d8551f02e20373f26f19a1e15f33c01","impliedFormat":99},{"version":"1e13585db16f3406755c1f7a3b32a2003d0b04f83df215fafae61c665de938a1","impliedFormat":99},{"version":"5dbbbf480819904753f84c454eba9bebc455b19966195e97f6b365e7cdcae4b6","impliedFormat":99},{"version":"a96af1a20dbd8aeffb9dc6c2461c7d9d131979390a8b41ca6975f652e2a897a4","impliedFormat":99},{"version":"46f1bf29bd4165fe2bff54eecd23a31f45b1432ab50ba3a0304100697dce91b0","impliedFormat":99},{"version":"1b5cbd26dd659f174a672c6d81b3088c9f46ae0dfd160ce93d2afad5bb7151a6","impliedFormat":99},{"version":"261790ad96cccfe94b94e4825a11a719210f9251d430e05829ed4f0b3d90fe48","impliedFormat":99},{"version":"55eca0dcf8f3192e5906e73aa8c7b88d898b1351bba84c69f4c5c38ab4e43f6f","impliedFormat":99},{"version":"686832a82159a4ea4dd25329c3a99c0a94d9b2a83e7b4ceeb6dfa58bd6707aa6","impliedFormat":99},{"version":"ac339c5b90464b77c2f28dcc686944c9d084f62e680960619f6832471da398ef","impliedFormat":99},{"version":"5090c4c9eb11d488fe455e985891f55e90d65365933d5797b30c188fdbe8015c","impliedFormat":99},{"version":"959c4529d03aebf8f76af7e58797800a25a6a14f560228a69235edcac4e8bfde","impliedFormat":99},{"version":"e893479eb9d99a9c479248a70650f43d3630614ccd0ef07352e208a23988f7cd","impliedFormat":99},{"version":"04d51092411e37e0df07828195cf9e0462861523461eba25eb9dc52344ead7d6","impliedFormat":99},{"version":"a2bf25976a0749263db4e003c2db061217b6e63aaa674f3f2e9dbaeb75f0cf87","impliedFormat":99},{"version":"92bf26f78ac596c9f643b340974854edb633f4719c7d9ad812367883e90272b0","impliedFormat":99},{"version":"afc885e843cf91a18e1c9b674d958c5c71ad3b8d2c9762c6983025d4a30d0918","impliedFormat":99},{"version":"01ff8948288663a825d5d4b5a48fc4dd54adfd4136c00c11d2a01eaf19084340","impliedFormat":99},{"version":"fc61c6168137297ec33f5720835a8f1e7fc424b00e0faab2d24e980dd0bd03c2","impliedFormat":99},{"version":"0b1cdbb2aef2c6ce308de85e80148059f7e16bf727eb3830a2e3e744c4aba6ad","impliedFormat":99},{"version":"1568200178c3ffe44819798eab91cbede3b302ff487149b18aa814a9410645c9","impliedFormat":99},{"version":"3ccf7553bce15ff3fd0b3a0cf4ee9177e9835577aeaeff17c1c6e04a5fa60b4b","impliedFormat":99},{"version":"44182cb6be79f471d6c4a40937a8d6d183af1a87cb26a3bad310c613d006351e","impliedFormat":99},{"version":"261e48dbb899ec4e2de4b9a72b8127a3314d28828b357288bcdebf39be32c126","impliedFormat":99},{"version":"80b3a4c879b86f31611611d628c61546a3924a17609d2abce5af75ab1526bd17","impliedFormat":99},{"version":"69260d8b8cd174b1fa69eb05cad51051da0cb9bad809cf391c1d8234d4b403a6","impliedFormat":99},{"version":"79992568ff244f34b7112942a3d52eb9375885d7e1d8099fc1a17552d14c6fe5","impliedFormat":99},{"version":"dde1fa7a49e820b8801d01bfe9e9b7829d9c62e8eb82a29ef2c28f239aadb5f2","impliedFormat":99},{"version":"e0cd63cf24da521cea60545e5573cbd7eb13d024cca52cc342032e2db987e7b0","impliedFormat":99},{"version":"b1c80011f3a457bbf2e1dec4b6016e63121e60de3b4ff805f261f69016aeada5","impliedFormat":99},{"version":"0bc0511027cff18f7025fdb354850e43083e16d2af2a48da869e095b8df05616","impliedFormat":99},{"version":"70aa4b70c1ebd45e76e4dc63b414d0a7e12fd41ed860da7d9e42737e2a11559b","impliedFormat":99},{"version":"7602bb9744aa13918f0dc9713e4a84e9e97cb099317eaf164c042a636311963d","impliedFormat":99},{"version":"de557ce115571688c6a8dbd486281e4d5a8068ba9c8339ab483014d4aee792a0","impliedFormat":99},{"version":"d942ff22d0bd25a2f26c50d2cc59d6cde2bf7a6cd428e3dd26356033015391ad","impliedFormat":99},{"version":"06e3997ff9c559d21c94113cb949cd03bc44f16cd77c2d7bd42f85c9be29a70f","impliedFormat":99},{"version":"f0029f53c3acc648f4215bc446cd60d92e8c29a8e73cd4008c1e1b37f91eb811","impliedFormat":99},{"version":"b59791581c6df3f62a81a0e96fb3e833a2f5b36304bcb074d765fd79d2fba320","impliedFormat":99},{"version":"627109c27f42261a2148c5758e5529e0694e09ffb74c2cb20e86a7a3682134c5","impliedFormat":99},{"version":"233ab9a64155ad15a070b03a52f8192ace79a29c03a9d4f7cea88929afeb1d45","impliedFormat":99},{"version":"fa7781b202e5845b1609eb84653dfc54189ce4e891909259a9aea73cba9859e9","impliedFormat":99},{"version":"a54b716354950f63c6e8ad298bdc90f94dab416eaecb3d32e9c6dd1bccc79eb0","impliedFormat":99},{"version":"38e6306d5a856a848f6737da319f79b703006b16ced089b2c4e80d04efa58adf","impliedFormat":99},{"version":"0f1434bb4e24cb50e9e73f0144414af44c1610514cb1ba0665936fb4993a3db3","impliedFormat":99},{"version":"1f15564e79f492da5842d1fd5f29cae75d37737e5abb4690b3b4bb9e7e856f3b","impliedFormat":99},{"version":"91d60e5b5441b738133c095fa2d6a809a3c3c4b75556d7f13cc19df48e20de12","impliedFormat":99},{"version":"6beeccd58698b2f72df8cff766e658f4adf98ef39ce1402799d4c18db1b2d803","impliedFormat":99},{"version":"980b920f853e57fd46983d0f5a2e7164f2d2e9dac780aa04d4325aa235c0a75e","impliedFormat":99},{"version":"3a39f3263b853937eca381775fb7558b4867a2714a89d770ea98c30dd1fadaa7","impliedFormat":99},{"version":"ced0b20e13a057c20d802a11870ff310579c6e80a5e5e45166de9e5f0e88f8d3","impliedFormat":99},{"version":"62f73fab1b97e50ee8092499cd5a8b5085abf700b372c24aa53e1879a3d68979","impliedFormat":99},{"version":"a393c298d0678e3e0b610c1069e3721de31dcdf1f885d6ecd813b837330156aa","impliedFormat":99},{"version":"e4784320828932df26b306ec4839c8f9496b959173179a29b29163efe301a7b6","impliedFormat":99},{"version":"5dbb45a20780dd90e28869e05035e681c90afa007364f01c3c3cf14c4cc04467","impliedFormat":99},{"version":"c9e1e15b8ab027dbbe4a2d85c0669905497e0d55a09c702f06c79e57645e347b","impliedFormat":99},{"version":"4078ccf4490cf97463a7ff2f9e02ff30a36ed2e4b18e8566445838d0d4d603a3","impliedFormat":99},{"version":"727d10cd972ba9b52da4defa702f65739323f9ff9949a7e9c7bd55fa76c37ac5","impliedFormat":99},{"version":"9420bd92184bda15c9a04daf532afae98df4fbbb17a930983ca664e929fe6c6a","impliedFormat":99},{"version":"684ad94c713c5f22951e9f433d52786c8508447510005459b6d9764c75cd2c74","impliedFormat":99},{"version":"3e00971f3dcbbe00de774d5387485454c98b8873bb636b8e6bcd4d9915ee0a2d","impliedFormat":99},{"version":"931674e02ae4a041ad822756dd4dbd30236bb3b52e21201aabe678b734baedbf","impliedFormat":99},{"version":"940961ea4bd1da98cef1edca26d6380eeb0c3e06df2e9d7a29dcb57303fb0326","impliedFormat":99},{"version":"581f0278df053736226f58d9a4671e2bdba6508f8be23751247c846e82736a0a","impliedFormat":99},{"version":"ff7eebe9610f96732e29782294ee55529ca5ff804e2325ba992cab0c2ca4c05f","impliedFormat":99},{"version":"e0138d7c2408df5bcc623e3e3b496ffbc405421c7dd7ae6ecf897a657926e2c4","impliedFormat":99},{"version":"18bbffdecf7eed3cefa4a37f34dc879050e629231ba0bd011e2ec6f7503a6cfa","impliedFormat":99},{"version":"fd2e67b97e27962b7c2a6083a2ebf936e7dc2b2335286df2ef5cfc09eb8522b9","impliedFormat":99},{"version":"2e1ccba239f8919e62f83719bc95b4d47b1d1e0187cc6968b5481c86dbbffe3c","impliedFormat":99},{"version":"983bd1482518c10b3e7549f6fe3f51cb7b9868f38ea715cc0c19c8ad6c5c34ca","impliedFormat":99},{"version":"728cf2a025de44eb3e43b3e87d9c8bb2feafe8a34de5ba9b2158317b265125f0","impliedFormat":99},{"version":"54977f121205ea7c7a6c86b42d813dc55d7db6e69c9554a262fc64cae0c7eacf","impliedFormat":99},{"version":"2f94e898529d71f48c841206140ac13988a5ab275bddf852922ad75b7021537d","impliedFormat":99},{"version":"d27f8ee9a610635b566109a359d00923f7e8f286ab7936894671c39a006e8b1a","impliedFormat":99},{"version":"5822d798e08ca6c321c2e5deb337e3b6d6472f2133440c4a2468f0ddee71ab75","signature":"f3688333654359ade219d4e94fb2a91f12339e46c19e03e451714a21e577435c"},{"version":"8db3f96517a120bade1643b55b24ed75a18970f4d38221402d9eccdbb5106fdb","signature":"f9a16b54b8913022325e94f69c48805664e20b6556089e3f3506306b97fe129e"},{"version":"e34e93f5498ed7bc9c53a9ee97d679f90fb941b9ab4190b5ba0747184ef12ad3","signature":"6411762007a15dd690acee3a833c9d06863ca69119d48bca9527fe1f81789e17"},{"version":"012e4aced863fe99eee38c5f48f5d3e79b2afbc9aaa56bef5daadd5ee6464b39","signature":"4c73137788f0bbedcc9e0442183a89a07c3b30303ebc069e6ab02ced95c249f7"},{"version":"e94284c314780e4dcde4b439901c6571045dcf62d3b0cf4d5dedd66ad196c0f2","signature":"a0cee837e5373cf7095c21e187a4580b8216a64ecd64aa80d19a6967e6d1bb59"},{"version":"e7434e5f90aaa3c85f4658f7e3d3dbe8fc90d286ec4338c282f90d3a6bda0e60","signature":"006d76e50e7f1e25a37da6d436aad571aeb2d646c47f833f9bff146080c9605d"},{"version":"f5c96a4ee0e61f5664d81620d619f5db8aee48c11dd320d767701570438cf0f4","signature":"52a7d02b1fe8db0b42f5259d721b5c7f6bea6d862b8c5ba4331b7169d058305f"},{"version":"65c1e989444a2f3f3f925187ac087ef5de0a880944945644a597a895057a5f1f","signature":"652b463ed1eaf99f278d254973cffb72ea7ef2e76ed166857abe2161fc04d04c"},{"version":"5a2672453bf725ec5acb835ae4d14225718773d493b82a18ddd09c0f1837399c","signature":"8e609a09273295280918847c8f011e60b4c35d8d6591355f4e803712f5f8d6c3"},{"version":"669951a67a7ba7d379550ef78843a23e40062cf93d4a4baad1fad7018df953e7","signature":"d630c6a442d5c14e074bace5418c267369eda92796da78edb6554cdaf7d17da4"},{"version":"6811d9d73b3cf2c48eff214d1506dd33481cbdcf2037ee71467348f18d044357","signature":"cafc2d90a1946bf6b9447683f11a384aeca03cb10c38b8945e4ceb287f8a97fb"},{"version":"5442b92ed2b5d6f2fa515b8df5c198aaf5b244babf631bbadefa1226284ea7fb","signature":"2864072f8edbbc54e60c45c4d397ec8b00fc21f2524747065c5da1814d77d30a"},{"version":"64e4a36aabd778ea468c12dba55fb5990d2c711b295f1b580c6c859e667492d0","signature":"404049eff8654148369e62fb3e430ac039e762b0f5e25f1aeec5b5c35499710e"},{"version":"002a6f95435929fb339cf9495f6faecc314a30fc0b434f08b7ece78e5152339f","signature":"b46a7be2bec9c094170bd64ff5e51062de78828862893ccb070c57c58948abf4"},{"version":"3a32529dc96bdcfe52cb9331b2cce36d8e92b1164a34ceb4a7443359aec49061","signature":"b0f87819f7baf6659bf4531fcf0687f9333c26d56bf86c00a5162165d68a2aeb"},{"version":"9628ac25df4cabcb3d7510fb0c46609851fd475327917bbedcd2b4f258657212","signature":"a0ad37ab2c1f847fe8bb1e9bdfef18ee4719429f1ed808f8aa543d363f23069a"},{"version":"fb7a0827a700b2998bc668b649c22d0cb606ff9557b0990672449e9f518fda72","signature":"e034180f88ee9195af896f639e7cee117b277e3b765995424f8991b3f6b4dda2"},{"version":"d6bc387f53aaf1f3d8cb4c5ea4e4c84865e4fa26d42472ee446bac50e0f7125b","signature":"b871a827a3198ec8b0e0281e1a5dad54d3a3cd2b319f4fd162d55c35833d91f8"},{"version":"ab596340e3c0d463ad4207a8c70118746d30d011de74dc13cd576754169b3b8f","signature":"842f5c6f3899cd852c58d006876c188b06c9093b7372f967948198882a9c32ba"},{"version":"f56f0523d9dfee6fcddcb10d3a6429ba50954f50ec0df71e0ce11e3e7f3ee200","signature":"c261639266a388d9118c5d2c16c6d68f83f9631c30aceb6898dfb173194ab716"},{"version":"bd7c206bd2bc79d129d22f89abdf25968fa171c6956ac9d0a92c0bdf3d3c04b7","signature":"9ff2309cbe37c21de662b3cfffc8755c3620f1b604e32454506cd2da292fa127"},{"version":"a28ac3e717907284b3910b8e9b3f9844a4e0b0a861bea7b923e5adf90f620330","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"82e5a50e17833a10eb091923b7e429dc846d42f1c6161eb6beeb964288d98a15","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0671b50bb99cc7ad46e9c68fa0e7f15ba4bc898b59c31a17ea4611fab5095da","affectsGlobalScope":true,"impliedFormat":1},{"version":"d802f0e6b5188646d307f070d83512e8eb94651858de8a82d1e47f60fb6da4e2","affectsGlobalScope":true,"impliedFormat":1},{"version":"aa83e100f0c74a06c9d24f40a096c9e9cc3c02704250d01541e22c0ae9264eda","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"3c8e93af4d6ce21eb4c8d005ad6dc02e7b5e6781f429d52a35290210f495a674","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"00b21ef538da5a2bbe419e2144f3be50661768e1e039ef2b57bb89f96aff9b18","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"e843e840f484f7e59b2ef9488501a301e3300a8e3e56aa84a02ddf915c7ce07d","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"78b29846349d4dfdd88bd6650cc5d2baaa67f2e89dc8a80c8e26ef7995386583","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"18f8cfbb14ba9405e67d30968ae67b8d19133867d13ebc49c8ed37ec64ce9bdb","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"830171b27c5fdf9bcbe4cf7d428fcf3ae2c67780fb7fbdccdf70d1623d938bc4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"f68328826a275104d92bd576c796c570f66365f25ea8bbaaa208727bce132d5f","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"18334defc3d0a0e1966f5f3c23c7c83b62c77811e51045c5a7ff3883b446f81f","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b17fcd63aa13734bf1d01419f4d6031b1c6a5fb2cbdb45e9839fb1762bdf0df","impliedFormat":1},{"version":"c4e8e8031808b158cfb5ac5c4b38d4a26659aec4b57b6a7e2ba0a141439c208c","impliedFormat":1},{"version":"2c91d8366ff2506296191c26fd97cc1990bab3ee22576275d28b654a21261a44","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"beb77fcd86c8cee62c32b2fb82753f5bc0e171d938e20af3cb0b8925db78d60b","impliedFormat":1},{"version":"289e9894a4668c61b5ffed09e196c1f0c2f87ca81efcaebdf6357cfb198dac14","impliedFormat":1},{"version":"25a1105595236f09f5bce42398be9f9ededc8d538c258579ab662d509aa3b98e","impliedFormat":1},{"version":"aa9224557befad144262c85b463c0a7ba8a3a0ad2a7c907349f8bb8bc3fe4abc","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c","impliedFormat":1},{"version":"8d86c8d8c43e04cc3dde9953e571656812c8964a3651203af7b3a1df832a34df","affectsGlobalScope":true,"impliedFormat":1},{"version":"0121911fcc364eb821d058cf4c3b9339f197eccbe298098a4c6be0396b607d90","impliedFormat":1},{"version":"c6176c7b9f3769ba7f076c7a791588562c653cc0ba08fb2184f87bf78db2a87c","impliedFormat":1},{"version":"6def204d0b267101d3a42300a7363f53406c5d86b932e76e2365bf89689a85c4","impliedFormat":1},{"version":"4f766affd1281935fe5f7fd5d7af693a7c26d81adef7c1aefb84b9cd573a9cbb","impliedFormat":1},{"version":"165a0c1f95bc939c72f18a280fc707fba6f2f349539246b050cfc09eb1d9f446","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"c0bb1b65757c72bbf8ddf7eaa532223bacf58041ff16c883e76f45506596e925","impliedFormat":1},{"version":"c8b85f7aed29f8f52b813f800611406b0bfe5cf3224d20a4bdda7c7f73ce368e","affectsGlobalScope":true,"impliedFormat":1},{"version":"7baae9bf5b50e572e7742c886c73c6f8fa50b34190bc5f0fd20dd7e706fda832","impliedFormat":1},{"version":"e99b0e71f07128fc32583e88ccd509a1aaa9524c290efb2f48c22f9bf8ba83b1","impliedFormat":1},{"version":"76957a6d92b94b9e2852cf527fea32ad2dc0ef50f67fe2b14bd027c9ceef2d86","impliedFormat":1},{"version":"5e9f8c1e042b0f598a9be018fc8c3cb670fe579e9f2e18e3388b63327544fe16","affectsGlobalScope":true,"impliedFormat":1},{"version":"a8a99a5e6ed33c4a951b67cc1fd5b64fd6ad719f5747845c165ca12f6c21ba16","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4","impliedFormat":1},{"version":"70b57b5529051497e9f6482b76d91c0dcbb103d9ead8a0549f5bab8f65e5d031","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"1013eb2e2547ad8c100aca52ef9df8c3f209edee32bb387121bb3227f7c00088","impliedFormat":1},{"version":"b827f8800f42858f0a751a605c003b7ab571ff7af184436f36cef9bdfebae808","impliedFormat":1},{"version":"363eedb495912790e867da6ff96e81bf792c8cfe386321e8163b71823a35719a","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"125d792ec6c0c0f657d758055c494301cc5fdb327d9d9d5960b3f129aff76093","impliedFormat":1},{"version":"6b306cd4282bbb54d4a6bb23cfb7a271160983dfc38c67b5a132504cfcc34896","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea713aa14a670b1ea0fbaaca4fd204e645f71ca7653a834a8ec07ee889c45de6","impliedFormat":1},{"version":"450172a56b944c2d83f45cc11c9a388ea967cd301a21202aa0a23c34c7506a18","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"3af7d02e5d6ecbf363e61fb842ee55d3518a140fd226bdfb24a3bca6768c58df","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"7dfa742c23851808a77ec27062fbbd381c8c36bb3cfdff46cb8af6c6c233bfc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb078cfcd14dc0b1700a48272958f803f30f13f99111c5978c75c3a0aa07e40e","affectsGlobalScope":true,"impliedFormat":1},{"version":"784490137935e1e38c49b9289110e74a1622baf8a8907888dcbe9e476d7c5e44","impliedFormat":1},{"version":"420fdd37c51263be9db3fcac35ffd836216c71e6000e6a9740bb950fb0540654","impliedFormat":1},{"version":"73b0bff83ee76e3a9320e93c7fc15596e858b33c687c39a57567e75c43f2a324","impliedFormat":1},{"version":"3c947600f6f5664cca690c07fcf8567ca58d029872b52c31c2f51d06fbdb581b","affectsGlobalScope":true,"impliedFormat":1},{"version":"493c64d062139b1849b0e9c4c3a6465e1227d2b42be9e26ec577ca728984c041","impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","impliedFormat":1}],"root":[[250,257],[406,426]],"options":{"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"declarationMap":false,"esModuleInterop":true,"jsx":4,"module":99,"outDir":"./lib-dist","rootDir":"./lib","skipLibCheck":true,"sourceMap":false,"strict":true,"target":7},"referencedMap":[[257,1],[414,2],[416,3],[413,4],[417,5],[418,6],[421,7],[423,8],[256,9],[255,10],[409,11],[424,12],[407,1],[253,13],[425,10],[415,10],[254,13],[426,14],[252,15],[410,16],[411,17],[420,18],[422,19],[406,20],[419,21],[408,21],[250,22],[251,23],[412,24],[249,25],[248,26],[155,27],[156,28],[157,29],[158,30],[159,31],[160,32],[161,33],[162,34],[164,35],[165,36],[166,37],[167,38],[168,39],[169,40],[170,41],[171,42],[172,43],[173,44],[174,45],[175,46],[176,47],[177,48],[178,49],[179,50],[180,51],[181,52],[182,43],[183,53],[184,54],[185,55],[187,56],[186,43],[189,57],[190,58],[188,43],[191,59],[192,60],[193,61],[194,62],[195,63],[196,64],[197,65],[163,43],[198,66],[199,67],[200,68],[201,69],[202,70],[203,71],[204,72],[205,73],[206,74],[207,75],[154,43],[208,76],[209,77],[210,78],[211,79],[212,80],[213,81],[214,82],[215,83],[216,84],[217,85],[218,86],[219,87],[220,88],[221,89],[222,90],[223,91],[224,43],[225,92],[226,93],[227,94],[228,95],[229,96],[230,97],[231,98],[232,99],[233,100],[234,101],[235,102],[236,103],[237,104],[238,105],[239,106],[240,107],[241,108],[242,109],[243,110],[244,111],[245,112],[246,113],[247,114],[405,115],[400,116],[271,117],[272,118],[273,119],[274,120],[275,121],[276,122],[277,123],[278,124],[279,125],[280,126],[281,127],[282,128],[283,129],[284,130],[261,43],[258,43],[262,131],[266,132],[264,133],[263,131],[268,134],[285,135],[286,136],[287,137],[288,138],[289,139],[290,140],[291,141],[292,142],[293,143],[294,144],[295,145],[297,146],[296,147],[298,148],[299,149],[300,150],[301,151],[302,152],[303,153],[259,154],[304,155],[305,156],[306,157],[260,158],[307,159],[269,160],[308,161],[309,162],[310,163],[311,164],[312,165],[313,166],[314,167],[315,168],[316,169],[317,170],[318,171],[319,172],[320,173],[321,174],[322,175],[323,176],[324,177],[325,178],[326,179],[327,180],[328,181],[329,182],[330,183],[331,184],[332,185],[333,186],[334,187],[335,188],[336,189],[265,190],[337,191],[338,192],[339,193],[270,43],[340,194],[341,195],[342,196],[343,197],[344,198],[345,199],[346,200],[347,201],[348,202],[349,203],[267,43],[350,204],[351,205],[352,206],[353,207],[354,208],[355,209],[356,210],[357,211],[358,212],[359,213],[360,214],[361,215],[362,216],[363,217],[364,218],[365,219],[366,220],[367,221],[368,222],[369,223],[370,224],[371,43],[372,225],[373,226],[374,227],[375,228],[376,229],[377,230],[378,231],[379,232],[381,233],[380,234],[382,235],[383,236],[384,237],[385,238],[386,239],[387,240],[388,241],[389,242],[390,243],[391,244],[392,245],[393,246],[394,247],[395,248],[396,249],[397,250],[398,251],[399,252],[401,253],[402,254],[403,254],[404,254],[77,255],[78,256],[76,257],[79,258],[90,259],[91,259],[92,259],[93,259],[98,260],[94,261],[95,261],[96,261],[97,261],[99,262],[89,263],[84,260],[88,264],[85,265],[86,260],[87,260],[82,266],[81,265],[83,267],[75,257],[74,268],[67,269],[68,257],[66,270],[72,271],[56,272],[60,273],[61,257],[62,257],[54,257],[55,257],[71,274],[63,257],[57,257],[58,257],[64,257],[65,257],[69,257],[59,257],[73,275],[153,276],[152,277],[100,278],[101,279],[102,280],[103,281],[104,282],[105,283],[106,284],[107,285],[108,286],[109,287],[110,288],[111,289],[112,290],[113,291],[114,292],[115,293],[116,294],[117,295],[118,296],[119,297],[120,298],[121,299],[122,300],[123,301],[124,302],[125,303],[126,304],[127,305],[128,306],[129,307],[130,308],[131,309],[132,43],[133,43],[134,310],[135,311],[136,312],[137,313],[138,314],[139,315],[140,316],[141,317],[142,43],[143,43],[144,43],[145,318],[146,319],[147,320],[148,321],[149,322],[150,323],[151,324],[429,325],[427,257],[80,257],[70,257],[432,326],[428,325],[430,327],[431,325],[433,257],[434,257],[488,328],[489,328],[490,329],[437,330],[491,331],[492,332],[493,333],[435,257],[494,334],[495,335],[496,336],[497,337],[498,338],[499,339],[500,339],[501,340],[502,341],[503,342],[504,343],[438,257],[436,257],[505,344],[506,345],[507,346],[541,347],[508,348],[509,257],[510,349],[511,350],[512,351],[513,352],[514,353],[515,354],[516,355],[517,356],[518,357],[519,357],[520,358],[521,257],[522,257],[523,359],[525,360],[524,361],[526,362],[527,363],[528,364],[529,365],[530,366],[531,367],[532,368],[533,369],[534,370],[535,371],[536,372],[537,373],[538,374],[439,257],[440,375],[441,257],[442,257],[484,376],[485,377],[486,257],[487,362],[539,378],[540,379],[542,380],[50,257],[52,381],[53,380],[51,257],[48,257],[49,257],[8,257],[9,257],[11,257],[10,257],[2,257],[12,257],[13,257],[14,257],[15,257],[16,257],[17,257],[18,257],[19,257],[3,257],[20,257],[21,257],[4,257],[22,257],[26,257],[23,257],[24,257],[25,257],[27,257],[28,257],[29,257],[5,257],[30,257],[31,257],[32,257],[33,257],[6,257],[37,257],[34,257],[35,257],[36,257],[38,257],[7,257],[39,257],[44,257],[45,257],[40,257],[41,257],[42,257],[43,257],[1,257],[46,257],[47,257],[460,382],[472,383],[458,384],[473,385],[482,386],[449,387],[450,388],[448,389],[481,390],[476,391],[480,392],[452,393],[469,394],[451,395],[479,396],[446,397],[447,391],[453,398],[454,257],[459,399],[457,398],[444,400],[483,401],[474,402],[463,403],[462,398],[464,404],[467,405],[461,406],[465,407],[477,390],[455,408],[456,409],[468,410],[445,385],[471,411],[470,398],[466,412],[475,257],[443,257],[478,413]],"latestChangedDtsFile":"./lib-dist/renderers/LeafletDocumentRenderer.d.ts","version":"5.9.3"}
+2 -1
tsconfig.node.json
··· 12 12 "allowImportingTsExtensions": true, 13 13 "verbatimModuleSyntax": true, 14 14 "moduleDetection": "force", 15 - "noEmit": true, 15 + "declaration": true, 16 + "emitDeclarationOnly": true, 16 17 17 18 /* Linting */ 18 19 "strict": true,
+2
vite.config.d.ts
··· 1 + declare const _default: import("vite").UserConfig; 2 + export default _default;
+73 -4
vite.config.ts
··· 1 - import { defineConfig } from 'vite' 2 - import react from '@vitejs/plugin-react' 1 + import { defineConfig } from 'vite'; 2 + import react from '@vitejs/plugin-react'; 3 + import dts from 'unplugin-dts/vite' 4 + import { resolve } from 'path'; 5 + import type { Plugin } from 'vite'; 6 + 7 + // Plugin to inject CSS import as a side effect in the main entry 8 + function injectCssImport(): Plugin { 9 + return { 10 + name: 'inject-css-import', 11 + generateBundle(_, bundle) { 12 + const indexFile = bundle['index.js']; 13 + if (indexFile && indexFile.type === 'chunk') { 14 + // Inject the CSS import at the top of the file 15 + indexFile.code = `import './styles.css';\n${indexFile.code}`; 16 + } 17 + } 18 + }; 19 + } 20 + 21 + const buildDemo = process.env.BUILD_TARGET === 'demo'; 3 22 4 23 // https://vite.dev/config/ 5 24 export default defineConfig({ 6 - plugins: [react()], 7 - }) 25 + plugins: buildDemo 26 + ? [react()] 27 + : [react(), dts({ tsconfigPath: './tsconfig.lib.json' }), injectCssImport()], 28 + 29 + // Demo app needs to resolve from src 30 + root: buildDemo ? '.' : undefined, 31 + 32 + build: buildDemo ? { 33 + // Demo app build configuration 34 + outDir: 'demo', 35 + rollupOptions: { 36 + input: resolve(__dirname, 'index.html') 37 + }, 38 + sourcemap: false 39 + } : { 40 + // Library build configuration 41 + lib: { 42 + entry: resolve(__dirname, 'lib/index.ts'), 43 + cssFileName: resolve(__dirname, 'lib/styles.css'), 44 + name: 'atproto-ui', 45 + formats: ['es'], 46 + fileName: 'atproto-ui' 47 + }, 48 + cssCodeSplit: false, 49 + outDir: 'lib-dist', 50 + rollupOptions: { 51 + // Externalize dependencies that shouldn't be bundled 52 + external: [ 53 + 'react', 54 + 'react-dom', 55 + 'react/jsx-runtime', 56 + '@atcute/atproto', 57 + '@atcute/bluesky', 58 + '@atcute/client', 59 + '@atcute/identity-resolver', 60 + '@atcute/tangled' 61 + ], 62 + output: { 63 + preserveModules: true, 64 + preserveModulesRoot: 'lib', 65 + entryFileNames: '[name].js', 66 + assetFileNames: (assetInfo) => { 67 + // Output CSS to root of lib-dist as styles.css 68 + if (assetInfo.name && assetInfo.name.endsWith('.css')) { 69 + return 'styles.css'; 70 + } 71 + return 'assets/[name][extname]'; 72 + } 73 + } 74 + }, 75 + } 76 + });