create-sia-app 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -6
- package/template/AGENTS.md +143 -0
- package/template/CLAUDE.md +25 -46
- package/template/README.md +6 -12
- package/template/_gitignore +0 -1
- package/template/dist/assets/index-BEylY2j7.css +1 -0
- package/template/dist/assets/index-CnYqArKN.js +8741 -0
- package/template/dist/assets/sia_bg-BTOHUC1A.wasm +0 -0
- package/template/dist/assets/slab-download-worker-DhW6ZBJs.js +2 -0
- package/template/dist/assets/slab-upload-worker-B2uSB2iY.js +2 -0
- package/template/dist/index.html +13 -0
- package/template/e2e/smoke.spec.ts +20 -0
- package/template/index.html +0 -1
- package/template/package.json +2 -2
- package/template/playwright.config.ts +13 -0
- package/template/src/components/Navbar.tsx +3 -3
- package/template/src/components/auth/ApproveScreen.tsx +10 -13
- package/template/src/components/auth/AuthFlow.tsx +13 -10
- package/template/src/components/auth/ConnectScreen.tsx +2 -3
- package/template/src/components/auth/RecoveryScreen.tsx +7 -7
- package/template/src/components/upload/UploadZone.tsx +192 -74
- package/template/src/index.css +14 -4
- package/template/src/stores/auth.ts +6 -12
- package/template/test-results/.last-run.json +4 -0
- package/template/tsconfig.app.json +1 -1
- package/template/tsconfig.node.json +0 -1
- package/template/vite.config.ts +2 -3
- package/template/rust/README.md +0 -16
- package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +0 -6
- package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +0 -16
- package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +0 -5
- package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +0 -13
- package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +0 -5
- package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +0 -5
- package/template/rust/sia-sdk-rs/.github/dependabot.yml +0 -10
- package/template/rust/sia-sdk-rs/.github/workflows/main.yml +0 -36
- package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +0 -34
- package/template/rust/sia-sdk-rs/.github/workflows/release.yml +0 -30
- package/template/rust/sia-sdk-rs/.rustfmt.toml +0 -4
- package/template/rust/sia-sdk-rs/Cargo.lock +0 -4127
- package/template/rust/sia-sdk-rs/Cargo.toml +0 -3
- package/template/rust/sia-sdk-rs/LICENSE +0 -21
- package/template/rust/sia-sdk-rs/README.md +0 -30
- package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +0 -79
- package/template/rust/sia-sdk-rs/indexd/Cargo.toml +0 -79
- package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +0 -258
- package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +0 -1710
- package/template/rust/sia-sdk-rs/indexd/src/builder.rs +0 -354
- package/template/rust/sia-sdk-rs/indexd/src/download.rs +0 -379
- package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +0 -659
- package/template/rust/sia-sdk-rs/indexd/src/lib.rs +0 -827
- package/template/rust/sia-sdk-rs/indexd/src/mock.rs +0 -162
- package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +0 -125
- package/template/rust/sia-sdk-rs/indexd/src/quic.rs +0 -575
- package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +0 -52
- package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +0 -497
- package/template/rust/sia-sdk-rs/indexd/src/upload.rs +0 -629
- package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +0 -41
- package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +0 -398
- package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +0 -76
- package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +0 -47
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +0 -10
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +0 -130
- package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +0 -3
- package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +0 -377
- package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +0 -155
- package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +0 -1039
- package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +0 -58
- package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +0 -23
- package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +0 -33
- package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +0 -818
- package/template/rust/sia-sdk-rs/knope.toml +0 -54
- package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +0 -38
- package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +0 -19
- package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +0 -278
- package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +0 -91
- package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +0 -59
- package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +0 -12
- package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +0 -22
- package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +0 -767
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +0 -257
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +0 -291
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +0 -26
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +0 -367
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +0 -6
- package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +0 -303
- package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +0 -347
- package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +0 -15
- package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +0 -435
- package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +0 -112
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +0 -357
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +0 -1507
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +0 -146
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +0 -7
- package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +0 -278
- package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +0 -236
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +0 -677
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +0 -450
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +0 -110
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +0 -778
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +0 -117
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +0 -1737
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +0 -1726
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +0 -59
- package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +0 -16
- package/template/scripts/setup-rust.js +0 -29
- package/template/src/lib/format.ts +0 -35
- package/template/src/lib/hex.ts +0 -13
- package/template/src/lib/sdk.ts +0 -25
- package/template/src/lib/wasm-env.ts +0 -5
- package/template/wasm/indexd_wasm/indexd_wasm.d.ts +0 -309
- package/template/wasm/indexd_wasm/indexd_wasm.js +0 -1507
- package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
- package/template/wasm/indexd_wasm/package.json +0 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-sia-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-sia-app": "./dist/index.js"
|
|
@@ -11,11 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsup src/index.ts --format esm --dts --clean && rm -rf ./template && cp -r ../../template ./template && rm -rf ./template/node_modules",
|
|
14
|
-
"dev": "tsup src/index.ts --format esm --watch"
|
|
15
|
-
"release:patch": "node -e \"const p=require('./package.json');const v=p.version.split('.');v[2]++;p.version=v.join('.');require('fs').writeFileSync('./package.json',JSON.stringify(p,null,2)+'\\n')\" && bun run release:push",
|
|
16
|
-
"release:minor": "node -e \"const p=require('./package.json');const v=p.version.split('.');v[1]++;v[2]=0;p.version=v.join('.');require('fs').writeFileSync('./package.json',JSON.stringify(p,null,2)+'\\n')\" && bun run release:push",
|
|
17
|
-
"release:major": "node -e \"const p=require('./package.json');const v=p.version.split('.');v[0]++;v[1]=0;v[2]=0;p.version=v.join('.');require('fs').writeFileSync('./package.json',JSON.stringify(p,null,2)+'\\n')\" && bun run release:push",
|
|
18
|
-
"release:push": "git add package.json && git commit -m \"v$(node -p 'require(\"./package.json\").version')\" && git tag v$(node -p 'require(\"./package.json\").version') && git push && git push --tags"
|
|
14
|
+
"dev": "tsup src/index.ts --format esm --watch"
|
|
19
15
|
},
|
|
20
16
|
"dependencies": {
|
|
21
17
|
"@clack/prompts": "^0.10.0",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Sia Starter — AI Assistant Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A starter template for building decentralized storage apps on the Sia network. Uses `@siafoundation/sia` — a TypeScript SDK that ships a pre-compiled WASM binary for encryption, uploads, downloads, and key management.
|
|
6
|
+
|
|
7
|
+
**Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand, `@siafoundation/sia`
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
### Auth Flow State Machine
|
|
12
|
+
|
|
13
|
+
The app uses a step-based auth flow managed by Zustand (`src/stores/auth.ts`):
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
loading → connect → approve → recovery → connected
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **loading**: WASM initializes, checks for stored app key
|
|
20
|
+
- **connect**: User enters indexer URL, app requests connection
|
|
21
|
+
- **approve**: User visits approval URL in another tab
|
|
22
|
+
- **recovery**: User generates or enters 12-word recovery phrase
|
|
23
|
+
- **connected**: SDK is ready, main app UI renders
|
|
24
|
+
|
|
25
|
+
### SDK
|
|
26
|
+
|
|
27
|
+
The SDK (`@siafoundation/sia`) is an npm package that handles:
|
|
28
|
+
- Encrypted file uploads/downloads (erasure coding + encryption)
|
|
29
|
+
- Key derivation from recovery phrases (BIP-39)
|
|
30
|
+
- Object pinning and metadata management
|
|
31
|
+
- Connection auth with indexers
|
|
32
|
+
- Parallel multi-worker uploads and downloads
|
|
33
|
+
|
|
34
|
+
### Zustand Persistence
|
|
35
|
+
|
|
36
|
+
Auth state persists to localStorage via Zustand's `persist` middleware. The storage key is `{app-name}-auth`. Persisted fields: `storedKeyHex`, `indexerUrl`.
|
|
37
|
+
|
|
38
|
+
## Key Files
|
|
39
|
+
|
|
40
|
+
| File | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `src/lib/constants.ts` | App key, app name, indexer URL, app metadata |
|
|
43
|
+
| `src/stores/auth.ts` | Auth state machine (Zustand + persist) |
|
|
44
|
+
| `src/stores/toast.ts` | Toast notification store (auto-dismiss) |
|
|
45
|
+
| `src/components/Navbar.tsx` | App navbar with title, public key, sign out |
|
|
46
|
+
| `src/components/Toast.tsx` | Toast overlay component |
|
|
47
|
+
| `src/components/CopyButton.tsx` | Copy-to-clipboard button with toast |
|
|
48
|
+
| `src/components/auth/AuthFlow.tsx` | Auth orchestrator |
|
|
49
|
+
| `src/components/auth/ConnectScreen.tsx` | Indexer connection + CORS fallback |
|
|
50
|
+
| `src/components/auth/ApproveScreen.tsx` | Approval polling (auto-polls on mount) |
|
|
51
|
+
| `src/components/auth/RecoveryScreen.tsx` | Recovery phrase generation/import |
|
|
52
|
+
| `src/components/upload/UploadZone.tsx` | File upload/download dropzone + file list |
|
|
53
|
+
| `src/components/DevNote.tsx` | Developer callout component (unused, available for customization) |
|
|
54
|
+
|
|
55
|
+
## SDK Usage Patterns
|
|
56
|
+
|
|
57
|
+
### Upload a file
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const pinnedObject = await client.upload(file, (progress) => {
|
|
61
|
+
// progress.phase: 'connecting' | 'uploading' | 'assembling' | 'pinning'
|
|
62
|
+
console.log(`${progress.slabsComplete}/${progress.slabsTotal} slabs`)
|
|
63
|
+
})
|
|
64
|
+
pinnedObject.updateMetadata(new TextEncoder().encode(JSON.stringify({
|
|
65
|
+
name: 'file.txt',
|
|
66
|
+
type: 'text/plain',
|
|
67
|
+
size: file.size,
|
|
68
|
+
hash: '...',
|
|
69
|
+
})))
|
|
70
|
+
await client.pinObject(pinnedObject)
|
|
71
|
+
await client.updateObjectMetadata(pinnedObject)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Download a file
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const data = await client.download(pinnedObject, (progress) => {
|
|
78
|
+
// progress.phase: 'connecting' | 'downloading' | 'assembling'
|
|
79
|
+
console.log(`${progress.slabsComplete}/${progress.slabsTotal} slabs`)
|
|
80
|
+
})
|
|
81
|
+
// data is Uint8Array
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### List files
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { decodeMetadata } from '@siafoundation/sia'
|
|
88
|
+
|
|
89
|
+
const events = await client.objectEvents(null, 100)
|
|
90
|
+
for (const event of events) {
|
|
91
|
+
if (!event.deleted && event.object) {
|
|
92
|
+
const meta = decodeMetadata(event.object.metadata())
|
|
93
|
+
console.log(meta.name, event.object.size())
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Delete a file
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
await client.deleteObject(objectId)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Share a file
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const validUntil = Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
108
|
+
const shareUrl = client.shareObject(pinnedObject, validUntil)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Download shared file
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const object = await client.sharedObject(shareUrl)
|
|
115
|
+
const data = await client.download(object)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Customization
|
|
119
|
+
|
|
120
|
+
### Change app key
|
|
121
|
+
|
|
122
|
+
Edit `src/lib/constants.ts`. The app key is a 32-byte hex string that identifies your app to the indexer. Generate one with:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
crypto.randomBytes(32).toString('hex')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Replace the upload UI
|
|
129
|
+
|
|
130
|
+
The post-auth UI is rendered in `src/App.tsx`. Replace `<UploadZone />` with your own component. The `SiaClient` is available via `useAuthStore((s) => s.client)`. `SiaClient` exposes all SDK methods directly — use `client.upload()`, `client.download()`, `client.objectEvents()`, etc.
|
|
131
|
+
|
|
132
|
+
### Add routes
|
|
133
|
+
|
|
134
|
+
Install `react-router-dom` and wrap your app. The auth flow should gate all routes.
|
|
135
|
+
|
|
136
|
+
## Common Commands
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
bun install # Install dependencies
|
|
140
|
+
bun dev # Start dev server
|
|
141
|
+
bun run build # Production build
|
|
142
|
+
bun run check # Lint with Biome
|
|
143
|
+
```
|
package/template/CLAUDE.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
A starter template for building decentralized storage apps on the Sia network. Uses a
|
|
5
|
+
A starter template for building decentralized storage apps on the Sia network. Uses `@siafoundation/sia` — a TypeScript SDK that ships a pre-compiled WASM binary for encryption, uploads, downloads, and key management.
|
|
6
6
|
|
|
7
|
-
**Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand,
|
|
7
|
+
**Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand, `@siafoundation/sia`
|
|
8
8
|
|
|
9
9
|
## Architecture
|
|
10
10
|
|
|
@@ -22,15 +22,14 @@ loading → connect → approve → recovery → connected
|
|
|
22
22
|
- **recovery**: User generates or enters 12-word recovery phrase
|
|
23
23
|
- **connected**: SDK is ready, main app UI renders
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### SDK
|
|
26
26
|
|
|
27
|
-
The SDK (`
|
|
27
|
+
The SDK (`@siafoundation/sia`) is an npm package that handles:
|
|
28
28
|
- Encrypted file uploads/downloads (erasure coding + encryption)
|
|
29
29
|
- Key derivation from recovery phrases (BIP-39)
|
|
30
30
|
- Object pinning and metadata management
|
|
31
31
|
- Connection auth with indexers
|
|
32
|
-
|
|
33
|
-
Initialized via `src/lib/sdk.ts`, which wraps the WASM init and re-exports types.
|
|
32
|
+
- Parallel multi-worker uploads and downloads
|
|
34
33
|
|
|
35
34
|
### Zustand Persistence
|
|
36
35
|
|
|
@@ -40,12 +39,8 @@ Auth state persists to localStorage via Zustand's `persist` middleware. The stor
|
|
|
40
39
|
|
|
41
40
|
| File | Description |
|
|
42
41
|
|------|-------------|
|
|
43
|
-
| `src/lib/sdk.ts` | WASM init + re-exports (AppKey, Builder, SDK, PinnedObject) |
|
|
44
42
|
| `src/lib/constants.ts` | App key, app name, indexer URL, app metadata |
|
|
45
|
-
| `src/
|
|
46
|
-
| `src/lib/format.ts` | FileMetadata type, formatBytes, formatDate |
|
|
47
|
-
| `src/lib/wasm-env.ts` | `now()` stub for WASM chrono |
|
|
48
|
-
| `src/stores/auth.ts` | Auth state machine + balanced preset |
|
|
43
|
+
| `src/stores/auth.ts` | Auth state machine (Zustand + persist) |
|
|
49
44
|
| `src/stores/toast.ts` | Toast notification store (auto-dismiss) |
|
|
50
45
|
| `src/components/Navbar.tsx` | App navbar with title, public key, sign out |
|
|
51
46
|
| `src/components/Toast.tsx` | Toast overlay component |
|
|
@@ -54,36 +49,34 @@ Auth state persists to localStorage via Zustand's `persist` middleware. The stor
|
|
|
54
49
|
| `src/components/auth/ConnectScreen.tsx` | Indexer connection + CORS fallback |
|
|
55
50
|
| `src/components/auth/ApproveScreen.tsx` | Approval polling (auto-polls on mount) |
|
|
56
51
|
| `src/components/auth/RecoveryScreen.tsx` | Recovery phrase generation/import |
|
|
57
|
-
| `src/components/upload/UploadZone.tsx` | File upload dropzone + file list |
|
|
52
|
+
| `src/components/upload/UploadZone.tsx` | File upload/download dropzone + file list |
|
|
58
53
|
| `src/components/DevNote.tsx` | Developer callout component (unused, available for customization) |
|
|
59
|
-
| `wasm/indexd_wasm/` | Pre-built WASM binary + JS glue + types |
|
|
60
|
-
| `rust/sia-sdk-rs/` | Rust SDK source (cloned on install, gitignored) |
|
|
61
54
|
|
|
62
55
|
## SDK Usage Patterns
|
|
63
56
|
|
|
64
57
|
### Upload a file
|
|
65
58
|
|
|
66
59
|
```ts
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
console.log(`${
|
|
60
|
+
const pinnedObject = await client.upload(file, (progress) => {
|
|
61
|
+
// progress.phase: 'connecting' | 'uploading' | 'assembling' | 'pinning'
|
|
62
|
+
console.log(`${progress.slabsComplete}/${progress.slabsTotal} slabs`)
|
|
70
63
|
})
|
|
71
64
|
pinnedObject.updateMetadata(new TextEncoder().encode(JSON.stringify({
|
|
72
65
|
name: 'file.txt',
|
|
73
66
|
type: 'text/plain',
|
|
74
|
-
size:
|
|
67
|
+
size: file.size,
|
|
75
68
|
hash: '...',
|
|
76
69
|
})))
|
|
77
|
-
await
|
|
78
|
-
await
|
|
70
|
+
await client.pinObject(pinnedObject)
|
|
71
|
+
await client.updateObjectMetadata(pinnedObject)
|
|
79
72
|
```
|
|
80
73
|
|
|
81
74
|
### Download a file
|
|
82
75
|
|
|
83
76
|
```ts
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
console.log(`${
|
|
77
|
+
const data = await client.download(pinnedObject, (progress) => {
|
|
78
|
+
// progress.phase: 'connecting' | 'downloading' | 'assembling'
|
|
79
|
+
console.log(`${progress.slabsComplete}/${progress.slabsTotal} slabs`)
|
|
87
80
|
})
|
|
88
81
|
// data is Uint8Array
|
|
89
82
|
```
|
|
@@ -91,10 +84,12 @@ const data = await sdk.downloadWithProgress(object, (current, total) => {
|
|
|
91
84
|
### List files
|
|
92
85
|
|
|
93
86
|
```ts
|
|
94
|
-
|
|
87
|
+
import { decodeMetadata } from '@siafoundation/sia'
|
|
88
|
+
|
|
89
|
+
const events = await client.objectEvents(null, 100)
|
|
95
90
|
for (const event of events) {
|
|
96
91
|
if (!event.deleted && event.object) {
|
|
97
|
-
const meta =
|
|
92
|
+
const meta = decodeMetadata(event.object.metadata())
|
|
98
93
|
console.log(meta.name, event.object.size())
|
|
99
94
|
}
|
|
100
95
|
}
|
|
@@ -103,21 +98,21 @@ for (const event of events) {
|
|
|
103
98
|
### Delete a file
|
|
104
99
|
|
|
105
100
|
```ts
|
|
106
|
-
await
|
|
101
|
+
await client.deleteObject(objectId)
|
|
107
102
|
```
|
|
108
103
|
|
|
109
104
|
### Share a file
|
|
110
105
|
|
|
111
106
|
```ts
|
|
112
107
|
const validUntil = Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
113
|
-
const shareUrl =
|
|
108
|
+
const shareUrl = client.shareObject(pinnedObject, validUntil)
|
|
114
109
|
```
|
|
115
110
|
|
|
116
111
|
### Download shared file
|
|
117
112
|
|
|
118
113
|
```ts
|
|
119
|
-
const object = await
|
|
120
|
-
const data = await
|
|
114
|
+
const object = await client.sharedObject(shareUrl)
|
|
115
|
+
const data = await client.download(object)
|
|
121
116
|
```
|
|
122
117
|
|
|
123
118
|
## Customization
|
|
@@ -132,28 +127,12 @@ crypto.randomBytes(32).toString('hex')
|
|
|
132
127
|
|
|
133
128
|
### Replace the upload UI
|
|
134
129
|
|
|
135
|
-
The post-auth UI is rendered in `src/App.tsx`. Replace `<UploadZone />` with your own component. The
|
|
130
|
+
The post-auth UI is rendered in `src/App.tsx`. Replace `<UploadZone />` with your own component. The `SiaClient` is available via `useAuthStore((s) => s.client)`. `SiaClient` exposes all SDK methods directly — use `client.upload()`, `client.download()`, `client.objectEvents()`, etc.
|
|
136
131
|
|
|
137
132
|
### Add routes
|
|
138
133
|
|
|
139
134
|
Install `react-router-dom` and wrap your app. The auth flow should gate all routes.
|
|
140
135
|
|
|
141
|
-
### Change performance defaults
|
|
142
|
-
|
|
143
|
-
Edit the `applyBalancedPreset` function in `src/stores/auth.ts`.
|
|
144
|
-
|
|
145
|
-
## WASM / Rust Setup
|
|
146
|
-
|
|
147
|
-
The `wasm/indexd_wasm/` directory contains pre-built WASM artifacts. The `rust/sia-sdk-rs/` directory is cloned automatically on `bun install` from [alexfreska/sia-sdk-rs](https://github.com/alexfreska/sia-sdk-rs) (branch `alex/wasm-experimental`). To rebuild:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
cd rust/sia-sdk-rs
|
|
151
|
-
cargo install wasm-pack
|
|
152
|
-
wasm-pack build indexd_wasm --target web --out-dir pkg
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Then copy the output to `wasm/indexd_wasm/`.
|
|
156
|
-
|
|
157
136
|
## Common Commands
|
|
158
137
|
|
|
159
138
|
```bash
|
package/template/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Open [http://localhost:5173](http://localhost:5173) in your browser.
|
|
|
23
23
|
|
|
24
24
|
- **Full auth flow** — connect to an indexer, approve the connection, set up a recovery phrase
|
|
25
25
|
- **File upload** — drag & drop files, track upload progress, list uploaded files
|
|
26
|
-
- **
|
|
26
|
+
- **Sia SDK** — handles encryption, erasure coding, and direct host transfers in the browser
|
|
27
27
|
- **Developer notes** — amber callout boxes throughout the UI explaining how everything works (remove them when you ship)
|
|
28
28
|
|
|
29
29
|
## How It Works
|
|
@@ -52,7 +52,7 @@ Indexer service
|
|
|
52
52
|
|
|
53
53
|
### Upload Flow
|
|
54
54
|
|
|
55
|
-
Files are encrypted in the browser, erasure coded into shards, and uploaded directly to Sia hosts. The SDK handles all of this — your code just calls `
|
|
55
|
+
Files are encrypted in the browser, erasure coded into shards, and uploaded directly to Sia hosts. The SDK handles all of this — your code just calls `client.upload(file, onProgress)`. Metadata (filename, type, size) is also encrypted and pinned to the indexer.
|
|
56
56
|
|
|
57
57
|
## Customization
|
|
58
58
|
|
|
@@ -65,13 +65,9 @@ Edit `src/lib/constants.ts` to set your app key, name, description, and indexer
|
|
|
65
65
|
The main post-auth component is `src/components/upload/UploadZone.tsx`. Replace it with your own UI — the SDK is available via:
|
|
66
66
|
|
|
67
67
|
```tsx
|
|
68
|
-
const
|
|
68
|
+
const client = useAuthStore((s) => s.client)
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
### Performance Presets
|
|
72
|
-
|
|
73
|
-
Edit `src/stores/auth.ts` to change the `PRESETS` object for concurrent operation limits.
|
|
74
|
-
|
|
75
71
|
## Tech Stack
|
|
76
72
|
|
|
77
73
|
- [React](https://react.dev) 19
|
|
@@ -80,23 +76,21 @@ Edit `src/stores/auth.ts` to change the `PRESETS` object for concurrent operatio
|
|
|
80
76
|
- [Tailwind CSS](https://tailwindcss.com) 4
|
|
81
77
|
- [Zustand](https://zustand.docs.pmnd.rs) (state management)
|
|
82
78
|
- [Biome](https://biomejs.dev) (linting & formatting)
|
|
83
|
-
- [
|
|
79
|
+
- [@siafoundation/sia](https://www.npmjs.com/package/@siafoundation/sia) (Sia SDK — encryption, erasure coding, host transfers)
|
|
84
80
|
|
|
85
81
|
## Project Structure
|
|
86
82
|
|
|
87
83
|
```
|
|
88
84
|
src/
|
|
89
|
-
├── lib/ #
|
|
85
|
+
├── lib/ # Constants, utilities
|
|
90
86
|
├── stores/ # Zustand auth store
|
|
91
87
|
└── components/
|
|
92
88
|
├── auth/ # Auth flow screens
|
|
93
89
|
├── upload/ # Upload dropzone
|
|
94
90
|
└── DevNote.tsx # Developer callout (remove in production)
|
|
95
|
-
wasm/ # Pre-built WASM binary
|
|
96
|
-
rust/ # Rust SDK source (cloned on install)
|
|
97
91
|
```
|
|
98
92
|
|
|
99
93
|
## Learn More
|
|
100
94
|
|
|
101
95
|
- [Sia Documentation](https://docs.sia.tech)
|
|
102
|
-
- [
|
|
96
|
+
- [@siafoundation/sia](https://www.npmjs.com/package/@siafoundation/sia)
|
package/template/_gitignore
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-950:oklch(27.9% .077 45.635);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.top-4{top:calc(var(--spacing)*4)}.bottom-6{bottom:calc(var(--spacing)*6)}.left-1\/2{left:50%}.z-50{z-index:50}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-4{margin-right:calc(var(--spacing)*4)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-2{height:calc(var(--spacing)*2)}.h-3\.5{height:calc(var(--spacing)*3.5)}.h-4{height:calc(var(--spacing)*4)}.h-8{height:calc(var(--spacing)*8)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-1\/4{width:25%}.w-2{width:calc(var(--spacing)*2)}.w-3\.5{width:calc(var(--spacing)*3.5)}.w-4{width:calc(var(--spacing)*4)}.w-8{width:calc(var(--spacing)*8)}.w-full{width:100%}.max-w-5xl{max-width:var(--container-5xl)}.max-w-md{max-width:var(--container-md)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.animate-ping{animation:var(--animate-ping)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-neutral-800\/60>:not(:last-child)){border-color:#26262699}@supports (color:color-mix(in lab,red,red)){:where(.divide-neutral-800\/60>:not(:last-child)){border-color:color-mix(in oklab,var(--color-neutral-800)60%,transparent)}}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-r-lg{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-500{border-color:var(--color-amber-500)}.border-green-500{border-color:var(--color-green-500)}.border-neutral-400{border-color:var(--color-neutral-400)}.border-neutral-600{border-color:var(--color-neutral-600)}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-neutral-800\/60{border-color:#26262699}@supports (color:color-mix(in lab,red,red)){.border-neutral-800\/60{border-color:color-mix(in oklab,var(--color-neutral-800)60%,transparent)}}.border-red-700{border-color:var(--color-red-700)}.border-red-900{border-color:var(--color-red-900)}.border-t-green-500{border-top-color:var(--color-green-500)}.border-t-white{border-top-color:var(--color-white)}.bg-amber-950\/50{background-color:#46190180}@supports (color:color-mix(in lab,red,red)){.bg-amber-950\/50{background-color:color-mix(in oklab,var(--color-amber-950)50%,transparent)}}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-500\/5{background-color:#00c7580d}@supports (color:color-mix(in lab,red,red)){.bg-green-500\/5{background-color:color-mix(in oklab,var(--color-green-500)5%,transparent)}}.bg-green-600{background-color:var(--color-green-600)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-neutral-950{background-color:var(--color-neutral-950)}.bg-red-900\/90{background-color:#82181ae6}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/90{background-color:color-mix(in oklab,var(--color-red-900)90%,transparent)}}.bg-red-950\/80{background-color:#460809cc}@supports (color:color-mix(in lab,red,red)){.bg-red-950\/80{background-color:color-mix(in oklab,var(--color-red-950)80%,transparent)}}.p-1{padding:calc(var(--spacing)*1)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-16{padding:calc(var(--spacing)*16)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-amber-200{color:var(--color-amber-200)}.text-amber-200\/80{color:#fee685cc}@supports (color:color-mix(in lab,red,red)){.text-amber-200\/80{color:color-mix(in oklab,var(--color-amber-200)80%,transparent)}}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.text-green-400{color:var(--color-green-400)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-700{color:var(--color-neutral-700)}.text-red-200{color:var(--color-red-200)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.placeholder-neutral-500::placeholder{color:var(--color-neutral-500)}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-300{--tw-duration:.3s;transition-duration:.3s}@media(hover:hover){.group-hover\:text-neutral-500:is(:where(.group):hover *){color:var(--color-neutral-500)}.hover\:border-neutral-600:hover{border-color:var(--color-neutral-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-neutral-700:hover{background-color:var(--color-neutral-700)}.hover\:text-neutral-300:hover{color:var(--color-neutral-300)}.hover\:text-red-300:hover{color:var(--color-red-300)}}.focus\:border-green-500:focus{border-color:var(--color-green-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-default:disabled{cursor:default}.disabled\:bg-neutral-700:disabled{background-color:var(--color-neutral-700)}.disabled\:text-neutral-500:disabled{color:var(--color-neutral-500)}.disabled\:opacity-30:disabled{opacity:.3}}body{color:#e5e5e5;background-color:#0a0a0a;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}@keyframes fade-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes indeterminate{0%{transform:translate(-100%)}to{transform:translate(400%)}}.animate-fade-in{animation:.2s ease-out fade-in}.animate-indeterminate{animation:1.5s ease-in-out infinite indeterminate}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}
|