+3
-1
.gitignore
+3
-1
.gitignore
+22
.npmignore
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
-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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
-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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
-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
+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
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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+3
tsconfig.app.json
+6
-3
tsconfig.lib.json
+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
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
+2
-1
tsconfig.node.json
+2
vite.config.d.ts
+2
vite.config.d.ts
+73
-4
vite.config.ts
+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
+
});